hover pseudo class

This commit is contained in:
Will McGugan
2022-01-10 15:18:58 +00:00
parent 3ffc5826c2
commit 4abfb77517
9 changed files with 99 additions and 54 deletions

View File

@@ -5,7 +5,8 @@ App > View {
} }
Widget:hover { Widget:hover {
outline: solid green; outline: heavy;
text: bold !important;
} }
#sidebar { #sidebar {

View File

@@ -228,8 +228,8 @@ class StyleProperty:
bgcolor = getattr(obj, self._bgcolor_name) bgcolor = getattr(obj, self._bgcolor_name)
style = Style.from_color(color, bgcolor) style = Style.from_color(color, bgcolor)
style_flags = getattr(obj, self._style_name) style_flags = getattr(obj, self._style_name)
if style_flags is not None: if style_flags:
style += Style.parse(style_flags) style += style_flags
return style return style
def __set__(self, obj: Styles, style: Style | str | None) -> Style | str | None: def __set__(self, obj: Styles, style: Style | str | None) -> Style | str | None:
@@ -241,13 +241,12 @@ class StyleProperty:
elif isinstance(style, Style): elif isinstance(style, Style):
setattr(obj, self._color_name, style.color) setattr(obj, self._color_name, style.color)
setattr(obj, self._bgcolor_name, style.bgcolor) setattr(obj, self._bgcolor_name, style.bgcolor)
setattr(obj, self._style_name, str(style.without_color)) setattr(obj, self._style_name, style.without_color)
elif isinstance(style, str): elif isinstance(style, str):
new_style = Style.parse(style) new_style = Style.parse(style)
setattr(obj, self._color_name, new_style.color) setattr(obj, self._color_name, new_style.color)
setattr(obj, self._bgcolor_name, new_style.bgcolor) setattr(obj, self._bgcolor_name, new_style.bgcolor)
style_str = str(new_style.without_color) setattr(obj, self._style_name, new_style.without_color)
setattr(obj, self._style_name, style_str if style_str != "none" else "")
return style return style
@@ -457,7 +456,7 @@ class StyleFlagsProperty:
for word in words: for word in words:
if not valid_word(word): if not valid_word(word):
raise StyleValueError(f"unknown word {word!r} in style flags") raise StyleValueError(f"unknown word {word!r} in style flags")
style = Style.parse(" ".join(words)) style = Style.parse(style_flags)
setattr(obj, self._internal_name, style) setattr(obj, self._internal_name, style)
return style_flags return style_flags

View File

