From 7049014faa79c423b8fa220731e65c04509882f0 Mon Sep 17 00:00:00 2001 From: darrenburns Date: Wed, 31 May 2023 16:13:21 +0100 Subject: [PATCH] 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 --- CHANGELOG.md | 1 + src/textual/screen.py | 2 +- src/textual/widget.py | 40 +++++++++++++++++++++++++++------------- 3 files changed, 29 insertions(+), 14 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index b64f2ada41..36dd080f42 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -22,6 +22,7 @@ and this project adheres to [Semantic Versioning](http://semver.org/). - `work` decorator accepts `description` parameter to add debug string https://github.com/Textualize/textual/issues/2597 - Added `SelectionList` widget https://github.com/Textualize/textual/pull/2652 - `App.AUTO_FOCUS` to set auto focus on all screens https://github.com/Textualize/textual/issues/2594 +- 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 - Added `Widget.tooltip` property https://github.com/Textualize/textual/pull/2670 - Added `Region.inflect` https://github.com/Textualize/textual/pull/2670 - `Suggester` API to compose with widgets for automatic suggestions https://github.com/Textualize/textual/issues/2330 diff --git a/src/textual/screen.py b/src/textual/screen.py index 6414ccc125..46ed3686a9 100644 --- a/src/textual/screen.py +++ b/src/textual/screen.py @@ -499,7 +499,7 @@ def set_focus(self, widget: Widget | None, scroll_visible: bool = True) -> None: def scroll_to_center(widget: Widget) -> None: """Scroll to center (after a refresh).""" if widget.has_focus and not self.screen.can_view(widget): - self.screen.scroll_to_center(widget) + self.screen.scroll_to_center(widget, origin_visible=True) self.call_after_refresh(scroll_to_center, widget) widget.post_message(events.Focus()) diff --git a/src/textual/widget.py b/src/textual/widget.py index da12d7e419..99e4b78eb4 100644 --- a/src/textual/widget.py +++ b/src/textual/widget.py @@ -2406,7 +2406,7 @@ def scroll_visible( force=force, ) - async def _scroll_to_center_of( + async def _scroll_widget_to_center_of_self( self, widget: Widget, animate: bool = True, @@ -2415,8 +2415,11 @@ async def _scroll_to_center_of( duration: float | None = None, easing: EasingFunction | str | None = None, force: bool = False, + origin_visible: bool = False, ) -> None: - """Scroll a widget to the center of this container. + """Scroll a widget to the center of this container. Note that this may + result in more than one container scrolling, since multiple containers + might be encountered on the path from `widget` to `self`. Args: widget: The widget to center. @@ -2425,6 +2428,7 @@ async def _scroll_to_center_of( duration: Duration of animation, if `animate` is `True` and `speed` is `None`. easing: An easing method for the scrolling animation. force: Force scrolling even when prohibited by overflow styling. + origin_visible: Ensure that the top left corner of the widget remains visible after the scroll. """ central_point = Offset( @@ -2435,15 +2439,19 @@ async def _scroll_to_center_of( container = widget.parent while isinstance(container, Widget) and widget is not self: container_virtual_region = container.virtual_region - # The region we want to scroll to must be centered around the central point. - # We make it as big as possible because `scroll_to_region` scrolls as little - # as possible. - target_region = Region( - central_point.x - container_virtual_region.width // 2, - central_point.y - container_virtual_region.height // 2, - container_virtual_region.width, - container_virtual_region.height, - ) + if origin_visible and widget.region.height > container.region.height: + target_region = widget.virtual_region + else: + # The region we want to scroll to must be centered around the central point. + # We make it as big as possible because `scroll_to_region` scrolls as little + # as possible. + target_region = Region( + central_point.x - container_virtual_region.width // 2, + central_point.y - container_virtual_region.height // 2, + container_virtual_region.width, + container_virtual_region.height, + ) + scroll = container.scroll_to_region( target_region, animate=animate, @@ -2480,25 +2488,31 @@ def scroll_to_center( duration: float | None = None, easing: EasingFunction | str | None = None, force: bool = False, + origin_visible: bool = False, ) -> None: - """Scroll this widget to the center of the screen. + """Scroll this widget to the center of self. + + The center of the widget will be scrolled to the center of the container. Args: + widget: The widget to scroll to the center of self. animate: Whether to animate the scroll. speed: Speed of scroll if animate is `True`; or `None` to use `duration`. duration: Duration of animation, if `animate` is `True` and `speed` is `None`. easing: An easing method for the scrolling animation. force: Force scrolling even when prohibited by overflow styling. + origin_visible: Ensure that the top left corner of the widget remains visible after the scroll. """ self.call_after_refresh( - self._scroll_to_center_of, + self._scroll_widget_to_center_of_self, widget=widget, animate=animate, speed=speed, duration=duration, easing=easing, force=force, + origin_visible=origin_visible, ) def can_view(self, widget: Widget) -> bool: