mirror of
https://github.com/Textualize/textual.git
synced 2025-10-17 02:38:12 +03:00
simplify layout
This commit is contained in:
3
notes/README.md
Normal file
3
notes/README.md
Normal file
@@ -0,0 +1,3 @@
|
|||||||
|
# Developer notes
|
||||||
|
|
||||||
|
These are notes made by the developer, and _not_ to be considered documentation.
|
||||||
11
notes/refresh.md
Normal file
11
notes/refresh.md
Normal file
@@ -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.
|
||||||
@@ -74,6 +74,7 @@ class Size(NamedTuple):
|
|||||||
height: int
|
height: int
|
||||||
|
|
||||||
def __bool__(self) -> bool:
|
def __bool__(self) -> bool:
|
||||||
|
"""A Size is Falsey if it has area 0"""
|
||||||
return self.width * self.height != 0
|
return self.width * self.height != 0
|
||||||
|
|
||||||
@property
|
@property
|
||||||
@@ -193,10 +194,12 @@ class Region(NamedTuple):
|
|||||||
|
|
||||||
@property
|
@property
|
||||||
def x_max(self) -> int:
|
def x_max(self) -> int:
|
||||||
|
"""Maximum X value (non inclusive)"""
|
||||||
return self.x + self.width
|
return self.x + self.width
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def y_max(self) -> int:
|
def y_max(self) -> int:
|
||||||
|
"""Maximum Y value (non inclusive)"""
|
||||||
return self.y + self.height
|
return self.y + self.height
|
||||||
|
|
||||||
@property
|
@property
|
||||||
@@ -226,10 +229,12 @@ class Region(NamedTuple):
|
|||||||
|
|
||||||
@property
|
@property
|
||||||
def x_range(self) -> range:
|
def x_range(self) -> range:
|
||||||
|
"""A range object for X coordinates"""
|
||||||
return range(self.x, self.x + self.width)
|
return range(self.x, self.x + self.width)
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def y_range(self) -> range:
|
def y_range(self) -> range:
|
||||||
|
"""A range object for Y coordinates"""
|
||||||
return range(self.y, self.y + self.height)
|
return range(self.y, self.y + self.height)
|
||||||
|
|
||||||
def __add__(self, other: Any) -> Region:
|
def __add__(self, other: Any) -> Region:
|
||||||
|
|||||||
@@ -13,10 +13,9 @@ if TYPE_CHECKING:
|
|||||||
|
|
||||||
@rich.repr.auto
|
@rich.repr.auto
|
||||||
class UpdateMessage(Message, verbosity=3):
|
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)
|
super().__init__(sender)
|
||||||
self.widget = widget
|
self.widget = widget
|
||||||
self.layout = layout
|
|
||||||
|
|
||||||
def __rich_repr__(self) -> rich.repr.Result:
|
def __rich_repr__(self) -> rich.repr.Result:
|
||||||
yield self.sender
|
yield self.sender
|
||||||
@@ -24,7 +23,7 @@ class UpdateMessage(Message, verbosity=3):
|
|||||||
|
|
||||||
def __eq__(self, other: object) -> bool:
|
def __eq__(self, other: object) -> bool:
|
||||||
if isinstance(other, UpdateMessage):
|
if isinstance(other, UpdateMessage):
|
||||||
return self.widget == other.widget and self.layout == other.layout
|
return self.widget == other.widget
|
||||||
return NotImplemented
|
return NotImplemented
|
||||||
|
|
||||||
def can_replace(self, message: Message) -> bool:
|
def can_replace(self, message: Message) -> bool:
|
||||||
|
|||||||
@@ -94,15 +94,12 @@ class View(Widget):
|
|||||||
widget = message.widget
|
widget = message.widget
|
||||||
assert isinstance(widget, 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)
|
display_update = self.root_view.layout.update_widget(self.console, widget)
|
||||||
if display_update is not None:
|
if display_update is not None:
|
||||||
self.app.display(display_update)
|
self.app.display(display_update)
|
||||||
|
|
||||||
async def message_layout(self, message: LayoutMessage) -> None:
|
async def message_layout(self, message: LayoutMessage) -> None:
|
||||||
|
message.stop()
|
||||||
await self.root_view.refresh_layout()
|
await self.root_view.refresh_layout()
|
||||||
self.app.refresh()
|
self.app.refresh()
|
||||||
|
|
||||||
|
|||||||
@@ -288,7 +288,7 @@ class Widget(MessagePump):
|
|||||||
elif self.check_repaint():
|
elif self.check_repaint():
|
||||||
self.render_cache = None
|
self.render_cache = None
|
||||||
self.reset_check_repaint()
|
self.reset_check_repaint()
|
||||||
await self.emit(UpdateMessage(self, self, layout=False))
|
await self.emit(UpdateMessage(self, self))
|
||||||
|
|
||||||
async def focus(self) -> None:
|
async def focus(self) -> None:
|
||||||
await self.app.set_focus(self)
|
await self.app.set_focus(self)
|
||||||
|
|||||||
Reference in New Issue
Block a user