Skip to content

Commit

Permalink
can view fixes
Browse files Browse the repository at this point in the history
  • Loading branch information
willmcgugan committed Oct 25, 2024
1 parent 369a832 commit e76e429
Show file tree
Hide file tree
Showing 4 changed files with 64 additions and 15 deletions.
2 changes: 2 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ and this project adheres to [Semantic Versioning](http://semver.org/).
- Grid will now size children to the maximum height of a row https://github.com/Textualize/textual/pull/5113
- Markdown links will be opened with `App.open_url` automatically https://github.com/Textualize/textual/pull/5113
- The universal selector (`*`) will now not match widgets with the class `-textual-system` (scrollbars, notifications etc) https://github.com/Textualize/textual/pull/5113
- Renamed `Screen.can_view` and `Widget.can_view` to `Screen.can_view_entire` and `Widget.can_view_entire` https://github.com/Textualize/textual/pull/5174

### Added

Expand All @@ -26,6 +27,7 @@ and this project adheres to [Semantic Versioning](http://semver.org/).
- Added `textual._loop.loop_from_index` https://github.com/Textualize/textual/pull/5164
- Added `min_color` and `max_color` to Sparklines constructor, which take precedence over CSS https://github.com/Textualize/textual/pull/5174
- Added new demo `python -m textual`, not *quite* finished but better than the old one https://github.com/Textualize/textual/pull/5174
- Added `Screen.can_view_partial` and `Widget.can_view_partial` https://github.com/Textualize/textual/pull/5174

### Fixed

Expand Down
12 changes: 6 additions & 6 deletions src/textual/demo/widgets.py
Original file line number Diff line number Diff line change
Expand Up @@ -339,25 +339,25 @@ def on_mount(self) -> None:
log = self.query_one(Log)
rich_log = self.query_one(RichLog)
log.write("I am a Log Widget")
rich_log.write("I am a [b]Rich Log Widget")
rich_log.write("I am a Rich Log Widget")
self.set_interval(0.25, self.update_log)
self.set_interval(1, self.update_rich_log)

def update_log(self) -> None:
"""Update the Log with new content."""
if not self.screen.can_view(self) or not self.screen.is_active:
log = self.query_one(Log)
if not self.screen.can_view_partial(log) or not self.screen.is_active:
return
self.log_count += 1
log = self.query_one(Log)
line_no = self.log_count % len(self.TEXT)
line = self.TEXT[self.log_count % len(self.TEXT)]
log.write_line(f"fear[{line_no}] = {line!r}")

def update_rich_log(self) -> None:
"""Update the Rich Log with content."""
if not self.screen.can_view(self) or not self.screen.is_active:
return
rich_log = self.query_one(RichLog)
if not self.screen.can_view_partial(rich_log) or not self.screen.is_active:
return
self.rich_log_count += 1
log_option = self.rich_log_count % 3
if log_option == 0:
Expand Down Expand Up @@ -425,7 +425,7 @@ def on_mount(self) -> None:

def update_sparks(self) -> None:
"""Update the sparks data."""
if not self.screen.can_view(self) or not self.screen.is_active:
if not self.screen.can_view_partial(self) or not self.screen.is_active:
return
self.count += 1
offset = self.count * 40
Expand Down
32 changes: 26 additions & 6 deletions src/textual/screen.py
Original file line number Diff line number Diff line change
Expand Up @@ -896,7 +896,7 @@ def set_focus(self, widget: Widget | None, scroll_visible: bool = True) -> None:

def scroll_to_center(widget: Widget) -> None:
"""Scroll to center (after a refresh)."""
if self.focused is widget and not self.can_view(widget):
if self.focused is widget and not self.can_view_entire(widget):
self.scroll_to_center(widget, origin_visible=True)

self.call_later(scroll_to_center, widget)
Expand Down Expand Up @@ -1480,24 +1480,44 @@ async def action_dismiss(self, result: ScreenResultType | None = None) -> None:
await self._flush_next_callbacks()
self.dismiss(result)

def can_view(self, widget: Widget) -> bool:
"""Check if a given widget is in the current view (scrollable area).
def can_view_entire(self, widget: Widget) -> bool:
"""Check if a given widget is fully within the current screen.
Note: This doesn't necessarily equate to a widget being visible.
There are other reasons why a widget may not be visible.
Args:
widget: A widget that is a descendant of self.
widget: A widget.
Returns:
True if the entire widget is in view, False if it is partially visible or not in view.
`True` if the entire widget is in view, `False` if it is partially visible or not in view.
"""
if widget not in self._compositor.visible_widgets:
return False
# If the widget is one that overlays the screen...
if widget.styles.overlay == "screen":
# ...simply check if it's within the screen's region.
return widget.region in self.region
# Failing that fall back to normal checking.
return super().can_view_entire(widget)

def can_view_partial(self, widget: Widget) -> bool:
"""Check if a given widget is at least partially within the current view.
Args:
widget: A widget.
Returns:
`True` if the any part of the widget is in view, `False` if it is completely outside of the screen.
"""
if widget not in self._compositor.visible_widgets:
return False
# If the widget is one that overlays the screen...
if widget.styles.overlay == "screen":
# ...simply check if it's within the screen's region.
return widget.region in self.region
# Failing that fall back to normal checking.
return super().can_view(widget)
return super().can_view_partial(widget)

def validate_title(self, title: Any) -> str | None:
"""Ensure the title is a string or `None`."""
Expand Down
33 changes: 30 additions & 3 deletions src/textual/widget.py
Original file line number Diff line number Diff line change
Expand Up @@ -3303,8 +3303,8 @@ def scroll_to_center(
immediate=immediate,
)

def can_view(self, widget: Widget) -> bool:
"""Check if a given widget is in the current view (scrollable area).
def can_view_entire(self, widget: Widget) -> bool:
"""Check if a given widget is *fully* within the current view (scrollable area).
Note: This doesn't necessarily equate to a widget being visible.
There are other reasons why a widget may not be visible.
Expand All @@ -3313,11 +3313,38 @@ def can_view(self, widget: Widget) -> bool:
widget: A widget that is a descendant of self.
Returns:
True if the entire widget is in view, False if it is partially visible or not in view.
`True` if the entire widget is in view, `False` if it is partially visible or not in view.
"""
if widget is self:
return True

if widget not in self.screen._compositor.visible_widgets:
return False

region = widget.region
node: Widget = widget

while isinstance(node.parent, Widget) and node is not self:
if region not in node.parent.scrollable_content_region:
return False
node = node.parent
return True

def can_view_partial(self, widget: Widget) -> bool:
"""Check if a given widget at least partially visible within the current view (scrollable area).
Args:
widget: A widget that is a descendant of self.
Returns:
`True` if any part of the widget is visible, `False` if it is outside of the viewable area.
"""
if widget is self:
return True

if widget not in self.screen._compositor.visible_widgets or not widget.display:
return False

region = widget.region
node: Widget = widget

Expand Down

0 comments on commit e76e429

Please sign in to comment.