mirror of
https://github.com/Textualize/textual.git
synced 2025-10-17 02:38:12 +03:00
narrow down rules
This commit is contained in:
@@ -52,3 +52,6 @@ Button {
|
||||
visibility: hidden
|
||||
}
|
||||
|
||||
/* #timers:hover {
|
||||
background: blue;
|
||||
} */
|
||||
|
||||
@@ -88,6 +88,7 @@ class StopwatchApp(App):
|
||||
new_stopwatch = Stopwatch()
|
||||
self.query_one("#timers").mount(new_stopwatch)
|
||||
new_stopwatch.scroll_visible()
|
||||
self.sub_title = str(len(self.query("Stopwatch")))
|
||||
|
||||
def action_remove_stopwatch(self) -> None:
|
||||
"""Called to remove a timer."""
|
||||
|
||||
@@ -487,9 +487,8 @@ class Compositor:
|
||||
"""Get the widget under the given point or None."""
|
||||
# TODO: Optimize with some line based lookup
|
||||
contains = Region.contains
|
||||
is_visible = self.visible_widgets.__contains__
|
||||
for widget, cropped_region, region, *_ in self:
|
||||
if is_visible(widget) and contains(cropped_region, x, y):
|
||||
if contains(cropped_region, x, y):
|
||||
return widget, region
|
||||
raise errors.NoWidget(f"No widget under screen coordinate ({x}, {y})")
|
||||
|
||||
|
||||
@@ -224,7 +224,7 @@ class App(Generic[ReturnType], DOMNode):
|
||||
self.design = DEFAULT_COLORS
|
||||
|
||||
self.stylesheet = Stylesheet(variables=self.get_css_variables())
|
||||
self._require_stylesheet_update = False
|
||||
self._require_stylesheet_update: set[DOMNode] = set()
|
||||
self.css_path = css_path or self.CSS_PATH
|
||||
|
||||
self._registry: WeakSet[DOMNode] = WeakSet()
|
||||
@@ -720,15 +720,13 @@ class App(Generic[ReturnType], DOMNode):
|
||||
Should be called whenever CSS classes / pseudo classes change.
|
||||
|
||||
"""
|
||||
if node is None:
|
||||
self._require_stylesheet_update = True
|
||||
self.check_idle()
|
||||
else:
|
||||
self.stylesheet.update(node, animate=True)
|
||||
self._require_stylesheet_update.add(self.screen if node is None else node)
|
||||
self.check_idle()
|
||||
|
||||
def update_visible_styles(self) -> None:
|
||||
"""Update visible styles only."""
|
||||
self.stylesheet.update_nodes(self.screen.visible_widgets)
|
||||
self._require_stylesheet_update.update(self.screen.visible_widgets)
|
||||
self.check_idle()
|
||||
|
||||
def mount(self, *anon_widgets: Widget, **widgets: Widget) -> None:
|
||||
"""Mount widgets. Widgets specified as positional args, or keywords args. If supplied
|
||||
@@ -1144,16 +1142,21 @@ class App(Generic[ReturnType], DOMNode):
|
||||
|
||||
self.set_timer(screenshot_timer, on_screenshot, name="screenshot timer")
|
||||
|
||||
def on_mount(self) -> None:
|
||||
def _on_mount(self) -> None:
|
||||
widgets = self.compose()
|
||||
if widgets:
|
||||
self.mount_all(widgets)
|
||||
|
||||
async def on_idle(self) -> None:
|
||||
def _on_idle(self) -> None:
|
||||
"""Perform actions when there are no messages in the queue."""
|
||||
if self._require_stylesheet_update:
|
||||
self._require_stylesheet_update = False
|
||||
self.stylesheet.update(self, animate=True)
|
||||
nodes: set[DOMNode] = {
|
||||
child
|
||||
for node in self._require_stylesheet_update
|
||||
for child in node.walk_children()
|
||||
}
|
||||
self._require_stylesheet_update.clear()
|
||||
self.stylesheet.update_nodes(nodes, animate=True)
|
||||
|
||||
def _register_child(self, parent: DOMNode, child: Widget) -> bool:
|
||||
if child not in self._registry:
|
||||
|
||||
@@ -160,11 +160,13 @@ class RuleSet:
|
||||
styles: Styles = field(default_factory=Styles)
|
||||
errors: list[tuple[Token, str]] = field(default_factory=list)
|
||||
|
||||
ids: set[str] = field(default_factory=set)
|
||||
is_default_rules: bool = False
|
||||
tie_breaker: int = 0
|
||||
selector_names: set[str] = field(default_factory=set)
|
||||
|
||||
def __hash__(self):
|
||||
return id(self)
|
||||
|
||||
@classmethod
|
||||
def _selector_to_css(cls, selectors: list[Selector]) -> str:
|
||||
tokens: list[str] = []
|
||||
@@ -200,10 +202,22 @@ class RuleSet:
|
||||
|
||||
class_type = SelectorType.CLASS
|
||||
id_type = SelectorType.ID
|
||||
type_type = SelectorType.TYPE
|
||||
universal_type = SelectorType.UNIVERSAL
|
||||
|
||||
update_selectors = self.selector_names.update
|
||||
|
||||
for selector_set in self.selector_set:
|
||||
update_selectors(
|
||||
"*"
|
||||
for selector in selector_set.selectors
|
||||
if selector.type == universal_type
|
||||
)
|
||||
update_selectors(
|
||||
selector.name
|
||||
for selector in selector_set.selectors
|
||||
if selector.type == type_type
|
||||
)
|
||||
update_selectors(
|
||||
f".{selector.name}"
|
||||
for selector in selector_set.selectors
|
||||
|
||||
@@ -312,7 +312,13 @@ class Stylesheet:
|
||||
if _check_selectors(selector_set.selectors, css_path_nodes):
|
||||
yield selector_set.specificity
|
||||
|
||||
def apply(self, node: DOMNode, animate: bool = False) -> None:
|
||||
def apply(
|
||||
self,
|
||||
node: DOMNode,
|
||||
*,
|
||||
limit_rules: set[RuleSet] | None = None,
|
||||
animate: bool = False,
|
||||
) -> None:
|
||||
"""Apply the stylesheet to a DOM node.
|
||||
|
||||
Args:
|
||||
@@ -334,12 +340,18 @@ class Stylesheet:
|
||||
_check_rule = self._check_rule
|
||||
|
||||
css_path_nodes = node.css_path_nodes
|
||||
|
||||
selector_names = {
|
||||
selector for node in css_path_nodes for selector in node._selector_names
|
||||
}
|
||||
|
||||
rules: Iterable[RuleSet]
|
||||
if limit_rules:
|
||||
rules = [rule for rule in reversed(self.rules) if rule in limit_rules]
|
||||
else:
|
||||
rules = reversed(self.rules)
|
||||
# Collect the rules defined in the stylesheet
|
||||
for rule in reversed(self.rules):
|
||||
for rule in rules:
|
||||
if rule.selector_names and not rule.selector_names.issubset(selector_names):
|
||||
continue
|
||||
|
||||
@@ -457,10 +469,17 @@ class Stylesheet:
|
||||
nodes (DOMNode): Nodes to update.
|
||||
animate (bool, optional): Enable CSS animation. Defaults to False.
|
||||
"""
|
||||
|
||||
rules_map: defaultdict[str, list[RuleSet]] = defaultdict(list)
|
||||
for rule in self.rules:
|
||||
for name in rule.selector_names:
|
||||
rules_map[name].append(rule)
|
||||
|
||||
apply = self.apply
|
||||
nodes = list(nodes)
|
||||
|
||||
for node in nodes:
|
||||
apply(node, animate=animate)
|
||||
rules = {rule for name in node._selector_names for rule in rules_map[name]}
|
||||
apply(node, limit_rules=rules, animate=animate)
|
||||
if isinstance(node, Widget) and node.is_scrollable:
|
||||
if node.show_vertical_scrollbar:
|
||||
apply(node.vertical_scrollbar)
|
||||
|
||||
@@ -320,6 +320,7 @@ class DOMNode(MessagePump):
|
||||
set[str]: Set of selector names.
|
||||
"""
|
||||
selectors: list[str] = [
|
||||
"*",
|
||||
*(f".{class_name}" for class_name in self._classes),
|
||||
*(f":{class_name}" for class_name in self.get_pseudo_classes()),
|
||||
*self._css_types,
|
||||
@@ -716,7 +717,7 @@ class DOMNode(MessagePump):
|
||||
if old_classes == self._classes:
|
||||
return
|
||||
try:
|
||||
self.app.stylesheet.update(self, animate=True)
|
||||
self.app.update_styles(self)
|
||||
except NoActiveAppError:
|
||||
pass
|
||||
|
||||
@@ -732,7 +733,7 @@ class DOMNode(MessagePump):
|
||||
if old_classes == self._classes:
|
||||
return
|
||||
try:
|
||||
self.app.stylesheet.update(self, animate=True)
|
||||
self.app.update_styles(self)
|
||||
except NoActiveAppError:
|
||||
pass
|
||||
|
||||
@@ -748,7 +749,7 @@ class DOMNode(MessagePump):
|
||||
if old_classes == self._classes:
|
||||
return
|
||||
try:
|
||||
self.app.stylesheet.update(self, animate=True)
|
||||
self.app.update_styles(self)
|
||||
except NoActiveAppError:
|
||||
pass
|
||||
|
||||
|
||||
Reference in New Issue
Block a user