33import sys
44import weakref
55from operator import attrgetter
6- from typing import TYPE_CHECKING , Any , Callable , Iterable , Iterator , Sequence , overload
6+ from typing import TYPE_CHECKING , Any , Callable , Iterator , Sequence , overload
77
88import rich .repr
99
1414 from textual .widget import Widget
1515
1616
17+ _display_getter = attrgetter ("display" )
18+ _visible_getter = attrgetter ("visible" )
19+
20+
1721class DuplicateIds (Exception ):
1822 """Raised when attempting to add a widget with an id that already exists."""
1923
@@ -41,6 +45,8 @@ def __init__(self, parent: DOMNode | None = None) -> None:
4145 # The nodes in the list
4246 self ._nodes : list [Widget ] = []
4347 self ._nodes_set : set [Widget ] = set ()
48+ self ._displayed_nodes : tuple [int , list [Widget ]] = (- 1 , [])
49+ self ._displayed_visible_nodes : tuple [int , list [Widget ]] = (- 1 , [])
4450
4551 # We cache widgets by their IDs too for a quick lookup
4652 # Note that only widgets with IDs are cached like this, so
@@ -69,8 +75,6 @@ def updated(self) -> None:
6975 """Mark the nodes as having been updated."""
7076 self ._updates += 1
7177 node = None if self ._parent is None else self ._parent ()
72- if node is None :
73- return
7478 while node is not None and (node := node ._parent ) is not None :
7579 node ._nodes ._updates += 1
7680
@@ -187,18 +191,29 @@ def __reversed__(self) -> Iterator[Widget]:
187191 return reversed (self ._nodes )
188192
189193 @property
190- def displayed (self ) -> Iterable [Widget ]:
194+ def displayed (self ) -> Sequence [Widget ]:
191195 """Just the nodes where `display==True`."""
192- for node in self ._nodes :
193- if node .display :
194- yield node
196+ if self ._displayed_nodes [0 ] != self ._updates :
197+ self ._displayed_nodes = (
198+ self ._updates ,
199+ list (filter (_display_getter , self ._nodes )),
200+ )
201+ return self ._displayed_nodes [1 ]
195202
196203 @property
197- def displayed_reverse (self ) -> Iterable [Widget ]:
204+ def displayed_and_visible (self ) -> Sequence [Widget ]:
205+ """Nodes with both `display==True` and `visible==True`."""
206+ if self ._displayed_visible_nodes [0 ] != self ._updates :
207+ self ._displayed_nodes = (
208+ self ._updates ,
209+ list (filter (_visible_getter , self .displayed )),
210+ )
211+ return self ._displayed_nodes [1 ]
212+
213+ @property
214+ def displayed_reverse (self ) -> Iterator [Widget ]:
198215 """Just the nodes where `display==True`, in reverse order."""
199- for node in reversed (self ._nodes ):
200- if node .display :
201- yield node
216+ return filter (_display_getter , reversed (self ._nodes ))
202217
203218 if TYPE_CHECKING :
204219
@@ -211,9 +226,11 @@ def __getitem__(self, index: slice) -> list[Widget]: ...
211226 def __getitem__ (self , index : int | slice ) -> Widget | list [Widget ]:
212227 return self ._nodes [index ]
213228
214- def __getattr__ (self , key : str ) -> object :
215- if key in {"clear" , "append" , "pop" , "insert" , "remove" , "extend" }:
216- raise ReadOnlyError (
217- "Widget.children is read-only: use Widget.mount(...) or Widget.remove(...) to add or remove widgets"
218- )
219- raise AttributeError (key )
229+ if not TYPE_CHECKING :
230+ # This confused the type checker for some reason
231+ def __getattr__ (self , key : str ) -> object :
232+ if key in {"clear" , "append" , "pop" , "insert" , "remove" , "extend" }:
233+ raise ReadOnlyError (
234+ "Widget.children is read-only: use Widget.mount(...) or Widget.remove(...) to add or remove widgets"
235+ )
236+ raise AttributeError (key )
0 commit comments