diff --git a/sandbox/uber.css b/sandbox/uber.css index 7739d920b..ee21135a9 100644 --- a/sandbox/uber.css +++ b/sandbox/uber.css @@ -10,7 +10,7 @@ /* height: 8; */ margin: 1 2; - text: on dark_blue; + text-background: dark_blue; } #child1 { diff --git a/sandbox/uber.py b/sandbox/uber.py index ecb28455c..1a1be59a3 100644 --- a/sandbox/uber.py +++ b/sandbox/uber.py @@ -8,6 +8,9 @@ from textual.widget import Widget class BasicApp(App): """Sandbox application used for testing/development by Textual developers""" + def on_load(self): + self.bind("q", "quit", "Quit") + def on_mount(self): """Build layout here.""" @@ -34,5 +37,8 @@ class BasicApp(App): async def on_key(self, event: events.Key) -> None: await self.dispatch_key(event) + def action_quit(self): + self.panic(self.screen.tree) + BasicApp.run(css_file="uber.css", log="textual.log") diff --git a/src/textual/_compositor.py b/src/textual/_compositor.py index 2240256f1..de3bf802b 100644 --- a/src/textual/_compositor.py +++ b/src/textual/_compositor.py @@ -43,6 +43,7 @@ class RenderRegion(NamedTuple): region: Region order: tuple[int, ...] clip: Region + virtual_size: Size RenderRegionMap: TypeAlias = dict[Widget, RenderRegion] @@ -133,7 +134,10 @@ class Compositor: self.width = size.width self.height = size.height - map, virtual_size, widgets = self._arrange_root(parent) + # TODO: Handle virtual size + map, widgets = self._arrange_root(parent) + + log(map) self._require_update = False @@ -149,7 +153,7 @@ class Compositor: # Copy renders if the size hasn't changed new_renders = { - widget: (region, clip) for widget, (region, _order, clip) in map.items() + widget: (region, clip) for widget, (region, _order, clip, _) in map.items() } self.regions = new_renders @@ -160,14 +164,13 @@ class Compositor: if widget in old_widgets and widget.size != region.size } - parent.virtual_size = virtual_size self.widgets.clear() self.widgets.update(widgets) return ReflowResult( hidden=hidden_widgets, shown=shown_widgets, resized=resized_widgets ) - def _arrange_root(self, root: Widget) -> tuple[RenderRegionMap, Size, set[Widget]]: + def _arrange_root(self, root: Widget) -> tuple[RenderRegionMap, set[Widget]]: """Arrange a widgets children based on its layout attribute. Args: @@ -177,9 +180,9 @@ class Compositor: map[dict[Widget, RenderRegion], Size]: A mapping of widget on to render region and the "virtual size" (scrollable reason) """ - size = Size(self.width, self.height) - ORIGIN = Offset(0, 0) + ORIGIN = Offset(0, 0) + size = root.size map: dict[Widget, RenderRegion] = {} widgets: set[Widget] = set() @@ -188,7 +191,7 @@ class Compositor: region: Region, order: tuple[int, ...], clip: Region, - ): + ) -> None: widgets.add(widget) styles_offset = widget.styles.offset total_region = region @@ -197,7 +200,6 @@ class Compositor: if styles_offset else ORIGIN ) - map[widget] = RenderRegion(region + layout_offset, order, clip) if widget.layout is not None: scroll = widget.scroll @@ -220,17 +222,21 @@ class Compositor: sub_widget.z + (z,), sub_clip, ) - return total_region.size - virtual_size = add_widget(root, size.region, (), size.region) - return map, virtual_size, widgets + map[widget] = RenderRegion( + region + layout_offset, order, clip, total_region.size + ) + + add_widget(root, size.region, (), size.region) + + return map, widgets async def mount_all(self, screen: Screen) -> None: screen.app.mount(*self.widgets) def __iter__(self) -> Iterator[tuple[Widget, Region, Region]]: layers = sorted(self.map.items(), key=lambda item: item[1].order, reverse=True) - for widget, (region, order, clip) in layers: + for widget, (region, _order, clip, _) in layers: yield widget, region.intersection(clip), region def get_offset(self, widget: Widget) -> Offset: @@ -312,7 +318,7 @@ class Compositor: screen_region = Region(0, 0, width, height) cuts = [[0, width] for _ in range(height)] - for region, order, clip in self.map.values(): + for region, order, clip, _ in self.map.values(): region = region.intersection(clip) if region and (region in screen_region): region_cuts = (region.x, region.x + region.width) @@ -337,7 +343,7 @@ class Compositor: widget_regions = sorted( [ (widget, region, order, clip) - for widget, (region, order, clip) in self.map.items() + for widget, (region, order, clip, _) in self.map.items() if widget.is_visual and widget.visible ], key=itemgetter(2), @@ -347,12 +353,11 @@ class Compositor: widget_regions = [] for widget, region, _order, clip in widget_regions: - - lines = widget._get_lines() - if region in clip: + lines = widget._get_lines() yield region, clip, lines elif clip.overlaps(region): + lines = widget._get_lines() new_region = region.intersection(clip) delta_x = new_region.x - region.x delta_y = new_region.y - region.y diff --git a/src/textual/dom.py b/src/textual/dom.py index 56a3408c3..b1e37298f 100644 --- a/src/textual/dom.py +++ b/src/textual/dom.py @@ -265,6 +265,7 @@ class DOMNode(MessagePump): Tree: A Rich object which may be printed. """ from rich.columns import Columns + from rich.console import Group from rich.panel import Panel highlighter = ReprHighlighter() @@ -272,21 +273,27 @@ class DOMNode(MessagePump): def add_children(tree, node): for child in node.node_list: - cols = [ - Pretty(child), - Text(f"{child.size.width} X {child.size.height}", style="dim"), - ] + info = Columns( + [ + Pretty(child), + highlighter(f"region={child.region!r}"), + highlighter( + f"virtual_size={child.virtual_size!r}", + ), + ] + ) css = child.styles.css if css: - cols.append( - Panel( + info = Group( + info, + Panel.fit( Text(child.styles.css), border_style="dim", title="css", title_align="left", - ) - ), - branch = tree.add(Columns(cols)) + ), + ) + branch = tree.add(info) if tree.children: add_children(branch, child) diff --git a/src/textual/geometry.py b/src/textual/geometry.py index 996ced0cb..5d0d65601 100644 --- a/src/textual/geometry.py +++ b/src/textual/geometry.py @@ -111,11 +111,6 @@ class Size(NamedTuple): """A Size is Falsy if it has area 0.""" return self.width * self.height != 0 - @property - def clamped(self) -> Size: - width, height = self - return Size(max(0, width), max(0, height)) - @property def area(self) -> int: """Get the area of the size. diff --git a/src/textual/widget.py b/src/textual/widget.py index 1b023c956..75b1c2f5f 100644 --- a/src/textual/widget.py +++ b/src/textual/widget.py @@ -29,7 +29,7 @@ from ._callback import invoke from ._context import active_app from ._types import Lines from .dom import DOMNode -from .geometry import Offset, Size +from .geometry import Offset, Region, Size from .message import Message from . import messages from .layout import Layout @@ -97,7 +97,7 @@ class Widget(DOMNode): if self.name: yield "name", self.name if self.classes: - yield "classes", self.classes + yield "classes", set(self.classes) pseudo_classes = self.pseudo_classes if pseudo_classes: yield "pseudo_classes", pseudo_classes @@ -159,6 +159,10 @@ class Widget(DOMNode): def size(self) -> Size: return self._size + @property + def region(self) -> Region: + return self.screen._compositor.get_widget_region(self) + @property def scroll(self) -> Offset: return Offset(self.scroll_x, self.scroll_y) @@ -265,7 +269,7 @@ class Widget(DOMNode): # Default displays a pretty repr in the center of the screen - label = f"{self.css_identifier_styled} {self.size}" + label = f"{self.css_identifier_styled} {self.size} {self.virtual_size}" return Align.center(label, vertical="middle")