event verbosity

This commit is contained in:
Will McGugan
2021-08-02 16:53:46 +01:00
parent 79e4a6003e
commit 3e7eb0e650
13 changed files with 52 additions and 102 deletions

View File

@@ -35,7 +35,7 @@ class GridTest(App):
area3=Placeholder(name="area3"), area3=Placeholder(name="area3"),
area4=Placeholder(name="area4"), area4=Placeholder(name="area4"),
) )
await self.view.update_layout() self.view.refresh(layout=True)
GridTest.run(title="Grid Test", log="textual.log") GridTest.run(title="Grid Test", log="textual.log")

View File

@@ -253,6 +253,7 @@ class App(MessagePump):
active_app.set(self) active_app.set(self)
driver = self._driver = self.driver_class(self.console, self) driver = self._driver = self.driver_class(self.console, self)
log("---")
log(f"driver={self.driver_class}") log(f"driver={self.driver_class}")
await self.dispatch_message(events.Load(sender=self)) await self.dispatch_message(events.Load(sender=self))
@@ -332,7 +333,7 @@ class App(MessagePump):
driver.disable_input() driver.disable_input()
await self.close_messages() await self.close_messages()
def refresh(self) -> None: def refresh(self, repaint: bool = True, layout: bool = False) -> None:
sync_available = os.environ.get("TERM_PROGRAM", "") != "Apple_Terminal" sync_available = os.environ.get("TERM_PROGRAM", "") != "Apple_Terminal"
if not self._closed: if not self._closed:
console = self.console console = self.console

View File

