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,33 @@ def is_maximized(self) -> bool:
587587 except NoScreen :
588588 return False
589589
590+ @property
591+ def _render_widget (self ) -> Widget :
592+ """The widget the compositor should render."""
593+ # Will return the "cover widget" if one is set, otherwise self.
594+ return self ._cover_widget if self ._cover_widget is not None else self
595+
596+ def _cover (self , widget : Widget ) -> None :
597+ """Set a widget used to replace the visuals of this widget (used for loading indicator).
598+
599+ Args:
600+ widget: A newly constructed, but unmounted widget.
601+ """
602+ self ._uncover ()
603+ self ._cover_widget = widget
604+ widget ._parent = self
605+ widget ._start_messages ()
606+ widget ._post_register (self .app )
607+ self .app .stylesheet .apply (widget )
608+ self .refresh (layout = True )
609+
610+ def _uncover (self ) -> None :
611+ """Remove any widget, previously set via [`_cover`][textual.widget.Widget._cover]."""
612+ if self ._cover_widget is not None :
613+ self ._cover_widget .remove ()
614+ self ._cover_widget = None
615+ self .refresh (layout = True )
616+
590617 def anchor (self , * , animate : bool = False ) -> None :
591618 """Anchor the widget, which scrolls it into view (like [scroll_visible][textual.widget.Widget.scroll_visible]),
592619 but also keeps it in view if the widget's size changes, or the size of its container changes.
@@ -716,7 +743,7 @@ def get_loading_widget(self) -> Widget:
716743 loading_widget = self .app .get_loading_widget ()
717744 return loading_widget
718745
719- def set_loading (self , loading : bool ) -> Awaitable :
746+ def set_loading (self , loading : bool ) -> None :
720747 """Set or reset the loading state of this widget.
721748
722749 A widget in a loading state will display a LoadingIndicator that obscures the widget.
@@ -728,19 +755,16 @@ def set_loading(self, loading: bool) -> Awaitable:
728755 An optional awaitable.
729756 """
730757 LOADING_INDICATOR_CLASS = "-textual-loading-indicator"
731- LOADING_INDICATOR_QUERY = f".{ LOADING_INDICATOR_CLASS } "
732- remove_indicator = self .query_children (LOADING_INDICATOR_QUERY ).remove ()
733758 if loading :
734759 loading_indicator = self .get_loading_widget ()
735760 loading_indicator .add_class (LOADING_INDICATOR_CLASS )
736- await_mount = self .mount (loading_indicator )
737- return AwaitComplete (remove_indicator , await_mount ).call_next (self )
761+ self ._cover (loading_indicator )
738762 else :
739- return remove_indicator
763+ self . _uncover ()
740764
741- async def _watch_loading (self , loading : bool ) -> None :
765+ def _watch_loading (self , loading : bool ) -> None :
742766 """Called when the 'loading' reactive is changed."""
743- await self .set_loading (loading )
767+ self .set_loading (loading )
744768
745769 ExpectType = TypeVar ("ExpectType" , bound = "Widget" )
746770
@@ -3993,6 +4017,7 @@ def _on_scroll_to_region(self, message: messages.ScrollToRegion) -> None:
39934017 self .scroll_to_region (message .region , animate = True )
39944018
39954019 def _on_unmount (self ) -> None :
4020+ self ._uncover ()
39964021 self .workers .cancel_node (self )
39974022
39984023 def action_scroll_home (self ) -> None :
0 commit comments