mirror of
https://github.com/Textualize/textual.git
synced 2025-10-17 02:38:12 +03:00
Compositor ignores non-mounted widgets.
This, in turn, ensures widgets are not rendered before they are mounted.
This commit is contained in:
@@ -21,6 +21,7 @@ and this project adheres to [Semantic Versioning](http://semver.org/).
|
||||
- Off-by-one in CSS error reporting https://github.com/Textualize/textual/issues/3625
|
||||
- Loading indicators and app notifications overlapped in the wrong order https://github.com/Textualize/textual/issues/3677
|
||||
- Widgets being loaded are disabled and have their scrolling explicitly disabled too https://github.com/Textualize/textual/issues/3677
|
||||
- Method render on a widget could be called before mounting said widget https://github.com/Textualize/textual/issues/2914
|
||||
|
||||
### Added
|
||||
|
||||
|
||||
@@ -573,6 +573,9 @@ class Compositor:
|
||||
visible: Whether the widget should be visible by default.
|
||||
This may be overridden by the CSS rule `visibility`.
|
||||
"""
|
||||
if not widget._is_mounted:
|
||||
return
|
||||
|
||||
styles = widget.styles
|
||||
visibility = styles.get_rule("visibility")
|
||||
if visibility is not None:
|
||||
|
||||
@@ -2202,6 +2202,7 @@ class App(Generic[ReturnType], DOMNode):
|
||||
self.check_idle()
|
||||
finally:
|
||||
self._mounted_event.set()
|
||||
self._is_mounted = True
|
||||
|
||||
Reactive._initialize_object(self)
|
||||
|
||||
|
||||
@@ -122,6 +122,13 @@ class MessagePump(metaclass=_MessagePumpMeta):
|
||||
self._last_idle: float = time()
|
||||
self._max_idle: float | None = None
|
||||
self._mounted_event = asyncio.Event()
|
||||
self._is_mounted = False
|
||||
"""Having this explicit Boolean is an optimization.
|
||||
|
||||
The same information could be retrieved from `self._mounted_event.is_set()`, but
|
||||
we need to access this frequently in the compositor and the attribute with the
|
||||
explicit Boolean value is faster than the two lookups and the function call.
|
||||
"""
|
||||
self._next_callbacks: list[events.Callback] = []
|
||||
self._thread_id: int = threading.get_ident()
|
||||
|
||||
@@ -508,6 +515,7 @@ class MessagePump(metaclass=_MessagePumpMeta):
|
||||
finally:
|
||||
# This is critical, mount may be waiting
|
||||
self._mounted_event.set()
|
||||
self._is_mounted = True
|
||||
return True
|
||||
|
||||
def _post_mount(self):
|
||||
@@ -547,6 +555,7 @@ class MessagePump(metaclass=_MessagePumpMeta):
|
||||
raise
|
||||
except Exception as error:
|
||||
self._mounted_event.set()
|
||||
self._is_mounted = True
|
||||
self.app._handle_exception(error)
|
||||
break
|
||||
finally:
|
||||
|
||||
@@ -396,7 +396,7 @@ class Widget(DOMNode):
|
||||
@property
|
||||
def is_mounted(self) -> bool:
|
||||
"""Check if this widget is mounted."""
|
||||
return self._mounted_event.is_set()
|
||||
return self._is_mounted
|
||||
|
||||
@property
|
||||
def siblings(self) -> list[Widget]:
|
||||
|
||||
27
tests/test_mount.py
Normal file
27
tests/test_mount.py
Normal file
@@ -0,0 +1,27 @@
|
||||
"""Regression test for https://github.com/Textualize/textual/issues/2914
|
||||
|
||||
Make sure that calls to render only happen after a widget being mounted.
|
||||
"""
|
||||
|
||||
import asyncio
|
||||
|
||||
from textual.app import App
|
||||
from textual.widget import Widget
|
||||
|
||||
|
||||
class W(Widget):
|
||||
def render(self):
|
||||
return self.renderable
|
||||
|
||||
async def on_mount(self):
|
||||
await asyncio.sleep(0.1)
|
||||
self.renderable = "1234"
|
||||
|
||||
|
||||
async def test_render_only_after_mount():
|
||||
"""Regression test for https://github.com/Textualize/textual/issues/2914"""
|
||||
app = App()
|
||||
async with app.run_test() as pilot:
|
||||
app.mount(W())
|
||||
app.mount(W())
|
||||
await pilot.pause()
|
||||
Reference in New Issue
Block a user