Merge pull request #6137 from Textualize/alternative-footer-flicker-fix

delay update for footer
This commit is contained in:
Will McGugan
2025-09-28 15:53:04 +01:00
committed by GitHub
7 changed files with 20 additions and 19 deletions

View File

@@ -14,6 +14,7 @@ and this project adheres to [Semantic Versioning](http://semver.org/).
- Reduced number of layout operations required to update the screen https://github.com/Textualize/textual/pull/6108
- The :hover pseudo-class no applies to the first widget under the mouse with a hover style set https://github.com/Textualize/textual/pull/6132
- The footer key hover background is more visible https://github.com/Textualize/textual/pull/6132
- Made `App.delay_update` public https://github.com/Textualize/textual/pull/6137
### Added

View File

@@ -1019,10 +1019,11 @@ class App(Generic[ReturnType], DOMNode):
if not self._batch_count:
self.check_idle()
def _delay_update(self, delay: float = 0.05) -> None:
def delay_update(self, delay: float = 0.05) -> None:
"""Delay updates for a short period of time.
May be used to mask a brief transition.
Consider this method only if you aren't able to use `App.batch_update`.
Args:
delay: Delay before updating.
@@ -1035,7 +1036,7 @@ class App(Generic[ReturnType], DOMNode):
if not self._batch_count:
self.screen.refresh()
self.set_timer(delay, end_batch, name="_delay_update")
self.set_timer(delay, end_batch, name="delay_update")
@contextmanager
def _context(self) -> Generator[None, None, None]:

View File

@@ -1230,7 +1230,7 @@ class CommandPalette(SystemModalScreen[None]):
# decide what to do with it (hopefully it'll run it).
self._cancel_gather_commands()
self.app.post_message(CommandPalette.Closed(option_selected=True))
self.app._delay_update()
self.app.delay_update()
self.dismiss()
self.app.call_later(self._selected_command.command)

View File

@@ -130,7 +130,7 @@ class FooterKey(Widget):
label_text.stylize_before(self.rich_style)
return label_text
async def on_mouse_down(self) -> None:
def on_mouse_down(self) -> None:
if self._disabled:
self.app.bell()
else:
@@ -332,7 +332,7 @@ class Footer(ScrollableContainer, can_focus=False, can_focus_children=False):
if not screen.app.app_focus:
return
if self.is_attached and screen is self.screen:
await self.recompose()
self.call_after_refresh(self.recompose)
def _on_mouse_scroll_down(self, event: events.MouseScrollDown) -> None:
if self.allow_horizontal_scroll:
@@ -351,12 +351,7 @@ class Footer(ScrollableContainer, can_focus=False, can_focus_children=False):
async def on_mount(self) -> None:
await asyncio.sleep(0)
self.call_next(self.bindings_changed, self.screen)
def bindings_changed(screen: Screen) -> None:
"""Update bindings after a short delay to avoid flicker."""
self.call_after_refresh(self.bindings_changed, screen)
self.screen.bindings_updated_signal.subscribe(self, bindings_changed)
self.screen.bindings_updated_signal.subscribe(self, self.bindings_changed)
def on_unmount(self) -> None:
self.screen.bindings_updated_signal.unsubscribe(self)

View File

@@ -79,7 +79,10 @@ class HelpPanel(Widget):
DEFAULT_CLASSES = "-textual-system"
def on_mount(self):
self.watch(self.screen, "focused", self.update_help)
def update_help(focused_widget: Widget | None):
self.update_help(focused_widget)
self.watch(self.screen, "focused", update_help)
def update_help(self, focused_widget: Widget | None) -> None:
"""Update the help for the focused widget.

View File

@@ -1,7 +1,6 @@
from __future__ import annotations
from collections import defaultdict
from functools import partial
from itertools import groupby
from operator import itemgetter
from typing import TYPE_CHECKING
@@ -164,16 +163,17 @@ class KeyPanel(VerticalScroll, can_focus=False):
yield BindingsTable(shrink=True, expand=False)
async def on_mount(self) -> None:
mount_screen = self.screen
async def bindings_changed(screen: Screen) -> None:
"""Update bindings."""
if not screen.app.app_focus:
return
if self.is_attached and screen is self.screen:
self.refresh(recompose=True)
if self.is_attached and screen is mount_screen:
await self.recompose()
def _bindings_changed(screen: Screen) -> None:
"""Update bindings after a short delay."""
screen.set_timer(1 / 20, partial(bindings_changed, screen))
self.call_after_refresh(bindings_changed, screen)
self.set_class(self.app.ansi_color, "-ansi-scrollbar")
self.screen.bindings_updated_signal.subscribe(self, _bindings_changed)

View File

@@ -89,10 +89,11 @@ async def test_modal_pop_screen():
app = ModalApp()
async with app.run_test() as pilot:
# Pause to ensure the footer is fully composed to avoid flakiness in CI
await pilot.pause(0.4)
await pilot.pause()
await app.wait_for_refresh()
await pilot.pause()
# Check clicking the footer brings up the quit screen
await pilot.click(Footer)
await pilot.click(Footer, offset=(1, 0))
await pilot.pause()
assert isinstance(pilot.app.screen, QuitScreen)
# Check activating the quit button exits the app