Skip to content

Commit

Permalink
Layout root nodes once per loop
Browse files Browse the repository at this point in the history
  • Loading branch information
HalfWhitt committed Dec 29, 2024
1 parent 55a8912 commit d1b61d8
Show file tree
Hide file tree
Showing 3 changed files with 71 additions and 4 deletions.
1 change: 1 addition & 0 deletions changes/2938.feature.rst
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
Widget layout calculations are now deferred and performed once per event loop, instead of being done immediately when requested.
52 changes: 48 additions & 4 deletions core/src/toga/widgets/base.py
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,9 @@
StyleT = TypeVar("StyleT", bound=BaseStyle)


TAB = " "


class Widget(Node):
_MIN_WIDTH = 100
_MIN_HEIGHT = 100
Expand Down Expand Up @@ -319,19 +322,60 @@ def enabled(self) -> bool:
def enabled(self, value: bool) -> None:
self._impl.set_enabled(bool(value))

_layouts = 0
_level = 0

def refresh(self) -> None:
name = type(self).__name__
# print(TAB * self._level + f"Refresh requested on {name}")

if not self.window:
# No need to do anything if the widget hasn't been added to a window.
return

self._impl.refresh()

# Refresh the layout
if self._root:
# We're not the root of the node hierarchy;
# defer the refresh call to the root node.
self._root.refresh()

else:
# We can't compute a layout until we have a container
if self._impl.container:
super().refresh(self._impl.container)
self._impl.container.refreshed()
# Uncomment to always compute layout:

# self._refresh_layout()
# return

if self.window._currently_laying_out:
self._refresh_layout()
return

from pudb import set_trace

set_trace()
print(TAB * self._level + f"Adding {name} to dirty set")
self.window._dirty_root_widgets.add(self)

if self.window._pending_layout is None:
self.window._pending_layout = self.app.loop.call_soon(
self.window._refresh_layouts
)

def _refresh_layout(self):
# print(self._level)
name = type(self).__name__

Widget._layouts += 1
print(TAB * self._level + f"#{self._layouts}. Laying out {name}")

Widget._level += 1

super().refresh(self._impl.container)
self._impl.container.refreshed()

Widget._level -= 1
# print(TAB * self._level + f"Done laying out {name}\n")

def focus(self) -> None:
"""Give this widget the input focus.
Expand Down
22 changes: 22 additions & 0 deletions core/src/toga/window.py
Original file line number Diff line number Diff line change
Expand Up @@ -208,6 +208,10 @@ def __init__(
size=Size(*size),
)

self._pending_layout = None
self._dirty_root_widgets = set()
self._currently_laying_out = False

# Add the window to the app
App.app.windows.add(self)

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

def _refresh_layouts(self):
from pudb import set_trace

set_trace()
self._currently_laying_out = True

toga.Widget._level += 1
print("\nLoop(")

while self._dirty_root_widgets:
self._dirty_root_widgets.pop()._refresh_layout()

print(")\n")
toga.Widget._level -= 1

self._currently_laying_out = False
self._pending_layout = None

@property
def widgets(self) -> FilteredWidgetRegistry:
"""The widgets contained in the window.
Expand Down

0 comments on commit d1b61d8

Please sign in to comment.