Skip to content

Commit d1b61d8

Browse files
committed
Layout root nodes once per loop
1 parent 55a8912 commit d1b61d8

File tree

3 files changed

+71
-4
lines changed

3 files changed

+71
-4
lines changed

changes/2938.feature.rst

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
Widget layout calculations are now deferred and performed once per event loop, instead of being done immediately when requested.

core/src/toga/widgets/base.py

Lines changed: 48 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,9 @@
1717
StyleT = TypeVar("StyleT", bound=BaseStyle)
1818

1919

20+
TAB = " "
21+
22+
2023
class Widget(Node):
2124
_MIN_WIDTH = 100
2225
_MIN_HEIGHT = 100
@@ -319,19 +322,60 @@ def enabled(self) -> bool:
319322
def enabled(self, value: bool) -> None:
320323
self._impl.set_enabled(bool(value))
321324

325+
_layouts = 0
326+
_level = 0
327+
322328
def refresh(self) -> None:
329+
name = type(self).__name__
330+
# print(TAB * self._level + f"Refresh requested on {name}")
331+
332+
if not self.window:
333+
# No need to do anything if the widget hasn't been added to a window.
334+
return
335+
323336
self._impl.refresh()
324337

325338
# Refresh the layout
326339
if self._root:
327340
# We're not the root of the node hierarchy;
328341
# defer the refresh call to the root node.
329342
self._root.refresh()
343+
330344
else:
331-
# We can't compute a layout until we have a container
332-
if self._impl.container:
333-
super().refresh(self._impl.container)
334-
self._impl.container.refreshed()
345+
# Uncomment to always compute layout:
346+
347+
# self._refresh_layout()
348+
# return
349+
350+
if self.window._currently_laying_out:
351+
self._refresh_layout()
352+
return
353+
354+
from pudb import set_trace
355+
356+
set_trace()
357+
print(TAB * self._level + f"Adding {name} to dirty set")
358+
self.window._dirty_root_widgets.add(self)
359+
360+
if self.window._pending_layout is None:
361+
self.window._pending_layout = self.app.loop.call_soon(
362+
self.window._refresh_layouts
363+
)
364+
365+
def _refresh_layout(self):
366+
# print(self._level)
367+
name = type(self).__name__
368+
369+
Widget._layouts += 1
370+
print(TAB * self._level + f"#{self._layouts}. Laying out {name}")
371+
372+
Widget._level += 1
373+
374+
super().refresh(self._impl.container)
375+
self._impl.container.refreshed()
376+
377+
Widget._level -= 1
378+
# print(TAB * self._level + f"Done laying out {name}\n")
335379

336380
def focus(self) -> None:
337381
"""Give this widget the input focus.

core/src/toga/window.py

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -208,6 +208,10 @@ def __init__(
208208
size=Size(*size),
209209
)
210210

211+
self._pending_layout = None
212+
self._dirty_root_widgets = set()
213+
self._currently_laying_out = False
214+
211215
# Add the window to the app
212216
App.app.windows.add(self)
213217

@@ -369,6 +373,24 @@ def content(self, widget: Widget) -> None:
369373
# Update the geometry of the widget
370374
widget.refresh()
371375

376+
def _refresh_layouts(self):
377+
from pudb import set_trace
378+
379+
set_trace()
380+
self._currently_laying_out = True
381+
382+
toga.Widget._level += 1
383+
print("\nLoop(")
384+
385+
while self._dirty_root_widgets:
386+
self._dirty_root_widgets.pop()._refresh_layout()
387+
388+
print(")\n")
389+
toga.Widget._level -= 1
390+
391+
self._currently_laying_out = False
392+
self._pending_layout = None
393+
372394
@property
373395
def widgets(self) -> FilteredWidgetRegistry:
374396
"""The widgets contained in the window.

0 commit comments

Comments
 (0)