mirror of
https://github.com/Textualize/textual.git
synced 2025-10-17 02:38:12 +03:00
convert styles to using sipler dict
This commit is contained in:
@@ -17,6 +17,7 @@ class BasicApp(App):
|
|||||||
footer=Widget(),
|
footer=Widget(),
|
||||||
sidebar=Widget(),
|
sidebar=Widget(),
|
||||||
)
|
)
|
||||||
|
self.panic(self.stylesheet.css)
|
||||||
|
|
||||||
|
|
||||||
BasicApp.run(css_file="basic.css", watch_css=True, log="textual.log")
|
BasicApp.run(css_file="basic.css", watch_css=True, log="textual.log")
|
||||||
|
|||||||
@@ -9,7 +9,8 @@ when setting and getting.
|
|||||||
|
|
||||||
from __future__ import annotations
|
from __future__ import annotations
|
||||||
|
|
||||||
from typing import Iterable, NamedTuple, Sequence, TYPE_CHECKING
|
|
||||||
|
from typing import Iterable, Iterator, NamedTuple, Sequence, TYPE_CHECKING
|
||||||
|
|
||||||
import rich.repr
|
import rich.repr
|
||||||
from rich.color import Color
|
from rich.color import Color
|
||||||
@@ -54,7 +55,6 @@ class ScalarProperty:
|
|||||||
|
|
||||||
def __set_name__(self, owner: Styles, name: str) -> None:
|
def __set_name__(self, owner: Styles, name: str) -> None:
|
||||||
self.name = name
|
self.name = name
|
||||||
self.internal_name = f"_rule_{name}"
|
|
||||||
|
|
||||||
def __get__(
|
def __get__(
|
||||||
self, obj: Styles, objtype: type[Styles] | None = None
|
self, obj: Styles, objtype: type[Styles] | None = None
|
||||||
@@ -68,7 +68,7 @@ class ScalarProperty:
|
|||||||
Returns:
|
Returns:
|
||||||
The Scalar object or ``None`` if it's not set.
|
The Scalar object or ``None`` if it's not set.
|
||||||
"""
|
"""
|
||||||
value = getattr(obj, self.internal_name)
|
value = obj._rules.get(self.name)
|
||||||
return value
|
return value
|
||||||
|
|
||||||
def __set__(self, obj: Styles, value: float | Scalar | str | None) -> None:
|
def __set__(self, obj: Styles, value: float | Scalar | str | None) -> None:
|
||||||
@@ -88,8 +88,9 @@ class ScalarProperty:
|
|||||||
cannot be parsed for any other reason.
|
cannot be parsed for any other reason.
|
||||||
"""
|
"""
|
||||||
if value is None:
|
if value is None:
|
||||||
new_value = None
|
obj._rules.pop(self.name, None)
|
||||||
elif isinstance(value, float):
|
return
|
||||||
|
if isinstance(value, float):
|
||||||
new_value = Scalar(float(value), Unit.CELLS, Unit.WIDTH)
|
new_value = Scalar(float(value), Unit.CELLS, Unit.WIDTH)
|
||||||
elif isinstance(value, Scalar):
|
elif isinstance(value, Scalar):
|
||||||
new_value = value
|
new_value = value
|
||||||
@@ -106,7 +107,7 @@ class ScalarProperty:
|
|||||||
)
|
)
|
||||||
if new_value is not None and new_value.is_percent:
|
if new_value is not None and new_value.is_percent:
|
||||||
new_value = Scalar(float(new_value.value), self.percent_unit, Unit.WIDTH)
|
new_value = Scalar(float(new_value.value), self.percent_unit, Unit.WIDTH)
|
||||||
setattr(obj, self.internal_name, new_value)
|
obj._rules[self.name] = new_value
|
||||||
obj.refresh()
|
obj.refresh()
|
||||||
|
|
||||||
|
|
||||||
@@ -118,7 +119,7 @@ class BoxProperty:
|
|||||||
DEFAULT = ("", Style())
|
DEFAULT = ("", Style())
|
||||||
|
|
||||||
def __set_name__(self, owner: Styles, name: str) -> None:
|
def __set_name__(self, owner: Styles, name: str) -> None:
|
||||||
self.internal_name = f"_rule_{name}"
|
self.name = name
|
||||||
_type, edge = name.split("_")
|
_type, edge = name.split("_")
|
||||||
self._type = _type
|
self._type = _type
|
||||||
self.edge = edge
|
self.edge = edge
|
||||||
@@ -136,8 +137,8 @@ class BoxProperty:
|
|||||||
A ``tuple[BoxType, Style]`` containing the string type of the box and
|
A ``tuple[BoxType, Style]`` containing the string type of the box and
|
||||||
it's style. Example types are "rounded", "solid", and "dashed".
|
it's style. Example types are "rounded", "solid", and "dashed".
|
||||||
"""
|
"""
|
||||||
value = getattr(obj, self.internal_name)
|
value = obj._rules.get(self.name) or self.DEFAULT
|
||||||
return value or self.DEFAULT
|
return value
|
||||||
|
|
||||||
def __set__(self, obj: Styles, border: tuple[BoxType, str | Color | Style] | None):
|
def __set__(self, obj: Styles, border: tuple[BoxType, str | Color | Style] | None):
|
||||||
"""Set the box property
|
"""Set the box property
|
||||||
@@ -152,7 +153,7 @@ class BoxProperty:
|
|||||||
StyleSyntaxError: If the string supplied for the color has invalid syntax.
|
StyleSyntaxError: If the string supplied for the color has invalid syntax.
|
||||||
"""
|
"""
|
||||||
if border is None:
|
if border is None:
|
||||||
new_value = None
|
obj._rules.pop(self.name, None)
|
||||||
else:
|
else:
|
||||||
_type, color = border
|
_type, color = border
|
||||||
if isinstance(color, str):
|
if isinstance(color, str):
|
||||||
@@ -161,7 +162,7 @@ class BoxProperty:
|
|||||||
new_value = (_type, Style.from_color(color))
|
new_value = (_type, Style.from_color(color))
|
||||||
else:
|
else:
|
||||||
new_value = (_type, Style.from_color(Color.parse(color)))
|
new_value = (_type, Style.from_color(Color.parse(color)))
|
||||||
setattr(obj, self.internal_name, new_value)
|
obj._rules[self.name] = new_value
|
||||||
obj.refresh()
|
obj.refresh()
|
||||||
|
|
||||||
|
|
||||||
@@ -169,13 +170,14 @@ class BoxProperty:
|
|||||||
class Edges(NamedTuple):
|
class Edges(NamedTuple):
|
||||||
"""Stores edges for border / outline."""
|
"""Stores edges for border / outline."""
|
||||||
|
|
||||||
|
css_name: str
|
||||||
top: tuple[BoxType, Style]
|
top: tuple[BoxType, Style]
|
||||||
right: tuple[BoxType, Style]
|
right: tuple[BoxType, Style]
|
||||||
bottom: tuple[BoxType, Style]
|
bottom: tuple[BoxType, Style]
|
||||||
left: tuple[BoxType, Style]
|
left: tuple[BoxType, Style]
|
||||||
|
|
||||||
def __rich_repr__(self) -> rich.repr.Result:
|
def __rich_repr__(self) -> rich.repr.Result:
|
||||||
top, right, bottom, left = self
|
_, top, right, bottom, left = self
|
||||||
if top[0]:
|
if top[0]:
|
||||||
yield "top", top
|
yield "top", top
|
||||||
if right[0]:
|
if right[0]:
|
||||||
@@ -191,7 +193,7 @@ class Edges(NamedTuple):
|
|||||||
Returns:
|
Returns:
|
||||||
tuple[int, int, int, int]: Spacing for top, right, bottom, and left.
|
tuple[int, int, int, int]: Spacing for top, right, bottom, and left.
|
||||||
"""
|
"""
|
||||||
top, right, bottom, left = self
|
_, top, right, bottom, left = self
|
||||||
return (
|
return (
|
||||||
1 if top[0] else 0,
|
1 if top[0] else 0,
|
||||||
1 if right[0] else 0,
|
1 if right[0] else 0,
|
||||||
@@ -204,6 +206,7 @@ class BorderProperty:
|
|||||||
"""Descriptor for getting and setting full borders and outlines."""
|
"""Descriptor for getting and setting full borders and outlines."""
|
||||||
|
|
||||||
def __set_name__(self, owner: Styles, name: str) -> None:
|
def __set_name__(self, owner: Styles, name: str) -> None:
|
||||||
|
self.name = name
|
||||||
self._properties = (
|
self._properties = (
|
||||||
f"{name}_top",
|
f"{name}_top",
|
||||||
f"{name}_right",
|
f"{name}_right",
|
||||||
@@ -222,7 +225,9 @@ class BorderProperty:
|
|||||||
An ``Edges`` object describing the type and style of each edge.
|
An ``Edges`` object describing the type and style of each edge.
|
||||||
"""
|
"""
|
||||||
top, right, bottom, left = self._properties
|
top, right, bottom, left = self._properties
|
||||||
|
|
||||||
border = Edges(
|
border = Edges(
|
||||||
|
self.name,
|
||||||
getattr(obj, top),
|
getattr(obj, top),
|
||||||
getattr(obj, right),
|
getattr(obj, right),
|
||||||
getattr(obj, bottom),
|
getattr(obj, bottom),
|
||||||
@@ -292,11 +297,6 @@ class StyleProperty:
|
|||||||
|
|
||||||
DEFAULT_STYLE = Style()
|
DEFAULT_STYLE = Style()
|
||||||
|
|
||||||
def __set_name__(self, owner: Styles, name: str) -> None:
|
|
||||||
self._color_name = f"_rule_{name}_color"
|
|
||||||
self._bgcolor_name = f"_rule_{name}_background"
|
|
||||||
self._style_name = f"_rule_{name}_style"
|
|
||||||
|
|
||||||
def __get__(self, obj: Styles, objtype: type[Styles] | None = None) -> Style:
|
def __get__(self, obj: Styles, objtype: type[Styles] | None = None) -> Style:
|
||||||
"""Get the Style
|
"""Get the Style
|
||||||
|
|
||||||
@@ -307,12 +307,11 @@ class StyleProperty:
|
|||||||
Returns:
|
Returns:
|
||||||
A ``Style`` object.
|
A ``Style`` object.
|
||||||
"""
|
"""
|
||||||
color = getattr(obj, self._color_name)
|
rules_get = obj._rules.get
|
||||||
bgcolor = getattr(obj, self._bgcolor_name)
|
color = rules_get("text_color") or Color.default()
|
||||||
|
bgcolor = rules_get("text_background") or Color.default()
|
||||||
style = Style.from_color(color, bgcolor)
|
style = Style.from_color(color, bgcolor)
|
||||||
style_flags = getattr(obj, self._style_name)
|
style += rules_get("text_style") or Style()
|
||||||
if style_flags:
|
|
||||||
style += style_flags
|
|
||||||
return style
|
return style
|
||||||
|
|
||||||
def __set__(self, obj: Styles, style: Style | str | None):
|
def __set__(self, obj: Styles, style: Style | str | None):
|
||||||
@@ -327,26 +326,28 @@ class StyleProperty:
|
|||||||
StyleSyntaxError: When the supplied style string has invalid syntax.
|
StyleSyntaxError: When the supplied style string has invalid syntax.
|
||||||
"""
|
"""
|
||||||
obj.refresh()
|
obj.refresh()
|
||||||
|
rules_set = obj._rules.__setitem__
|
||||||
if style is None:
|
if style is None:
|
||||||
setattr(obj, self._color_name, None)
|
rules = obj._rules
|
||||||
setattr(obj, self._bgcolor_name, None)
|
rules.pop("text_color")
|
||||||
setattr(obj, self._style_name, None)
|
rules.pop("text_background")
|
||||||
|
rules.pop("text_style")
|
||||||
elif isinstance(style, Style):
|
elif isinstance(style, Style):
|
||||||
setattr(obj, self._color_name, style.color)
|
rules_set("text_color", style.color)
|
||||||
setattr(obj, self._bgcolor_name, style.bgcolor)
|
rules_set("text_background", style.bgcolor)
|
||||||
setattr(obj, self._style_name, style.without_color)
|
rules_set("text_style", 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)
|
rules_set("text_color", new_style.color)
|
||||||
setattr(obj, self._bgcolor_name, new_style.bgcolor)
|
rules_set("text_background", new_style.bgcolor)
|
||||||
setattr(obj, self._style_name, new_style.without_color)
|
rules_set("text_style", new_style.without_color)
|
||||||
|
|
||||||
|
|
||||||
class SpacingProperty:
|
class SpacingProperty:
|
||||||
"""Descriptor for getting and setting spacing properties (e.g. padding and margin)."""
|
"""Descriptor for getting and setting spacing properties (e.g. padding and margin)."""
|
||||||
|
|
||||||
def __set_name__(self, owner: Styles, name: str) -> None:
|
def __set_name__(self, owner: Styles, name: str) -> None:
|
||||||
self._internal_name = f"_rule_{name}"
|
self.name = name
|
||||||
|
|
||||||
def __get__(self, obj: Styles, objtype: type[Styles] | None = None) -> Spacing:
|
def __get__(self, obj: Styles, objtype: type[Styles] | None = None) -> Spacing:
|
||||||
"""Get the Spacing
|
"""Get the Spacing
|
||||||
@@ -358,9 +359,9 @@ class SpacingProperty:
|
|||||||
Returns:
|
Returns:
|
||||||
Spacing: The Spacing. If unset, returns the null spacing ``(0, 0, 0, 0)``.
|
Spacing: The Spacing. If unset, returns the null spacing ``(0, 0, 0, 0)``.
|
||||||
"""
|
"""
|
||||||
return getattr(obj, self._internal_name) or NULL_SPACING
|
return obj._rules.get(self.name, NULL_SPACING)
|
||||||
|
|
||||||
def __set__(self, obj: Styles, spacing: SpacingDimensions):
|
def __set__(self, obj: Styles, spacing: SpacingDimensions | None):
|
||||||
"""Set the Spacing
|
"""Set the Spacing
|
||||||
|
|
||||||
Args:
|
Args:
|
||||||
@@ -373,8 +374,10 @@ class SpacingProperty:
|
|||||||
not 1, 2, or 4.
|
not 1, 2, or 4.
|
||||||
"""
|
"""
|
||||||
obj.refresh(layout=True)
|
obj.refresh(layout=True)
|
||||||
spacing = Spacing.unpack(spacing)
|
if spacing is None:
|
||||||
setattr(obj, self._internal_name, spacing)
|
obj._rules.pop(self.name)
|
||||||
|
else:
|
||||||
|
obj._rules[self.name] = Spacing.unpack(spacing)
|
||||||
|
|
||||||
|
|
||||||
class DocksProperty:
|
class DocksProperty:
|
||||||
@@ -394,7 +397,7 @@ class DocksProperty:
|
|||||||
Returns:
|
Returns:
|
||||||
tuple[DockGroup, ...]: A ``tuple`` containing the defined docks.
|
tuple[DockGroup, ...]: A ``tuple`` containing the defined docks.
|
||||||
"""
|
"""
|
||||||
return obj._rule_docks or ()
|
return obj._rules.get("docks") or ()
|
||||||
|
|
||||||
def __set__(self, obj: Styles, docks: Iterable[DockGroup] | None):
|
def __set__(self, obj: Styles, docks: Iterable[DockGroup] | None):
|
||||||
"""Set the Docks property
|
"""Set the Docks property
|
||||||
@@ -405,9 +408,9 @@ class DocksProperty:
|
|||||||
"""
|
"""
|
||||||
obj.refresh(layout=True)
|
obj.refresh(layout=True)
|
||||||
if docks is None:
|
if docks is None:
|
||||||
obj._rule_docks = None
|
obj._rules.pop("docks")
|
||||||
else:
|
else:
|
||||||
obj._rule_docks = tuple(docks)
|
obj._rules["docks"] = tuple(docks)
|
||||||
|
|
||||||
|
|
||||||
class DockProperty:
|
class DockProperty:
|
||||||
@@ -427,7 +430,7 @@ class DockProperty:
|
|||||||
Returns:
|
Returns:
|
||||||
str: The dock name as a string, or "" if the rule is not set.
|
str: The dock name as a string, or "" if the rule is not set.
|
||||||
"""
|
"""
|
||||||
return obj._rule_dock or ""
|
return obj._rules.get("dock") or ""
|
||||||
|
|
||||||
def __set__(self, obj: Styles, spacing: str | None):
|
def __set__(self, obj: Styles, spacing: str | None):
|
||||||
"""Set the Dock property
|
"""Set the Dock property
|
||||||
@@ -437,14 +440,17 @@ class DockProperty:
|
|||||||
spacing (str | None): The spacing to use.
|
spacing (str | None): The spacing to use.
|
||||||
"""
|
"""
|
||||||
obj.refresh(layout=True)
|
obj.refresh(layout=True)
|
||||||
obj._rule_dock = spacing
|
if spacing is None:
|
||||||
|
obj._rules.pop("dock")
|
||||||
|
else:
|
||||||
|
obj._rules["dock"] = spacing
|
||||||
|
|
||||||
|
|
||||||
class LayoutProperty:
|
class LayoutProperty:
|
||||||
"""Descriptor for getting and setting layout."""
|
"""Descriptor for getting and setting layout."""
|
||||||
|
|
||||||
def __set_name__(self, owner: Styles, name: str) -> None:
|
def __set_name__(self, owner: Styles, name: str) -> None:
|
||||||
self._internal_name = f"_rule_{name}"
|
self.name = name
|
||||||
|
|
||||||
def __get__(
|
def __get__(
|
||||||
self, obj: Styles, objtype: type[Styles] | None = None
|
self, obj: Styles, objtype: type[Styles] | None = None
|
||||||
@@ -456,9 +462,9 @@ class LayoutProperty:
|
|||||||
Returns:
|
Returns:
|
||||||
The ``Layout`` object.
|
The ``Layout`` object.
|
||||||
"""
|
"""
|
||||||
return getattr(obj, self._internal_name)
|
return obj._rules.get(self.name)
|
||||||
|
|
||||||
def __set__(self, obj: Styles, layout: str | Layout):
|
def __set__(self, obj: Styles, layout: str | Layout | None):
|
||||||
"""
|
"""
|
||||||
Args:
|
Args:
|
||||||
obj (Styles): The Styles object.
|
obj (Styles): The Styles object.
|
||||||
@@ -469,11 +475,13 @@ class LayoutProperty:
|
|||||||
from ..layouts.factory import get_layout, Layout # Prevents circular import
|
from ..layouts.factory import get_layout, Layout # Prevents circular import
|
||||||
|
|
||||||
obj.refresh(layout=True)
|
obj.refresh(layout=True)
|
||||||
if isinstance(layout, Layout):
|
|
||||||
new_layout = layout
|
if layout is None:
|
||||||
|
obj._rules.pop("layout")
|
||||||
|
elif isinstance(layout, Layout):
|
||||||
|
obj._rules["layout"] = layout
|
||||||
else:
|
else:
|
||||||
new_layout = get_layout(layout)
|
obj._rules["layout"] = get_layout(layout)
|
||||||
setattr(obj, self._internal_name, new_layout)
|
|
||||||
|
|
||||||
|
|
||||||
class OffsetProperty:
|
class OffsetProperty:
|
||||||
@@ -483,7 +491,7 @@ class OffsetProperty:
|
|||||||
"""
|
"""
|
||||||
|
|
||||||
def __set_name__(self, owner: Styles, name: str) -> None:
|
def __set_name__(self, owner: Styles, name: str) -> None:
|
||||||
self._internal_name = f"_rule_{name}"
|
self.name = name
|
||||||
|
|
||||||
def __get__(self, obj: Styles, objtype: type[Styles] | None = None) -> ScalarOffset:
|
def __get__(self, obj: Styles, objtype: type[Styles] | None = None) -> ScalarOffset:
|
||||||
"""Get the offset
|
"""Get the offset
|
||||||
@@ -496,11 +504,11 @@ class OffsetProperty:
|
|||||||
ScalarOffset: The ``ScalarOffset`` indicating the adjustment that
|
ScalarOffset: The ``ScalarOffset`` indicating the adjustment that
|
||||||
will be made to widget position prior to it being rendered.
|
will be made to widget position prior to it being rendered.
|
||||||
"""
|
"""
|
||||||
return getattr(obj, self._internal_name) or ScalarOffset(
|
return obj._rules.get(self.name, ScalarOffset.null())
|
||||||
Scalar.from_number(0), Scalar.from_number(0)
|
|
||||||
)
|
|
||||||
|
|
||||||
def __set__(self, obj: Styles, offset: tuple[int | str, int | str] | ScalarOffset):
|
def __set__(
|
||||||
|
self, obj: Styles, offset: tuple[int | str, int | str] | ScalarOffset | None
|
||||||
|
):
|
||||||
"""Set the offset
|
"""Set the offset
|
||||||
|
|
||||||
Args:
|
Args:
|
||||||
@@ -515,9 +523,11 @@ class OffsetProperty:
|
|||||||
be parsed into a Scalar. For example, if you specify an non-existent unit.
|
be parsed into a Scalar. For example, if you specify an non-existent unit.
|
||||||
"""
|
"""
|
||||||
obj.refresh(layout=True)
|
obj.refresh(layout=True)
|
||||||
if isinstance(offset, ScalarOffset):
|
if offset is None:
|
||||||
setattr(obj, self._internal_name, offset)
|
obj._rules.pop(self.name, None)
|
||||||
return offset
|
elif isinstance(offset, ScalarOffset):
|
||||||
|
obj._rules[self.name] = offset
|
||||||
|
else:
|
||||||
x, y = offset
|
x, y = offset
|
||||||
scalar_x = (
|
scalar_x = (
|
||||||
Scalar.parse(x, Unit.WIDTH)
|
Scalar.parse(x, Unit.WIDTH)
|
||||||
@@ -530,42 +540,7 @@ class OffsetProperty:
|
|||||||
else Scalar(float(y), Unit.CELLS, Unit.HEIGHT)
|
else Scalar(float(y), Unit.CELLS, Unit.HEIGHT)
|
||||||
)
|
)
|
||||||
_offset = ScalarOffset(scalar_x, scalar_y)
|
_offset = ScalarOffset(scalar_x, scalar_y)
|
||||||
setattr(obj, self._internal_name, _offset)
|
obj._rules[self.name] = _offset
|
||||||
|
|
||||||
|
|
||||||
class IntegerProperty:
|
|
||||||
"""Descriptor for getting and setting integer properties"""
|
|
||||||
|
|
||||||
def __set_name__(self, owner: Styles, name: str) -> None:
|
|
||||||
self._name = name
|
|
||||||
self._internal_name = f"_{name}"
|
|
||||||
|
|
||||||
def __get__(self, obj: Styles, objtype: type[Styles] | None = None) -> int:
|
|
||||||
"""Get the integer property, or the default ``0`` if not set.
|
|
||||||
|
|
||||||
Args:
|
|
||||||
obj (Styles): The ``Styles`` object.
|
|
||||||
objtype (type[Styles]): The ``Styles`` class.
|
|
||||||
|
|
||||||
Returns:
|
|
||||||
int: The integer property value
|
|
||||||
"""
|
|
||||||
return getattr(obj, self._internal_name, 0)
|
|
||||||
|
|
||||||
def __set__(self, obj: Styles, value: int):
|
|
||||||
"""Set the integer property
|
|
||||||
|
|
||||||
Args:
|
|
||||||
obj: The ``Styles`` object
|
|
||||||
value: The value to set the integer to
|
|
||||||
|
|
||||||
Raises:
|
|
||||||
StyleTypeError: If the supplied value is not an integer.
|
|
||||||
"""
|
|
||||||
obj.refresh()
|
|
||||||
if not isinstance(value, int):
|
|
||||||
raise StyleTypeError(f"{self._name} must be an integer")
|
|
||||||
setattr(obj, self._internal_name, value)
|
|
||||||
|
|
||||||
|
|
||||||
class StringEnumProperty:
|
class StringEnumProperty:
|
||||||
@@ -578,8 +553,7 @@ class StringEnumProperty:
|
|||||||
self._default = default
|
self._default = default
|
||||||
|
|
||||||
def __set_name__(self, owner: Styles, name: str) -> None:
|
def __set_name__(self, owner: Styles, name: str) -> None:
|
||||||
self._name = name
|
self.name = name
|
||||||
self._internal_name = f"_rule_{name}"
|
|
||||||
|
|
||||||
def __get__(self, obj: Styles, objtype: type[Styles] | None = None) -> str:
|
def __get__(self, obj: Styles, objtype: type[Styles] | None = None) -> str:
|
||||||
"""Get the string property, or the default value if it's not set
|
"""Get the string property, or the default value if it's not set
|
||||||
@@ -591,7 +565,7 @@ class StringEnumProperty:
|
|||||||
Returns:
|
Returns:
|
||||||
str: The string property value
|
str: The string property value
|
||||||
"""
|
"""
|
||||||
return getattr(obj, self._internal_name, None) or self._default
|
return obj._rules.get(self.name, self._default)
|
||||||
|
|
||||||
def __set__(self, obj: Styles, value: str | None = None):
|
def __set__(self, obj: Styles, value: str | None = None):
|
||||||
"""Set the string property and ensure it is in the set of allowed values.
|
"""Set the string property and ensure it is in the set of allowed values.
|
||||||
@@ -604,12 +578,14 @@ class StringEnumProperty:
|
|||||||
StyleValueError: If the value is not in the set of valid values.
|
StyleValueError: If the value is not in the set of valid values.
|
||||||
"""
|
"""
|
||||||
obj.refresh()
|
obj.refresh()
|
||||||
if value is not None:
|
if value is None:
|
||||||
|
obj._rules.pop(self.name, None)
|
||||||
|
else:
|
||||||
if value not in self._valid_values:
|
if value not in self._valid_values:
|
||||||
raise StyleValueError(
|
raise StyleValueError(
|
||||||
f"{self._name} must be one of {friendly_list(self._valid_values)}"
|
f"{self.name} must be one of {friendly_list(self._valid_values)}"
|
||||||
)
|
)
|
||||||
setattr(obj, self._internal_name, value)
|
obj._rules[self.name] = value
|
||||||
|
|
||||||
|
|
||||||
class NameProperty:
|
class NameProperty:
|
||||||
@@ -617,7 +593,6 @@ class NameProperty:
|
|||||||
|
|
||||||
def __set_name__(self, owner: Styles, name: str) -> None:
|
def __set_name__(self, owner: Styles, name: str) -> None:
|
||||||
self._name = name
|
self._name = name
|
||||||
self._internal_name = f"_rule_{name}"
|
|
||||||
|
|
||||||
def __get__(self, obj: Styles, objtype: type[Styles] | None) -> str:
|
def __get__(self, obj: Styles, objtype: type[Styles] | None) -> str:
|
||||||
"""Get the name property
|
"""Get the name property
|
||||||
@@ -629,7 +604,7 @@ class NameProperty:
|
|||||||
Returns:
|
Returns:
|
||||||
str: The name
|
str: The name
|
||||||
"""
|
"""
|
||||||
return getattr(obj, self._internal_name) or ""
|
return obj.get(self.name, "")
|
||||||
|
|
||||||
def __set__(self, obj: Styles, name: str | None):
|
def __set__(self, obj: Styles, name: str | None):
|
||||||
"""Set the name property
|
"""Set the name property
|
||||||
@@ -642,42 +617,42 @@ class NameProperty:
|
|||||||
StyleTypeError: If the value is not a ``str``.
|
StyleTypeError: If the value is not a ``str``.
|
||||||
"""
|
"""
|
||||||
obj.refresh(layout=True)
|
obj.refresh(layout=True)
|
||||||
|
if name is None:
|
||||||
|
obj._rules.pop(self.name, None)
|
||||||
|
else:
|
||||||
if not isinstance(name, str):
|
if not isinstance(name, str):
|
||||||
raise StyleTypeError(f"{self._name} must be a str")
|
raise StyleTypeError(f"{self._name} must be a str")
|
||||||
setattr(obj, self._internal_name, name)
|
obj._rules[self.name] = name
|
||||||
|
|
||||||
|
|
||||||
class NameListProperty:
|
class NameListProperty:
|
||||||
def __set_name__(self, owner: Styles, name: str) -> None:
|
def __set_name__(self, owner: Styles, name: str) -> None:
|
||||||
self._name = name
|
self._name = name
|
||||||
self._internal_name = f"_rule_{name}"
|
|
||||||
|
|
||||||
def __get__(
|
def __get__(
|
||||||
self, obj: Styles, objtype: type[Styles] | None = None
|
self, obj: Styles, objtype: type[Styles] | None = None
|
||||||
) -> tuple[str, ...]:
|
) -> tuple[str, ...]:
|
||||||
return getattr(obj, self._internal_name, None) or ()
|
return obj._rules.get(self.name, ())
|
||||||
|
|
||||||
def __set__(
|
def __set__(
|
||||||
self, obj: Styles, names: str | tuple[str] | None = None
|
self, obj: Styles, names: str | tuple[str] | None = None
|
||||||
) -> str | tuple[str] | None:
|
) -> str | tuple[str] | None:
|
||||||
obj.refresh(layout=True)
|
obj.refresh(layout=True)
|
||||||
names_value: tuple[str, ...] | None = None
|
if names is None:
|
||||||
if isinstance(names, str):
|
obj._rules.pop(self.name, None)
|
||||||
names_value = tuple(name.strip().lower() for name in names.split(" "))
|
elif isinstance(names, str):
|
||||||
|
obj._rules[self.name] = tuple(
|
||||||
|
name.strip().lower() for name in names.split(" ")
|
||||||
|
)
|
||||||
elif isinstance(names, tuple):
|
elif isinstance(names, tuple):
|
||||||
names_value = names
|
obj._rules[self.name] = names
|
||||||
elif names is None:
|
|
||||||
names_value = None
|
|
||||||
setattr(obj, self._internal_name, names_value)
|
|
||||||
return names
|
|
||||||
|
|
||||||
|
|
||||||
class ColorProperty:
|
class ColorProperty:
|
||||||
"""Descriptor for getting and setting color properties."""
|
"""Descriptor for getting and setting color properties."""
|
||||||
|
|
||||||
def __set_name__(self, owner: Styles, name: str) -> None:
|
def __set_name__(self, owner: Styles, name: str) -> None:
|
||||||
self._name = name
|
self.name = name
|
||||||
self._internal_name = f"_rule_{name}"
|
|
||||||
|
|
||||||
def __get__(self, obj: Styles, objtype: type[Styles] | None = None) -> Color:
|
def __get__(self, obj: Styles, objtype: type[Styles] | None = None) -> Color:
|
||||||
"""Get the ``Color``, or ``Color.default()`` if no color is set.
|
"""Get the ``Color``, or ``Color.default()`` if no color is set.
|
||||||
@@ -689,7 +664,7 @@ class ColorProperty:
|
|||||||
Returns:
|
Returns:
|
||||||
Color: The Color
|
Color: The Color
|
||||||
"""
|
"""
|
||||||
return getattr(obj, self._internal_name, None) or Color.default()
|
return obj._rules.get(self.name) or Color.default()
|
||||||
|
|
||||||
def __set__(self, obj: Styles, color: Color | str | None):
|
def __set__(self, obj: Styles, color: Color | str | None):
|
||||||
"""Set the Color
|
"""Set the Color
|
||||||
@@ -705,13 +680,11 @@ class ColorProperty:
|
|||||||
"""
|
"""
|
||||||
obj.refresh()
|
obj.refresh()
|
||||||
if color is None:
|
if color is None:
|
||||||
setattr(self, self._internal_name, None)
|
obj._rules.pop(self.name, None)
|
||||||
else:
|
elif isinstance(color, Color):
|
||||||
if isinstance(color, Color):
|
obj._rules[self.name] = color
|
||||||
setattr(self, self._internal_name, color)
|
|
||||||
elif isinstance(color, str):
|
elif isinstance(color, str):
|
||||||
new_color = Color.parse(color)
|
obj._rules[self.name] = Color.parse(color)
|
||||||
setattr(self, self._internal_name, new_color)
|
|
||||||
|
|
||||||
|
|
||||||
class StyleFlagsProperty:
|
class StyleFlagsProperty:
|
||||||
@@ -731,8 +704,7 @@ class StyleFlagsProperty:
|
|||||||
}
|
}
|
||||||
|
|
||||||
def __set_name__(self, owner: Styles, name: str) -> None:
|
def __set_name__(self, owner: Styles, name: str) -> None:
|
||||||
self._name = name
|
self.name = name
|
||||||
self._internal_name = f"_rule_{name}"
|
|
||||||
|
|
||||||
def __get__(self, obj: Styles, objtype: type[Styles] | None = None) -> Style:
|
def __get__(self, obj: Styles, objtype: type[Styles] | None = None) -> Style:
|
||||||
"""Get the ``Style``
|
"""Get the ``Style``
|
||||||
@@ -744,7 +716,7 @@ class StyleFlagsProperty:
|
|||||||
Returns:
|
Returns:
|
||||||
Style: The ``Style`` object
|
Style: The ``Style`` object
|
||||||
"""
|
"""
|
||||||
return getattr(obj, self._internal_name, None) or Style.null()
|
return obj._rules.get(self.name, Style.null())
|
||||||
|
|
||||||
def __set__(self, obj: Styles, style_flags: str | None):
|
def __set__(self, obj: Styles, style_flags: str | None):
|
||||||
"""Set the style using a style flag string
|
"""Set the style using a style flag string
|
||||||
@@ -759,7 +731,7 @@ class StyleFlagsProperty:
|
|||||||
"""
|
"""
|
||||||
obj.refresh()
|
obj.refresh()
|
||||||
if style_flags is None:
|
if style_flags is None:
|
||||||
setattr(self, self._internal_name, None)
|
obj._styles.pop(self.name, None)
|
||||||
else:
|
else:
|
||||||
words = [word.strip() for word in style_flags.split(" ")]
|
words = [word.strip() for word in style_flags.split(" ")]
|
||||||
valid_word = self._VALID_PROPERTIES.__contains__
|
valid_word = self._VALID_PROPERTIES.__contains__
|
||||||
@@ -770,15 +742,14 @@ class StyleFlagsProperty:
|
|||||||
f"valid values are {friendly_list(self._VALID_PROPERTIES)}"
|
f"valid values are {friendly_list(self._VALID_PROPERTIES)}"
|
||||||
)
|
)
|
||||||
style = Style.parse(style_flags)
|
style = Style.parse(style_flags)
|
||||||
setattr(obj, self._internal_name, style)
|
obj._rules[self.name] = style
|
||||||
|
|
||||||
|
|
||||||
class TransitionsProperty:
|
class TransitionsProperty:
|
||||||
"""Descriptor for getting transitions properties"""
|
"""Descriptor for getting transitions properties"""
|
||||||
|
|
||||||
def __set_name__(self, owner: Styles, name: str) -> None:
|
def __set_name__(self, owner: Styles, name: str) -> None:
|
||||||
self._name = name
|
self.name = name
|
||||||
self._internal_name = f"_rule_{name}"
|
|
||||||
|
|
||||||
def __get__(
|
def __get__(
|
||||||
self, obj: Styles, objtype: type[Styles] | None = None
|
self, obj: Styles, objtype: type[Styles] | None = None
|
||||||
@@ -794,4 +765,4 @@ class TransitionsProperty:
|
|||||||
e.g. ``{"offset": Transition(...), ...}``. If no transitions have been set, an empty ``dict``
|
e.g. ``{"offset": Transition(...), ...}``. If no transitions have been set, an empty ``dict``
|
||||||
is returned.
|
is returned.
|
||||||
"""
|
"""
|
||||||
return getattr(obj, self._internal_name, None) or {}
|
return obj._rules.get(self.name, {})
|
||||||
|
|||||||
@@ -71,7 +71,7 @@ class StylesBuilder:
|
|||||||
if name == "token":
|
if name == "token":
|
||||||
value = value.lower()
|
value = value.lower()
|
||||||
if value in VALID_DISPLAY:
|
if value in VALID_DISPLAY:
|
||||||
self.styles._rule_display = cast(Display, value)
|
self.styles._rules["display"] = cast(Display, value)
|
||||||
else:
|
else:
|
||||||
self.error(
|
self.error(
|
||||||
name,
|
name,
|
||||||
@@ -85,7 +85,7 @@ class StylesBuilder:
|
|||||||
if not tokens:
|
if not tokens:
|
||||||
return
|
return
|
||||||
if len(tokens) == 1:
|
if len(tokens) == 1:
|
||||||
setattr(self.styles, name, Scalar.parse(tokens[0].value))
|
self.styles._rules[name] = Scalar.parse(tokens[0].value)
|
||||||
else:
|
else:
|
||||||
self.error(name, tokens[0], "a single scalar is expected")
|
self.error(name, tokens[0], "a single scalar is expected")
|
||||||
|
|
||||||
@@ -113,7 +113,7 @@ class StylesBuilder:
|
|||||||
if name == "token":
|
if name == "token":
|
||||||
value = value.lower()
|
value = value.lower()
|
||||||
if value in VALID_VISIBILITY:
|
if value in VALID_VISIBILITY:
|
||||||
self.styles._rule_visibility = cast(Visibility, value)
|
self.styles._rules["visibility"] = cast(Visibility, value)
|
||||||
else:
|
else:
|
||||||
self.error(
|
self.error(
|
||||||
name,
|
name,
|
||||||
@@ -139,11 +139,7 @@ class StylesBuilder:
|
|||||||
self.error(
|
self.error(
|
||||||
name, tokens[0], f"1, 2, or 4 values expected; received {len(space)}"
|
name, tokens[0], f"1, 2, or 4 values expected; received {len(space)}"
|
||||||
)
|
)
|
||||||
setattr(
|
self.styles._rules[name] = Spacing.unpack(cast(SpacingDimensions, tuple(space)))
|
||||||
self.styles,
|
|
||||||
f"_rule_{name}",
|
|
||||||
Spacing.unpack(cast(SpacingDimensions, tuple(space))),
|
|
||||||
)
|
|
||||||
|
|
||||||
def process_padding(self, name: str, tokens: list[Token], important: bool) -> None:
|
def process_padding(self, name: str, tokens: list[Token], important: bool) -> None:
|
||||||
self._process_space(name, tokens)
|
self._process_space(name, tokens)
|
||||||
@@ -176,13 +172,13 @@ class StylesBuilder:
|
|||||||
|
|
||||||
def _process_border(self, edge: str, name: str, tokens: list[Token]) -> None:
|
def _process_border(self, edge: str, name: str, tokens: list[Token]) -> None:
|
||||||
border = self._parse_border("border", tokens)
|
border = self._parse_border("border", tokens)
|
||||||
setattr(self.styles, f"_rule_border_{edge}", border)
|
self.styles._rules[f"border_{edge}"] = border
|
||||||
|
|
||||||
def process_border(self, name: str, tokens: list[Token], important: bool) -> 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
|
rules = self.styles._rules
|
||||||
styles._rule_border_top = styles._rule_border_right = border
|
rules["border_top"] = rules["border_right"] = border
|
||||||
styles._rule_border_bottom = styles._rule_border_left = border
|
rules["border_bottom"] = rules["border_left"] = border
|
||||||
|
|
||||||
def process_border_top(
|
def process_border_top(
|
||||||
self, name: str, tokens: list[Token], important: bool
|
self, name: str, tokens: list[Token], important: bool
|
||||||
@@ -206,13 +202,13 @@ class StylesBuilder:
|
|||||||
|
|
||||||
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)
|
self.styles._rules[f"outline_{edge}"] = border
|
||||||
|
|
||||||
def process_outline(self, name: str, tokens: list[Token], important: bool) -> 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
|
rules = self.styles._rules
|
||||||
styles._rule_outline_top = styles._rule_outline_right = border
|
rules["outline_top"] = rules["outline_right"] = border
|
||||||
styles._rule_outline_bottom = styles._rule_outline_left = border
|
rules["outline_bottom"] = rules["outline_left"] = border
|
||||||
|
|
||||||
def process_outline_top(
|
def process_outline_top(
|
||||||
self, name: str, tokens: list[Token], important: bool
|
self, name: str, tokens: list[Token], important: bool
|
||||||
@@ -255,7 +251,7 @@ class StylesBuilder:
|
|||||||
|
|
||||||
scalar_x = Scalar.parse(token1.value, Unit.WIDTH)
|
scalar_x = Scalar.parse(token1.value, Unit.WIDTH)
|
||||||
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._rules["offset"] = ScalarOffset(scalar_x, scalar_y)
|
||||||
|
|
||||||
def process_offset_x(self, name: str, tokens: list[Token], important: bool) -> None:
|
def process_offset_x(self, name: str, tokens: list[Token], important: bool) -> None:
|
||||||
if not tokens:
|
if not tokens:
|
||||||
@@ -268,7 +264,7 @@ class StylesBuilder:
|
|||||||
self.error(name, token, f"expected a scalar; found {token.value!r}")
|
self.error(name, token, f"expected a scalar; found {token.value!r}")
|
||||||
x = Scalar.parse(token.value, Unit.WIDTH)
|
x = Scalar.parse(token.value, Unit.WIDTH)
|
||||||
y = self.styles.offset.y
|
y = self.styles.offset.y
|
||||||
self.styles._rule_offset = ScalarOffset(x, y)
|
self.styles._rules["offset"] = ScalarOffset(x, y)
|
||||||
|
|
||||||
def process_offset_y(self, name: str, tokens: list[Token], important: bool) -> None:
|
def process_offset_y(self, name: str, tokens: list[Token], important: bool) -> None:
|
||||||
if not tokens:
|
if not tokens:
|
||||||
@@ -281,7 +277,7 @@ class StylesBuilder:
|
|||||||
self.error(name, token, f"expected a scalar; found {token.value!r}")
|
self.error(name, token, f"expected a scalar; found {token.value!r}")
|
||||||
y = Scalar.parse(token.value, Unit.HEIGHT)
|
y = Scalar.parse(token.value, Unit.HEIGHT)
|
||||||
x = self.styles.offset.x
|
x = self.styles.offset.x
|
||||||
self.styles._rule_offset = ScalarOffset(x, y)
|
self.styles._rules["offset"] = ScalarOffset(x, y)
|
||||||
|
|
||||||
def process_layout(self, name: str, tokens: list[Token], important: bool) -> None:
|
def process_layout(self, name: str, tokens: list[Token], important: bool) -> None:
|
||||||
from ..layouts.factory import get_layout, MissingLayout, LAYOUT_MAP
|
from ..layouts.factory import get_layout, MissingLayout, LAYOUT_MAP
|
||||||
@@ -293,7 +289,7 @@ class StylesBuilder:
|
|||||||
value = tokens[0].value
|
value = tokens[0].value
|
||||||
layout_name = value
|
layout_name = value
|
||||||
try:
|
try:
|
||||||
self.styles._rule_layout = get_layout(layout_name)
|
self.styles._rules["layout"] = get_layout(layout_name)
|
||||||
except MissingLayout:
|
except MissingLayout:
|
||||||
self.error(
|
self.error(
|
||||||
name,
|
name,
|
||||||
@@ -311,7 +307,7 @@ class StylesBuilder:
|
|||||||
self.styles.important.update(
|
self.styles.important.update(
|
||||||
{"text_style", "text_background", "text_color"}
|
{"text_style", "text_background", "text_color"}
|
||||||
)
|
)
|
||||||
self.styles.text = style
|
self.styles._rules["text"] = style
|
||||||
|
|
||||||
def process_text_color(
|
def process_text_color(
|
||||||
self, name: str, tokens: list[Token], important: bool
|
self, name: str, tokens: list[Token], important: bool
|
||||||
@@ -319,7 +315,7 @@ class StylesBuilder:
|
|||||||
for token in tokens:
|
for token in tokens:
|
||||||
if token.name in ("color", "token"):
|
if token.name in ("color", "token"):
|
||||||
try:
|
try:
|
||||||
self.styles._rule_text_color = Color.parse(token.value)
|
self.styles._rules["text_color"] = Color.parse(token.value)
|
||||||
except Exception as error:
|
except Exception as error:
|
||||||
self.error(
|
self.error(
|
||||||
name, token, f"failed to parse color {token.value!r}; {error}"
|
name, token, f"failed to parse color {token.value!r}; {error}"
|
||||||
@@ -335,7 +331,7 @@ class StylesBuilder:
|
|||||||
for token in tokens:
|
for token in tokens:
|
||||||
if token.name in ("color", "token"):
|
if token.name in ("color", "token"):
|
||||||
try:
|
try:
|
||||||
self.styles._rule_text_background = Color.parse(token.value)
|
self.styles._rules["text_background"] = Color.parse(token.value)
|
||||||
except Exception as error:
|
except Exception as error:
|
||||||
self.error(
|
self.error(
|
||||||
name, token, f"failed to parse color {token.value!r}; {error}"
|
name, token, f"failed to parse color {token.value!r}; {error}"
|
||||||
@@ -359,7 +355,7 @@ class StylesBuilder:
|
|||||||
tokens[1],
|
tokens[1],
|
||||||
f"unexpected tokens in dock declaration",
|
f"unexpected tokens in dock declaration",
|
||||||
)
|
)
|
||||||
self.styles._rule_dock = tokens[0].value if tokens else ""
|
self.styles._rules["dock"] = tokens[0].value if tokens else ""
|
||||||
|
|
||||||
def process_docks(self, name: str, tokens: list[Token], important: bool) -> None:
|
def process_docks(self, name: str, tokens: list[Token], important: bool) -> None:
|
||||||
docks: list[DockGroup] = []
|
docks: list[DockGroup] = []
|
||||||
@@ -390,12 +386,12 @@ class StylesBuilder:
|
|||||||
token,
|
token,
|
||||||
f"unexpected token {token.value!r} in docks declaration",
|
f"unexpected token {token.value!r} in docks declaration",
|
||||||
)
|
)
|
||||||
self.styles._rule_docks = tuple(docks + [DockGroup("_default", "top", 0)])
|
self.styles._rules["docks"] = tuple(docks + [DockGroup("_default", "top", 0)])
|
||||||
|
|
||||||
def process_layer(self, name: str, tokens: list[Token], important: bool) -> 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._rules["layer"] = tokens[0].value
|
||||||
|
|
||||||
def process_layers(self, name: str, tokens: list[Token], important: bool) -> None:
|
def process_layers(self, name: str, tokens: list[Token], important: bool) -> None:
|
||||||
layers: list[str] = []
|
layers: list[str] = []
|
||||||
@@ -403,7 +399,7 @@ class StylesBuilder:
|
|||||||
if token.name != "token":
|
if token.name != "token":
|
||||||
self.error(name, token, "{token.name} not expected here")
|
self.error(name, token, "{token.name} not expected here")
|
||||||
layers.append(token.value)
|
layers.append(token.value)
|
||||||
self.styles._rule_layers = tuple(layers)
|
self.styles._rules["layers"] = tuple(layers)
|
||||||
|
|
||||||
def process_transition(
|
def process_transition(
|
||||||
self, name: str, tokens: list[Token], important: bool
|
self, name: str, tokens: list[Token], important: bool
|
||||||
@@ -468,4 +464,4 @@ class StylesBuilder:
|
|||||||
pass
|
pass
|
||||||
transitions[css_property] = Transition(duration, easing, delay)
|
transitions[css_property] = Transition(duration, easing, delay)
|
||||||
|
|
||||||
self.styles._rule_transitions = transitions
|
self.styles._rules["transitions"] = transitions
|
||||||
|
|||||||
@@ -142,7 +142,7 @@ class DOMQuery:
|
|||||||
layout (bool): Layout node(s). Defaults to False.
|
layout (bool): Layout node(s). Defaults to False.
|
||||||
|
|
||||||
Returns:
|
Returns:
|
||||||
[type]: [description]
|
DOMQuery: Query for chaining.
|
||||||
"""
|
"""
|
||||||
for node in self._nodes:
|
for node in self._nodes:
|
||||||
node.refresh(repaint=repaint, layout=layout)
|
node.refresh(repaint=repaint, layout=layout)
|
||||||
|
|||||||
@@ -145,6 +145,10 @@ class ScalarOffset(NamedTuple):
|
|||||||
x: Scalar
|
x: Scalar
|
||||||
y: Scalar
|
y: Scalar
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def null(cls) -> ScalarOffset:
|
||||||
|
return cls(Scalar.from_number(0), Scalar.from_number(0))
|
||||||
|
|
||||||
def __bool__(self) -> bool:
|
def __bool__(self) -> bool:
|
||||||
x, y = self
|
x, y = self
|
||||||
return bool(x.value or y.value)
|
return bool(x.value or y.value)
|
||||||
|
|||||||
@@ -60,7 +60,7 @@ class ScalarAnimation(Animation):
|
|||||||
return True
|
return True
|
||||||
|
|
||||||
offset = self.start + (self.destination - self.start) * eased_factor
|
offset = self.start + (self.destination - self.start) * eased_factor
|
||||||
current = getattr(self.styles, f"_rule_{self.attribute}")
|
current = self.styles._rules[self.attribute]
|
||||||
if current != offset:
|
if current != offset:
|
||||||
setattr(self.styles, f"{self.attribute}", offset)
|
setattr(self.styles, f"{self.attribute}", offset)
|
||||||
|
|
||||||
|
|||||||
@@ -2,8 +2,8 @@ from __future__ import annotations
|
|||||||
|
|
||||||
from dataclasses import dataclass, field
|
from dataclasses import dataclass, field
|
||||||
from functools import lru_cache
|
from functools import lru_cache
|
||||||
from operator import attrgetter
|
import sys
|
||||||
from typing import Any, Iterable, NamedTuple, TYPE_CHECKING
|
from typing import Any, cast, Iterable, NamedTuple, TYPE_CHECKING
|
||||||
|
|
||||||
import rich.repr
|
import rich.repr
|
||||||
from rich.color import Color
|
from rich.color import Color
|
||||||
@@ -45,25 +45,56 @@ from ..geometry import Spacing, SpacingDimensions
|
|||||||
from .._box import BoxType
|
from .._box import BoxType
|
||||||
|
|
||||||
|
|
||||||
|
if sys.version_info >= (3, 8):
|
||||||
|
from typing import TypedDict
|
||||||
|
else:
|
||||||
|
from typing_extensions import TypedDict
|
||||||
|
|
||||||
|
|
||||||
if TYPE_CHECKING:
|
if TYPE_CHECKING:
|
||||||
from ..layout import Layout
|
from ..layout import Layout
|
||||||
from ..dom import DOMNode
|
from ..dom import DOMNode
|
||||||
|
|
||||||
|
|
||||||
_text_getter = attrgetter(
|
class RulesMap(TypedDict):
|
||||||
"_rule_text_color", "_rule_text_background", "_rule_text_style"
|
|
||||||
)
|
|
||||||
|
|
||||||
_border_getter = attrgetter(
|
display: Display
|
||||||
"_rule_border_top", "_rule_border_right", "_rule_border_bottom", "_rule_border_left"
|
visibility: Visibility
|
||||||
)
|
layout: "Layout"
|
||||||
|
|
||||||
_outline_getter = attrgetter(
|
text_color: Color
|
||||||
"_rule_outline_top",
|
text_background: Color
|
||||||
"_rule_outline_right",
|
text_style: Style
|
||||||
"_rule_outline_bottom",
|
|
||||||
"_rule_outline_left",
|
padding: Spacing
|
||||||
)
|
margin: Spacing
|
||||||
|
offset: ScalarOffset
|
||||||
|
|
||||||
|
border_top: tuple[str, Style]
|
||||||
|
border_right: tuple[str, Style]
|
||||||
|
border_bottom: tuple[str, Style]
|
||||||
|
border_left: tuple[str, Style]
|
||||||
|
|
||||||
|
outline_top: tuple[str, Style]
|
||||||
|
outline_right: tuple[str, Style]
|
||||||
|
outline_bottom: tuple[str, Style]
|
||||||
|
outline_left: tuple[str, Style]
|
||||||
|
|
||||||
|
width: Scalar
|
||||||
|
height: Scalar
|
||||||
|
min_width: Scalar
|
||||||
|
min_height: Scalar
|
||||||
|
|
||||||
|
dock: str
|
||||||
|
docks: tuple[DockGroup, ...]
|
||||||
|
|
||||||
|
layers: tuple[str, ...]
|
||||||
|
layer: str
|
||||||
|
|
||||||
|
transitions: dict[str, Transition]
|
||||||
|
|
||||||
|
|
||||||
|
RULE_NAMES = list(RulesMap.__annotations__.keys())
|
||||||
|
|
||||||
|
|
||||||
class DockGroup(NamedTuple):
|
class DockGroup(NamedTuple):
|
||||||
@@ -78,40 +109,7 @@ class Styles:
|
|||||||
|
|
||||||
node: DOMNode | None = None
|
node: DOMNode | None = None
|
||||||
|
|
||||||
_rule_display: Display | None = None
|
_rules: RulesMap = field(default_factory=dict)
|
||||||
_rule_visibility: Visibility | None = None
|
|
||||||
_rule_layout: "Layout" | None = None
|
|
||||||
|
|
||||||
_rule_text_color: Color | None = None
|
|
||||||
_rule_text_background: Color | None = None
|
|
||||||
_rule_text_style: Style | None = None
|
|
||||||
|
|
||||||
_rule_padding: Spacing | None = None
|
|
||||||
_rule_margin: Spacing | None = None
|
|
||||||
_rule_offset: ScalarOffset | None = None
|
|
||||||
|
|
||||||
_rule_border_top: tuple[str, Style] | None = None
|
|
||||||
_rule_border_right: tuple[str, Style] | None = None
|
|
||||||
_rule_border_bottom: tuple[str, Style] | None = None
|
|
||||||
_rule_border_left: tuple[str, Style] | None = None
|
|
||||||
|
|
||||||
_rule_outline_top: tuple[str, Style] | None = None
|
|
||||||
_rule_outline_right: tuple[str, Style] | None = None
|
|
||||||
_rule_outline_bottom: tuple[str, Style] | None = None
|
|
||||||
_rule_outline_left: tuple[str, Style] | None = None
|
|
||||||
|
|
||||||
_rule_width: Scalar | None = None
|
|
||||||
_rule_height: Scalar | None = None
|
|
||||||
_rule_min_width: Scalar | None = None
|
|
||||||
_rule_min_height: Scalar | None = None
|
|
||||||
|
|
||||||
_rule_dock: str | None = None
|
|
||||||
_rule_docks: tuple[DockGroup, ...] | None = None
|
|
||||||
|
|
||||||
_rule_layers: tuple[str, ...] | None = None
|
|
||||||
_rule_layer: str | None = None
|
|
||||||
|
|
||||||
_rule_transitions: dict[str, Transition] | None = None
|
|
||||||
|
|
||||||
_layout_required: bool = False
|
_layout_required: bool = False
|
||||||
_repaint_required: bool = False
|
_repaint_required: bool = False
|
||||||
@@ -120,16 +118,26 @@ class Styles:
|
|||||||
|
|
||||||
def has_rule(self, rule: str) -> bool:
|
def has_rule(self, rule: str) -> bool:
|
||||||
"""Check if a rule has been set."""
|
"""Check if a rule has been set."""
|
||||||
if rule in RULE_NAMES and getattr(self, f"_rule_{rule}") is not None:
|
if rule in RULE_NAMES and rule in self._rules:
|
||||||
return True
|
return True
|
||||||
|
has_rule = self._rules.__contains__
|
||||||
if rule == "text":
|
if rule == "text":
|
||||||
return not all(rule is None for rule in _text_getter(self))
|
return (
|
||||||
|
has_rule("text_color")
|
||||||
|
or has_rule("text_bgcolor")
|
||||||
|
or has_rule("text_style")
|
||||||
|
)
|
||||||
if rule == "border" and any(self.border):
|
if rule == "border" and any(self.border):
|
||||||
return not all(rule is None for rule in _border_getter(self))
|
return True
|
||||||
if rule == "outline" and any(self.outline):
|
if rule == "outline" and any(self.outline):
|
||||||
return not all(rule is None for rule in _outline_getter(self))
|
return True
|
||||||
return False
|
return False
|
||||||
|
|
||||||
|
def get_rules(self) -> RulesMap:
|
||||||
|
"""Get rules as a dictionary."""
|
||||||
|
rules = self._rules.copy()
|
||||||
|
return rules
|
||||||
|
|
||||||
display = StringEnumProperty(VALID_DISPLAY, "block")
|
display = StringEnumProperty(VALID_DISPLAY, "block")
|
||||||
visibility = StringEnumProperty(VALID_VISIBILITY, "visible")
|
visibility = StringEnumProperty(VALID_VISIBILITY, "visible")
|
||||||
layout = LayoutProperty()
|
layout = LayoutProperty()
|
||||||
@@ -196,31 +204,6 @@ class Styles:
|
|||||||
styles.node = node
|
styles.node = node
|
||||||
return styles
|
return styles
|
||||||
|
|
||||||
def __textual_animation__(
|
|
||||||
self,
|
|
||||||
attribute: str,
|
|
||||||
value: Any,
|
|
||||||
start_time: float,
|
|
||||||
duration: float | None,
|
|
||||||
speed: float | None,
|
|
||||||
easing: EasingFunction,
|
|
||||||
) -> Animation | None:
|
|
||||||
from ..widget import Widget
|
|
||||||
|
|
||||||
assert isinstance(self.node, Widget)
|
|
||||||
if isinstance(value, ScalarOffset):
|
|
||||||
return ScalarAnimation(
|
|
||||||
self.node,
|
|
||||||
self,
|
|
||||||
start_time,
|
|
||||||
attribute,
|
|
||||||
value,
|
|
||||||
duration=duration,
|
|
||||||
speed=speed,
|
|
||||||
easing=easing,
|
|
||||||
)
|
|
||||||
return None
|
|
||||||
|
|
||||||
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
|
||||||
@@ -245,54 +228,49 @@ class Styles:
|
|||||||
"""
|
"""
|
||||||
Reset internal style rules to ``None``, reverting to default styles.
|
Reset internal style rules to ``None``, reverting to default styles.
|
||||||
"""
|
"""
|
||||||
for rule_name in INTERNAL_RULE_NAMES:
|
self._rules.clear()
|
||||||
setattr(self, rule_name, None)
|
|
||||||
|
|
||||||
def extract_rules(
|
def extract_rules(
|
||||||
self, specificity: Specificity3
|
self, specificity: Specificity3
|
||||||
) -> list[tuple[str, Specificity4, Any]]:
|
) -> list[tuple[str, Specificity4, Any]]:
|
||||||
is_important = self.important.__contains__
|
is_important = self.important.__contains__
|
||||||
|
|
||||||
rules = [
|
rules = [
|
||||||
(
|
(rule_name, (int(is_important(rule_name)), *specificity), rule_value)
|
||||||
rule_name,
|
for rule_name, rule_value in self._rules.items()
|
||||||
(int(is_important(rule_name)), *specificity),
|
|
||||||
getattr(self, f"_rule_{rule_name}"),
|
|
||||||
)
|
|
||||||
for rule_name in RULE_NAMES
|
|
||||||
if getattr(self, f"_rule_{rule_name}") is not None
|
|
||||||
]
|
]
|
||||||
|
|
||||||
return rules
|
return rules
|
||||||
|
|
||||||
def apply_rules(self, rules: Iterable[tuple[str, object]], animate: bool = False):
|
def apply_rules(self, rules: RulesMap, animate: bool = False):
|
||||||
if animate or self.node is None:
|
if animate or self.node is None:
|
||||||
for key, value in rules:
|
self._rules.update(rules)
|
||||||
setattr(self, f"_rule_{key}", value)
|
|
||||||
else:
|
else:
|
||||||
styles = self
|
styles = self
|
||||||
is_animatable = styles.ANIMATABLE.__contains__
|
is_animatable = styles.ANIMATABLE.__contains__
|
||||||
for key, value in rules:
|
_rules = self._rules
|
||||||
current = getattr(styles, f"_rule_{key}")
|
for key, value in rules.items():
|
||||||
|
current = _rules.get(key)
|
||||||
if current == value:
|
if current == value:
|
||||||
continue
|
continue
|
||||||
if is_animatable(key):
|
if is_animatable(key):
|
||||||
transition = styles.get_transition(key)
|
transition = styles.get_transition(key)
|
||||||
if transition is None:
|
if transition is None:
|
||||||
setattr(styles, f"_rule_{key}", value)
|
_rules[key] = value
|
||||||
else:
|
else:
|
||||||
duration, easing, delay = transition
|
duration, easing, delay = transition
|
||||||
self.node.app.animator.animate(
|
self.node.app.animator.animate(
|
||||||
styles, key, value, duration=duration, easing=easing
|
styles, key, value, duration=duration, easing=easing
|
||||||
)
|
)
|
||||||
else:
|
else:
|
||||||
setattr(styles, f"_rule_{key}", value)
|
rules[key] = value
|
||||||
|
|
||||||
if self.node is not None:
|
if self.node is not None:
|
||||||
self.node.on_style_change()
|
self.node.on_style_change()
|
||||||
|
|
||||||
def __rich_repr__(self) -> rich.repr.Result:
|
def __rich_repr__(self) -> rich.repr.Result:
|
||||||
for rule_name, internal_rule_name in zip(RULE_NAMES, INTERNAL_RULE_NAMES):
|
for name, value in self._rules.items():
|
||||||
if getattr(self, internal_rule_name) is not None:
|
yield name, value
|
||||||
yield rule_name, getattr(self, rule_name)
|
|
||||||
if self.important:
|
if self.important:
|
||||||
yield "important", self.important
|
yield "important", self.important
|
||||||
|
|
||||||
@@ -302,10 +280,63 @@ class Styles:
|
|||||||
Args:
|
Args:
|
||||||
other (Styles): A Styles object.
|
other (Styles): A Styles object.
|
||||||
"""
|
"""
|
||||||
for name in INTERNAL_RULE_NAMES:
|
|
||||||
value = getattr(other, name)
|
self._rules.update(other._rules)
|
||||||
if value is not None:
|
|
||||||
setattr(self, name, value)
|
def _get_border_css_lines(
|
||||||
|
self, rules: RulesMap, name: str
|
||||||
|
) -> Iterable[tuple[str, str]]:
|
||||||
|
"""Get CSS lines for border / outline
|
||||||
|
|
||||||
|
Args:
|
||||||
|
rules (RulesMap): A rules map.
|
||||||
|
name (str): Name of rules (border or outline)
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
Iterable[tuple[str, str]]: An iterable of CSS declarations.
|
||||||
|
|
||||||
|
"""
|
||||||
|
|
||||||
|
has_rule = rules.__contains__
|
||||||
|
get_rule = rules.__getitem__
|
||||||
|
|
||||||
|
has_top = has_rule(f"{name}_top")
|
||||||
|
has_right = has_rule(f"{name}_right")
|
||||||
|
has_bottom = has_rule(f"{name}_bottom")
|
||||||
|
has_left = has_rule(f"{name}_left")
|
||||||
|
if not any((has_top, has_right, has_bottom, has_left)):
|
||||||
|
# No border related rules
|
||||||
|
return
|
||||||
|
|
||||||
|
if all((has_top, has_right, has_bottom, has_left)):
|
||||||
|
# All rules are set
|
||||||
|
# See if we can set them with a single border: declaration
|
||||||
|
top = get_rule(f"{name}_top")
|
||||||
|
right = get_rule(f"{name}_right")
|
||||||
|
bottom = get_rule(f"{name}_bottom")
|
||||||
|
left = get_rule(f"{name}_left")
|
||||||
|
|
||||||
|
if top == right and right == bottom and bottom == left:
|
||||||
|
border_type, border_style = rules[f"{name}_top"]
|
||||||
|
yield name, f"{border_type} {border_style}"
|
||||||
|
return
|
||||||
|
|
||||||
|
# Check for edges
|
||||||
|
if has_top:
|
||||||
|
border_type, border_style = rules[f"{name}_top"]
|
||||||
|
yield f"{name}-top", f"{border_type} {border_style}"
|
||||||
|
|
||||||
|
if has_right:
|
||||||
|
border_type, border_style = rules[f"{name}_right"]
|
||||||
|
yield f"{name}-right", f"{border_type} {border_style}"
|
||||||
|
|
||||||
|
if has_bottom:
|
||||||
|
border_type, border_style = rules[f"{name}_bottom"]
|
||||||
|
yield f"{name}-bottom", f"{border_type} {border_style}"
|
||||||
|
|
||||||
|
if has_left:
|
||||||
|
border_type, border_style = rules[f"{name}_left"]
|
||||||
|
yield f"{name}-left", f"{border_type} {border_style}"
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def css_lines(self) -> list[str]:
|
def css_lines(self) -> list[str]:
|
||||||
@@ -318,91 +349,69 @@ class Styles:
|
|||||||
else:
|
else:
|
||||||
append(f"{name}: {value};")
|
append(f"{name}: {value};")
|
||||||
|
|
||||||
if self._rule_display is not None:
|
rules = self.get_rules()
|
||||||
append_declaration("display", self._rule_display)
|
get_rule = rules.get
|
||||||
if self._rule_visibility is not None:
|
has_rule = rules.__contains__
|
||||||
append_declaration("visibility", self._rule_visibility)
|
|
||||||
if self._rule_padding is not None:
|
|
||||||
append_declaration("padding", self._rule_padding.packed)
|
|
||||||
if self._rule_margin is not None:
|
|
||||||
append_declaration("margin", self._rule_margin.packed)
|
|
||||||
|
|
||||||
if (
|
if has_rule("display"):
|
||||||
self._rule_border_top is not None
|
append_declaration("display", rules["display"])
|
||||||
and self._rule_border_top == self._rule_border_right
|
if has_rule("visibility"):
|
||||||
and self._rule_border_right == self._rule_border_bottom
|
append_declaration("visibility", rules["visibility"])
|
||||||
and self._rule_border_bottom == self._rule_border_left
|
if has_rule("padding"):
|
||||||
):
|
append_declaration("padding", rules["padding"].css)
|
||||||
_type, style = self._rule_border_top
|
if has_rule("margin"):
|
||||||
append_declaration("border", f"{_type} {style}")
|
append_declaration("margin", rules["margin"].css)
|
||||||
else:
|
|
||||||
if self._rule_border_top is not None:
|
|
||||||
_type, style = self._rule_border_top
|
|
||||||
append_declaration("border-top", f"{_type} {style}")
|
|
||||||
if self._rule_border_right is not None:
|
|
||||||
_type, style = self._rule_border_right
|
|
||||||
append_declaration("border-right", f"{_type} {style}")
|
|
||||||
if self._rule_border_bottom is not None:
|
|
||||||
_type, style = self._rule_border_bottom
|
|
||||||
append_declaration("border-bottom", f"{_type} {style}")
|
|
||||||
if self._rule_border_left is not None:
|
|
||||||
_type, style = self._rule_border_left
|
|
||||||
append_declaration("border-left", f"{_type} {style}")
|
|
||||||
|
|
||||||
if (
|
for name, rule in self._get_border_css_lines(rules, "border"):
|
||||||
self._rule_outline_top is not None
|
append_declaration(name, rule)
|
||||||
and self._rule_outline_top == self._rule_outline_right
|
|
||||||
and self._rule_outline_right == self._rule_outline_bottom
|
|
||||||
and self._rule_outline_bottom == self._rule_outline_left
|
|
||||||
):
|
|
||||||
_type, style = self._rule_outline_top
|
|
||||||
append_declaration("outline", f"{_type} {style}")
|
|
||||||
else:
|
|
||||||
if self._rule_outline_top is not None:
|
|
||||||
_type, style = self._rule_outline_top
|
|
||||||
append_declaration("outline-top", f"{_type} {style}")
|
|
||||||
if self._rule_outline_right is not None:
|
|
||||||
_type, style = self._rule_outline_right
|
|
||||||
append_declaration("outline-right", f"{_type} {style}")
|
|
||||||
if self._rule_outline_bottom is not None:
|
|
||||||
_type, style = self._rule_outline_bottom
|
|
||||||
append_declaration("outline-bottom", f"{_type} {style}")
|
|
||||||
if self._rule_outline_left is not None:
|
|
||||||
_type, style = self._rule_outline_left
|
|
||||||
append_declaration("outline-left", f"{_type} {style}")
|
|
||||||
|
|
||||||
if self._rule_offset is not None:
|
for name, rule in self._get_border_css_lines(rules, "outline"):
|
||||||
|
append_declaration(name, rule)
|
||||||
|
|
||||||
|
if has_rule("offset"):
|
||||||
x, y = self.offset
|
x, y = self.offset
|
||||||
append_declaration("offset", f"{x} {y}")
|
append_declaration("offset", f"{x} {y}")
|
||||||
if self._rule_dock:
|
if has_rule("dock"):
|
||||||
append_declaration("dock", self._rule_dock)
|
append_declaration("dock", rules["dock"])
|
||||||
if self._rule_docks:
|
if has_rule("docks"):
|
||||||
append_declaration(
|
append_declaration(
|
||||||
"docks",
|
"docks",
|
||||||
" ".join(
|
" ".join(
|
||||||
(f"{name}={edge}/{z}" if z else f"{name}={edge}")
|
(f"{name}={edge}/{z}" if z else f"{name}={edge}")
|
||||||
for name, edge, z in self._rule_docks
|
for name, edge, z in rules["docks"]
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
if self._rule_layers is not None:
|
if has_rule("layers"):
|
||||||
append_declaration("layers", " ".join(self.layers))
|
append_declaration("layers", " ".join(self.layers))
|
||||||
if self._rule_layer is not None:
|
if has_rule("layer"):
|
||||||
append_declaration("layer", self.layer)
|
append_declaration("layer", self.layer)
|
||||||
if self._rule_layout is not None:
|
if has_rule("layout"):
|
||||||
assert self.layout is not None
|
assert self.layout is not None
|
||||||
append_declaration("layout", self.layout.name)
|
append_declaration("layout", self.layout.name)
|
||||||
if self._rule_text_color or self._rule_text_background or self._rule_text_style:
|
|
||||||
append_declaration("text", str(self.text))
|
|
||||||
|
|
||||||
if self._rule_width is not None:
|
if (
|
||||||
|
has_rule("text_color")
|
||||||
|
and has_rule("text_bgcolor")
|
||||||
|
and has_rule("text_style")
|
||||||
|
):
|
||||||
|
append_declaration("text", str(self.text))
|
||||||
|
else:
|
||||||
|
if has_rule("text_color"):
|
||||||
|
append_declaration("text-color", str(get_rule("text_color")))
|
||||||
|
if has_rule("text_bgcolor"):
|
||||||
|
append_declaration("text-bgcolor", str(get_rule("text_bgcolor")))
|
||||||
|
if has_rule("text_style"):
|
||||||
|
append_declaration("text-style", str(get_rule("text_style")))
|
||||||
|
|
||||||
|
if has_rule("width"):
|
||||||
append_declaration("width", str(self.width))
|
append_declaration("width", str(self.width))
|
||||||
if self._rule_height is not None:
|
if has_rule("height"):
|
||||||
append_declaration("height", str(self.height))
|
append_declaration("height", str(self.height))
|
||||||
if self._rule_min_width is not None:
|
if has_rule("min-width"):
|
||||||
append_declaration("min-width", str(self.min_width))
|
append_declaration("min-width", str(self.min_width))
|
||||||
if self._rule_min_height is not None:
|
if has_rule("min_height"):
|
||||||
append_declaration("min-height", str(self.min_height))
|
append_declaration("min-height", str(self.min_height))
|
||||||
if self._rule_transitions is not None:
|
if has_rule("transitions"):
|
||||||
append_declaration(
|
append_declaration(
|
||||||
"transition",
|
"transition",
|
||||||
", ".join(
|
", ".join(
|
||||||
@@ -419,10 +428,6 @@ class Styles:
|
|||||||
return "\n".join(self.css_lines)
|
return "\n".join(self.css_lines)
|
||||||
|
|
||||||
|
|
||||||
RULE_NAMES = [name[6:] for name in dir(Styles) if name.startswith("_rule_")]
|
|
||||||
INTERNAL_RULE_NAMES = [f"_rule_{name}" for name in RULE_NAMES]
|
|
||||||
|
|
||||||
|
|
||||||
from typing import Generic, TypeVar
|
from typing import Generic, TypeVar
|
||||||
|
|
||||||
GetType = TypeVar("GetType")
|
GetType = TypeVar("GetType")
|
||||||
@@ -433,25 +438,35 @@ class StyleViewProperty(Generic[GetType, SetType]):
|
|||||||
"""Presents a view of a base Styles object, plus inline styles."""
|
"""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: StylesView, name: str) -> None:
|
||||||
self._name = name
|
self.name = name
|
||||||
self._internal_name = f"_rule_{name}"
|
|
||||||
|
|
||||||
def __set__(self, obj: StylesView, value: SetType) -> None:
|
def __set__(self, obj: StylesView, value: SetType) -> None:
|
||||||
setattr(obj._inline_styles, self._name, value)
|
setattr(obj._inline_styles, self.name, value)
|
||||||
|
|
||||||
def __get__(
|
def __get__(
|
||||||
self, obj: StylesView, objtype: type[StylesView] | None = None
|
self, obj: StylesView, objtype: type[StylesView] | None = None
|
||||||
) -> GetType:
|
) -> GetType:
|
||||||
if obj._inline_styles.has_rule(self._name):
|
if obj._inline_styles.has_rule(self.name):
|
||||||
return getattr(obj._inline_styles, self._name)
|
return getattr(obj._inline_styles, self.name)
|
||||||
return getattr(obj._base_styles, self._name)
|
return getattr(obj._base_styles, self.name)
|
||||||
|
|
||||||
|
|
||||||
@rich.repr.auto
|
@rich.repr.auto
|
||||||
class StylesView:
|
class StylesView:
|
||||||
"""Presents a combined view of two Styles object: a base Styles and inline Styles."""
|
"""Presents a combined view of two Styles object: a base Styles and inline Styles."""
|
||||||
|
|
||||||
def __init__(self, base: Styles, inline_styles: Styles) -> None:
|
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._base_styles = base
|
||||||
self._inline_styles = inline_styles
|
self._inline_styles = inline_styles
|
||||||
|
|
||||||
@@ -500,6 +515,42 @@ class StylesView:
|
|||||||
"""Check if a rule has been set."""
|
"""Check if a rule has been set."""
|
||||||
return self._inline_styles.has_rule(rule) or self._base_styles.has_rule(rule)
|
return self._inline_styles.has_rule(rule) or self._base_styles.has_rule(rule)
|
||||||
|
|
||||||
|
def get_rules(self) -> RulesMap:
|
||||||
|
"""Get rules as a dictionary"""
|
||||||
|
rules = {**self._base_styles._rules, **self._inline_styles._rules}
|
||||||
|
return cast(RulesMap, rules)
|
||||||
|
|
||||||
|
def __textual_animation__(
|
||||||
|
self,
|
||||||
|
attribute: str,
|
||||||
|
value: Any,
|
||||||
|
start_time: float,
|
||||||
|
duration: float | None,
|
||||||
|
speed: float | None,
|
||||||
|
easing: EasingFunction,
|
||||||
|
) -> Animation | None:
|
||||||
|
from ..widget import Widget
|
||||||
|
|
||||||
|
assert isinstance(self.node, Widget)
|
||||||
|
if isinstance(value, ScalarOffset):
|
||||||
|
return ScalarAnimation(
|
||||||
|
self.node,
|
||||||
|
self,
|
||||||
|
start_time,
|
||||||
|
attribute,
|
||||||
|
value,
|
||||||
|
duration=duration,
|
||||||
|
speed=speed,
|
||||||
|
easing=easing,
|
||||||
|
)
|
||||||
|
return None
|
||||||
|
|
||||||
|
def get_transition(self, key: str) -> Transition | None:
|
||||||
|
if key in self.ANIMATABLE:
|
||||||
|
return self.transitions.get(key, None)
|
||||||
|
else:
|
||||||
|
return None
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def css(self) -> str:
|
def css(self) -> str:
|
||||||
"""Get the CSS for the combined styles."""
|
"""Get the CSS for the combined styles."""
|
||||||
@@ -511,7 +562,7 @@ class StylesView:
|
|||||||
|
|
||||||
display: StyleViewProperty[str, str | None] = StyleViewProperty()
|
display: StyleViewProperty[str, str | None] = StyleViewProperty()
|
||||||
visibility: StyleViewProperty[str, str | None] = StyleViewProperty()
|
visibility: StyleViewProperty[str, str | None] = StyleViewProperty()
|
||||||
layout: StyleViewProperty[Layout | None, str | Layout] = StyleViewProperty()
|
layout: StyleViewProperty[Layout | None, str | Layout | None] = StyleViewProperty()
|
||||||
|
|
||||||
text: StyleViewProperty[Style, Style | str | None] = StyleViewProperty()
|
text: StyleViewProperty[Style, Style | str | None] = StyleViewProperty()
|
||||||
text_color: StyleViewProperty[Color, Color | str | None] = StyleViewProperty()
|
text_color: StyleViewProperty[Color, Color | str | None] = StyleViewProperty()
|
||||||
|
|||||||
@@ -148,10 +148,10 @@ class Stylesheet:
|
|||||||
|
|
||||||
# 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 = [
|
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._css_styles.apply_rules(node_rules)
|
node._css_styles.apply_rules(node_rules)
|
||||||
|
|
||||||
|
|||||||
@@ -42,7 +42,7 @@ class DOMNode(MessagePump):
|
|||||||
self.children = NodeList()
|
self.children = NodeList()
|
||||||
self._css_styles: Styles = Styles(self)
|
self._css_styles: Styles = Styles(self)
|
||||||
self._inline_styles: Styles = Styles.parse(self.STYLES, repr(self), node=self)
|
self._inline_styles: Styles = Styles.parse(self.STYLES, repr(self), node=self)
|
||||||
self.styles = StylesView(self._css_styles, self._inline_styles)
|
self.styles = StylesView(self, self._css_styles, self._inline_styles)
|
||||||
super().__init__()
|
super().__init__()
|
||||||
self.default_styles = Styles.parse(self.DEFAULT_STYLES, repr(self))
|
self.default_styles = Styles.parse(self.DEFAULT_STYLES, repr(self))
|
||||||
self._default_rules = self.default_styles.extract_rules((0, 0, 0))
|
self._default_rules = self.default_styles.extract_rules((0, 0, 0))
|
||||||
@@ -53,10 +53,6 @@ class DOMNode(MessagePump):
|
|||||||
if self._classes:
|
if self._classes:
|
||||||
yield "classes", self._classes
|
yield "classes", self._classes
|
||||||
|
|
||||||
@property
|
|
||||||
def inline_styles(self) -> Styles:
|
|
||||||
return self._inline_styles
|
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def parent(self) -> DOMNode:
|
def parent(self) -> DOMNode:
|
||||||
"""Get the parent node.
|
"""Get the parent node.
|
||||||
@@ -310,18 +306,41 @@ class DOMNode(MessagePump):
|
|||||||
self.refresh()
|
self.refresh()
|
||||||
|
|
||||||
def has_class(self, *class_names: str) -> bool:
|
def has_class(self, *class_names: str) -> bool:
|
||||||
|
"""Check if the Node has all the given class names.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
*class_names (str): CSS class names to check.
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
bool: ``True`` if the node has all the given class names, otherwise ``False``.
|
||||||
|
"""
|
||||||
return self._classes.issuperset(class_names)
|
return self._classes.issuperset(class_names)
|
||||||
|
|
||||||
def add_class(self, *class_names: str) -> None:
|
def add_class(self, *class_names: str) -> None:
|
||||||
"""Add class names."""
|
"""Add class names to this Node.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
*class_names (str): CSS class names to add.
|
||||||
|
|
||||||
|
"""
|
||||||
self._classes.update(class_names)
|
self._classes.update(class_names)
|
||||||
|
|
||||||
def remove_class(self, *class_names: str) -> None:
|
def remove_class(self, *class_names: str) -> None:
|
||||||
"""Remove class names"""
|
"""Remove class names from this Node.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
*class_names (str): CSS class names to remove.
|
||||||
|
|
||||||
|
"""
|
||||||
self._classes.difference_update(class_names)
|
self._classes.difference_update(class_names)
|
||||||
|
|
||||||
def toggle_class(self, *class_names: str) -> None:
|
def toggle_class(self, *class_names: str) -> None:
|
||||||
"""Toggle class names"""
|
"""Toggle class names on this Node.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
*class_names (str): CSS class names to toggle.
|
||||||
|
|
||||||
|
"""
|
||||||
self._classes.symmetric_difference_update(class_names)
|
self._classes.symmetric_difference_update(class_names)
|
||||||
self.app.stylesheet.update(self.app)
|
self.app.stylesheet.update(self.app)
|
||||||
|
|
||||||
|
|||||||
@@ -514,6 +514,16 @@ class Spacing(NamedTuple):
|
|||||||
else:
|
else:
|
||||||
return f"{top}, {right}, {bottom}, {left}"
|
return f"{top}, {right}, {bottom}, {left}"
|
||||||
|
|
||||||
|
@property
|
||||||
|
def css(self) -> str:
|
||||||
|
top, right, bottom, left = self
|
||||||
|
if top == right == bottom == left:
|
||||||
|
return f"{top}"
|
||||||
|
if (top, right) == (bottom, left):
|
||||||
|
return f"{top} {right}"
|
||||||
|
else:
|
||||||
|
return f"{top} {right} {bottom} {left}"
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def unpack(cls, pad: SpacingDimensions) -> Spacing:
|
def unpack(cls, pad: SpacingDimensions) -> Spacing:
|
||||||
"""Unpack padding specified in CSS style."""
|
"""Unpack padding specified in CSS style."""
|
||||||
|
|||||||
@@ -32,13 +32,6 @@ class View(Widget):
|
|||||||
)
|
)
|
||||||
super().__init__(name=name, id=id)
|
super().__init__(name=name, id=id)
|
||||||
|
|
||||||
# def __init_subclass__(
|
|
||||||
# cls, layout: Callable[[], Layout] | None = None, **kwargs
|
|
||||||
# ) -> None:
|
|
||||||
# if layout is not None:
|
|
||||||
# cls.layout_factory = layout
|
|
||||||
# super().__init_subclass__(**kwargs)
|
|
||||||
|
|
||||||
background: Reactive[str] = Reactive("")
|
background: Reactive[str] = Reactive("")
|
||||||
scroll_x: Reactive[int] = Reactive(0)
|
scroll_x: Reactive[int] = Reactive(0)
|
||||||
scroll_y: Reactive[int] = Reactive(0)
|
scroll_y: Reactive[int] = Reactive(0)
|
||||||
|
|||||||
Reference in New Issue
Block a user