Skip to content

Commit

Permalink
Merge pull request #2255 from Textualize/scroll_to_center
Browse files Browse the repository at this point in the history
Add scroll_to_center method.
  • Loading branch information
rodrigogiraoserrao authored Apr 11, 2023
2 parents 3c8f2ec + 349f414 commit 48e8b51
Show file tree
Hide file tree
Showing 5 changed files with 309 additions and 0 deletions.
7 changes: 7 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,13 @@ All notable changes to this project will be documented in this file.
The format is based on [Keep a Changelog](http://keepachangelog.com/)
and this project adheres to [Semantic Versioning](http://semver.org/).


## Unreleased

### Added

- `Widget.scroll_to_center` now scrolls the widget to the center of the screen https://github.com/Textualize/textual/pull/2255

## [0.19.1] - 2023-04-10

### Fixed
Expand Down
94 changes: 94 additions & 0 deletions src/textual/widget.py
Original file line number Diff line number Diff line change
Expand Up @@ -2408,6 +2408,100 @@ def scroll_visible(
force=force,
)

async def _scroll_to_center_of(
self,
widget: Widget,
animate: bool = True,
*,
speed: float | None = None,
duration: float | None = None,
easing: EasingFunction | str | None = None,
force: bool = False,
) -> None:
"""Scroll a widget to the center of this container.
Args:
widget: The widget to center.
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.
"""

central_point = Offset(
widget.virtual_region.x + (1 + widget.virtual_region.width) // 2,
widget.virtual_region.y + (1 + widget.virtual_region.height) // 2,
)

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,
)
scroll = container.scroll_to_region(
target_region,
animate=animate,
speed=speed,
duration=duration,
easing=easing,
force=force,
)

# We scroll `widget` within `container` with the central point written in
# the frame of reference of `container`. However, we need to update it so
# that we are ready to scroll `container` within _its_ container.
# To do this, notice that
# (central_point.y - container.scroll_offset.y - scroll.y) is the number
# of rows of `widget` that are visible within `container`.
# We add that to `container_virtual_region.y` to find the total vertical
# offset of the central point with respect to the container of `container`.
# A similar calculation is made for the horizontal update.
central_point = (
container_virtual_region.offset
+ central_point
- container.scroll_offset
- scroll
)
widget = container
container = widget.parent

def scroll_to_center(
self,
animate: bool = True,
*,
speed: float | None = None,
duration: float | None = None,
easing: EasingFunction | str | None = None,
force: bool = False,
) -> None:
"""Scroll this widget to the center of the screen.
Args:
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.
"""

self.call_after_refresh(
self.screen._scroll_to_center_of,
widget=self,
animate=animate,
speed=speed,
duration=duration,
easing=easing,
force=force,
)

def __init_subclass__(
cls,
can_focus: bool | None = None,
Expand Down
Loading

0 comments on commit 48e8b51

Please sign in to comment.