diff --git a/notes/README.md b/notes/README.md new file mode 100644 index 000000000..4577ddf9b --- /dev/null +++ b/notes/README.md @@ -0,0 +1,3 @@ +# Developer notes + +These are notes made by the developer, and _not_ to be considered documentation. diff --git a/notes/refresh.md b/notes/refresh.md new file mode 100644 index 000000000..25e94cdb4 --- /dev/null +++ b/notes/refresh.md @@ -0,0 +1,11 @@ +# Refresh system + +This note describes how Textual updates widgets on-screen. + +if widget has made some changes and wishes to update visuals it can call Widget.refresh. There are two flags on this method; `repaint` which will repaint just the widget, and `layout` which will re-layout the screen. A layout must be done if the widget has changed size / position / visibility. Otherwise repaint will refresh just the widget area. + +A refresh won't happen immediately when `refresh()` is called, rather it sets internal flags. The `on_idle` method of Widget checks these flags. This is so that multiple changes made to the UI while processing events don't cause excessive repainting of the screen (which makes the UI slow and jumpy). + +In the case of a repaint. The Widget.on_idle handler will emit (send to the parent) an UpdateMessage. This message will be handled by the parent view, which will update the widget (a particular part of the screen). + +In the case of a layout. The Widget.on_idle handler will emit a LayoutMessage. This message will be handled by the parent view, which calls refresh_layout on the root view, which will layout and repaint the entire screen. diff --git a/src/textual/geometry.py b/src/textual/geometry.py index 5f7bd1444..f057f998b 100644 --- a/src/textual/geometry.py +++ b/src/textual/geometry.py @@ -74,6 +74,7 @@ class Size(NamedTuple): height: int def __bool__(self) -> bool: + """A Size is Falsey if it has area 0""" return self.width * self.height != 0 @property @@ -193,10 +194,12 @@ class Region(NamedTuple): @property def x_max(self) -> int: + """Maximum X value (non inclusive)""" return self.x + self.width @property def y_max(self) -> int: + """Maximum Y value (non inclusive)""" return self.y + self.height @property @@ -226,10 +229,12 @@ class Region(NamedTuple): @property def x_range(self) -> range: + """A range object for X coordinates""" return range(self.x, self.x + self.width) @property def y_range(self) -> range: + """A range object for Y coordinates""" return range(self.y, self.y + self.height) def __add__(self, other: Any) -> Region: diff --git a/src/textual/messages.py b/src/textual/messages.py index f207c18f3..ba1b4876c 100644 --- a/src/textual/messages.py +++ b/src/textual/messages.py @@ -13,10 +13,9 @@ if TYPE_CHECKING: @rich.repr.auto class UpdateMessage(Message, verbosity=3): - def __init__(self, sender: MessagePump, widget: Widget, layout: bool = False): + def __init__(self, sender: MessagePump, widget: Widget): super().__init__(sender) self.widget = widget - self.layout = layout def __rich_repr__(self) -> rich.repr.Result: yield self.sender @@ -24,7 +23,7 @@ class UpdateMessage(Message, verbosity=3): def __eq__(self, other: object) -> bool: if isinstance(other, UpdateMessage): - return self.widget == other.widget and self.layout == other.layout + return self.widget == other.widget return NotImplemented def can_replace(self, message: Message) -> bool: diff --git a/src/textual/view.py b/src/textual/view.py index 83c7a08a5..cd758cbce 100644 --- a/src/textual/view.py +++ b/src/textual/view.py @@ -94,15 +94,12 @@ class View(Widget): widget = message.widget assert isinstance(widget, Widget) - if message.layout: - await self.root_view.refresh_layout() - self.log("LAYOUT") - display_update = self.root_view.layout.update_widget(self.console, widget) if display_update is not None: self.app.display(display_update) async def message_layout(self, message: LayoutMessage) -> None: + message.stop() await self.root_view.refresh_layout() self.app.refresh() diff --git a/src/textual/widget.py b/src/textual/widget.py index 42ebf994f..df0de0439 100644 --- a/src/textual/widget.py +++ b/src/textual/widget.py @@ -288,7 +288,7 @@ class Widget(MessagePump): elif self.check_repaint(): self.render_cache = None self.reset_check_repaint() - await self.emit(UpdateMessage(self, self, layout=False)) + await self.emit(UpdateMessage(self, self)) async def focus(self) -> None: await self.app.set_focus(self)