narrow down rules

This commit is contained in:
Will McGugan
2022-08-26 10:12:59 +01:00
parent e19a0090f7
commit f49b8d2fad
7 changed files with 61 additions and 21 deletions

View File

@@ -52,3 +52,6 @@ Button {
visibility: hidden
}
/* #timers:hover {
background: blue;
} */

View File

@@ -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."""

View File

@@ -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})")

View File

@@ -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:

View File

@@ -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

View File

@@ -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)

View File

@@ -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