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 Will McGugan
parent a5d46d6adc
commit 566eb837b7
3 changed files with 63 additions and 40 deletions

View File

@@ -42,7 +42,7 @@ App > Screen {
background: $primary-background-darken-2;
color: $text-primary-darken-2 ;
border-right: outer $primary-darken-3;
content-align: center middle;
content-align: center middle;
}
#sidebar .user {
@@ -71,7 +71,7 @@ App > Screen {
color: $text-background;
background: $background;
layout: vertical;
overflow-y: scroll;
overflow-y: scroll;
}
@@ -109,6 +109,7 @@ Tweet {
}
}
TweetHeader {
height:1;
@@ -136,7 +137,7 @@ Tweet.scroll-horizontal TweetBody {
/* border-top: hidden $accent-darken-3; */
border: tall $accent-darken-2;
/* border-left: tall $accent-darken-1; */
/* padding: 1 0 0 0 ; */
@@ -151,10 +152,10 @@ Tweet.scroll-horizontal TweetBody {
height: 3;
border: tall $accent-darken-1;
/* border-left: tall $accent-darken-3; */
}
#footer {
@@ -194,11 +195,11 @@ Error {
background: $error;
color: $text-error;
border-top: hkey $error-darken-2;
border-bottom: hkey $error-darken-2;
border-bottom: hkey $error-darken-2;
margin: 1 3;
text-style: bold;
align-horizontal: center;
align-horizontal: center;
}
Warning {
@@ -207,7 +208,7 @@ Warning {
background: $warning;
color: $text-warning-fade-1;
border-top: hkey $warning-darken-2;
border-bottom: hkey $warning-darken-2;
border-bottom: hkey $warning-darken-2;
margin: 1 2;
text-style: bold;
align-horizontal: center;
@@ -220,7 +221,7 @@ Success {
background: $success-lighten-3;
color: $text-success-lighten-3-fade-1;
border-top: hkey $success;
border-bottom: hkey $success;
border-bottom: hkey $success;
margin: 1 2;
text-style: bold;
align-horizontal: center;

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(
@@ -157,6 +159,9 @@ class BasicApp(App, css_path="basic.css"):
tweet_body = self.query("TweetBody").first()
tweet_body.short_lorem = not tweet_body.short_lorem
def key_v(self):
self.get_child(id="content").scroll_to_widget(self.scroll_to_target)
app = BasicApp()
@@ -176,5 +181,5 @@ if __name__ == "__main__":
print(Scalar.resolve_dimension.cache_info())
from rich.style import Style
print(Style._add_cache)

View File

@@ -564,48 +564,65 @@ 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.
"""
try:
widget_region = widget.content_region
container_region = self.content_region
except errors.NoWidget:
return False
scrolls = set()
if widget_region in container_region:
# Widget is visible, nothing to do
return False
node = widget.parent
child = widget
while node:
try:
widget_region = child.region
container_region = node.region
except (errors.NoWidget, AttributeError):
return False
# 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
top_delta = widget_region.origin - container_region.origin
if widget_region in container_region:
# Widget is visible, nothing to do
child = node
node = node.parent
continue
bottom_delta = widget_region.origin - (
container_region.origin
+ Offset(0, container_region.height - widget_region.height)
)
# 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
top_delta = widget_region.origin - container_region.origin
if widget_region.width > container_region.width:
delta_x = top_delta.x
else:
delta_x = min(top_delta.x, bottom_delta.x, key=abs)
bottom_delta = widget_region.origin - (
container_region.origin
+ Offset(0, container_region.height - widget_region.height)
)
if widget_region.height > container_region.height:
delta_y = top_delta.y
else:
delta_y = min(top_delta.y, bottom_delta.y, key=abs)
if widget_region.width > container_region.width:
delta_x = top_delta.x
else:
delta_x = min(top_delta.x, bottom_delta.x, key=abs)
return self.scroll_relative(
delta_x or None, delta_y or None, animate=animate, duration=0.2
)
if widget_region.height > container_region.height:
delta_y = top_delta.y
else:
delta_y = min(top_delta.y, bottom_delta.y, key=abs)
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,