|
4 | 4 |
|
5 | 5 | from __future__ import annotations |
6 | 6 |
|
| 7 | +from functools import partial |
| 8 | + |
7 | 9 | from textual.widget import Widget |
8 | 10 |
|
9 | 11 |
|
@@ -61,3 +63,85 @@ async def mount() -> None: |
61 | 63 | await self.remove() |
62 | 64 |
|
63 | 65 | self.call_after_refresh(mount) |
| 66 | + |
| 67 | + |
| 68 | +class Reveal(Widget): |
| 69 | + DEFAULT_CSS = """ |
| 70 | + Reveal { |
| 71 | + display: none; |
| 72 | + } |
| 73 | + """ |
| 74 | + |
| 75 | + def __init__(self, widget: Widget, delay: float = 1 / 60) -> None: |
| 76 | + """Similar to [Lazy][textual.lazy.Lazy], but also displays *children* sequentially. |
| 77 | +
|
| 78 | + The first frame will display the first child with all other children hidden. |
| 79 | + The remaining children will be displayed 1-by-1, over as may frames are required. |
| 80 | +
|
| 81 | + This is useful when you have so many child widgets that there is a noticeable delay before |
| 82 | + you see anything. By mounting the children over several frames, the user will feel that |
| 83 | + something is happening. |
| 84 | +
|
| 85 | + Example: |
| 86 | + ```python |
| 87 | + def compose(self) -> ComposeResult: |
| 88 | + with lazy.Reveal(containers.VerticalScroll(can_focus=False)): |
| 89 | + yield Markdown(WIDGETS_MD, classes="column") |
| 90 | + yield Buttons() |
| 91 | + yield Checkboxes() |
| 92 | + yield Datatables() |
| 93 | + yield Inputs() |
| 94 | + yield ListViews() |
| 95 | + yield Logs() |
| 96 | + yield Sparklines() |
| 97 | + yield Footer() |
| 98 | + ``` |
| 99 | +
|
| 100 | + Args: |
| 101 | + widget: A widget that should be mounted after a refresh. |
| 102 | + delay: A (short) delay between mounting widgets. |
| 103 | + """ |
| 104 | + self._replace_widget = widget |
| 105 | + self._delay = delay |
| 106 | + super().__init__() |
| 107 | + |
| 108 | + @classmethod |
| 109 | + def _reveal(cls, parent: Widget, delay: float = 1 / 60) -> None: |
| 110 | + """Reveal children lazily. |
| 111 | +
|
| 112 | + Args: |
| 113 | + parent: The parent widget. |
| 114 | + delay: A delay between reveals. |
| 115 | + """ |
| 116 | + |
| 117 | + def check_children() -> None: |
| 118 | + """Check for un-displayed children.""" |
| 119 | + iter_children = iter(parent.children) |
| 120 | + for child in iter_children: |
| 121 | + if not child.display: |
| 122 | + child.display = True |
| 123 | + break |
| 124 | + for child in iter_children: |
| 125 | + if not child.display: |
| 126 | + parent.set_timer( |
| 127 | + delay, partial(parent.call_after_refresh, check_children) |
| 128 | + ) |
| 129 | + break |
| 130 | + |
| 131 | + check_children() |
| 132 | + |
| 133 | + def compose_add_child(self, widget: Widget) -> None: |
| 134 | + widget.display = False |
| 135 | + self._replace_widget.compose_add_child(widget) |
| 136 | + |
| 137 | + async def mount_composed_widgets(self, widgets: list[Widget]) -> None: |
| 138 | + parent = self.parent |
| 139 | + if parent is None: |
| 140 | + return |
| 141 | + assert isinstance(parent, Widget) |
| 142 | + |
| 143 | + if self._replace_widget.children: |
| 144 | + self._replace_widget.children[0].display = True |
| 145 | + await parent.mount(self._replace_widget, after=self) |
| 146 | + await self.remove() |
| 147 | + self._reveal(self._replace_widget, self._delay) |
0 commit comments