mirror of
https://github.com/Textualize/textual.git
synced 2025-10-17 02:38:12 +03:00
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:
committed by
Will McGugan
parent
a5d46d6adc
commit
566eb837b7
@@ -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;
|
||||
|
||||
@@ -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)
|
||||
|
||||
|
||||
@@ -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,
|
||||
|
||||
Reference in New Issue
Block a user