mirror of
https://github.com/Textualize/textual.git
synced 2025-10-17 02:38:12 +03:00
scalar resolve
This commit is contained in:
@@ -18,8 +18,11 @@ if TYPE_CHECKING:
|
||||
|
||||
|
||||
class ScalarProperty:
|
||||
def __init__(self, units: set[Unit] | None = None) -> None:
|
||||
def __init__(
|
||||
self, units: set[Unit] | None = None, percent_unit: Unit = Unit.WIDTH
|
||||
) -> None:
|
||||
self.units: set[Unit] = units or {*UNIT_SYMBOL}
|
||||
self.percent_unit = percent_unit
|
||||
super().__init__()
|
||||
|
||||
def __set_name__(self, owner: Styles, name: str) -> None:
|
||||
@@ -53,6 +56,8 @@ class ScalarProperty:
|
||||
raise StyleValueError(
|
||||
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)
|
||||
setattr(obj, self.internal_name, new_value)
|
||||
return value
|
||||
|
||||
|
||||
@@ -94,7 +94,7 @@ class StylesBuilder:
|
||||
|
||||
def process_visibility(self, name: str, tokens: list[Token]) -> None:
|
||||
for token in tokens:
|
||||
_, _, location, name, value = token
|
||||
name, value, _, _, location = token
|
||||
if name == "token":
|
||||
value = value.lower()
|
||||
if value in VALID_VISIBILITY:
|
||||
@@ -114,12 +114,15 @@ class StylesBuilder:
|
||||
for token in tokens:
|
||||
(token_name, value, _, _, location) = token
|
||||
if token_name == "scalar":
|
||||
append(int(value))
|
||||
try:
|
||||
append(int(value))
|
||||
except ValueError:
|
||||
self.error(name, token, f"expected a number here; found {value!r}")
|
||||
else:
|
||||
self.error(name, token, f"unexpected token {value!r} in declaration")
|
||||
if len(space) not in (1, 2, 4):
|
||||
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,
|
||||
|
||||
@@ -4,6 +4,18 @@ from enum import Enum, unique
|
||||
import re
|
||||
from typing import Iterable, NamedTuple
|
||||
|
||||
import rich.repr
|
||||
|
||||
from ..geometry import Offset
|
||||
|
||||
|
||||
class ScalarResolveError(Exception):
|
||||
pass
|
||||
|
||||
|
||||
class ScalarParseError(Exception):
|
||||
pass
|
||||
|
||||
|
||||
@unique
|
||||
class Unit(Enum):
|
||||
@@ -31,6 +43,15 @@ SYMBOL_UNIT = {v: k for k, v in UNIT_SYMBOL.items()}
|
||||
_MATCH_SCALAR = re.compile(r"^(\d+\.?\d*)(fr|%|w|h|vw|vh)?$").match
|
||||
|
||||
|
||||
RESOLVE_MAP = {
|
||||
Unit.CELLS: lambda value, size, viewport: value,
|
||||
Unit.WIDTH: lambda value, size, viewport: size[0],
|
||||
Unit.HEIGHT: lambda value, size, viewport: size[1],
|
||||
Unit.VIEW_WIDTH: lambda value, size, viewport: viewport[0],
|
||||
Unit.VIEW_HEIGHT: lambda value, size, viewport: viewport[1],
|
||||
}
|
||||
|
||||
|
||||
def get_symbols(units: Iterable[Unit]) -> list[str]:
|
||||
"""Get symbols for an iterable of units.
|
||||
|
||||
@@ -43,10 +64,6 @@ def get_symbols(units: Iterable[Unit]) -> list[str]:
|
||||
return [UNIT_SYMBOL[unit] for unit in units]
|
||||
|
||||
|
||||
class ScalarParseError(Exception):
|
||||
pass
|
||||
|
||||
|
||||
class Scalar(NamedTuple):
|
||||
"""A numeric value and a unit."""
|
||||
|
||||
@@ -57,6 +74,10 @@ class Scalar(NamedTuple):
|
||||
value, _unit = self
|
||||
return f"{int(value) if value.is_integer() else value}{self.symbol}"
|
||||
|
||||
@property
|
||||
def is_percent(self) -> bool:
|
||||
return self.unit == Unit.PERCENT
|
||||
|
||||
@property
|
||||
def cells(self) -> int | None:
|
||||
value, unit = self
|
||||
@@ -91,6 +112,37 @@ class Scalar(NamedTuple):
|
||||
scalar = cls(float(value), SYMBOL_UNIT[unit_name or ""])
|
||||
return scalar
|
||||
|
||||
def resolve(
|
||||
self,
|
||||
size: tuple[int, int],
|
||||
viewport: tuple[int, int],
|
||||
percent_unit: Unit = Unit.WIDTH,
|
||||
) -> float:
|
||||
value, unit = self
|
||||
if unit == Unit.PERCENT:
|
||||
unit = percent_unit
|
||||
try:
|
||||
return RESOLVE_MAP[unit](value, size, viewport)
|
||||
except KeyError:
|
||||
raise ScalarResolveError("unable to resolve {self!r}")
|
||||
|
||||
|
||||
@rich.repr.auto(angular=True)
|
||||
class ScalarOffset(NamedTuple):
|
||||
x: Scalar
|
||||
y: Scalar
|
||||
|
||||
def __rich_repr__(self) -> rich.repr.Result:
|
||||
yield str(self.x)
|
||||
yield str(self.y)
|
||||
|
||||
def resolve(self, size: tuple[int, int], viewport: tuple[int, int]) -> Offset:
|
||||
x, y = self
|
||||
return Offset(
|
||||
round(x.resolve(size, viewport, percent_unit=Unit.WIDTH)),
|
||||
round(y.resolve(size, viewport, percent_unit=Unit.HEIGHT)),
|
||||
)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
|
||||
|
||||
@@ -18,7 +18,7 @@ from .constants import (
|
||||
NULL_SPACING,
|
||||
)
|
||||
from ..geometry import NULL_OFFSET, Offset, Spacing
|
||||
from .scalar import Scalar
|
||||
from .scalar import Scalar, Unit
|
||||
from ._style_properties import (
|
||||
BorderProperty,
|
||||
BoxProperty,
|
||||
@@ -115,10 +115,10 @@ class Styles:
|
||||
outline_bottom = BoxProperty()
|
||||
outline_left = BoxProperty()
|
||||
|
||||
width = ScalarProperty()
|
||||
height = ScalarProperty()
|
||||
min_width = ScalarProperty()
|
||||
min_height = ScalarProperty()
|
||||
width = ScalarProperty(percent_unit=Unit.WIDTH)
|
||||
height = ScalarProperty(percent_unit=Unit.HEIGHT)
|
||||
min_width = ScalarProperty(percent_unit=Unit.WIDTH)
|
||||
min_height = ScalarProperty(percent_unit=Unit.HEIGHT)
|
||||
|
||||
dock_group = DockGroupProperty()
|
||||
docks = DocksProperty()
|
||||
|
||||
Reference in New Issue
Block a user