@@ -43,9 +43,8 @@ class StylesBuilder:
def add_declaration(self, declaration: Declaration) -> None: def add_declaration(self, declaration: Declaration) -> None:
if not declaration.tokens: if not declaration.tokens:
return return
process_method = getattr( rule_name = declaration.name.replace("-", "_")
self, f"process_{declaration.name.replace('-', '_')}", None process_method = getattr(self, f"process_{rule_name}", None)
)
if process_method is None: if process_method is None:
self.error( self.error(
@@ -55,17 +54,19 @@ class StylesBuilder:
) )
else: else:
tokens = declaration.tokens tokens = declaration.tokens
if tokens[-1].name == "important":
important = tokens[-1].name == "important"
if important:
tokens = tokens[:-1] tokens = tokens[:-1]
self.styles.important.add(declaration.name) self.styles.important.add(rule_name)
try: try:
process_method(declaration.name, tokens) process_method(declaration.name, tokens, important)
except DeclarationError as error: except DeclarationError as error:
raise raise
except Exception as error: except Exception as error:
self.error(declaration.name, declaration.token, str(error)) self.error(declaration.name, declaration.token, str(error))
def process_display(self, name: str, tokens: list[Token]) -> None: def process_display(self, name: str, tokens: list[Token], important: bool) -> None:
for token in tokens: for token in tokens:
name, value, _, _, location = token name, value, _, _, location = token
@@ -90,19 +91,25 @@ class StylesBuilder:
else: else:
self.error(name, tokens[0], "a single scalar is expected") self.error(name, tokens[0], "a single scalar is expected")
def process_width(self, name: str, tokens: list[Token]) -> None: def process_width(self, name: str, tokens: list[Token], important: bool) -> None:
self._process_scalar(name, tokens) self._process_scalar(name, tokens)
def process_height(self, name: str, tokens: list[Token]) -> None: def process_height(self, name: str, tokens: list[Token], important: bool) -> None:
self._process_scalar(name, tokens) self._process_scalar(name, tokens)
def process_min_width(self, name: str, tokens: list[Token]) -> None: def process_min_width(
self, name: str, tokens: list[Token], important: bool
) -> None:
self._process_scalar(name, tokens) self._process_scalar(name, tokens)
def process_min_height(self, name: str, tokens: list[Token]) -> None: def process_min_height(
self, name: str, tokens: list[Token], important: bool
) -> None:
self._process_scalar(name, tokens) self._process_scalar(name, tokens)
def process_visibility(self, name: str, tokens: list[Token]) -> None: def process_visibility(
self, name: str, tokens: list[Token], important: bool
) -> None:
for token in tokens: for token in tokens:
name, value, _, _, location = token name, value, _, _, location = token
if name == "token": if name == "token":
@@ -140,10 +147,10 @@ class StylesBuilder:
Spacing.unpack(cast(SpacingDimensions, tuple(space))), Spacing.unpack(cast(SpacingDimensions, tuple(space))),
) )
def process_padding(self, name: str, tokens: list[Token]) -> None: def process_padding(self, name: str, tokens: list[Token], important: bool) -> None:
self._process_space(name, tokens) self._process_space(name, tokens)
def process_margin(self, name: str, tokens: list[Token]) -> None: def process_margin(self, name: str, tokens: list[Token], important: bool) -> None:
self._process_space(name, tokens) self._process_space(name, tokens)
def _parse_border(self, name: str, tokens: list[Token]) -> tuple[str, Style]: def _parse_border(self, name: str, tokens: list[Token]) -> tuple[str, Style]:
@@ -173,47 +180,63 @@ class StylesBuilder:
border = self._parse_border("border", tokens) border = self._parse_border("border", tokens)
setattr(self.styles, f"_rule_border_{edge}", border) setattr(self.styles, f"_rule_border_{edge}", border)
def process_border(self, name: str, tokens: list[Token]) -> None: def process_border(self, name: str, tokens: list[Token], important: bool) -> None:
border = self._parse_border("border", tokens) border = self._parse_border("border", tokens)
styles = self.styles styles = self.styles
styles._rule_border_top = styles._rule_border_right = border styles._rule_border_top = styles._rule_border_right = border
styles._rule_border_bottom = styles._rule_border_left = border styles._rule_border_bottom = styles._rule_border_left = border
def process_border_top(self, name: str, tokens: list[Token]) -> None: def process_border_top(
self, name: str, tokens: list[Token], important: bool
) -> None:
self._process_border("top", name, tokens) self._process_border("top", name, tokens)
def process_border_right(self, name: str, tokens: list[Token]) -> None: def process_border_right(
self, name: str, tokens: list[Token], important: bool
) -> None:
self._process_border("right", name, tokens) self._process_border("right", name, tokens)
def process_border_bottom(self, name: str, tokens: list[Token]) -> None: def process_border_bottom(
self, name: str, tokens: list[Token], important: bool
) -> None:
self._process_border("bottom", name, tokens) self._process_border("bottom", name, tokens)
def process_border_left(self, name: str, tokens: list[Token]) -> None: def process_border_left(
self, name: str, tokens: list[Token], important: bool
) -> None:
self._process_border("left", name, tokens) self._process_border("left", name, tokens)
def _process_outline(self, edge: str, name: str, tokens: list[Token]) -> None: def _process_outline(self, edge: str, name: str, tokens: list[Token]) -> None:
border = self._parse_border("outline", tokens) border = self._parse_border("outline", tokens)
setattr(self.styles, f"_rule_outline_{edge}", border) setattr(self.styles, f"_rule_outline_{edge}", border)
def process_outline(self, name: str, tokens: list[Token]) -> None: def process_outline(self, name: str, tokens: list[Token], important: bool) -> None:
border = self._parse_border("outline", tokens) border = self._parse_border("outline", tokens)
styles = self.styles styles = self.styles
styles._rule_outline_top = styles._rule_outline_right = border styles._rule_outline_top = styles._rule_outline_right = border
styles._rule_outline_bottom = styles._rule_outline_left = border styles._rule_outline_bottom = styles._rule_outline_left = border
def process_outline_top(self, name: str, tokens: list[Token]) -> None: def process_outline_top(
self, name: str, tokens: list[Token], important: bool
) -> None:
self._process_outline("top", name, tokens) self._process_outline("top", name, tokens)
def process_parse_border_right(self, name: str, tokens: list[Token]) -> None: def process_parse_border_right(
self, name: str, tokens: list[Token], important: bool
) -> None:
self._process_outline("right", name, tokens) self._process_outline("right", name, tokens)
def process_outline_bottom(self, name: str, tokens: list[Token]) -> None: def process_outline_bottom(
self, name: str, tokens: list[Token], important: bool
) -> None:
self._process_outline("bottom", name, tokens) self._process_outline("bottom", name, tokens)
def process_outline_left(self, name: str, tokens: list[Token]) -> None: def process_outline_left(
self, name: str, tokens: list[Token], important: bool
) -> None:
self._process_outline("left", name, tokens) self._process_outline("left", name, tokens)
def process_offset(self, name: str, tokens: list[Token]) -> None: def process_offset(self, name: str, tokens: list[Token], important: bool) -> None:
if not tokens: if not tokens:
return return
if len(tokens) != 2: if len(tokens) != 2:
@@ -229,7 +252,7 @@ class StylesBuilder:
scalar_y = Scalar.parse(token2.value, Unit.HEIGHT) scalar_y = Scalar.parse(token2.value, Unit.HEIGHT)
self.styles._rule_offset = ScalarOffset(scalar_x, scalar_y) self.styles._rule_offset = ScalarOffset(scalar_x, scalar_y)
def process_offset_x(self, name: str, tokens: list[Token]) -> None: def process_offset_x(self, name: str, tokens: list[Token], important: bool) -> None:
if not tokens: if not tokens:
return return
if len(tokens) != 1: if len(tokens) != 1:
@@ -242,7 +265,7 @@ class StylesBuilder:
y = self.styles.offset.y y = self.styles.offset.y
self.styles._rule_offset = ScalarOffset(x, y) self.styles._rule_offset = ScalarOffset(x, y)
def process_offset_y(self, name: str, tokens: list[Token]) -> None: def process_offset_y(self, name: str, tokens: list[Token], important: bool) -> None:
if not tokens: if not tokens:
return return
if len(tokens) != 1: if len(tokens) != 1:
@@ -255,22 +278,28 @@ class StylesBuilder:
x = self.styles.offset.x x = self.styles.offset.x
self.styles._rule_offset = ScalarOffset(x, y) self.styles._rule_offset = ScalarOffset(x, y)
def process_layout(self, name: str, tokens: list[Token]) -> None: def process_layout(self, name: str, tokens: list[Token], important: bool) -> None:
if tokens: if tokens:
if len(tokens) != 1: if len(tokens) != 1:
self.error(name, tokens[0], "unexpected tokens in declaration") self.error(name, tokens[0], "unexpected tokens in declaration")
else: else:
self.styles._rule_layout = tokens[0].value self.styles._rule_layout = tokens[0].value
def process_text(self, name: str, tokens: list[Token]) -> None: def process_text(self, name: str, tokens: list[Token], important: bool) -> None:
style_definition = " ".join(token.value for token in tokens) style_definition = " ".join(token.value for token in tokens)
try: try:
style = Style.parse(style_definition) style = Style.parse(style_definition)
except Exception as error: except Exception as error:
self.error(name, tokens[0], f"failed to parse style; {error}") self.error(name, tokens[0], f"failed to parse style; {error}")
if important:
self.styles.important.update(
{"text_style", "text_background", "text_color"}
)
self.styles.text = style self.styles.text = style
def process_text_color(self, name: str, tokens: list[Token]) -> None: def process_text_color(
self, name: str, tokens: list[Token], important: bool
) -> None:
for token in tokens: for token in tokens:
if token.name in ("color", "token"): if token.name in ("color", "token"):
try: try:
@@ -284,7 +313,9 @@ class StylesBuilder:
name, token, f"unexpected token {token.value!r} in declaration" name, token, f"unexpected token {token.value!r} in declaration"
) )
def process_text_background(self, name: str, tokens: list[Token]) -> None: def process_text_background(
self, name: str, tokens: list[Token], important: bool
) -> None:
for token in tokens: for token in tokens:
if token.name in ("color", "token"): if token.name in ("color", "token"):
try: try:
@@ -298,11 +329,13 @@ class StylesBuilder:
name, token, f"unexpected token {token.value!r} in declaration" name, token, f"unexpected token {token.value!r} in declaration"
) )
def process_text_style(self, name: str, tokens: list[Token]) -> None: def process_text_style(
self, name: str, tokens: list[Token], important: bool
) -> None:
style_definition = " ".join(token.value for token in tokens) style_definition = " ".join(token.value for token in tokens)
self.styles.text_style = style_definition self.styles.text_style = style_definition
def process_dock(self, name: str, tokens: list[Token]) -> None: def process_dock(self, name: str, tokens: list[Token], important: bool) -> None:
if len(tokens) > 1: if len(tokens) > 1:
self.error( self.error(
@@ -312,7 +345,7 @@ class StylesBuilder:
) )
self.styles._rule_dock = tokens[0].value if tokens else "" self.styles._rule_dock = tokens[0].value if tokens else ""
def process_docks(self, name: str, tokens: list[Token]) -> None: def process_docks(self, name: str, tokens: list[Token], important: bool) -> None:
docks: list[DockGroup] = [] docks: list[DockGroup] = []
for token in tokens: for token in tokens:
if token.name == "key_value": if token.name == "key_value":
@@ -343,12 +376,12 @@ class StylesBuilder:
) )
self.styles._rule_docks = tuple(docks + [DockGroup("_default", "top", 0)]) self.styles._rule_docks = tuple(docks + [DockGroup("_default", "top", 0)])
def process_layer(self, name: str, tokens: list[Token]) -> None: def process_layer(self, name: str, tokens: list[Token], important: bool) -> None:
if len(tokens) > 1: if len(tokens) > 1:
self.error(name, tokens[1], f"unexpected tokens in dock-edge declaration") self.error(name, tokens[1], f"unexpected tokens in dock-edge declaration")
self.styles._rule_layer = tokens[0].value self.styles._rule_layer = tokens[0].value
def process_layers(self, name: str, tokens: list[Token]) -> None: def process_layers(self, name: str, tokens: list[Token], important: bool) -> None:
layers: list[str] = [] layers: list[str] = []
for token in tokens: for token in tokens:
if token.name != "token": if token.name != "token":
@@ -356,7 +389,9 @@ class StylesBuilder:
layers.append(token.value) layers.append(token.value)
self.styles._rule_layers = tuple(layers) self.styles._rule_layers = tuple(layers)
def process_transition(self, name: str, tokens: list[Token]) -> None: def process_transition(
self, name: str, tokens: list[Token], important: bool
) -> None:
transitions: dict[str, Transition] = {} transitions: dict[str, Transition] = {}
css_property = "" css_property = ""

