mirror of
https://github.com/Textualize/textual.git
synced 2025-10-17 02:38:12 +03:00
optimize stylesheet apply
This commit is contained in:
@@ -160,6 +160,8 @@ class RuleSet:
|
|||||||
styles: Styles = field(default_factory=Styles)
|
styles: Styles = field(default_factory=Styles)
|
||||||
errors: list[tuple[Token, str]] = field(default_factory=list)
|
errors: list[tuple[Token, str]] = field(default_factory=list)
|
||||||
classes: set[str] = field(default_factory=set)
|
classes: set[str] = field(default_factory=set)
|
||||||
|
pseudo_classes: set[str] = field(default_factory=set)
|
||||||
|
ids: set[str] = field(default_factory=set)
|
||||||
is_default_rules: bool = False
|
is_default_rules: bool = False
|
||||||
tie_breaker: int = 0
|
tie_breaker: int = 0
|
||||||
|
|
||||||
@@ -195,11 +197,25 @@ class RuleSet:
|
|||||||
def _post_parse(self) -> None:
|
def _post_parse(self) -> None:
|
||||||
"""Called after the RuleSet is parsed."""
|
"""Called after the RuleSet is parsed."""
|
||||||
# Build a set of the class names that have been updated
|
# Build a set of the class names that have been updated
|
||||||
update = self.classes.update
|
update_classes = self.classes.update
|
||||||
|
update_ids = self.ids.update
|
||||||
|
update_pseudo_classes = self.pseudo_classes.update
|
||||||
class_type = SelectorType.CLASS
|
class_type = SelectorType.CLASS
|
||||||
|
id_type = SelectorType.ID
|
||||||
|
|
||||||
for selector_set in self.selector_set:
|
for selector_set in self.selector_set:
|
||||||
update(
|
update_classes(
|
||||||
selector.name
|
selector.name
|
||||||
for selector in selector_set.selectors
|
for selector in selector_set.selectors
|
||||||
if selector.type == class_type
|
if selector.type == class_type
|
||||||
)
|
)
|
||||||
|
update_ids(
|
||||||
|
selector.name
|
||||||
|
for selector in selector_set.selectors
|
||||||
|
if selector.type == id_type
|
||||||
|
)
|
||||||
|
update_pseudo_classes(
|
||||||
|
pseudo_class
|
||||||
|
for selector in selector_set.selectors
|
||||||
|
for pseudo_class in selector.pseudo_classes
|
||||||
|
)
|
||||||
|
|||||||
@@ -305,9 +305,11 @@ class Stylesheet:
|
|||||||
self.source = stylesheet.source
|
self.source = stylesheet.source
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def _check_rule(cls, rule: RuleSet, node: DOMNode) -> Iterable[Specificity3]:
|
def _check_rule(
|
||||||
|
cls, rule: RuleSet, css_path_nodes: list[DOMNode]
|
||||||
|
) -> Iterable[Specificity3]:
|
||||||
for selector_set in rule.selector_set:
|
for selector_set in rule.selector_set:
|
||||||
if _check_selectors(selector_set.selectors, node.css_path_nodes):
|
if _check_selectors(selector_set.selectors, css_path_nodes):
|
||||||
yield selector_set.specificity
|
yield selector_set.specificity
|
||||||
|
|
||||||
def apply(self, node: DOMNode, animate: bool = False) -> None:
|
def apply(self, node: DOMNode, animate: bool = False) -> None:
|
||||||
@@ -332,21 +334,47 @@ class Stylesheet:
|
|||||||
# same attribute, then we can choose the most specific rule and use that.
|
# same attribute, then we can choose the most specific rule and use that.
|
||||||
rule_attributes: dict[str, list[tuple[Specificity6, object]]]
|
rule_attributes: dict[str, list[tuple[Specificity6, object]]]
|
||||||
rule_attributes = {}
|
rule_attributes = {}
|
||||||
|
rule_attributes_setdefault = rule_attributes.setdefault
|
||||||
|
|
||||||
_check_rule = self._check_rule
|
_check_rule = self._check_rule
|
||||||
|
|
||||||
|
css_path_nodes = node.css_path_nodes
|
||||||
|
|
||||||
|
path_ids = {path_node._id for path_node in css_path_nodes if path_node._id}
|
||||||
|
path_classes = {
|
||||||
|
class_name
|
||||||
|
for path_node in css_path_nodes
|
||||||
|
for class_name in path_node.classes
|
||||||
|
}
|
||||||
|
path_pseudo_classes = {
|
||||||
|
pseudo_class
|
||||||
|
for path_node in css_path_nodes
|
||||||
|
for pseudo_class in path_node.get_pseudo_classes()
|
||||||
|
}
|
||||||
|
|
||||||
# Collect the rules defined in the stylesheet
|
# Collect the rules defined in the stylesheet
|
||||||
for rule in reversed(self.rules):
|
for rule in reversed(self.rules):
|
||||||
|
if rule.ids and not rule.ids.issubset(path_ids):
|
||||||
|
continue
|
||||||
|
if rule.classes and not rule.classes.issubset(path_classes):
|
||||||
|
continue
|
||||||
|
if rule.pseudo_classes and not rule.pseudo_classes.issubset(
|
||||||
|
path_pseudo_classes
|
||||||
|
):
|
||||||
|
continue
|
||||||
|
|
||||||
is_default_rules = rule.is_default_rules
|
is_default_rules = rule.is_default_rules
|
||||||
tie_breaker = rule.tie_breaker
|
tie_breaker = rule.tie_breaker
|
||||||
for base_specificity in _check_rule(rule, node):
|
for base_specificity in _check_rule(rule, css_path_nodes):
|
||||||
for key, rule_specificity, value in rule.styles.extract_rules(
|
for key, rule_specificity, value in rule.styles.extract_rules(
|
||||||
base_specificity, is_default_rules, tie_breaker
|
base_specificity, is_default_rules, tie_breaker
|
||||||
):
|
):
|
||||||
rule_attributes.setdefault(key, []).append(
|
rule_attributes_setdefault(key, []).append(
|
||||||
(rule_specificity, value)
|
(rule_specificity, value)
|
||||||
)
|
)
|
||||||
|
|
||||||
|
if not rule_attributes:
|
||||||
|
return
|
||||||
# For each rule declared for this node, keep only the most specific one
|
# For each rule declared for this node, keep only the most specific one
|
||||||
get_first_item = itemgetter(0)
|
get_first_item = itemgetter(0)
|
||||||
node_rules: RulesMap = cast(
|
node_rules: RulesMap = cast(
|
||||||
@@ -382,7 +410,7 @@ class Stylesheet:
|
|||||||
base_styles = styles.base
|
base_styles = styles.base
|
||||||
|
|
||||||
# Styles currently used on new rules
|
# Styles currently used on new rules
|
||||||
modified_rule_keys = {*base_styles.get_rules().keys(), *rules.keys()}
|
modified_rule_keys = base_styles.get_rules().keys() | rules.keys()
|
||||||
# Current render rules (missing rules are filled with default)
|
# Current render rules (missing rules are filled with default)
|
||||||
current_render_rules = styles.get_render_rules()
|
current_render_rules = styles.get_render_rules()
|
||||||
|
|
||||||
@@ -442,10 +470,9 @@ class Stylesheet:
|
|||||||
root (DOMNode): Root note to update.
|
root (DOMNode): Root note to update.
|
||||||
animate (bool, optional): Enable CSS animation. Defaults to False.
|
animate (bool, optional): Enable CSS animation. Defaults to False.
|
||||||
"""
|
"""
|
||||||
print("update", root)
|
|
||||||
self.update_nodes(root.walk_children(), animate=animate)
|
self.update_nodes(root.walk_children(), animate=animate)
|
||||||
|
|
||||||
@timer("update_nodes")
|
|
||||||
def update_nodes(self, nodes: Iterable[DOMNode], animate: bool = False) -> None:
|
def update_nodes(self, nodes: Iterable[DOMNode], animate: bool = False) -> None:
|
||||||
"""Update styles for nodes.
|
"""Update styles for nodes.
|
||||||
|
|
||||||
@@ -455,7 +482,6 @@ class Stylesheet:
|
|||||||
"""
|
"""
|
||||||
apply = self.apply
|
apply = self.apply
|
||||||
nodes = list(nodes)
|
nodes = list(nodes)
|
||||||
print(len(nodes), "NODES")
|
|
||||||
for node in nodes:
|
for node in nodes:
|
||||||
apply(node, animate=animate)
|
apply(node, animate=animate)
|
||||||
if isinstance(node, Widget) and node.is_scrollable:
|
if isinstance(node, Widget) and node.is_scrollable:
|
||||||
|
|||||||
@@ -163,6 +163,7 @@ class DevtoolsClient:
|
|||||||
if isinstance(log, str):
|
if isinstance(log, str):
|
||||||
await websocket.send_str(log)
|
await websocket.send_str(log)
|
||||||
else:
|
else:
|
||||||
|
assert isinstance(log, bytes)
|
||||||
await websocket.send_bytes(log)
|
await websocket.send_bytes(log)
|
||||||
log_queue.task_done()
|
log_queue.task_done()
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user