simplify layout

This commit is contained in:
Will McGugan
2021-08-27 11:45:59 +01:00
parent 1e24aeadfd
commit c492df381e
6 changed files with 23 additions and 8 deletions

3
notes/README.md Normal file
View 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
View 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.

View File

@@ -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:

View File

@@ -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:

View File

@@ -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()

View File

@@ -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)