diff --git a/src/textual/_compositor.py b/src/textual/_compositor.py index 1611f51bf..758b9d65c 100644 --- a/src/textual/_compositor.py +++ b/src/textual/_compositor.py @@ -26,6 +26,7 @@ from rich.style import Style from . import errors from .geometry import Region, Offset, Size +from ._profile import timer from ._loop import loop_last from ._segment_tools import line_crop from ._types import Lines @@ -214,6 +215,7 @@ class Compositor: Returns: ReflowResult: Hidden shown and resized widgets """ + print("REFLOW") self._cuts = None self.root = parent self.size = size @@ -327,9 +329,7 @@ class Compositor: total_region = child_region.reset_origin # Arrange the layout - placements, arranged_widgets = widget.layout.arrange( - widget, child_region.size - ) + placements, arranged_widgets = widget._arrange(child_region.size) widgets.update(arranged_widgets) placements = sorted(placements, key=get_order) @@ -557,6 +557,7 @@ class Compositor: ] return segment_lines + @timer("render") def render(self, full: bool = False) -> RenderableType: """Render a layout. diff --git a/src/textual/_layout.py b/src/textual/_layout.py index 704b099b4..f652a6f4a 100644 --- a/src/textual/_layout.py +++ b/src/textual/_layout.py @@ -84,6 +84,6 @@ class Layout(ABC): if not widget.displayed_children: height = container.height else: - placements, widgets = self.arrange(widget, Size(width, container.height)) + placements, widgets = widget._arrange(Size(width, container.height)) height = max(placement.region.y_max for placement in placements) return height diff --git a/src/textual/geometry.py b/src/textual/geometry.py index 2fab41600..fbe6db3d1 100644 --- a/src/textual/geometry.py +++ b/src/textual/geometry.py @@ -665,7 +665,17 @@ class Spacing(NamedTuple): @classmethod def unpack(cls, pad: SpacingDimensions) -> Spacing: - """Unpack padding specified in CSS style.""" + """Unpack padding specified in CSS style. + + Args: + pad (SpacingDimensions): An integer, or tuple of 1, 2, or 4 integers. + + Raises: + ValueError: If `pad` is an invalid value. + + Returns: + Spacing: New Spacing object. + """ if isinstance(pad, int): return cls(pad, pad, pad, pad) pad_len = len(pad) diff --git a/src/textual/widget.py b/src/textual/widget.py index d7f776e22..24439f950 100644 --- a/src/textual/widget.py +++ b/src/textual/widget.py @@ -23,10 +23,12 @@ from . import errors from . import events from ._animator import BoundAnimator from ._border import Border +from ._profile import timer from .box_model import BoxModel, get_box_model from ._context import active_app from ._types import Lines from .dom import DOMNode +from ._layout import ArrangeResult from .geometry import clamp, Offset, Region, Size from .layouts.vertical import VerticalLayout from .message import Message @@ -101,6 +103,9 @@ class Widget(DOMNode): self._content_width_cache: tuple[object, int] = (None, 0) self._content_height_cache: tuple[object, int] = (None, 0) + self._arrangement: ArrangeResult | None = None + self._arrangement_size: Size = Size() + super().__init__(name=name, id=id, classes=classes) self.add_children(*children) @@ -116,6 +121,13 @@ class Widget(DOMNode): show_vertical_scrollbar = Reactive(False, layout=True) show_horizontal_scrollbar = Reactive(False, layout=True) + def _arrange(self, size: Size) -> ArrangeResult: + if self._arrangement is not None and size == self._arrangement_size: + return self._arrangement + self._arrangement = self.layout.arrange(self, size) + self._arrangement_size = size + return self._arrangement + def watch_show_horizontal_scrollbar(self, value: bool) -> None: """Watch function for show_horizontal_scrollbar attribute. @@ -611,19 +623,17 @@ class Widget(DOMNode): """Arrange the 'chrome' widgets (typically scrollbars) for a layout element. Args: - size (Size): _description_ + size (Size): Size of the containing region. Returns: - Iterable[tuple[Widget, Region]]: _description_ + Iterable[tuple[Widget, Region]]: Tuples of scrollbar Widget and region. - Yields: - Iterator[Iterable[tuple[Widget, Region]]]: _description_ """ region = size.region show_vertical_scrollbar, show_horizontal_scrollbar = self.scrollbars_enabled - horizontal_scrollbar_thickness = self.scrollbar_size_horizontal - vertical_scrollbar_thickness = self.scrollbar_size_vertical + scrollbar_size_horizontal = self.scrollbar_size_horizontal + scrollbar_size_vertical = self.scrollbar_size_vertical if show_horizontal_scrollbar and show_vertical_scrollbar: ( _, @@ -631,20 +641,19 @@ class Widget(DOMNode): horizontal_scrollbar_region, _, ) = region.split( - -vertical_scrollbar_thickness, -horizontal_scrollbar_thickness + -scrollbar_size_vertical, + -scrollbar_size_horizontal, ) if vertical_scrollbar_region: yield self.vertical_scrollbar, vertical_scrollbar_region if horizontal_scrollbar_region: yield self.horizontal_scrollbar, horizontal_scrollbar_region elif show_vertical_scrollbar: - _, scrollbar_region = region.split_vertical(-vertical_scrollbar_thickness) + _, scrollbar_region = region.split_vertical(-scrollbar_size_vertical) if scrollbar_region: yield self.vertical_scrollbar, scrollbar_region elif show_horizontal_scrollbar: - _, scrollbar_region = region.split_horizontal( - -horizontal_scrollbar_thickness - ) + _, scrollbar_region = region.split_horizontal(-scrollbar_size_horizontal) if scrollbar_region: yield self.horizontal_scrollbar, scrollbar_region @@ -838,11 +847,11 @@ class Widget(DOMNode): """ if self._dirty_regions: self._render_lines() - if self.is_container: - if self.show_horizontal_scrollbar: - self.horizontal_scrollbar.refresh() - if self.show_vertical_scrollbar: - self.vertical_scrollbar.refresh() + # if self.is_container: + # if self.show_horizontal_scrollbar: + # self.horizontal_scrollbar.refresh() + # if self.show_vertical_scrollbar: + # self.vertical_scrollbar.refresh() lines = self._render_cache.lines[start:end] return lines