Skip to content

Commit 129bef4

Browse files
authored
Alternative scroll to center (#2787)
* remove commented out code * origin_visible flag * comments * docstring * test fix
1 parent 65fff4f commit 129bef4

File tree

5 files changed

+109
-139
lines changed

5 files changed

+109
-139
lines changed

CHANGELOG.md

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,10 @@ and this project adheres to [Semantic Versioning](http://semver.org/).
1313
- Class variable `CSS` to screens https://github.com/Textualize/textual/issues/2137
1414
- Class variable `CSS_PATH` to screens https://github.com/Textualize/textual/issues/2137
1515
- Added `cursor_foreground_priority` and `cursor_background_priority` to `DataTable` https://github.com/Textualize/textual/pull/2736
16+
- Added Region.center
17+
- Added `center` parameter to `Widget.scroll_to_region`
18+
- Added `origin_visible` parameter to `Widget.scroll_to_region`
19+
- Added `origin_visible` parameter to `Widget.scroll_to_center`
1620
- Added `TabbedContent.tab_count` https://github.com/Textualize/textual/pull/2751
1721
- Added `TabbedContnet.add_pane` https://github.com/Textualize/textual/pull/2751
1822
- Added `TabbedContent.remove_pane` https://github.com/Textualize/textual/pull/2751

src/textual/geometry.py

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -439,6 +439,18 @@ def offset(self) -> Offset:
439439
"""
440440
return Offset(*self[:2])
441441

442+
@property
443+
def center(self) -> tuple[float, float]:
444+
"""The center of the region.
445+
446+
Note, that this does *not* return an `Offset`, because the center may not be an integer coordinate.
447+
448+
Returns:
449+
Tuple of floats.
450+
"""
451+
x, y, width, height = self
452+
return (x + width / 2.0, y + height / 2.0)
453+
442454
@property
443455
def bottom_left(self) -> Offset:
444456
"""Bottom left offset of the region.

src/textual/widget.py

Lines changed: 31 additions & 78 deletions
Original file line numberDiff line numberDiff line change
@@ -2260,7 +2260,9 @@ def scroll_to_widget(
22602260
speed: float | None = None,
22612261
duration: float | None = None,
22622262
easing: EasingFunction | str | None = None,
2263+
center: bool = False,
22632264
top: bool = False,
2265+
origin_visible: bool = True,
22642266
force: bool = False,
22652267
) -> bool:
22662268
"""Scroll scrolling to bring a widget in to view.
@@ -2272,6 +2274,7 @@ def scroll_to_widget(
22722274
duration: Duration of animation, if `animate` is `True` and `speed` is `None`.
22732275
easing: An easing method for the scrolling animation.
22742276
top: Scroll widget to top of container.
2277+
origin_visible: Ensure that the top left of the widget is within the window.
22752278
force: Force scrolling even when prohibited by overflow styling.
22762279
22772280
Returns:
@@ -2293,8 +2296,10 @@ def scroll_to_widget(
22932296
animate=animate,
22942297
speed=speed,
22952298
duration=duration,
2299+
center=center,
22962300
top=top,
22972301
easing=easing,
2302+
origin_visible=origin_visible,
22982303
force=force,
22992304
)
23002305
if scroll_offset:
@@ -2325,7 +2330,9 @@ def scroll_to_region(
23252330
speed: float | None = None,
23262331
duration: float | None = None,
23272332
easing: EasingFunction | str | None = None,
2333+
center: bool = False,
23282334
top: bool = False,
2335+
origin_visible: bool = True,
23292336
force: bool = False,
23302337
) -> Offset:
23312338
"""Scrolls a given region in to view, if required.
@@ -2341,6 +2348,7 @@ def scroll_to_region(
23412348
duration: Duration of animation, if `animate` is `True` and `speed` is `None`.
23422349
easing: An easing method for the scrolling animation.
23432350
top: Scroll `region` to top of container.
2351+
origin_visible: Ensure that the top left of the widget is within the window.
23442352
force: Force scrolling even when prohibited by overflow styling.
23452353
23462354
Returns:
@@ -2350,11 +2358,28 @@ def scroll_to_region(
23502358
if spacing is not None:
23512359
window = window.shrink(spacing)
23522360

2353-
if window in region and not top:
2361+
if window in region and not (top or center):
23542362
return Offset()
23552363

2356-
delta_x, delta_y = Region.get_scroll_to_visible(window, region, top=top)
2364+
if center:
2365+
region_center_x, region_center_y = region.center
2366+
window_center_x, window_center_y = window.center
2367+
center_delta = Offset(
2368+
round(region_center_x - window_center_x),
2369+
round(region_center_y - window_center_y),
2370+
)
2371+
if origin_visible and region.offset not in window.translate(center_delta):
2372+
center_delta = Region.get_scroll_to_visible(window, region, top=True)
2373+
delta_x, delta_y = center_delta
2374+
else:
2375+
delta_x, delta_y = Region.get_scroll_to_visible(window, region, top=top)
23572376
scroll_x, scroll_y = self.scroll_offset
2377+
2378+
if not self.allow_horizontal_scroll and not force:
2379+
delta_x = 0
2380+
if not self.allow_vertical_scroll and not force:
2381+
delta_y = 0
2382+
23582383
delta = Offset(
23592384
clamp(scroll_x + delta_x, 0, self.max_scroll_x) - scroll_x,
23602385
clamp(scroll_y + delta_y, 0, self.max_scroll_y) - scroll_y,
@@ -2365,7 +2390,7 @@ def scroll_to_region(
23652390
self.scroll_relative(
23662391
delta.x or None,
23672392
delta.y or None,
2368-
animate=animate if (abs(delta_y) > 1 or delta_x) else False,
2393+
animate=animate,
23692394
speed=speed,
23702395
duration=duration,
23712396
easing=easing,
@@ -2406,79 +2431,6 @@ def scroll_visible(
24062431
force=force,
24072432
)
24082433

2409-
async def _scroll_widget_to_center_of_self(
2410-
self,
2411-
widget: Widget,
2412-
animate: bool = True,
2413-
*,
2414-
speed: float | None = None,
2415-
duration: float | None = None,
2416-
easing: EasingFunction | str | None = None,
2417-
force: bool = False,
2418-
origin_visible: bool = False,
2419-
) -> None:
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`.
2423-
2424-
Args:
2425-
widget: The widget to center.
2426-
animate: Whether to animate the scroll.
2427-
speed: Speed of scroll if animate is `True`; or `None` to use `duration`.
2428-
duration: Duration of animation, if `animate` is `True` and `speed` is `None`.
2429-
easing: An easing method for the scrolling animation.
2430-
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.
2432-
"""
2433-
2434-
central_point = Offset(
2435-
widget.virtual_region.x + (1 + widget.virtual_region.width) // 2,
2436-
widget.virtual_region.y + (1 + widget.virtual_region.height) // 2,
2437-
)
2438-
2439-
container = widget.parent
2440-
while isinstance(container, Widget) and widget is not self:
2441-
container_virtual_region = container.virtual_region
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-
2455-
scroll = container.scroll_to_region(
2456-
target_region,
2457-
animate=animate,
2458-
speed=speed,
2459-
duration=duration,
2460-
easing=easing,
2461-
force=force,
2462-
)
2463-
2464-
# We scroll `widget` within `container` with the central point written in
2465-
# the frame of reference of `container`. However, we need to update it so
2466-
# that we are ready to scroll `container` within _its_ container.
2467-
# To do this, notice that
2468-
# (central_point.y - container.scroll_offset.y - scroll.y) is the number
2469-
# of rows of `widget` that are visible within `container`.
2470-
# We add that to `container_virtual_region.y` to find the total vertical
2471-
# offset of the central point with respect to the container of `container`.
2472-
# A similar calculation is made for the horizontal update.
2473-
central_point = (
2474-
container_virtual_region.offset
2475-
+ central_point
2476-
- container.scroll_offset
2477-
- scroll
2478-
)
2479-
widget = container
2480-
container = widget.parent
2481-
24822434
def scroll_to_center(
24832435
self,
24842436
widget: Widget,
@@ -2488,7 +2440,7 @@ def scroll_to_center(
24882440
duration: float | None = None,
24892441
easing: EasingFunction | str | None = None,
24902442
force: bool = False,
2491-
origin_visible: bool = False,
2443+
origin_visible: bool = True,
24922444
) -> None:
24932445
"""Scroll this widget to the center of self.
24942446
@@ -2505,13 +2457,14 @@ def scroll_to_center(
25052457
"""
25062458

25072459
self.call_after_refresh(
2508-
self._scroll_widget_to_center_of_self,
2460+
self.scroll_to_widget,
25092461
widget=widget,
25102462
animate=animate,
25112463
speed=speed,
25122464
duration=duration,
25132465
easing=easing,
25142466
force=force,
2467+
center=True,
25152468
origin_visible=origin_visible,
25162469
)
25172470

0 commit comments

Comments
 (0)