Skip to content

Commit 665610e

Browse files
committed
faster layout in stream
1 parent 46cc188 commit 665610e

File tree

5 files changed

+38
-11
lines changed

5 files changed

+38
-11
lines changed

src/textual/_arrange.py

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -53,7 +53,6 @@ def arrange(
5353
Returns:
5454
Widget arrangement information.
5555
"""
56-
print("ARRANGE", widget)
5756
placements: list[WidgetPlacement] = []
5857
scroll_spacing = NULL_SPACING
5958
styles = widget.styles

src/textual/layouts/stream.py

Lines changed: 21 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
from __future__ import annotations
22

3+
from itertools import zip_longest
34
from typing import TYPE_CHECKING
45

56
from textual.geometry import NULL_OFFSET, Region, Size
@@ -30,6 +31,9 @@ class StreamLayout(Layout):
3031

3132
name = "stream"
3233

34+
def __init__(self) -> None:
35+
self._cache: dict[object, list[WidgetPlacement]] = {}
36+
3337
def arrange(
3438
self, parent: Widget, children: list[Widget], size: Size, greedy: bool = True
3539
) -> ArrangeResult:
@@ -38,6 +42,10 @@ def arrange(
3842
return []
3943
viewport = parent.app.viewport_size
4044

45+
cache_key = (size, viewport)
46+
previous_results = self._cache.get(cache_key, None) or []
47+
layout_widgets = parent.screen._layout_widgets.get(parent, [])
48+
4149
_Region = Region
4250
_WidgetPlacement = WidgetPlacement
4351

@@ -48,7 +56,18 @@ def arrange(
4856
previous_margin = first_child_styles.margin.top
4957
null_offset = NULL_OFFSET
5058

51-
for widget in children:
59+
pre_populate = bool(previous_results and layout_widgets)
60+
for widget, placement in zip_longest(children, previous_results):
61+
if pre_populate and placement is not None and widget == placement.widget:
62+
if widget in layout_widgets:
63+
pre_populate = False
64+
else:
65+
placements.append(placement)
66+
y = placement.region.bottom
67+
continue
68+
if widget is None:
69+
break
70+
5271
styles = widget.styles._base_styles
5372
margin = styles.margin
5473
gutter_width, gutter_height = styles.gutter.totals
@@ -85,6 +104,7 @@ def arrange(
85104
)
86105
y += height
87106

107+
self._cache[cache_key] = placements
88108
return placements
89109

90110
def get_content_width(self, widget: Widget, container: Size, viewport: Size) -> int:

src/textual/layouts/vertical.py

Lines changed: 0 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -11,19 +11,15 @@
1111
from textual.geometry import Spacing
1212
from textual.widget import Widget
1313

14-
from textual._profile import timer
15-
1614

1715
class VerticalLayout(Layout):
1816
"""Used to layout Widgets vertically on screen, from top to bottom."""
1917

2018
name = "vertical"
2119

22-
@timer("Vertical.arrange")
2320
def arrange(
2421
self, parent: Widget, children: list[Widget], size: Size, greedy: bool = True
2522
) -> ArrangeResult:
26-
print(parent, size, greedy, len(children))
2723
parent.pre_layout(self)
2824
placements: list[WidgetPlacement] = []
2925
add_placement = placements.append

src/textual/reactive.py

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -362,8 +362,6 @@ def _set(self, obj: Reactable, value: ReactiveType, always: bool = False) -> Non
362362

363363
# Refresh according to descriptor flags
364364
if self._layout or self._repaint or self._recompose:
365-
if self._layout:
366-
print(self, "layout", obj)
367365
obj.refresh(
368366
repaint=self._repaint,
369367
layout=self._layout,

src/textual/screen.py

Lines changed: 17 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -321,7 +321,7 @@ def __init__(
321321
self._css_update_count = -1
322322
"""Track updates to CSS."""
323323

324-
self._layout_widgets: set[Widget] = set()
324+
self._layout_widgets: dict[DOMNode, set[Widget]] = {}
325325
"""Widgets whose layout may have changed."""
326326

327327
@property
@@ -1353,8 +1353,22 @@ async def _on_update(self, message: messages.Update) -> None:
13531353
async def _on_layout(self, message: messages.Layout) -> None:
13541354
message.stop()
13551355
message.prevent_default()
1356-
if message.widget not in self._layout_widgets:
1357-
self._layout_widgets.add(message.widget)
1356+
1357+
layout_required = True
1358+
widget: DOMNode = message.widget
1359+
for ancestor in message.widget.ancestors:
1360+
if not isinstance(ancestor, Widget):
1361+
break
1362+
if ancestor not in self._layout_widgets:
1363+
self._layout_widgets[ancestor] = set()
1364+
# assert isinstance(widget, Widget)
1365+
self._layout_widgets[ancestor].add(widget)
1366+
layout_required = True
1367+
if not ancestor.styles.auto_dimensions:
1368+
break
1369+
widget = ancestor
1370+
1371+
if layout_required:
13581372
self._layout_required = True
13591373
self.check_idle()
13601374

0 commit comments

Comments
 (0)