diff --git a/examples/code_viewer.py b/examples/code_viewer.py index 6fbdbafbf..9a0175e8e 100644 --- a/examples/code_viewer.py +++ b/examples/code_viewer.py @@ -29,7 +29,9 @@ class MyApp(App): await self.view.dock(Header(), edge="top") await self.view.dock(Footer(), edge="bottom") - await self.view.dock(self.directory, edge="left", size=32, name="sidebar") + await self.view.dock( + ScrollView(self.directory), edge="left", size=32, name="sidebar" + ) await self.view.dock(self.body, edge="right") async def message_file_click(self, message: FileClick) -> None: diff --git a/src/textual/events.py b/src/textual/events.py index f9edeab39..cdb3b9b83 100644 --- a/src/textual/events.py +++ b/src/textual/events.py @@ -127,7 +127,7 @@ class Show(Event): class Hide(Event): - """Send when a widget has been hidden. + """Sent when a widget has been hidden. A widget may be hidden by setting its `visible` flag to `False`, if it is no longer in a layout, or if it has been offset beyond the edges of the terminal. diff --git a/src/textual/layout_map.py b/src/textual/layout_map.py index 4855efa6b..be42e610d 100644 --- a/src/textual/layout_map.py +++ b/src/textual/layout_map.py @@ -17,12 +17,13 @@ class RenderRegion(NamedTuple): class LayoutMap: def __init__(self, size: Dimensions) -> None: - self.region = size.region + self.size = size + self.contents_region = Region(0, 0, 0, 0) self.widgets: dict[Widget, RenderRegion] = {} @property - def size(self) -> Dimensions: - return self.region.size + def virtual_size(self) -> Dimensions: + return self.contents_region.size def __getitem__(self, widget: Widget) -> RenderRegion: return self.widgets[widget] @@ -51,7 +52,7 @@ class LayoutMap: region += widget.layout_offset self.widgets[widget] = RenderRegion(region, order, clip) - self.region = self.region.union(region) + self.contents_region = self.contents_region.union(region) if isinstance(widget, View): sub_map = widget.layout.generate_map( diff --git a/src/textual/view.py b/src/textual/view.py index 35c51156d..358fc109e 100644 --- a/src/textual/view.py +++ b/src/textual/view.py @@ -56,9 +56,11 @@ class View(Widget): def scroll(self) -> Offset: return Offset(self.scroll_x, self.scroll_y) - @property - def virtual_size(self) -> Dimensions: - return self.layout.map.size if self.layout.map else Dimensions(0, 0) + virtual_size: Reactive[Dimensions] = Reactive(Dimensions(0, 0)) + + # @property + # def virtual_size(self) -> Dimensions: + # return self.layout.map.size if self.layout.map else Dimensions(0, 0) # virtual_width: Reactive[int | None] = Reactive(None) # virtual_height: Reactive[int | None] = Reactive(None) @@ -157,6 +159,7 @@ class View(Widget): hidden, shown, resized = self.layout.reflow( self.console, width, height, self.scroll ) + self.virtual_size = self.layout.map.virtual_size self.app.refresh() for widget in hidden: diff --git a/src/textual/views/_window_view.py b/src/textual/views/_window_view.py index 57941a74d..36dde2da2 100644 --- a/src/textual/views/_window_view.py +++ b/src/textual/views/_window_view.py @@ -2,12 +2,18 @@ from __future__ import annotations from rich.console import RenderableType +from ..geometry import Offset, Dimensions from ..layouts.vertical import VerticalLayout from ..view import View +from ..message import Message from ..widget import Widget from ..widgets import Static +class VirtualSizeChange(Message): + pass + + class WindowView(View, layout=VerticalLayout): def __init__( self, @@ -26,4 +32,8 @@ class WindowView(View, layout=VerticalLayout): assert isinstance(layout, VerticalLayout) layout.clear() layout.add(widget if isinstance(widget, Widget) else Static(widget)) + await self.refresh_layout() self.require_layout() + + async def watch_virtual_size(self, size: Dimensions) -> None: + await self.emit(VirtualSizeChange(self)) diff --git a/src/textual/widgets/_scroll_view.py b/src/textual/widgets/_scroll_view.py index 9e978423f..bf9066bab 100644 --- a/src/textual/widgets/_scroll_view.py +++ b/src/textual/widgets/_scroll_view.py @@ -9,11 +9,11 @@ from .. import events from ..layouts.grid import GridLayout from ..message import Message from ..scrollbar import ScrollTo, ScrollBar -from ..geometry import clamp +from ..geometry import clamp, Offset, Dimensions from ..page import Page +from ..reactive import watch from ..view import View -from ..widgets import Placeholder - +from ..widget import Widget from ..reactive import Reactive @@ -21,7 +21,7 @@ from ..reactive import Reactive class ScrollView(View): def __init__( self, - renderable: RenderableType | None = None, + contents: RenderableType | Widget | None = None, *, name: str | None = None, style: StyleType = "", @@ -32,7 +32,7 @@ class ScrollView(View): self.fluid = fluid self.vscroll = ScrollBar(vertical=True) self.hscroll = ScrollBar(vertical=False) - self.window = WindowView("" if renderable is None else renderable) + self.window = WindowView("" if contents is None else contents) layout = GridLayout() layout.add_column("main") layout.add_column("vscroll", size=1) @@ -70,9 +70,11 @@ class ScrollView(View): async def watch_y(self, new_value: float) -> None: self.window.scroll_y = round(new_value) self.vscroll.position = round(new_value) + # self.window.require_repaint() + self.window.require_layout() - async def update(self, renderabe: RenderableType) -> None: - await self.window.update(renderabe) + async def update(self, renderable: RenderableType) -> None: + await self.window.update(renderable) async def on_mount(self, event: events.Mount) -> None: assert isinstance(self.layout, GridLayout) @@ -172,19 +174,17 @@ class ScrollView(View): self.animate("x", self.target_x, speed=150, easing="out_cubic") self.animate("y", self.target_y, speed=150, easing="out_cubic") - async def message_page_update(self, message: Message) -> None: + async def message_virtual_size_change(self, message: Message) -> None: + virtual_size = self.window.virtual_size + self.log("VIRTUAL_SIZE", self.size, virtual_size) self.x = self.validate_x(self.x) self.y = self.validate_y(self.y) - self.vscroll.virtual_size = self.window.virtual_size.height + self.vscroll.virtual_size = virtual_size.height self.vscroll.window_size = self.size.height assert isinstance(self.layout, GridLayout) - if self.layout.show_column( - "vscroll", self.window.virtual_size.height > self.size.height - ): + if self.layout.show_column("vscroll", virtual_size.height > self.size.height): self.require_layout() - if self.layout.show_row( - "hscroll", self.window.virtual_size.width > self.size.width - ): + if self.layout.show_row("hscroll", virtual_size.width > self.size.width): self.require_layout()