@@ -27,13 +27,13 @@ class Event(Message):
super().__init_subclass__(bubble=bubble, verbosity=verbosity) super().__init_subclass__(bubble=bubble, verbosity=verbosity)
class Null(Event): class Null(Event, verbosity=3):
def can_replace(self, message: Message) -> bool: def can_replace(self, message: Message) -> bool:
return isinstance(message, Null) return isinstance(message, Null)
@rich.repr.auto @rich.repr.auto
class Callback(Event, bubble=False): class Callback(Event, bubble=False, verbosity=3):
def __init__( def __init__(
self, sender: MessageTarget, callback: Callable[[], Awaitable[None]] self, sender: MessageTarget, callback: Callable[[], Awaitable[None]]
) -> None: ) -> None:
@@ -88,7 +88,7 @@ class Action(Event, bubble=True):
yield "action", self.action yield "action", self.action
class Resize(Event): class Resize(Event, verbosity=2):
"""Sent when the app or widget has been resized.""" """Sent when the app or widget has been resized."""
__slots__ = ["size"] __slots__ = ["size"]
@@ -360,7 +360,7 @@ class DoubleClick(MouseEvent):
@rich.repr.auto @rich.repr.auto
class Timer(Event): class Timer(Event, verbosity=3):
__slots__ = ["time", "count", "callback"] __slots__ = ["time", "count", "callback"]
def __init__( def __init__(

View File

@@ -421,7 +421,6 @@ class Layout(ABC):
def update_widget(self, console: Console, widget: Widget) -> LayoutUpdate | None: def update_widget(self, console: Console, widget: Widget) -> LayoutUpdate | None:
log("UPDATE", widget)
if widget not in self.regions: if widget not in self.regions:
return None return None
@@ -430,18 +429,7 @@ class Layout(ABC):
if not region.size: if not region.size:
return None return None
widget._clear_render_cache() widget.clear_render_cache()
# if not region or not clip:
# return
# widget._clear_render_cache()
# widget.render_lines()
# new_lines = console.render_lines(
# widget, console.options.update_dimensions(region.width, region.height)
# )
# self.regions[widget] = (region, clip, new_lines)
update_region = region.intersection(clip) update_region = region.intersection(clip)
update_lines = self.render(console, update_region).lines update_lines = self.render(console, update_region).lines

View File

@@ -58,7 +58,7 @@ class LayoutMap:
sub_map = widget.layout.generate_map( sub_map = widget.layout.generate_map(
console, region.size, clip, widget.scroll console, region.size, clip, widget.scroll
) )
for widget, (sub_region, sub_order, sub_clip) in sub_map.items(): for sub_widget, (sub_region, sub_order, sub_clip) in sub_map.items():
sub_region += region.origin sub_region += region.origin
sub_clip = sub_clip.intersection(clip) sub_clip = sub_clip.intersection(clip)
self.add_widget(console, widget, sub_region, sub_order, sub_clip) self.add_widget(console, sub_widget, sub_region, sub_order, sub_clip)

View File

@@ -3,6 +3,7 @@ from __future__ import annotations
import asyncio import asyncio
from asyncio import CancelledError from asyncio import CancelledError
from asyncio import Queue, QueueEmpty, Task from asyncio import Queue, QueueEmpty, Task
import inspect
from typing import TYPE_CHECKING, Awaitable, Iterable, Callable from typing import TYPE_CHECKING, Awaitable, Iterable, Callable
from weakref import WeakSet from weakref import WeakSet
@@ -24,6 +25,10 @@ class NoParent(Exception):
pass pass
class CallbackError(Exception):
pass
class MessagePumpClosed(Exception): class MessagePumpClosed(Exception):
pass pass
@@ -301,4 +306,11 @@ class MessagePump:
event.prevent_default() event.prevent_default()
event.stop() event.stop()
if event.callback is not None: if event.callback is not None:
await event.callback() try:
callback_result = event.callback()
if inspect.isawaitable(callback_result):
await callback_result
except Exception as error:
raise CallbackError(
f"unable to run callback {event.callback!r}; {error}"
)

View File

@@ -12,7 +12,7 @@ if TYPE_CHECKING:
@rich.repr.auto @rich.repr.auto
class UpdateMessage(Message): class UpdateMessage(Message, verbosity=3):
def __init__( def __init__(
self, self,
sender: MessagePump, sender: MessagePump,
@@ -30,6 +30,6 @@ class UpdateMessage(Message):
@rich.repr.auto @rich.repr.auto
class LayoutMessage(Message): class LayoutMessage(Message, verbosity=3):
def can_replace(self, message: Message) -> bool: def can_replace(self, message: Message) -> bool:
return isinstance(message, LayoutMessage) return isinstance(message, LayoutMessage)

View File

@@ -79,9 +79,9 @@ class Reactive(Generic[ReactiveType]):
self.check_watchers(obj, name, current_value) self.check_watchers(obj, name, current_value)
if self.layout: if self.layout:
obj.require_layout() obj.refresh(layout=True)
elif self.repaint: elif self.repaint:
obj.require_repaint() obj.refresh()
@classmethod @classmethod
def check_watchers(cls, obj: Reactable, name: str, old_value: Any) -> None: def check_watchers(cls, obj: Reactable, name: str, old_value: Any) -> None:

View File

@@ -57,40 +57,6 @@ class View(Widget):
virtual_size: Reactive[Size] = Reactive(Size(0, 0)) virtual_size: Reactive[Size] = Reactive(Size(0, 0))
# @property
# def virtual_size(self) -> Dimensions:
# return self.layout.map.size if self.layout.map else Dimensions(0, 0)
# virtual_width: Reactive[int | None] = Reactive(None)
# virtual_height: Reactive[int | None] = Reactive(None)
# @property
# def virtual_size(self) -> Dimensions:
# virtual_width = self.virtual_width
# virtual_height = self.virtual_height
# return Dimensions(
# (virtual_width if virtual_width is not None else self.size.width),
# (virtual_height if virtual_height is not None else self.size.height),
# )
# @virtual_size.setter
# def virtual_size(self, size: tuple[int, int]) -> None:
# width, height = size
# self.virtual_width = width
# self.virtual_height = height
# @property
# def offset(self) -> Point:
# return Point(self.offset_x, self.offset_y)
# @property
# def viewport(self) -> Region:
# virtual_width = self.virtual_width
# virtual_height = self.virtual_height
# width = virtual_width if virtual_width is not None else self.size.width
# height = virtual_height if virtual_height is not None else self.size.height
# return Region(self.offset_x, self.offset_y, width, height)
def __rich_console__( def __rich_console__(
self, console: Console, options: ConsoleOptions self, console: Console, options: ConsoleOptions
) -> RenderResult: ) -> RenderResult:
@@ -109,7 +75,7 @@ class View(Widget):
@property @property
def is_root_view(self) -> bool: def is_root_view(self) -> bool:
return self._parent and self.parent is self.app return bool(self._parent and self.parent is self.app)
def is_mounted(self, widget: Widget) -> bool: def is_mounted(self, widget: Widget) -> bool:
return widget in self.widgets return widget in self.widgets
@@ -159,16 +125,12 @@ class View(Widget):
return return
width, height = self.console.size width, height = self.console.size
# virtual_width, virtual_height = self.virtual_size
hidden, shown, resized = self.layout.reflow( hidden, shown, resized = self.layout.reflow(
self.console, width, height, self.scroll self.console, width, height, self.scroll
) )
assert self.layout.map is not None assert self.layout.map is not None
self.virtual_size = self.layout.map.virtual_size # self.virtual_size = self.layout.map.virtual_size
# for widget, region in self.layout: self.log("VIRTUAL_SIZE", self, type(self.layout), self.virtual_size)
# widget._update_size(region.size)
# self.app.refresh()
for widget in hidden: for widget in hidden:
widget.post_message_no_wait(events.Hide(self)) widget.post_message_no_wait(events.Hide(self))

View File

@@ -40,8 +40,9 @@ class WindowView(View, layout=VerticalLayout):
await self.emit(VirtualSizeChange(self)) await self.emit(VirtualSizeChange(self))
async def watch_virtual_size(self, size: Size) -> None: async def watch_virtual_size(self, size: Size) -> None:
self.log("VIRTUAL SIZE CHAGE")
await self.emit(VirtualSizeChange(self)) await self.emit(VirtualSizeChange(self))
async def on_resize(self, event: events.Resize) -> None: # async def on_resize(self, event: events.Resize) -> None:
# self.layout.renders.pop(self.widget) # # self.layout.renders.pop(self.widget)
self.require_repaint() # self.require_repaint()

View File

@@ -123,10 +123,6 @@ class Widget(MessagePump):
def _update_size(self, size: Size) -> None: def _update_size(self, size: Size) -> None:
self._size = size self._size = size
# if self._render_cache and self._render_cache.size != size:
# self.render_lines()
# self.require_repaint()
# self.size = size
def render_lines(self) -> RenderCache: def render_lines(self) -> RenderCache:
width, height = self.size width, height = self.size
@@ -161,7 +157,7 @@ class Widget(MessagePump):
lines = self.render_cache.lines lines = self.render_cache.lines
return lines return lines
def _clear_render_cache(self) -> None: def clear_render_cache(self) -> None:
self.render_cache = None self.render_cache = None
def require_repaint(self) -> None: def require_repaint(self) -> None:
@@ -199,17 +195,22 @@ class Widget(MessagePump):
async def forward_event(self, event: events.Event) -> None: async def forward_event(self, event: events.Event) -> None:
await self.post_message(event) await self.post_message(event)
async def refresh(self) -> None: def refresh(self, repaint: bool = True, layout: bool = False) -> None:
"""Re-render the window and repaint it.""" """Initiate a refresh of the widget.
self.require_repaint()
await self.repaint()
async def repaint(self) -> None: This method sets an internal flag to perform a refresh, which will be done on the
"""Instructs parent to repaint this widget.""" next idle event. Only one refresh will be done even if this method is called multiple times.
await self.emit(UpdateMessage(self, self))
async def update_layout(self) -> None: Args:
await self.emit(LayoutMessage(self)) repaint (bool, optional): Repaint the widget (will call render() again). Defaults to True.
layout (bool, optional): Also layout widgets in the view. Defaults to False.
"""
if layout:
self._layout_required = True
elif repaint:
self.clear_render_cache()
self._repaint_required = True
self.post_message_no_wait(events.Null(self))
def render(self) -> RenderableType: def render(self) -> RenderableType:
"""Get renderable for widget. """Get renderable for widget.
@@ -231,25 +232,17 @@ class Widget(MessagePump):
self.log(self, "IS NOT RUNNING") self.log(self, "IS NOT RUNNING")
return await super().post_message(message) return await super().post_message(message)
# async def on_event(self, event: events.Event) -> None:
# if isinstance(event, events.Resize):
# if self.size != event.size:
# # self.size = event.size
# self.require_repaint()
# await super().on_event(event)
async def on_resize(self, event: events.Resize) -> None: async def on_resize(self, event: events.Resize) -> None:
self.log("RESIZE", self)
self.render_lines() self.render_lines()
async def on_idle(self, event: events.Idle) -> None: async def on_idle(self, event: events.Idle) -> None:
if self.check_layout(): if self.check_layout():
self.reset_check_repaint() self.reset_check_repaint()
self.reset_check_layout() self.reset_check_layout()
await self.update_layout() await self.emit(LayoutMessage(self))
elif self.check_repaint(): elif self.check_repaint():
self.reset_check_repaint() self.reset_check_repaint()
await self.repaint() 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)
@@ -276,10 +269,6 @@ class Widget(MessagePump):
if key_method is not None: if key_method is not None:
await key_method() await key_method()
# async def on_repaint(self) -> None:
# if self._render_cache is None or self._render_cache.size != self.size:
# self._render_cache = self.render_lines()
async def on_mouse_down(self, event: events.MouseUp) -> None: async def on_mouse_down(self, event: events.MouseUp) -> None:
await self.broker_event("mouse.down", event) await self.broker_event("mouse.down", event)

View File

@@ -185,12 +185,9 @@ class ScrollView(View):
self.animate("y", self.target_y, speed=150, easing="out_cubic") self.animate("y", self.target_y, speed=150, easing="out_cubic")
async def message_virtual_size_change(self, message: Message) -> None: async def message_virtual_size_change(self, message: Message) -> None:
virtual_size = self.window.virtual_size virtual_size = self.window.virtual_size
self.log("VIRTUAL_SIZE", self.size, virtual_size)
self.x = self.validate_x(self.x) self.x = self.validate_x(self.x)
self.y = self.validate_y(self.y) self.y = self.validate_y(self.y)
self.log(self.y)
self.vscroll.virtual_size = virtual_size.height self.vscroll.virtual_size = virtual_size.height
self.vscroll.window_size = self.size.height self.vscroll.window_size = self.size.height

View File

@@ -21,7 +21,7 @@ class Static(Widget):
self.padding = padding self.padding = padding
def render(self) -> RenderableType: def render(self) -> RenderableType:
self.log("RENDERING", self.renderable) # self.log("RENDERING", self.renderable)
renderable = self.renderable renderable = self.renderable
if self.padding: if self.padding:
renderable = Padding(renderable, self.padding) renderable = Padding(renderable, self.padding)