Merge pull request #5267 from Textualize/loading-input-disable

disable loading widgets
This commit is contained in:
Will McGugan
2024-11-21 17:57:01 +00:00
committed by GitHub
4 changed files with 52 additions and 2 deletions

View File

@@ -15,6 +15,7 @@ and this project adheres to [Semantic Versioning](http://semver.org/).
### Fixed
- Fixed offset applied to docked widgets https://github.com/Textualize/textual/pull/5264
- Fixed loading widgets responding to input https://github.com/Textualize/textual/pull/5267
## [0.86.3] - 2024-11-19

View File

@@ -562,7 +562,7 @@ class Screen(Generic[ScreenResultType], Widget):
except NoWidget:
return None
if widget.has_class("-textual-system"):
if widget.has_class("-textual-system") or widget.loading:
# Clicking Textual system widgets should not focus anything
return None
@@ -1421,6 +1421,8 @@ class Screen(Generic[ScreenResultType], Widget):
if focusable_widget:
self.set_focus(focusable_widget, scroll_visible=False)
event.style = self.get_style_at(event.screen_x, event.screen_y)
if widget.loading:
return
if widget is self:
event._set_forwarded()
self.post_message(event)

View File

@@ -8,8 +8,10 @@ from rich.text import Text
if TYPE_CHECKING:
from textual.app import RenderResult
from textual import on
from textual.color import Gradient
from textual.events import Mount
from textual.events import InputEvent, Mount
from textual.widget import Widget
@@ -56,6 +58,12 @@ class LoadingIndicator(Widget):
self._start_time = time()
self.auto_refresh = 1 / 16
@on(InputEvent)
def on_input(self, event: InputEvent) -> None:
"""Prevent all input events from bubbling, thus disabling widgets in a loading state."""
event.stop()
event.prevent_default()
def render(self) -> RenderResult:
if self.app.animation_level == "none":
return Text("Loading...")

View File

@@ -445,6 +445,45 @@ async def test_loading():
assert label._cover_widget is None
async def test_loading_button():
"""Test loading indicator renders buttons unclickable."""
counter = 0
class LoadingApp(App):
def compose(self) -> ComposeResult:
yield Button("Hello, World", action="app.inc")
def action_inc(self) -> None:
nonlocal counter
counter += 1
async with LoadingApp().run_test() as pilot:
# Sanity check
assert counter == 0
button = pilot.app.query_one(Button)
button.active_effect_duration = 0
# Click the button to advance the counter
await pilot.click(button)
assert counter == 1
# Set the button to loading state
button.loading = True
# A click should do nothing
await pilot.click(button)
assert counter == 1
# Set the button to not loading
button.loading = False
# Click should advance counter
await pilot.click(button)
assert counter == 2
async def test_is_mounted_property():
class TestWidgetIsMountedApp(App):
pass