This commit is contained in:
Will McGugan
2021-12-04 18:56:29 +00:00
parent 3d3b73209c
commit f40b339888
9 changed files with 70 additions and 26 deletions

View File

@@ -12,6 +12,7 @@ App > DockView {
layer: panels;
border-right: outer #09312e;
offset-x: -50%;
transition: offset-x 1.2s in_cubic 200ms, offset-y 1s linear;
}
#header {

View File

@@ -14,6 +14,7 @@ class BasicApp(App):
footer=Widget(),
sidebar=Widget(),
)
self.panic(self.query("#sidebar").first().styles)
BasicApp.run(log="textual.log", css_file="basic.css")

6
poetry.lock generated
View File

@@ -547,7 +547,7 @@ python-versions = "*"
[[package]]
name = "rich"
version = "10.14.0"
version = "10.15.2"
description = "Render rich text, tables, progress bars, syntax highlighting, markdown and more to the terminal"
category = "main"
optional = false
@@ -995,8 +995,8 @@ regex = [
{file = "regex-2021.8.21.tar.gz", hash = "sha256:faf08b0341828f6a29b8f7dd94d5cf8cc7c39bfc3e67b78514c54b494b66915a"},
]
rich = [
{file = "rich-10.14.0-py3-none-any.whl", hash = "sha256:ab9cbfd7a3802d8c6f0fa91e974630e2a69447972dcbb9dfe9b01016dd95e38e"},
{file = "rich-10.14.0.tar.gz", hash = "sha256:8bfe4546d56b4131298d3a9e571a0742de342f1593770bd0d4707299f772a0af"},
{file = "rich-10.15.2-py3-none-any.whl", hash = "sha256:43b2c6ad51f46f6c94992aee546f1c177719f4e05aff8f5ea4d2efae3ebdac89"},
{file = "rich-10.15.2.tar.gz", hash = "sha256:1dded089b79dd042b3ab5cd63439a338e16652001f0c16e73acdcf4997ad772d"},
]
six = [
{file = "six-1.16.0-py2.py3-none-any.whl", hash = "sha256:8abb2f1d86890a2dfb989f9a77cfcfd3e47c2a354b01111771326f8aa26e0254"},

View File

@@ -17,6 +17,7 @@ from .scalar import (
from ..geometry import Offset, Spacing, SpacingDimensions
from .constants import NULL_SPACING, VALID_EDGE
from .errors import StyleTypeError, StyleValueError
from .transition import Transition
from ._error_tools import friendly_list
if TYPE_CHECKING:
@@ -49,7 +50,7 @@ class ScalarProperty:
if value is None:
new_value = None
elif isinstance(value, float):
new_value = Scalar(value, Unit.CELLS, Unit.WIDTH)
new_value = Scalar(float(value), Unit.CELLS, Unit.WIDTH)
elif isinstance(value, Scalar):
new_value = value
elif isinstance(value, str):
@@ -64,7 +65,7 @@ class ScalarProperty:
f"{self.name} units must be one of {friendly_list(get_symbols(self.units))}"
)
if new_value is not None and new_value.is_percent:
new_value = Scalar(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)
return value
@@ -277,12 +278,12 @@ class OffsetProperty:
scalar_x = (
Scalar.parse(x, Unit.WIDTH)
if isinstance(x, str)
else Scalar(x, Unit.CELLS, Unit.WIDTH)
else Scalar(float(x), Unit.CELLS, Unit.WIDTH)
)
scalar_y = (
Scalar.parse(y, Unit.HEIGHT)
if isinstance(y, str)
else Scalar(y, Unit.CELLS, Unit.HEIGHT)
else Scalar(float(y), Unit.CELLS, Unit.HEIGHT)
)
_offset = ScalarOffset(scalar_x, scalar_y)
setattr(obj, self._internal_name, _offset)
@@ -419,3 +420,14 @@ class StyleFlagsProperty:
style = Style.parse(" ".join(words))
setattr(obj, self._internal_name, style)
return style_flags
class TransitionsProperty:
def __set_name__(self, owner: Styles, name: str) -> None:
self._name = name
self._internal_name = f"_rule_{name}"
def __get__(
self, obj: Styles, objtype: type[Styles] | None = None
) -> dict[str, Transition]:
return getattr(obj, self._internal_name, None) or {}

View File

@@ -12,7 +12,7 @@ from ._error_tools import friendly_list
from .._easing import EASING
from ..geometry import Offset, Spacing, SpacingDimensions
from .model import Declaration
from .scalar import Scalar, ScalarOffset, Unit, ScalarParseError
from .scalar import Scalar, ScalarOffset, Unit, ScalarError
from .styles import DockGroup, Styles
from .types import Edge, Display, Visibility
from .tokenize import Token
@@ -53,7 +53,7 @@ class StylesBuilder:
try:
process_method(declaration.name, tokens)
except DeclarationError as error:
self.error(error.name, error.token, error.message)
raise
except Exception as error:
self.error(declaration.name, declaration.token, str(error))
@@ -389,26 +389,30 @@ class StylesBuilder:
self.error(name, token, "expected time")
try:
duration = Scalar.parse(token.value).resolve_time()
except ScalarParseError as error:
except ScalarError as error:
self.error(name, token, str(error))
token = next(iter_tokens)
if token.name != "token":
if token.value not in EASING:
self.error(
name,
token,
f"expected easing function; found {token.value!r}",
)
easing = token.value
self.error(name, token, "easing function expected")
if token.value not in EASING:
self.error(
name,
token,
f"expected easing function; found {token.value!r}",
)
easing = token.value
token = next(iter_tokens)
if token.name != "scalar":
self.error(name, token, "expected time")
try:
delay = Scalar.parse(token.value).resolve_time()
except ScalarParseError as error:
except ScalarError as error:
self.error(name, token, str(error))
except StopIteration:
pass
transitions[css_property] = Transition(duration, easing, delay)
self.styles._rule_transitions = transitions

View File

@@ -9,11 +9,15 @@ import rich.repr
from ..geometry import Offset
class ScalarResolveError(Exception):
class ScalarError(Exception):
pass
class ScalarParseError(Exception):
class ScalarResolveError(ScalarError):
pass
class ScalarParseError(ScalarError):
pass
@@ -99,7 +103,7 @@ class Scalar(NamedTuple):
@classmethod
def from_number(cls, value: float) -> Scalar:
return cls(value, Unit.CELLS, Unit.WIDTH)
return cls(float(value), Unit.CELLS, Unit.WIDTH)
@classmethod
def parse(cls, token: str, percent_unit: Unit = Unit.WIDTH) -> Scalar:
@@ -130,7 +134,7 @@ class Scalar(NamedTuple):
try:
return RESOLVE_MAP[unit](value, size, viewport)
except KeyError:
raise ScalarResolveError(f"unable to resolve {self!r} as dimensions")
raise ScalarResolveError(f"expected dimensions; found {str(self)!r}")
def resolve_time(self) -> float:
value, unit, _ = self
@@ -138,7 +142,7 @@ class Scalar(NamedTuple):
return value / 1000.0
elif unit == Unit.SECONDS:
return value
raise ScalarResolveError(f"unable to resolve {self!r} as time")
raise ScalarResolveError(f"expected time; found {str(self)!r}")
@rich.repr.auto(angular=True)
@@ -147,8 +151,8 @@ class ScalarOffset(NamedTuple):
y: Scalar
def __rich_repr__(self) -> rich.repr.Result:
yield str(self.x)
yield str(self.y)
yield None, str(self.x)
yield None, str(self.y)
def resolve(self, size: tuple[int, int], viewport: tuple[int, int]) -> Offset:
x, y = self

View File

@@ -19,6 +19,7 @@ from .constants import (
)
from ..geometry import NULL_OFFSET, Offset, Spacing
from .scalar import Scalar, ScalarOffset, Unit
from .transition import Transition
from ._style_properties import (
BorderProperty,
BoxProperty,
@@ -33,6 +34,7 @@ from ._style_properties import (
StringProperty,
StyleProperty,
StyleFlagsProperty,
TransitionsProperty,
)
from .types import Display, Edge, Visibility
@@ -88,6 +90,8 @@ class Styles:
_rule_layers: tuple[str, ...] | None = None
_rule_layer: str | None = None
_rule_transitions: dict[str, Transition] | None = None
important: set[str] = field(default_factory=set)
display = StringProperty(VALID_DISPLAY, "block")
@@ -125,6 +129,7 @@ class Styles:
layer = NameProperty()
layers = NameListProperty()
transitions = TransitionsProperty()
@property
def has_border(self) -> bool:
@@ -282,6 +287,14 @@ class Styles:
append_declaration("min-width", str(self.min_width))
if self._rule_min_height is not None:
append_declaration("min-height", str(self.min_height))
if self._rule_transitions is not None:
append_declaration(
"transition",
", ".join(
f"{name} {transition}"
for name, transition in self.transitions.items()
),
)
lines.sort()
return lines

View File

@@ -44,7 +44,7 @@ expect_declaration_content = Expect(
declaration_end=r"\n|;",
whitespace=r"\s+",
comment_start=r"\/\*",
scalar=r"\-?\d+\.?\d*(?:fr|%|w|h|vw|vh)?",
scalar=r"\-?\d+\.?\d*(?:fr|%|w|h|vw|vh|s|ms)?",
color=r"\#[0-9a-fA-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_-]*=[0-9a-zA-Z_\-\/]+",
token="[a-zA-Z_-]+",

View File

@@ -5,3 +5,12 @@ class Transition(NamedTuple):
duration: float = 1.0
easing: str = "linear"
delay: float = 0.0
def __str__(self) -> str:
duration, easing, delay = self
if delay:
return f"{duration:.1f}s {easing} {delay:.1f}"
elif easing != "linear":
return f"{duration:.1f}s {easing}"
else:
return f"{duration:.1f}s"