diff --git a/src/textual/_compositor.py b/src/textual/_compositor.py index ca09f8ff4..76bb223e4 100644 --- a/src/textual/_compositor.py +++ b/src/textual/_compositor.py @@ -245,19 +245,15 @@ class ChopsUpdate(CompositorUpdate): append(strip.render(console)) continue - if x < x1: - strip = strip.crop(x1 - x, x2 - x) - append(move_to(x1, y).segment.text) - else: - strip = strip.crop(0, x2 - x) - append(move_to(x, y).segment.text) - + strip = strip.crop(0, min(end, x2) - x) + append(move_to(x, y).segment.text) append(strip.render(console)) if y != last_y: append("\n") - return "".join(sequences) + terminal_sequences = "".join(sequences) + return terminal_sequences def __rich_repr__(self) -> rich.repr.Result: yield from () diff --git a/src/textual/filter.py b/src/textual/filter.py index 271636a64..65378818e 100644 --- a/src/textual/filter.py +++ b/src/textual/filter.py @@ -117,12 +117,8 @@ def dim_style(style: Style, background: Color, factor: float) -> Style: style + Style.from_color( dim_color( - ( - background.rich_color - if style.bgcolor.is_default - else (style.bgcolor or DEFAULT_COLOR) - ), - style.color or DEFAULT_COLOR, + (background.rich_color if style.bgcolor.is_default else style.bgcolor), + style.color, factor, ), None, diff --git a/src/textual/pilot.py b/src/textual/pilot.py index e1786421d..68dcf8f25 100644 --- a/src/textual/pilot.py +++ b/src/textual/pilot.py @@ -141,6 +141,7 @@ class Pilot(Generic[ReturnType]): delay: Seconds to pause, or None to wait for cpu idle. """ # These sleep zeros, are to force asyncio to give up a time-slice, + self.app.screen._on_timer_update() # Force one last repaint if delay is None: await wait_for_idle(0) else: diff --git a/src/textual/screen.py b/src/textual/screen.py index dc4717926..b56213fbe 100644 --- a/src/textual/screen.py +++ b/src/textual/screen.py @@ -42,8 +42,8 @@ from .widget import Widget if TYPE_CHECKING: from typing_extensions import Final -# Screen updates will be batched so that they don't happen more often than 120 times per second: -UPDATE_PERIOD: Final[float] = 1 / 120 +# Screen updates will be batched so that they don't happen more often than 60 times per second: +UPDATE_PERIOD: Final[float] = 1 / 60 ScreenResultType = TypeVar("ScreenResultType") """The result type of a screen.""" @@ -437,42 +437,49 @@ class Screen(Generic[ScreenResultType], Widget): event.prevent_default() if not self.app._batch_count and self.is_current: - async with self.app._dom_lock: - if self.is_current: - if self._layout_required: - self._refresh_layout() - self._layout_required = False - self._scroll_required = False - self._dirty_widgets.clear() - elif self._scroll_required: - self._refresh_layout(scroll=True) - self._scroll_required = False - - if self._repaint_required: - self._dirty_widgets.clear() - self._dirty_widgets.add(self) - self._repaint_required = False - - if self._dirty_widgets: - self._update_timer.resume() + if ( + self._layout_required + or self._scroll_required + or self._repaint_required + or self._dirty_widgets + ): + self._update_timer.resume() # The Screen is idle - a good opportunity to invoke the scheduled callbacks - await self._invoke_and_clear_callbacks() + + if self._callbacks: + self._on_timer_update() def _on_timer_update(self) -> None: """Called by the _update_timer.""" - # Render widgets together - if self._dirty_widgets and self.is_current: - self._compositor.update_widgets(self._dirty_widgets) - update = self._compositor.render_update( - screen_stack=self.app._background_screens - ) - self.app._display(self, update) - self._dirty_widgets.clear() - if self._callbacks: - self.post_message(events.InvokeCallbacks()) self._update_timer.pause() + if self.is_current: + if self._layout_required: + self._refresh_layout() + self._layout_required = False + self._scroll_required = False + self._dirty_widgets.clear() + elif self._scroll_required: + self._refresh_layout(scroll=True) + self._scroll_required = False + + if self._repaint_required: + self._update_timer.resume() + self._dirty_widgets.clear() + self._dirty_widgets.add(self) + self._repaint_required = False + + if self._dirty_widgets and self.is_current: + self._compositor.update_widgets(self._dirty_widgets) + update = self._compositor.render_update( + screen_stack=self.app._background_screens + ) + self.app._display(self, update) + self._dirty_widgets.clear() + + if self._callbacks: + self.post_message(events.InvokeCallbacks()) async def _on_invoke_callbacks(self, event: events.InvokeCallbacks) -> None: """Handle PostScreenUpdate events, which are sent after the screen is updated""" diff --git a/src/textual/strip.py b/src/textual/strip.py index a44bead11..c297e9559 100644 --- a/src/textual/strip.py +++ b/src/textual/strip.py @@ -282,6 +282,7 @@ class Strip: cached_strip = Strip( filter.apply(self._segments, background), self._cell_length ) + self._filter_cache[(filter, background)] = cached_strip return cached_strip def style_links(self, link_id: str, link_style: Style) -> Strip: @@ -312,7 +313,7 @@ class Strip: ] return Strip(segments, self._cell_length) - def crop(self, start: int, end: int) -> Strip: + def crop(self, start: int, end: int | None = None) -> Strip: """Crop a strip between two cell positions. Args: @@ -323,7 +324,7 @@ class Strip: A new Strip. """ start = max(0, start) - end = min(self.cell_length, end) + end = self.cell_length if end is None else min(self.cell_length, end) if start == 0 and end == self.cell_length: return self cache_key = (start, end)