Scrolling nested containers (#538)

* Scrolling nested containers

* Return boolean if any scrolling occurred in any descendant in scroll_to_widget
This commit is contained in:
darrenburns
2022-06-08 16:20:53 +01:00
committed by GitHub
parent 0438a6b78e
commit 7366421635
3 changed files with 65 additions and 44 deletions

View File

@@ -78,7 +78,6 @@ App > Screen {
Tweet {
height: 12;
width: 80;
margin: 1 3;
background: $panel;
color: $text-panel;
@@ -109,7 +108,6 @@ Tweet {
}
TweetHeader {
height:1;
background: $accent;

View File

@@ -100,6 +100,8 @@ class BasicApp(App, css_path="basic.css"):
def on_mount(self):
"""Build layout here."""
self.scroll_to_target = Tweet(TweetBody())
self.mount(
header=Static(
Text.from_markup(
@@ -110,6 +112,7 @@ class BasicApp(App, css_path="basic.css"):
Tweet(TweetBody()),
Widget(
Static(Syntax(CODE, "python"), classes="code"),
self.scroll_to_target,
classes="scrollable",
),
Error(),
@@ -150,6 +153,9 @@ class BasicApp(App, css_path="basic.css"):
tweet_body.short_lorem = not tweet_body.short_lorem
tweet_body.refresh(layout=True)
def key_v(self):
self.get_child(id="content").scroll_to_widget(self.scroll_to_target)
app = BasicApp()

View File

@@ -543,25 +543,34 @@ class Widget(DOMNode):
)
def scroll_to_widget(self, widget: Widget, *, animate: bool = True) -> bool:
"""Scroll so that a child widget is in the visible area.
"""Starting from `widget`, travel up the DOM to this node, scrolling all containers such that
every widget is visible within its parent container. This will, in the majority of cases,
bring the target widget into
Args:
widget (Widget): A Widget in the children.
widget (Widget): A descendant widget.
animate (bool, optional): True to animate, or False to jump. Defaults to True.
Returns:
bool: True if the scroll position changed, otherwise False.
bool: True if any scrolling has occurred in any descendant, otherwise False.
"""
scrolls = set()
node = widget.parent
child = widget
while node:
try:
widget_region = widget.content_region
container_region = self.content_region
except errors.NoWidget:
widget_region = child.region
container_region = node.region
except (errors.NoWidget, AttributeError):
return False
if widget_region in container_region:
# Widget is visible, nothing to do
return False
child = node
node = node.parent
continue
# We can either scroll so the widget is at the top of the container, or so that
# it is at the bottom. We want to pick which has the shortest distance
@@ -582,9 +591,17 @@ class Widget(DOMNode):
else:
delta_y = min(top_delta.y, bottom_delta.y, key=abs)
return self.scroll_relative(
scrolled = node.scroll_relative(
delta_x or None, delta_y or None, animate=animate, duration=0.2
)
scrolls.add(scrolled)
if node == self:
break
child = node
node = node.parent
return any(scrolls)
def __init_subclass__(
cls,