mirror of
https://github.com/Textualize/textual.git
synced 2025-10-17 02:38:12 +03:00
refactor of Styles in to StylesBase
This commit is contained in:
@@ -10,7 +10,7 @@ when setting and getting.
|
||||
from __future__ import annotations
|
||||
|
||||
|
||||
from typing import Iterable, NamedTuple, Sequence, TYPE_CHECKING
|
||||
from typing import Iterable, NamedTuple, TYPE_CHECKING
|
||||
|
||||
import rich.repr
|
||||
from rich.color import Color
|
||||
@@ -67,7 +67,7 @@ class ScalarProperty:
|
||||
Returns:
|
||||
The Scalar object or ``None`` if it's not set.
|
||||
"""
|
||||
value = obj._rules.get(self.name)
|
||||
value = obj.get_rule(self.name)
|
||||
return value
|
||||
|
||||
def __set__(self, obj: Styles, value: float | Scalar | str | None) -> None:
|
||||
@@ -87,7 +87,7 @@ class ScalarProperty:
|
||||
cannot be parsed for any other reason.
|
||||
"""
|
||||
if value is None:
|
||||
obj._rules.pop(self.name, None)
|
||||
obj.clear_rule(self.name)
|
||||
return
|
||||
if isinstance(value, float):
|
||||
new_value = Scalar(float(value), Unit.CELLS, Unit.WIDTH)
|
||||
@@ -106,7 +106,7 @@ class ScalarProperty:
|
||||
)
|
||||
if new_value is not None and new_value.is_percent:
|
||||
new_value = Scalar(float(new_value.value), self.percent_unit, Unit.WIDTH)
|
||||
obj._rules[self.name] = new_value
|
||||
obj.set_rule(self.name, new_value)
|
||||
obj.refresh()
|
||||
|
||||
|
||||
@@ -136,7 +136,7 @@ class BoxProperty:
|
||||
A ``tuple[BoxType, Style]`` containing the string type of the box and
|
||||
it's style. Example types are "rounded", "solid", and "dashed".
|
||||
"""
|
||||
box_type, color = obj._rules.get(self.name) or self.DEFAULT
|
||||
box_type, color = obj.get_rule(self.name) or self.DEFAULT
|
||||
return (box_type, color)
|
||||
|
||||
def __set__(self, obj: Styles, border: tuple[BoxType, str | Color] | None):
|
||||
@@ -152,7 +152,7 @@ class BoxProperty:
|
||||
StyleSyntaxError: If the string supplied for the color has invalid syntax.
|
||||
"""
|
||||
if border is None:
|
||||
obj._rules.pop(self.name, None)
|
||||
obj.clear_rule(self.name)
|
||||
else:
|
||||
_type, color = border
|
||||
new_value = border
|
||||
@@ -160,7 +160,7 @@ class BoxProperty:
|
||||
new_value = (_type, Color.parse(color))
|
||||
elif isinstance(color, Color):
|
||||
new_value = (_type, color)
|
||||
obj._rules[self.name] = new_value
|
||||
obj.set_rule(self.name, new_value)
|
||||
obj.refresh()
|
||||
|
||||
|
||||
@@ -361,7 +361,7 @@ class SpacingProperty:
|
||||
Returns:
|
||||
Spacing: The Spacing. If unset, returns the null spacing ``(0, 0, 0, 0)``.
|
||||
"""
|
||||
return obj._rules.get(self.name, NULL_SPACING)
|
||||
return obj.get_rule(self.name, NULL_SPACING)
|
||||
|
||||
def __set__(self, obj: Styles, spacing: SpacingDimensions | None):
|
||||
"""Set the Spacing
|
||||
@@ -377,9 +377,9 @@ class SpacingProperty:
|
||||
"""
|
||||
obj.refresh(layout=True)
|
||||
if spacing is None:
|
||||
obj._rules.pop(self.name)
|
||||
obj.clear_rule(self.name)
|
||||
else:
|
||||
obj._rules[self.name] = Spacing.unpack(spacing)
|
||||
obj.set_rule(self.name, Spacing.unpack(spacing))
|
||||
|
||||
|
||||
class DocksProperty:
|
||||
@@ -399,7 +399,7 @@ class DocksProperty:
|
||||
Returns:
|
||||
tuple[DockGroup, ...]: A ``tuple`` containing the defined docks.
|
||||
"""
|
||||
return obj._rules.get("docks") or ()
|
||||
return obj.get_rule("docks", ())
|
||||
|
||||
def __set__(self, obj: Styles, docks: Iterable[DockGroup] | None):
|
||||
"""Set the Docks property
|
||||
@@ -410,9 +410,10 @@ class DocksProperty:
|
||||
"""
|
||||
obj.refresh(layout=True)
|
||||
if docks is None:
|
||||
obj._rules.pop("docks")
|
||||
obj.clear_rule("docks")
|
||||
|
||||
else:
|
||||
obj._rules["docks"] = tuple(docks)
|
||||
obj.set_rule("docks", tuple(docks))
|
||||
|
||||
|
||||
class DockProperty:
|
||||
@@ -432,7 +433,7 @@ class DockProperty:
|
||||
Returns:
|
||||
str: The dock name as a string, or "" if the rule is not set.
|
||||
"""
|
||||
return obj._rules.get("dock") or "_default"
|
||||
return obj.get_rule("dock", "_default")
|
||||
|
||||
def __set__(self, obj: Styles, spacing: str | None):
|
||||
"""Set the Dock property
|
||||
@@ -442,10 +443,7 @@ class DockProperty:
|
||||
spacing (str | None): The spacing to use.
|
||||
"""
|
||||
obj.refresh(layout=True)
|
||||
if spacing is None:
|
||||
obj._rules.pop("dock")
|
||||
else:
|
||||
obj._rules["dock"] = spacing
|
||||
obj.set_rule("dock", spacing)
|
||||
|
||||
|
||||
class LayoutProperty:
|
||||
@@ -464,7 +462,7 @@ class LayoutProperty:
|
||||
Returns:
|
||||
The ``Layout`` object.
|
||||
"""
|
||||
return obj._rules.get(self.name)
|
||||
return obj.get_rule(self.name)
|
||||
|
||||
def __set__(self, obj: Styles, layout: str | Layout | None):
|
||||
"""
|
||||
@@ -479,11 +477,11 @@ class LayoutProperty:
|
||||
obj.refresh(layout=True)
|
||||
|
||||
if layout is None:
|
||||
obj._rules.pop("layout")
|
||||
obj.clear_rule("layout")
|
||||
elif isinstance(layout, Layout):
|
||||
obj._rules["layout"] = layout
|
||||
obj.set_rule("layout", layout)
|
||||
else:
|
||||
obj._rules["layout"] = get_layout(layout)
|
||||
obj.set_rule("layout", get_layout(layout))
|
||||
|
||||
|
||||
class OffsetProperty:
|
||||
@@ -506,7 +504,7 @@ class OffsetProperty:
|
||||
ScalarOffset: The ``ScalarOffset`` indicating the adjustment that
|
||||
will be made to widget position prior to it being rendered.
|
||||
"""
|
||||
return obj._rules.get(self.name, ScalarOffset.null())
|
||||
return obj.get_rule(self.name, ScalarOffset.null())
|
||||
|
||||
def __set__(
|
||||
self, obj: Styles, offset: tuple[int | str, int | str] | ScalarOffset | None
|
||||
@@ -526,9 +524,9 @@ class OffsetProperty:
|
||||
"""
|
||||
obj.refresh(layout=True)
|
||||
if offset is None:
|
||||
obj._rules.pop(self.name, None)
|
||||
obj.clear_rule(self.name)
|
||||
elif isinstance(offset, ScalarOffset):
|
||||
obj._rules[self.name] = offset
|
||||
obj.set_rule(self.name, offset)
|
||||
else:
|
||||
x, y = offset
|
||||
scalar_x = (
|
||||
@@ -542,7 +540,7 @@ class OffsetProperty:
|
||||
else Scalar(float(y), Unit.CELLS, Unit.HEIGHT)
|
||||
)
|
||||
_offset = ScalarOffset(scalar_x, scalar_y)
|
||||
obj._rules[self.name] = _offset
|
||||
obj.set_rule(self.name, _offset)
|
||||
|
||||
|
||||
class StringEnumProperty:
|
||||
@@ -567,7 +565,7 @@ class StringEnumProperty:
|
||||
Returns:
|
||||
str: The string property value
|
||||
"""
|
||||
return obj._rules.get(self.name, self._default)
|
||||
return obj.get_rule(self.name, self._default)
|
||||
|
||||
def __set__(self, obj: Styles, value: str | None = None):
|
||||
"""Set the string property and ensure it is in the set of allowed values.
|
||||
@@ -581,13 +579,13 @@ class StringEnumProperty:
|
||||
"""
|
||||
obj.refresh()
|
||||
if value is None:
|
||||
obj._rules.pop(self.name, None)
|
||||
obj.clear_rule(self.name)
|
||||
else:
|
||||
if value not in self._valid_values:
|
||||
raise StyleValueError(
|
||||
f"{self.name} must be one of {friendly_list(self._valid_values)}"
|
||||
)
|
||||
obj._rules[self.name] = value
|
||||
obj.set_rule(self.name, value)
|
||||
|
||||
|
||||
class NameProperty:
|
||||
@@ -606,7 +604,7 @@ class NameProperty:
|
||||
Returns:
|
||||
str: The name
|
||||
"""
|
||||
return obj._rules.get(self.name, "")
|
||||
return obj.get_rule(self.name, "")
|
||||
|
||||
def __set__(self, obj: Styles, name: str | None):
|
||||
"""Set the name property
|
||||
@@ -620,11 +618,11 @@ class NameProperty:
|
||||
"""
|
||||
obj.refresh(layout=True)
|
||||
if name is None:
|
||||
obj._rules.pop(self.name, None)
|
||||
obj.clear_rule(self.name)
|
||||
else:
|
||||
if not isinstance(name, str):
|
||||
raise StyleTypeError(f"{self._name} must be a str")
|
||||
obj._rules[self.name] = name
|
||||
raise StyleTypeError(f"{self.name} must be a str")
|
||||
obj.set_rule(self.name, name)
|
||||
|
||||
|
||||
class NameListProperty:
|
||||
@@ -634,20 +632,20 @@ class NameListProperty:
|
||||
def __get__(
|
||||
self, obj: Styles, objtype: type[Styles] | None = None
|
||||
) -> tuple[str, ...]:
|
||||
return obj._rules.get(self.name, ())
|
||||
return obj.get_rule(self.name, ())
|
||||
|
||||
def __set__(
|
||||
self, obj: Styles, names: str | tuple[str] | None = None
|
||||
) -> str | tuple[str] | None:
|
||||
obj.refresh(layout=True)
|
||||
if names is None:
|
||||
obj._rules.pop(self.name, None)
|
||||
obj.clear_rule(self.name)
|
||||
elif isinstance(names, str):
|
||||
obj._rules[self.name] = tuple(
|
||||
name.strip().lower() for name in names.split(" ")
|
||||
obj.set_rule(
|
||||
self.name, tuple(name.strip().lower() for name in names.split(" "))
|
||||
)
|
||||
elif isinstance(names, tuple):
|
||||
obj._rules[self.name] = names
|
||||
obj.set_rule(self.name, names)
|
||||
|
||||
|
||||
class ColorProperty:
|
||||
@@ -666,7 +664,7 @@ class ColorProperty:
|
||||
Returns:
|
||||
Color: The Color
|
||||
"""
|
||||
return obj._rules.get(self.name) or Color.default()
|
||||
return obj.get_rule(self.name) or Color.default()
|
||||
|
||||
def __set__(self, obj: Styles, color: Color | str | None):
|
||||
"""Set the Color
|
||||
@@ -682,11 +680,11 @@ class ColorProperty:
|
||||
"""
|
||||
obj.refresh()
|
||||
if color is None:
|
||||
obj._rules.pop(self.name, None)
|
||||
obj.clear_rule(self.name)
|
||||
elif isinstance(color, Color):
|
||||
obj._rules[self.name] = color
|
||||
obj.set_rule(self.name, color)
|
||||
elif isinstance(color, str):
|
||||
obj._rules[self.name] = Color.parse(color)
|
||||
obj.set_rule(self.name, Color.parse(color))
|
||||
|
||||
|
||||
class StyleFlagsProperty:
|
||||
@@ -719,7 +717,7 @@ class StyleFlagsProperty:
|
||||
Returns:
|
||||
Style: The ``Style`` object
|
||||
"""
|
||||
return obj._rules.get(self.name, Style.null())
|
||||
return obj.get_rule(self.name, Style.null())
|
||||
|
||||
def __set__(self, obj: Styles, style_flags: str | None):
|
||||
"""Set the style using a style flag string
|
||||
@@ -734,7 +732,7 @@ class StyleFlagsProperty:
|
||||
"""
|
||||
obj.refresh()
|
||||
if style_flags is None:
|
||||
obj._styles.pop(self.name, None)
|
||||
obj.clear_rule(self.name)
|
||||
else:
|
||||
words = [word.strip() for word in style_flags.split(" ")]
|
||||
valid_word = self._VALID_PROPERTIES.__contains__
|
||||
@@ -745,7 +743,7 @@ class StyleFlagsProperty:
|
||||
f"valid values are {friendly_list(self._VALID_PROPERTIES)}"
|
||||
)
|
||||
style = Style.parse(style_flags)
|
||||
obj._rules[self.name] = style
|
||||
obj.set_rule(self.name, style)
|
||||
|
||||
|
||||
class TransitionsProperty:
|
||||
@@ -768,4 +766,4 @@ class TransitionsProperty:
|
||||
e.g. ``{"offset": Transition(...), ...}``. If no transitions have been set, an empty ``dict``
|
||||
is returned.
|
||||
"""
|
||||
return obj._rules.get(self.name, {})
|
||||
return obj.get_rule(self.name, {})
|
||||
|
||||
@@ -1,17 +1,17 @@
|
||||
from __future__ import annotations
|
||||
|
||||
from abc import ABC, abstractmethod
|
||||
from dataclasses import dataclass, field
|
||||
from functools import lru_cache
|
||||
import sys
|
||||
from typing import Any, cast, Iterable, NamedTuple, TYPE_CHECKING
|
||||
from typing import Any, cast, Iterable, NamedTuple, TYPE_CHECKING, TypeVar
|
||||
|
||||
import rich.repr
|
||||
from rich.color import Color
|
||||
from rich.style import Style
|
||||
|
||||
from .. import log
|
||||
from ._style_properties import (
|
||||
Edges,
|
||||
BorderDefinition,
|
||||
BorderProperty,
|
||||
BoxProperty,
|
||||
ColorProperty,
|
||||
@@ -106,59 +106,102 @@ class DockGroup(NamedTuple):
|
||||
z: int
|
||||
|
||||
|
||||
@rich.repr.auto
|
||||
@dataclass
|
||||
class Styles:
|
||||
class StylesBase(ABC):
|
||||
"""A common base class for Styles and RenderStyles"""
|
||||
|
||||
node: DOMNode | None = None
|
||||
|
||||
_rules: RulesMap = field(default_factory=dict)
|
||||
|
||||
_layout_required: bool = False
|
||||
_repaint_required: bool = False
|
||||
|
||||
important: set[str] = field(default_factory=set)
|
||||
ANIMATABLE = {
|
||||
"offset",
|
||||
"padding",
|
||||
"margin",
|
||||
"width",
|
||||
"height",
|
||||
"min_width",
|
||||
"min_height",
|
||||
}
|
||||
|
||||
@abstractmethod
|
||||
def has_rule(self, rule: str) -> bool:
|
||||
"""Check if a rule has been set."""
|
||||
if rule in RULE_NAMES and rule in self._rules:
|
||||
return True
|
||||
if rule == "text":
|
||||
return not (
|
||||
{"text_color", "text_background", "text_style"}.isdisjoint(
|
||||
self._rules.keys()
|
||||
)
|
||||
)
|
||||
|
||||
if rule == "border":
|
||||
return not (
|
||||
{
|
||||
"border_top",
|
||||
"border_right",
|
||||
"border_bottom",
|
||||
"border_left",
|
||||
}.isdisjoint(self._rules.keys())
|
||||
)
|
||||
|
||||
if rule == "outline" and any(self.outline):
|
||||
return not (
|
||||
{
|
||||
"outline_top",
|
||||
"outline_right",
|
||||
"outline_bottom",
|
||||
"outline_left",
|
||||
}.isdisjoint(self._rules.keys())
|
||||
)
|
||||
return False
|
||||
...
|
||||
|
||||
@abstractmethod
|
||||
def clear_rule(self, rule_name: str) -> None:
|
||||
"""Clear a rule."""
|
||||
self._rules.pop(rule_name, None)
|
||||
|
||||
@abstractmethod
|
||||
def get_rules(self) -> RulesMap:
|
||||
"""Get rules as a dictionary."""
|
||||
rules = self._rules.copy()
|
||||
return rules
|
||||
|
||||
@abstractmethod
|
||||
def set_rule(self, rule: str, value: object | None) -> None:
|
||||
"""Set an individual rule.
|
||||
|
||||
Args:
|
||||
rule (str): Name of rule.
|
||||
value (object): Value of rule.
|
||||
"""
|
||||
|
||||
@abstractmethod
|
||||
def get_rule(self, rule: str, default: object = None) -> object:
|
||||
"""Get an individual rule.
|
||||
|
||||
Args:
|
||||
rule (str): Name of rule.
|
||||
default (object, optional): Default if rule does not exists. Defaults to None.
|
||||
|
||||
Returns:
|
||||
object: Rule value or default.
|
||||
"""
|
||||
|
||||
@abstractmethod
|
||||
def refresh(self, layout: bool = False) -> None:
|
||||
"""Mark the styles are requiring a refresh"""
|
||||
|
||||
@abstractmethod
|
||||
def check_refresh(self) -> tuple[bool, bool]:
|
||||
"""Check if the Styles must be refreshed.
|
||||
|
||||
Returns:
|
||||
tuple[bool, bool]: (repaint required, layout_required)
|
||||
"""
|
||||
|
||||
@abstractmethod
|
||||
def get_transition(self, key: str) -> Transition | None:
|
||||
"""Get a transition for a given key (or ``None`` if it doesn't exist)"""
|
||||
|
||||
@abstractmethod
|
||||
def reset(self) -> None:
|
||||
"""Reset the rules to initial state."""
|
||||
|
||||
@abstractmethod
|
||||
def merge(self, other: StylesBase) -> None:
|
||||
"""Merge values from another Styles.
|
||||
|
||||
Args:
|
||||
other (Styles): A Styles object.
|
||||
"""
|
||||
|
||||
@classmethod
|
||||
def is_animatable(cls, rule_name: str) -> bool:
|
||||
return rule_name in cls.ANIMATABLE
|
||||
|
||||
@classmethod
|
||||
@lru_cache(maxsize=1024)
|
||||
def parse(cls, css: str, path: str, *, node: DOMNode = None) -> Styles:
|
||||
"""Parse CSS and return a Styles object.
|
||||
|
||||
Args:
|
||||
css (str): [description]
|
||||
path (str): [description]
|
||||
node (DOMNode, optional): [description]. Defaults to None.
|
||||
|
||||
Returns:
|
||||
Styles: A Styles instance containing result of parsing CSS.
|
||||
"""
|
||||
from .parse import parse_declarations
|
||||
|
||||
styles = parse_declarations(css, path)
|
||||
styles.node = node
|
||||
return styles
|
||||
|
||||
display = StringEnumProperty(VALID_DISPLAY, "block")
|
||||
visibility = StringEnumProperty(VALID_VISIBILITY, "visible")
|
||||
@@ -197,34 +240,37 @@ class Styles:
|
||||
layers = NameListProperty()
|
||||
transitions = TransitionsProperty()
|
||||
|
||||
ANIMATABLE = {
|
||||
"offset",
|
||||
"padding",
|
||||
"margin",
|
||||
"width",
|
||||
"height",
|
||||
"min_width",
|
||||
"min_height",
|
||||
}
|
||||
|
||||
@property
|
||||
def gutter(self) -> Spacing:
|
||||
"""Get the gutter (additional space reserved for margin / padding / border).
|
||||
@rich.repr.auto
|
||||
@dataclass
|
||||
class Styles(StylesBase):
|
||||
|
||||
Returns:
|
||||
Spacing: Space around edges.
|
||||
"""
|
||||
gutter = self.margin + self.padding + self.border.spacing
|
||||
return gutter
|
||||
node: DOMNode | None = None
|
||||
|
||||
@classmethod
|
||||
@lru_cache(maxsize=1024)
|
||||
def parse(cls, css: str, path: str, *, node: DOMNode = None) -> Styles:
|
||||
from .parse import parse_declarations
|
||||
_rules: RulesMap = field(default_factory=dict)
|
||||
|
||||
styles = parse_declarations(css, path)
|
||||
styles.node = node
|
||||
return styles
|
||||
_layout_required: bool = False
|
||||
_repaint_required: bool = False
|
||||
|
||||
important: set[str] = field(default_factory=set)
|
||||
|
||||
def has_rule(self, rule: str) -> bool:
|
||||
return rule in self._rules
|
||||
|
||||
def clear_rule(self, rule: str) -> None:
|
||||
self._rules.pop(rule, None)
|
||||
|
||||
def get_rules(self) -> RulesMap:
|
||||
return self._rules.copy()
|
||||
|
||||
def set_rule(self, rule: str, value: object | None) -> None:
|
||||
if value is None:
|
||||
self._rules.pop(rule, None)
|
||||
else:
|
||||
self._rules[rule] = value
|
||||
|
||||
def get_rule(self, rule: str, default: object = None) -> object:
|
||||
return self._rules.get(rule, default)
|
||||
|
||||
def refresh(self, layout: bool = False) -> None:
|
||||
self._repaint_required = True
|
||||
@@ -252,6 +298,15 @@ class Styles:
|
||||
"""
|
||||
self._rules.clear()
|
||||
|
||||
def merge(self, other: Styles) -> None:
|
||||
"""Merge values from another Styles.
|
||||
|
||||
Args:
|
||||
other (Styles): A Styles object.
|
||||
"""
|
||||
|
||||
self._rules.update(other._rules)
|
||||
|
||||
def extract_rules(
|
||||
self, specificity: Specificity3
|
||||
) -> list[tuple[str, Specificity4, Any]]:
|
||||
@@ -272,9 +327,8 @@ class Styles:
|
||||
animate (bool, optional): ``True`` if the rules should animate, or ``False``
|
||||
to set rules without any animation. Defaults to ``False``.
|
||||
"""
|
||||
if not animate or self.node is None:
|
||||
self._rules.update(rules)
|
||||
else:
|
||||
|
||||
if animate and self.node is not None:
|
||||
styles = self
|
||||
is_animatable = styles.ANIMATABLE.__contains__
|
||||
_rules = self._rules
|
||||
@@ -293,6 +347,8 @@ class Styles:
|
||||
)
|
||||
else:
|
||||
rules[key] = value
|
||||
else:
|
||||
self._rules.update(rules)
|
||||
|
||||
if self.node is not None:
|
||||
self.node.on_style_change()
|
||||
@@ -303,15 +359,6 @@ class Styles:
|
||||
if self.important:
|
||||
yield "important", self.important
|
||||
|
||||
def merge(self, other: Styles) -> None:
|
||||
"""Merge values from another Styles.
|
||||
|
||||
Args:
|
||||
other (Styles): A Styles object.
|
||||
"""
|
||||
|
||||
self._rules.update(other._rules)
|
||||
|
||||
def _get_border_css_lines(
|
||||
self, rules: RulesMap, name: str
|
||||
) -> Iterable[tuple[str, str]]:
|
||||
@@ -381,9 +428,6 @@ class Styles:
|
||||
rules = self.get_rules()
|
||||
get_rule = rules.get
|
||||
has_rule = rules.__contains__
|
||||
from rich import print
|
||||
|
||||
print(rules)
|
||||
|
||||
if has_rule("display"):
|
||||
append_declaration("display", rules["display"])
|
||||
@@ -469,14 +513,14 @@ SetType = TypeVar("SetType")
|
||||
class StyleViewProperty(Generic[GetType, SetType]):
|
||||
"""Presents a view of a base Styles object, plus inline styles."""
|
||||
|
||||
def __set_name__(self, owner: StylesView, name: str) -> None:
|
||||
def __set_name__(self, owner: RenderStyles, name: str) -> None:
|
||||
self.name = name
|
||||
|
||||
def __set__(self, obj: StylesView, value: SetType) -> None:
|
||||
def __set__(self, obj: RenderStyles, value: SetType) -> None:
|
||||
setattr(obj._inline_styles, self.name, value)
|
||||
|
||||
def __get__(
|
||||
self, obj: StylesView, objtype: type[StylesView] | None = None
|
||||
self, obj: RenderStyles, objtype: type[RenderStyles] | None = None
|
||||
) -> GetType:
|
||||
if obj._inline_styles.has_rule(self.name):
|
||||
return getattr(obj._inline_styles, self.name)
|
||||
@@ -484,39 +528,27 @@ class StyleViewProperty(Generic[GetType, SetType]):
|
||||
|
||||
|
||||
@rich.repr.auto
|
||||
class StylesView:
|
||||
class RenderStyles(StylesBase):
|
||||
"""Presents a combined view of two Styles object: a base Styles and inline Styles."""
|
||||
|
||||
ANIMATABLE = {
|
||||
"offset",
|
||||
"padding",
|
||||
"margin",
|
||||
"width",
|
||||
"height",
|
||||
"min_width",
|
||||
"min_height",
|
||||
}
|
||||
|
||||
def __init__(self, node: DOMNode, base: Styles, inline_styles: Styles) -> None:
|
||||
self.node = node
|
||||
self._base_styles = base
|
||||
self._inline_styles = inline_styles
|
||||
|
||||
@property
|
||||
def base(self) -> Styles:
|
||||
return self._base_styles
|
||||
|
||||
@property
|
||||
def inline(self) -> Styles:
|
||||
return self._inline_styles
|
||||
|
||||
def __rich_repr__(self) -> rich.repr.Result:
|
||||
for rule_name in RULE_NAMES:
|
||||
if self.has_rule(rule_name):
|
||||
yield rule_name, getattr(self, rule_name)
|
||||
|
||||
@property
|
||||
def gutter(self) -> Spacing:
|
||||
"""Get the gutter (additional space reserved for margin / padding / border).
|
||||
|
||||
Returns:
|
||||
Spacing: Space around edges.
|
||||
"""
|
||||
gutter = self.margin + self.padding + self.border.spacing
|
||||
return gutter
|
||||
|
||||
def reset(self) -> None:
|
||||
"""Reset the inline styles."""
|
||||
self._inline_styles.reset()
|
||||
@@ -547,6 +579,14 @@ class StylesView:
|
||||
"""Check if a rule has been set."""
|
||||
return self._inline_styles.has_rule(rule) or self._base_styles.has_rule(rule)
|
||||
|
||||
def set_rule(self, rule: str, value: object | None) -> None:
|
||||
self._inline_styles.set_rule(rule, value)
|
||||
|
||||
def get_rule(self, rule: str, default: object = None) -> object:
|
||||
if self._inline_styles.has_rule(rule):
|
||||
return self._inline_styles.get_rule(rule, default)
|
||||
return self._base_styles.get_rule(rule, default)
|
||||
|
||||
def clear_rule(self, rule_name: str) -> None:
|
||||
"""Clear a rule (from inline)."""
|
||||
self._inline_styles.clear_rule(rule_name)
|
||||
@@ -596,74 +636,6 @@ class StylesView:
|
||||
combined_css = styles.css
|
||||
return combined_css
|
||||
|
||||
display: StyleViewProperty[str, str | None] = StyleViewProperty()
|
||||
visibility: StyleViewProperty[str, str | None] = StyleViewProperty()
|
||||
layout: StyleViewProperty[Layout | None, str | Layout | None] = StyleViewProperty()
|
||||
|
||||
text = StyleProperty()
|
||||
text_color: StyleViewProperty[Color, Color | str | None] = StyleViewProperty()
|
||||
text_background: StyleViewProperty[Color, Color | str | None] = StyleViewProperty()
|
||||
text_style: StyleViewProperty[Style, str | None] = StyleViewProperty()
|
||||
|
||||
padding: StyleViewProperty[Spacing, SpacingDimensions] = StyleViewProperty()
|
||||
margin: StyleViewProperty[Spacing, SpacingDimensions] = StyleViewProperty()
|
||||
offset: StyleViewProperty[
|
||||
ScalarOffset, tuple[int | str, int | str] | ScalarOffset
|
||||
] = StyleViewProperty()
|
||||
|
||||
border = BorderProperty()
|
||||
border_top: StyleViewProperty[
|
||||
tuple[BoxType, Color], tuple[BoxType, str | Color] | None
|
||||
] = StyleViewProperty()
|
||||
border_right: StyleViewProperty[
|
||||
tuple[BoxType, Color], tuple[BoxType, str | Color] | None
|
||||
] = StyleViewProperty()
|
||||
border_bottom: StyleViewProperty[
|
||||
tuple[BoxType, Color], tuple[BoxType, str | Color] | None
|
||||
] = StyleViewProperty()
|
||||
border_left: StyleViewProperty[
|
||||
tuple[BoxType, Color], tuple[BoxType, str | Color] | None
|
||||
] = StyleViewProperty()
|
||||
|
||||
outline = BorderProperty()
|
||||
outline_top: StyleViewProperty[
|
||||
tuple[BoxType, Color], tuple[BoxType, str | Color] | None
|
||||
] = StyleViewProperty()
|
||||
outline_right: StyleViewProperty[
|
||||
tuple[BoxType, Color], tuple[BoxType, str | Color] | None
|
||||
] = StyleViewProperty()
|
||||
outline_bottom: StyleViewProperty[
|
||||
tuple[BoxType, Color], tuple[BoxType, str | Color] | None
|
||||
] = StyleViewProperty()
|
||||
outline_left: StyleViewProperty[
|
||||
tuple[BoxType, Color], tuple[BoxType, str | Color] | None
|
||||
] = StyleViewProperty()
|
||||
|
||||
width: StyleViewProperty[
|
||||
Scalar | None, float | Scalar | str | None
|
||||
] = StyleViewProperty()
|
||||
height: StyleViewProperty[
|
||||
Scalar | None, float | Scalar | str | None
|
||||
] = StyleViewProperty()
|
||||
min_width: StyleViewProperty[
|
||||
Scalar | None, float | Scalar | str | None
|
||||
] = StyleViewProperty()
|
||||
min_height: StyleViewProperty[
|
||||
Scalar | None, float | Scalar | str | None
|
||||
] = StyleViewProperty()
|
||||
|
||||
dock: StyleViewProperty[str, str | None] = StyleViewProperty()
|
||||
docks: StyleViewProperty[
|
||||
tuple[DockGroup, ...], Iterable[DockGroup] | None
|
||||
] = StyleViewProperty()
|
||||
|
||||
layer: StyleViewProperty[str, str | None] = StyleViewProperty()
|
||||
layers: StyleViewProperty[
|
||||
tuple[str, ...], str | tuple[str] | None
|
||||
] = StyleViewProperty()
|
||||
|
||||
transitions: StyleViewProperty[dict[str, Transition], None] = StyleViewProperty()
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
styles = Styles()
|
||||
|
||||
@@ -134,8 +134,8 @@ class Stylesheet:
|
||||
if _check_selectors(selector_set.selectors, node):
|
||||
yield selector_set.specificity
|
||||
|
||||
def apply(self, node: DOMNode) -> None:
|
||||
"""Apply the stylesheet to a DOM node.
|
||||
def apply(self, node: DOMNode, animate: bool = False) -> None:
|
||||
"""pply the stylesheet to a DOM node.
|
||||
|
||||
Args:
|
||||
node (DOMNode): The ``DOMNode`` to apply the stylesheet to.
|
||||
@@ -182,14 +182,43 @@ class Stylesheet:
|
||||
for name, specificity_rules in rule_attributes.items()
|
||||
},
|
||||
)
|
||||
node._css_styles.apply_rules(node_rules, animate=animate)
|
||||
|
||||
node._css_styles.apply_rules(node_rules)
|
||||
@classmethod
|
||||
def apply_rules(
|
||||
cls, node: DOMNode | None, rules: RulesMap, animate: bool = False
|
||||
) -> None:
|
||||
|
||||
def update(self, root: DOMNode) -> None:
|
||||
if animate and node is not None:
|
||||
|
||||
is_animatable = node._css_styles.ANIMATABLE.__contains__
|
||||
_rules = node.styles.get_rules()
|
||||
for key, value in rules.items():
|
||||
current = _rules.get(key)
|
||||
if current == value:
|
||||
continue
|
||||
if is_animatable(key):
|
||||
transition = styles.get_transition(key)
|
||||
if transition is None:
|
||||
_rules[key] = value
|
||||
else:
|
||||
duration, easing, delay = transition
|
||||
self.node.app.animator.animate(
|
||||
styles, key, value, duration=duration, easing=easing
|
||||
)
|
||||
else:
|
||||
rules[key] = value
|
||||
else:
|
||||
self._rules.update(rules)
|
||||
|
||||
if self.node is not None:
|
||||
self.node.on_style_change()
|
||||
|
||||
def update(self, root: DOMNode, animate: bool = False) -> None:
|
||||
"""Update a node and its children."""
|
||||
apply = self.apply
|
||||
for node in root.walk_children():
|
||||
apply(node)
|
||||
apply(node, animate=animate)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
|
||||
@@ -12,7 +12,7 @@ from ._node_list import NodeList
|
||||
from .css._error_tools import friendly_list
|
||||
from .css.constants import VALID_DISPLAY, VALID_VISIBILITY
|
||||
from .css.errors import StyleValueError
|
||||
from .css.styles import Styles, StylesView
|
||||
from .css.styles import Styles, RenderStyles
|
||||
from .css.parse import parse_declarations
|
||||
from .message_pump import MessagePump
|
||||
|
||||
@@ -42,7 +42,7 @@ class DOMNode(MessagePump):
|
||||
self.children = NodeList()
|
||||
self._css_styles: Styles = Styles(self)
|
||||
self._inline_styles: Styles = Styles.parse(self.STYLES, repr(self), node=self)
|
||||
self.styles = StylesView(self, self._css_styles, self._inline_styles)
|
||||
self.styles = RenderStyles(self, self._css_styles, self._inline_styles)
|
||||
self.default_styles = Styles.parse(self.DEFAULT_STYLES, repr(self))
|
||||
self._default_rules = self.default_styles.extract_rules((0, 0, 0))
|
||||
super().__init__()
|
||||
@@ -341,7 +341,7 @@ class DOMNode(MessagePump):
|
||||
|
||||
"""
|
||||
self._classes.symmetric_difference_update(class_names)
|
||||
self.app.stylesheet.update(self.app)
|
||||
self.app.stylesheet.update(self.app, animate=True)
|
||||
|
||||
def has_pseudo_class(self, *class_names: str) -> bool:
|
||||
"""Check for pseudo class (such as hover, focus etc)"""
|
||||
|
||||
@@ -208,7 +208,8 @@ class Widget(DOMNode):
|
||||
Returns:
|
||||
Spacing: [description]
|
||||
"""
|
||||
gutter = self.styles.gutter
|
||||
styles = self.styles
|
||||
gutter = styles.margin + styles.padding + styles.border.spacing
|
||||
return gutter
|
||||
|
||||
def on_style_change(self) -> None:
|
||||
|
||||
Reference in New Issue
Block a user