Skip to content

Commit 7049014

Browse files
Option to ensure scroll_to_center doesn't scroll so as to hide the top left corner of the widget (#2682)
* Option to ensure origin of widget is visible when calling scroll to center * Update CHANGELOG.md --------- Co-authored-by: Will McGugan <[email protected]>
1 parent a14e469 commit 7049014

File tree

3 files changed

+29
-14
lines changed

3 files changed

+29
-14
lines changed

CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@ and this project adheres to [Semantic Versioning](http://semver.org/).
2222
- `work` decorator accepts `description` parameter to add debug string https://github.com/Textualize/textual/issues/2597
2323
- Added `SelectionList` widget https://github.com/Textualize/textual/pull/2652
2424
- `App.AUTO_FOCUS` to set auto focus on all screens https://github.com/Textualize/textual/issues/2594
25+
- Option to `scroll_to_center` to ensure we don't scroll such that the top left corner of the widget is not visible https://github.com/Textualize/textual/pull/2682
2526
- Added `Widget.tooltip` property https://github.com/Textualize/textual/pull/2670
2627
- Added `Region.inflect` https://github.com/Textualize/textual/pull/2670
2728
- `Suggester` API to compose with widgets for automatic suggestions https://github.com/Textualize/textual/issues/2330

src/textual/screen.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -499,7 +499,7 @@ def set_focus(self, widget: Widget | None, scroll_visible: bool = True) -> None:
499499
def scroll_to_center(widget: Widget) -> None:
500500
"""Scroll to center (after a refresh)."""
501501
if widget.has_focus and not self.screen.can_view(widget):
502-
self.screen.scroll_to_center(widget)
502+
self.screen.scroll_to_center(widget, origin_visible=True)
503503

504504
self.call_after_refresh(scroll_to_center, widget)
505505
widget.post_message(events.Focus())

src/textual/widget.py

Lines changed: 27 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -2406,7 +2406,7 @@ def scroll_visible(
24062406
force=force,
24072407
)
24082408

2409-
async def _scroll_to_center_of(
2409+
async def _scroll_widget_to_center_of_self(
24102410
self,
24112411
widget: Widget,
24122412
animate: bool = True,
@@ -2415,8 +2415,11 @@ async def _scroll_to_center_of(
24152415
duration: float | None = None,
24162416
easing: EasingFunction | str | None = None,
24172417
force: bool = False,
2418+
origin_visible: bool = False,
24182419
) -> None:
2419-
"""Scroll a widget to the center of this container.
2420+
"""Scroll a widget to the center of this container. Note that this may
2421+
result in more than one container scrolling, since multiple containers
2422+
might be encountered on the path from `widget` to `self`.
24202423
24212424
Args:
24222425
widget: The widget to center.
@@ -2425,6 +2428,7 @@ async def _scroll_to_center_of(
24252428
duration: Duration of animation, if `animate` is `True` and `speed` is `None`.
24262429
easing: An easing method for the scrolling animation.
24272430
force: Force scrolling even when prohibited by overflow styling.
2431+
origin_visible: Ensure that the top left corner of the widget remains visible after the scroll.
24282432
"""
24292433

24302434
central_point = Offset(
@@ -2435,15 +2439,19 @@ async def _scroll_to_center_of(
24352439
container = widget.parent
24362440
while isinstance(container, Widget) and widget is not self:
24372441
container_virtual_region = container.virtual_region
2438-
# The region we want to scroll to must be centered around the central point.
2439-
# We make it as big as possible because `scroll_to_region` scrolls as little
2440-
# as possible.
2441-
target_region = Region(
2442-
central_point.x - container_virtual_region.width // 2,
2443-
central_point.y - container_virtual_region.height // 2,
2444-
container_virtual_region.width,
2445-
container_virtual_region.height,
2446-
)
2442+
if origin_visible and widget.region.height > container.region.height:
2443+
target_region = widget.virtual_region
2444+
else:
2445+
# The region we want to scroll to must be centered around the central point.
2446+
# We make it as big as possible because `scroll_to_region` scrolls as little
2447+
# as possible.
2448+
target_region = Region(
2449+
central_point.x - container_virtual_region.width // 2,
2450+
central_point.y - container_virtual_region.height // 2,
2451+
container_virtual_region.width,
2452+
container_virtual_region.height,
2453+
)
2454+
24472455
scroll = container.scroll_to_region(
24482456
target_region,
24492457
animate=animate,
@@ -2480,25 +2488,31 @@ def scroll_to_center(
24802488
duration: float | None = None,
24812489
easing: EasingFunction | str | None = None,
24822490
force: bool = False,
2491+
origin_visible: bool = False,
24832492
) -> None:
2484-
"""Scroll this widget to the center of the screen.
2493+
"""Scroll this widget to the center of self.
2494+
2495+
The center of the widget will be scrolled to the center of the container.
24852496
24862497
Args:
2498+
widget: The widget to scroll to the center of self.
24872499
animate: Whether to animate the scroll.
24882500
speed: Speed of scroll if animate is `True`; or `None` to use `duration`.
24892501
duration: Duration of animation, if `animate` is `True` and `speed` is `None`.
24902502
easing: An easing method for the scrolling animation.
24912503
force: Force scrolling even when prohibited by overflow styling.
2504+
origin_visible: Ensure that the top left corner of the widget remains visible after the scroll.
24922505
"""
24932506

24942507
self.call_after_refresh(
2495-
self._scroll_to_center_of,
2508+
self._scroll_widget_to_center_of_self,
24962509
widget=widget,
24972510
animate=animate,
24982511
speed=speed,
24992512
duration=duration,
25002513
easing=easing,
25012514
force=force,
2515+
origin_visible=origin_visible,
25022516
)
25032517

25042518
def can_view(self, widget: Widget) -> bool:

0 commit comments

Comments
 (0)