1414from typing import (
1515 TYPE_CHECKING ,
1616 AsyncGenerator ,
17- Awaitable ,
1817 ClassVar ,
1918 Collection ,
2019 Generator ,
5857from textual ._styles_cache import StylesCache
5958from textual ._types import AnimationLevel
6059from textual .actions import SkipAction
61- from textual .await_complete import AwaitComplete
6260from textual .await_remove import AwaitRemove
6361from textual .box_model import BoxModel
6462from textual .cache import FIFOCache
@@ -333,6 +331,38 @@ class Widget(DOMNode):
333331 loading : Reactive [bool ] = Reactive (False )
334332 """If set to `True` this widget will temporarily be replaced with a loading indicator."""
335333
334+ virtual_size : Reactive [Size ] = Reactive (Size (0 , 0 ), layout = True )
335+ """The virtual (scrollable) [size][textual.geometry.Size] of the widget."""
336+
337+ has_focus : Reactive [bool ] = Reactive (False , repaint = False )
338+ """Does this widget have focus? Read only."""
339+
340+ mouse_hover : Reactive [bool ] = Reactive (False , repaint = False )
341+ """Is the mouse over this widget? Read only."""
342+
343+ scroll_x : Reactive [float ] = Reactive (0.0 , repaint = False , layout = False )
344+ """The scroll position on the X axis."""
345+
346+ scroll_y : Reactive [float ] = Reactive (0.0 , repaint = False , layout = False )
347+ """The scroll position on the Y axis."""
348+
349+ scroll_target_x = Reactive (0.0 , repaint = False )
350+ """Scroll target destination, X coord."""
351+
352+ scroll_target_y = Reactive (0.0 , repaint = False )
353+ """Scroll target destination, Y coord."""
354+
355+ show_vertical_scrollbar : Reactive [bool ] = Reactive (False , layout = True )
356+ """Show a vertical scrollbar?"""
357+
358+ show_horizontal_scrollbar : Reactive [bool ] = Reactive (False , layout = True )
359+ """Show a horizontal scrollbar?"""
360+
361+ border_title : str | Text | None = _BorderTitle () # type: ignore
362+ """A title to show in the top border (if there is one)."""
363+ border_subtitle : str | Text | None = _BorderTitle () # type: ignore
364+ """A title to show in the bottom border (if there is one)."""
365+
336366 # Default sort order, incremented by constructor
337367 _sort_order : ClassVar [int ] = 0
338368
@@ -430,38 +460,8 @@ def __init__(
430460 """An anchored child widget, or `None` if no child is anchored."""
431461 self ._anchor_animate : bool = False
432462 """Flag to enable animation when scrolling anchored widgets."""
433-
434- virtual_size : Reactive [Size ] = Reactive (Size (0 , 0 ), layout = True )
435- """The virtual (scrollable) [size][textual.geometry.Size] of the widget."""
436-
437- has_focus : Reactive [bool ] = Reactive (False , repaint = False )
438- """Does this widget have focus? Read only."""
439-
440- mouse_hover : Reactive [bool ] = Reactive (False , repaint = False )
441- """Is the mouse over this widget? Read only."""
442-
443- scroll_x : Reactive [float ] = Reactive (0.0 , repaint = False , layout = False )
444- """The scroll position on the X axis."""
445-
446- scroll_y : Reactive [float ] = Reactive (0.0 , repaint = False , layout = False )
447- """The scroll position on the Y axis."""
448-
449- scroll_target_x = Reactive (0.0 , repaint = False )
450- """Scroll target destination, X coord."""
451-
452- scroll_target_y = Reactive (0.0 , repaint = False )
453- """Scroll target destination, Y coord."""
454-
455- show_vertical_scrollbar : Reactive [bool ] = Reactive (False , layout = True )
456- """Show a vertical scrollbar?"""
457-
458- show_horizontal_scrollbar : Reactive [bool ] = Reactive (False , layout = True )
459- """Show a horizontal scrollbar?"""
460-
461- border_title : str | Text | None = _BorderTitle () # type: ignore
462- """A title to show in the top border (if there is one)."""
463- border_subtitle : str | Text | None = _BorderTitle () # type: ignore
464- """A title to show in the bottom border (if there is one)."""
463+ self ._cover_widget : Widget | None = None
464+ """Widget to render over this widget (used by loading indicator)."""
465465
466466 @property
467467 def is_mounted (self ) -> bool :
@@ -587,6 +587,31 @@ def is_maximized(self) -> bool:
587587 except NoScreen :
588588 return False
589589
590+ @property
591+ def _render_widget (self ) -> Widget :
592+ return self ._cover_widget if self ._cover_widget is not None else self
593+
594+ def _cover (self , widget : Widget ) -> None :
595+ """Set a widget used to replace the visuals of this widget (used for loading indicator).
596+
597+ Args:
598+ widget: A newly constructed, but unmounted widget.
599+ """
600+ self ._uncover ()
601+ self ._cover_widget = widget
602+ widget ._parent = self
603+ widget ._start_messages ()
604+ widget ._post_register (self .app )
605+ self .app .stylesheet .apply (widget )
606+ self .refresh (layout = True )
607+
608+ def _uncover (self ) -> None :
609+ """Remove any widget, previously set via [`_cover`][textual.widget.Widget._cover]."""
610+ if self ._cover_widget is not None :
611+ self ._cover_widget .remove ()
612+ self ._cover_widget = None
613+ self .refresh (layout = True )
614+
590615 def anchor (self , * , animate : bool = False ) -> None :
591616 """Anchor the widget, which scrolls it into view (like [scroll_visible][textual.widget.Widget.scroll_visible]),
592617 but also keeps it in view if the widget's size changes, or the size of its container changes.
@@ -716,7 +741,7 @@ def get_loading_widget(self) -> Widget:
716741 loading_widget = self .app .get_loading_widget ()
717742 return loading_widget
718743
719- def set_loading (self , loading : bool ) -> Awaitable :
744+ def set_loading (self , loading : bool ) -> None :
720745 """Set or reset the loading state of this widget.
721746
722747 A widget in a loading state will display a LoadingIndicator that obscures the widget.
@@ -728,19 +753,16 @@ def set_loading(self, loading: bool) -> Awaitable:
728753 An optional awaitable.
729754 """
730755 LOADING_INDICATOR_CLASS = "-textual-loading-indicator"
731- LOADING_INDICATOR_QUERY = f".{ LOADING_INDICATOR_CLASS } "
732- remove_indicator = self .query_children (LOADING_INDICATOR_QUERY ).remove ()
733756 if loading :
734757 loading_indicator = self .get_loading_widget ()
735758 loading_indicator .add_class (LOADING_INDICATOR_CLASS )
736- await_mount = self .mount (loading_indicator )
737- return AwaitComplete (remove_indicator , await_mount ).call_next (self )
759+ self ._cover (loading_indicator )
738760 else :
739- return remove_indicator
761+ self . _uncover ()
740762
741- async def _watch_loading (self , loading : bool ) -> None :
763+ def _watch_loading (self , loading : bool ) -> None :
742764 """Called when the 'loading' reactive is changed."""
743- await self .set_loading (loading )
765+ self .set_loading (loading )
744766
745767 ExpectType = TypeVar ("ExpectType" , bound = "Widget" )
746768
@@ -3993,6 +4015,7 @@ def _on_scroll_to_region(self, message: messages.ScrollToRegion) -> None:
39934015 self .scroll_to_region (message .region , animate = True )
39944016
39954017 def _on_unmount (self ) -> None :
4018+ self ._uncover ()
39964019 self .workers .cancel_node (self )
39974020
39984021 def action_scroll_home (self ) -> None :
0 commit comments