View File

@@ -64,6 +64,11 @@ class Selector:
SelectorType.ID: self._check_id, SelectorType.ID: self._check_id,
} }
def _add_pseudo_class(self, pseudo_class: str) -> None:
self.pseudo_classes.append(pseudo_class)
specificity1, specificity2, specificity3 = self.specificity
self.specificity = (specificity1, specificity2 + 1, specificity3)
def check(self, node: DOMNode) -> bool: def check(self, node: DOMNode) -> bool:
return self._checks[self.type](node) return self._checks[self.type](node)

View File

@@ -48,7 +48,7 @@ def parse_selectors(css_selectors: str) -> tuple[SelectorSet, ...]:
except EOFError: except EOFError:
break break
if token.name == "pseudo_class": if token.name == "pseudo_class":
selectors[-1].pseudo_classes.append(token.value.lstrip(":")) selectors[-1]._add_pseudo_class(token.value.lstrip(":"))
elif token.name == "whitespace": elif token.name == "whitespace":
if combinator is None or combinator == CombinatorType.SAME: if combinator is None or combinator == CombinatorType.SAME:
combinator = CombinatorType.DESCENDENT combinator = CombinatorType.DESCENDENT
@@ -92,7 +92,7 @@ def parse_rule_set(tokens: Iterator[Token], token: Token) -> Iterable[RuleSet]:
while True: while True:
if token.name == "pseudo_class": if token.name == "pseudo_class":
selectors[-1].pseudo_classes.append(token.value.lstrip(":")) selectors[-1]._add_pseudo_class(token.value.lstrip(":"))
elif token.name == "whitespace": elif token.name == "whitespace":
if combinator is None or combinator == CombinatorType.SAME: if combinator is None or combinator == CombinatorType.SAME:
combinator = CombinatorType.DESCENDENT combinator = CombinatorType.DESCENDENT

