mirror of
https://github.com/Textualize/textual.git
synced 2025-10-17 02:38:12 +03:00
stylesheet
This commit is contained in:
@@ -6,6 +6,7 @@ import rich.repr
|
||||
from rich.color import Color
|
||||
from rich.style import Style
|
||||
|
||||
from .scalar import Scalar, ScalarParseError
|
||||
from ..geometry import Offset, Spacing, SpacingDimensions
|
||||
from .constants import NULL_SPACING, VALID_EDGE
|
||||
from .errors import StyleTypeError, StyleValueError
|
||||
@@ -15,6 +16,33 @@ if TYPE_CHECKING:
|
||||
from .styles import Styles
|
||||
|
||||
|
||||
class ScalarProperty:
|
||||
def __set_name__(self, owner: Styles, name: str) -> None:
|
||||
self.internal_name = f"_rule_{name}"
|
||||
|
||||
def __get__(self, obj: Styles, objtype: type[Styles] | None = None) -> Scalar:
|
||||
value = getattr(obj, self.internal_name)
|
||||
return value
|
||||
|
||||
def __set__(
|
||||
self, obj: Styles, value: float | Scalar | str | None
|
||||
) -> float | Scalar | str | None:
|
||||
if value is None:
|
||||
setattr(obj, self.internal_name, None)
|
||||
elif isinstance(value, float):
|
||||
setattr(obj, self.internal_name, Scalar(value, "cells"))
|
||||
elif isinstance(value, Scalar):
|
||||
setattr(obj, self.internal_name, value)
|
||||
elif isinstance(value, str):
|
||||
try:
|
||||
setattr(obj, self.internal_name, Scalar.parse(value))
|
||||
except ScalarParseError:
|
||||
raise StyleValueError("unable to parse scalar from {value!r}")
|
||||
else:
|
||||
raise StyleValueError("expected float, Scalar, or None")
|
||||
return value
|
||||
|
||||
|
||||
class BoxProperty:
|
||||
|
||||
DEFAULT = ("", Style())
|
||||
@@ -25,7 +53,9 @@ class BoxProperty:
|
||||
self._type = _type
|
||||
self.edge = edge
|
||||
|
||||
def __get__(self, obj: Styles, objtype=None) -> tuple[str, Style]:
|
||||
def __get__(
|
||||
self, obj: Styles, objtype: type[Styles] | None = None
|
||||
) -> tuple[str, Style]:
|
||||
value = getattr(obj, self.internal_name)
|
||||
return value or self.DEFAULT
|
||||
|
||||
@@ -141,12 +171,12 @@ class StyleProperty:
|
||||
|
||||
def __get__(self, obj: Styles, objtype: type[Styles] | None = None) -> Style:
|
||||
|
||||
color = getattr(obj, self._color_name) or Color.default()
|
||||
bgcolor = getattr(obj, self._bgcolor_name) or Color.default()
|
||||
color = getattr(obj, self._color_name)
|
||||
bgcolor = getattr(obj, self._bgcolor_name)
|
||||
style = Style.from_color(color, bgcolor)
|
||||
style_flags = getattr(obj, self._style_name)
|
||||
if style_flags is not None:
|
||||
style += style_flags
|
||||
style += Style.parse(style_flags)
|
||||
return style
|
||||
|
||||
def __set__(self, obj: Styles, style: Style | str | None) -> Style | str | None:
|
||||
|
||||
@@ -11,6 +11,7 @@ from .errors import DeclarationError, StyleValueError
|
||||
from ._error_tools import friendly_list
|
||||
from ..geometry import Offset, Spacing, SpacingDimensions
|
||||
from .model import Declaration
|
||||
from .scalar import Scalar
|
||||
from .styles import Styles
|
||||
from .types import Display, Visibility
|
||||
from .tokenize import Token
|
||||
@@ -67,6 +68,26 @@ class StylesBuilder:
|
||||
else:
|
||||
self.error(name, token, f"invalid token {value!r} in this context")
|
||||
|
||||
def _process_scalar(self, name: str, tokens: list[Token]) -> None:
|
||||
if not tokens:
|
||||
return
|
||||
if len(tokens) == 1:
|
||||
setattr(self.styles, f"_rule_{name}", Scalar.parse(tokens[0].value))
|
||||
else:
|
||||
self.error(name, tokens[0], "a single scalar is expected")
|
||||
|
||||
def process_width(self, name: str, tokens: list[Token]) -> None:
|
||||
self._process_scalar(name, tokens)
|
||||
|
||||
def process_height(self, name: str, tokens: list[Token]) -> None:
|
||||
self._process_scalar(name, tokens)
|
||||
|
||||
def process_min_width(self, name: str, tokens: list[Token]) -> None:
|
||||
self._process_scalar(name, tokens)
|
||||
|
||||
def process_min_height(self, name: str, tokens: list[Token]) -> None:
|
||||
self._process_scalar(name, tokens)
|
||||
|
||||
def process_visibility(self, name: str, tokens: list[Token]) -> None:
|
||||
for token in tokens:
|
||||
_, _, location, name, value = token
|
||||
@@ -87,8 +108,8 @@ class StylesBuilder:
|
||||
space: list[int] = []
|
||||
append = space.append
|
||||
for token in tokens:
|
||||
_, _, location, toke_name, value = token
|
||||
if toke_name == "number":
|
||||
_, _, location, token_name, value = token
|
||||
if token_name == "number":
|
||||
append(int(value))
|
||||
else:
|
||||
self.error(name, token, f"unexpected token {value!r} in declaration")
|
||||
|
||||
43
src/textual/css/scalar.py
Normal file
43
src/textual/css/scalar.py
Normal file
@@ -0,0 +1,43 @@
|
||||
from __future__ import annotations
|
||||
|
||||
import re
|
||||
from typing import NamedTuple
|
||||
|
||||
|
||||
_MATCH_SCALAR = re.compile(r"^(\d+\.?\d*)(fr|%)?$").match
|
||||
|
||||
|
||||
class ScalarParseError(Exception):
|
||||
pass
|
||||
|
||||
|
||||
class Scalar(NamedTuple):
|
||||
"""A numeric value and a unit."""
|
||||
|
||||
value: float
|
||||
unit: str
|
||||
|
||||
@classmethod
|
||||
def parse(cls, token: str) -> Scalar:
|
||||
"""Parse a string in to a Scalar
|
||||
|
||||
Args:
|
||||
token (str): A string containing a scalar, e.g. "3.14fr"
|
||||
|
||||
Raises:
|
||||
ScalarParseError: If the value is not a valid scalar
|
||||
|
||||
Returns:
|
||||
Scalar: New scalar
|
||||
"""
|
||||
match = _MATCH_SCALAR(token)
|
||||
if match is None:
|
||||
raise ScalarParseError(f"{token!r} is not a valid scalar")
|
||||
value, unit = match.groups()
|
||||
scalar = cls(float(value), unit or "")
|
||||
return scalar
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
|
||||
print(Scalar.parse("3.14"))
|
||||
@@ -17,6 +17,7 @@ from .constants import (
|
||||
NULL_SPACING,
|
||||
)
|
||||
from ..geometry import NULL_OFFSET, Offset, Spacing
|
||||
from .scalar import Scalar
|
||||
from ._style_properties import (
|
||||
BorderProperty,
|
||||
BoxProperty,
|
||||
@@ -24,10 +25,10 @@ from ._style_properties import (
|
||||
DockEdgeProperty,
|
||||
DocksProperty,
|
||||
DockGroupProperty,
|
||||
IntegerProperty,
|
||||
OffsetProperty,
|
||||
NameProperty,
|
||||
NameListProperty,
|
||||
ScalarProperty,
|
||||
SpacingProperty,
|
||||
StringProperty,
|
||||
StyleProperty,
|
||||
@@ -61,9 +62,10 @@ class Styles:
|
||||
_rule_outline_bottom: tuple[str, Style] | None = None
|
||||
_rule_outline_left: tuple[str, Style] | None = None
|
||||
|
||||
_rule_size: int | None = None
|
||||
_rule_fraction: int | None = None
|
||||
_rule_min_size: int | 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_layout: str | None = None
|
||||
|
||||
@@ -101,9 +103,10 @@ class Styles:
|
||||
outline_bottom = BoxProperty()
|
||||
outline_left = BoxProperty()
|
||||
|
||||
size = IntegerProperty()
|
||||
fraction = IntegerProperty()
|
||||
min_size = IntegerProperty()
|
||||
width = ScalarProperty()
|
||||
height = ScalarProperty()
|
||||
min_width = ScalarProperty()
|
||||
min_height = ScalarProperty()
|
||||
|
||||
dock_group = DockGroupProperty()
|
||||
docks = DocksProperty()
|
||||
@@ -148,12 +151,16 @@ class Styles:
|
||||
yield "dock_edge", self.dock_edge, ""
|
||||
yield "dock_group", self.dock_group, ""
|
||||
yield "docks", self.docks, ()
|
||||
yield "width", self.width, None
|
||||
yield "height", self.height, None
|
||||
yield "min_width", self.min_width, None
|
||||
yield "min_height", self.min_height, None
|
||||
yield "margin", self.margin, NULL_SPACING
|
||||
yield "offset", self.offset, NULL_OFFSET
|
||||
if self.has_outline:
|
||||
yield "outline", self.outline
|
||||
yield "padding", self.padding, NULL_SPACING
|
||||
yield "text", self.text, ""
|
||||
yield "text", self.text, Style()
|
||||
yield "visibility", self.visibility, "visible"
|
||||
yield "layers", self.layers, ()
|
||||
yield "layer", self.layer, ""
|
||||
@@ -257,6 +264,15 @@ class Styles:
|
||||
if self._rule_text_color or self._rule_text_bgcolor or self._rule_text_style:
|
||||
append_declaration("text", str(self.text))
|
||||
|
||||
if self._rule_width is not None:
|
||||
append_declaration("width", str(self.width))
|
||||
if self._rule_height is not None:
|
||||
append_declaration("height", str(self.height))
|
||||
if self._rule_min_width is not None:
|
||||
append_declaration("min-width", str(self.min_width))
|
||||
if self._rule_min_height is not None:
|
||||
append_declaration("min-height", str(self.min_height))
|
||||
|
||||
lines.sort()
|
||||
return lines
|
||||
|
||||
|
||||
@@ -179,8 +179,8 @@ if __name__ == "__main__":
|
||||
CSS = """
|
||||
App > View {
|
||||
layout: dock;
|
||||
docks: sidebar=left | widgets=top;
|
||||
fart: poo
|
||||
/* docks: sidebar=left | widgets=top; */
|
||||
|
||||
}
|
||||
|
||||
#sidebar {
|
||||
@@ -190,6 +190,7 @@ if __name__ == "__main__":
|
||||
#widget1 {
|
||||
text: on blue;
|
||||
dock-group: widgets;
|
||||
width: 10%
|
||||
}
|
||||
|
||||
#widget2 {
|
||||
@@ -200,15 +201,15 @@ if __name__ == "__main__":
|
||||
"""
|
||||
|
||||
stylesheet = Stylesheet()
|
||||
stylesheet.read("~/example.css")
|
||||
stylesheet.parse(CSS)
|
||||
|
||||
print(stylesheet.error_renderable)
|
||||
|
||||
# print(widget1.styles)
|
||||
print(widget1.styles)
|
||||
|
||||
# stylesheet.apply(widget1)
|
||||
stylesheet.apply(widget1)
|
||||
|
||||
# print(widget1.styles)
|
||||
print(widget1.styles)
|
||||
|
||||
# from .query import DOMQuery
|
||||
|
||||
|
||||
@@ -45,7 +45,7 @@ expect_declaration_content = Expect(
|
||||
whitespace=r"\s+",
|
||||
comment_start=r"\/\*",
|
||||
percentage=r"\d+\%",
|
||||
number=r"\d+\.?\d*",
|
||||
scalar=r"\d+\.?\d*(?:fr|%)?",
|
||||
color=r"\#[0-9a-f]{6}|color\([0-9]{1,3}\)|rgb\(\d{1,3}\,\s?\d{1,3}\,\s?\d{1,3}\)",
|
||||
key_value=r"[a-zA-Z_-][a-zA-Z0-9_-]*=[a-zA-Z_-]+",
|
||||
token="[a-zA-Z_-]+",
|
||||
|
||||
Reference in New Issue
Block a user