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 {
outline: solid green;
outline: heavy;
text: bold !important;
}
#sidebar {

View File

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

View File

@@ -43,9 +43,8 @@ class StylesBuilder:
def add_declaration(self, declaration: Declaration) -> None:
if not declaration.tokens:
return
process_method = getattr(
self, f"process_{declaration.name.replace('-', '_')}", None
)
rule_name = declaration.name.replace("-", "_")
process_method = getattr(self, f"process_{rule_name}", None)
if process_method is None:
self.error(
@@ -55,17 +54,19 @@ class StylesBuilder:
)
else:
tokens = declaration.tokens
if tokens[-1].name == "important":
important = tokens[-1].name == "important"
if important:
tokens = tokens[:-1]
self.styles.important.add(declaration.name)
self.styles.important.add(rule_name)
try:
process_method(declaration.name, tokens)
process_method(declaration.name, tokens, important)
except DeclarationError as error:
raise
except Exception as 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:
name, value, _, _, location = token
@@ -90,19 +91,25 @@ class StylesBuilder:
else:
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)
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)
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)
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)
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:
name, value, _, _, location = token
if name == "token":
@@ -140,10 +147,10 @@ class StylesBuilder:
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)
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)
def _parse_border(self, name: str, tokens: list[Token]) -> tuple[str, Style]:
@@ -173,47 +180,63 @@ class StylesBuilder:
border = self._parse_border("border", tokens)
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)
styles = self.styles
styles._rule_border_top = styles._rule_border_right = 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)
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)
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)
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)
def _process_outline(self, edge: str, name: str, tokens: list[Token]) -> None:
border = self._parse_border("outline", tokens)
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)
styles = self.styles
styles._rule_outline_top = styles._rule_outline_right = 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)
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)
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)
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)
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:
return
if len(tokens) != 2:
@@ -229,7 +252,7 @@ class StylesBuilder:
scalar_y = Scalar.parse(token2.value, Unit.HEIGHT)
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:
return
if len(tokens) != 1:
@@ -242,7 +265,7 @@ class StylesBuilder:
y = self.styles.offset.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:
return
if len(tokens) != 1:
@@ -255,22 +278,28 @@ class StylesBuilder:
x = self.styles.offset.x
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 len(tokens) != 1:
self.error(name, tokens[0], "unexpected tokens in declaration")
else:
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)
try:
style = Style.parse(style_definition)
except Exception as 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
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:
if token.name in ("color", "token"):
try:
@@ -284,7 +313,9 @@ class StylesBuilder:
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:
if token.name in ("color", "token"):
try:
@@ -298,11 +329,13 @@ class StylesBuilder:
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)
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:
self.error(
@@ -312,7 +345,7 @@ class StylesBuilder:
)
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] = []
for token in tokens:
if token.name == "key_value":
@@ -343,12 +376,12 @@ class StylesBuilder:
)
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:
self.error(name, tokens[1], f"unexpected tokens in dock-edge declaration")
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] = []
for token in tokens:
if token.name != "token":
@@ -356,7 +389,9 @@ class StylesBuilder:
layers.append(token.value)
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] = {}
css_property = ""

View File

@@ -64,6 +64,11 @@ class Selector:
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:
return self._checks[self.type](node)

View File

@@ -48,7 +48,7 @@ def parse_selectors(css_selectors: str) -> tuple[SelectorSet, ...]:
except EOFError:
break
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":
if combinator is None or combinator == CombinatorType.SAME:
combinator = CombinatorType.DESCENDENT
@@ -92,7 +92,7 @@ def parse_rule_set(tokens: Iterator[Token], token: Token) -> Iterable[RuleSet]:
while True:
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":
if combinator is None or combinator == CombinatorType.SAME:
combinator = CombinatorType.DESCENDENT

View File

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

View File

@@ -124,10 +124,17 @@ class Stylesheet:
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 = [
(name, max(specificity_rules, key=get_first_item)[1])
for name, specificity_rules in rule_attributes.items()
]
node.styles.apply_rules(node_rules)
def update(self, root: DOMNode) -> None:

View File

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

View File

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