View File

@@ -16,6 +16,7 @@ from .._types import MessageTarget
from .errors import StyleValueError from .errors import StyleValueError
from .. import events from .. import events
from ._error_tools import friendly_list from ._error_tools import friendly_list
from .types import Specificity3, Specificity4
from .constants import ( from .constants import (
VALID_DISPLAY, VALID_DISPLAY,
VALID_VISIBILITY, VALID_VISIBILITY,
@@ -159,7 +160,7 @@ class Styles:
"""Get the gutter (additional space reserved for margin / padding / border). """Get the gutter (additional space reserved for margin / padding / border).
Returns: Returns:
Spacing: [description] Spacing: Space around edges.
""" """
gutter = self.margin + self.padding + self.border.spacing gutter = self.margin + self.padding + self.border.spacing
return gutter return gutter
@@ -200,8 +201,6 @@ class Styles:
def refresh(self, layout: bool = False) -> None: def refresh(self, layout: bool = False) -> None:
self._repaint_required = True self._repaint_required = True
self._layout_required = layout self._layout_required = layout
# if self.node is not None:
# self.node.post_message_no_wait(events.Null(self.node))
def check_refresh(self) -> tuple[bool, bool]: def check_refresh(self) -> tuple[bool, bool]:
result = (self._repaint_required, self._layout_required) result = (self._repaint_required, self._layout_required)
@@ -237,8 +236,8 @@ class Styles:
return None return None
def extract_rules( def extract_rules(
self, specificity: tuple[int, int, int] self, specificity: Specificity3
) -> list[tuple[str, tuple[int, int, int, int], Any]]: ) -> list[tuple[str, Specificity4, Any]]:
is_important = self.important.__contains__ is_important = self.important.__contains__
rules = [ rules = [
( (

View File

@@ -124,10 +124,17 @@ class Stylesheet:
get_first_item = itemgetter(0) get_first_item = itemgetter(0)
log("")
log(node)
for key, attributes in rule_attributes.items():
log(key, key in node.styles.important)
log("\t", attributes)
node_rules = [ node_rules = [
(name, max(specificity_rules, key=get_first_item)[1]) (name, max(specificity_rules, key=get_first_item)[1])
for name, specificity_rules in rule_attributes.items() for name, specificity_rules in rule_attributes.items()
] ]
node.styles.apply_rules(node_rules) node.styles.apply_rules(node_rules)
def update(self, root: DOMNode) -> None: def update(self, root: DOMNode) -> None:

View File

@@ -102,7 +102,6 @@ class DOMNode(MessagePump):
def pseudo_classes(self) -> frozenset[str]: def pseudo_classes(self) -> frozenset[str]:
"""Get a set of all pseudo classes""" """Get a set of all pseudo classes"""
pseudo_classes = frozenset({*self.get_pseudo_classes()}) pseudo_classes = frozenset({*self.get_pseudo_classes()})
self.log(pseudo_classes)
return pseudo_classes return pseudo_classes
@property @property

View File

@@ -176,7 +176,7 @@ class Widget(DOMNode):
if styles.has_outline: if styles.has_outline:
renderable = Border( renderable = Border(
renderable, styles.outline, outline=True, style=parent_text_style renderable, styles.outline, outline=True, style=renderable_text_style
) )
return renderable return renderable
@@ -221,7 +221,7 @@ class Widget(DOMNode):
return gutter return gutter
def on_style_change(self) -> None: def on_style_change(self) -> None:
self.log("style_Change", self) self.log("style_change", self)
self.clear_render_cache() self.clear_render_cache()
def _update_size(self, size: Size) -> None: def _update_size(self, size: Size) -> None: