refactor of Styles in to StylesBase

This commit is contained in:
Will McGugan
2022-02-08 11:47:29 +00:00
parent 1173f14020
commit 309130bd77
5 changed files with 233 additions and 233 deletions

View File

@@ -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, {})

View File

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

View File

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

View File

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

View File

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