mirror of
https://github.com/Textualize/textual.git
synced 2025-10-17 02:38:12 +03:00
Add opacity, PercentageProperty
This commit is contained in:
@@ -34,6 +34,7 @@ Widget:hover {
|
||||
|
||||
#header {
|
||||
text: $text on $primary;
|
||||
opacity: 0.2;
|
||||
height: 3;
|
||||
border-bottom: hkey $secondary;
|
||||
}
|
||||
|
||||
@@ -9,8 +9,7 @@ when setting and getting.
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
|
||||
from typing import Iterable, NamedTuple, TYPE_CHECKING
|
||||
from typing import Iterable, NamedTuple, TYPE_CHECKING, cast
|
||||
|
||||
import rich.repr
|
||||
from rich.color import Color
|
||||
@@ -28,7 +27,7 @@ from .scalar import (
|
||||
ScalarParseError,
|
||||
)
|
||||
from .transition import Transition
|
||||
from ..geometry import Spacing, SpacingDimensions
|
||||
from ..geometry import Spacing, SpacingDimensions, clamp
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from ..layout import Layout
|
||||
@@ -756,7 +755,7 @@ class TransitionsProperty:
|
||||
def __get__(
|
||||
self, obj: Styles, objtype: type[Styles] | None = None
|
||||
) -> dict[str, Transition]:
|
||||
"""Get a mapping of properties to the the transitions applied to them.
|
||||
"""Get a mapping of properties to the transitions applied to them.
|
||||
|
||||
Args:
|
||||
obj (Styles): The ``Styles`` object.
|
||||
@@ -774,3 +773,51 @@ class TransitionsProperty:
|
||||
obj.clear_rule("transitions")
|
||||
else:
|
||||
obj.set_rule("transitions", transitions.copy())
|
||||
|
||||
|
||||
class PercentageProperty:
|
||||
"""Property that can be set either as a float (e.g. 0.1) or a
|
||||
string percentage (e.g. '10%'). Values will be clamped to the range (0, 1).
|
||||
"""
|
||||
|
||||
def __init__(self, default: float = 1.0):
|
||||
self.default = default
|
||||
|
||||
def __set_name__(self, owner: Styles, name: str) -> None:
|
||||
self.name = name
|
||||
|
||||
def __get__(self, obj: Styles, type: type[Styles]) -> float:
|
||||
"""Get the property value as a float between 0 and 1
|
||||
|
||||
Args:
|
||||
obj (Styles): The ``Styles`` object.
|
||||
objtype (type[Styles]): The ``Styles`` class.
|
||||
|
||||
Returns:
|
||||
float: The value of the property (in the range (0, 1))
|
||||
"""
|
||||
return cast(float, obj.get_rule(self.name, self.default))
|
||||
|
||||
def __set__(self, obj: Styles, value: float | str | None) -> None:
|
||||
"""Set the property value, clamping it between 0 and 1.
|
||||
|
||||
Args:
|
||||
obj (Styles): The Styles object.
|
||||
value (float|str|None): The value to set as a float between 0 and 1, or
|
||||
as a percentage string such as '10%'.
|
||||
"""
|
||||
obj.refresh()
|
||||
name = self.name
|
||||
if value is None:
|
||||
obj.clear_rule(name)
|
||||
return
|
||||
|
||||
if isinstance(value, float):
|
||||
float_value = value
|
||||
elif isinstance(value, str):
|
||||
float_value = float(Scalar.parse(value).value) / 100
|
||||
else:
|
||||
raise StyleTypeError(
|
||||
f"{self.name} must be a str (e.g. '10%') or a float (e.g. 0.1)"
|
||||
)
|
||||
obj.set_rule(name, clamp(float_value, 0, 1))
|
||||
|
||||
@@ -17,8 +17,7 @@ from .transition import Transition
|
||||
from .types import Edge, Display, Visibility
|
||||
from .._duration import _duration_as_seconds
|
||||
from .._easing import EASING
|
||||
from .._loop import loop_last
|
||||
from ..geometry import Spacing, SpacingDimensions
|
||||
from ..geometry import Spacing, SpacingDimensions, clamp
|
||||
|
||||
|
||||
class StylesBuilder:
|
||||
@@ -124,6 +123,39 @@ class StylesBuilder:
|
||||
else:
|
||||
self.error(name, token, f"invalid token {value!r} in this context")
|
||||
|
||||
def process_opacity(self, name: str, tokens: list[Token], important: bool) -> None:
|
||||
if not tokens:
|
||||
return
|
||||
token = tokens[0]
|
||||
if len(tokens) != 1:
|
||||
self.error(name, token, "expected a single number")
|
||||
else:
|
||||
token_name = token.name
|
||||
value = token.value
|
||||
if token_name == "scalar" and value.endswith("%"):
|
||||
percentage = value[:-1]
|
||||
try:
|
||||
opacity = clamp(float(percentage) / 100, 0, 1)
|
||||
self.styles.set_rule(name, opacity)
|
||||
except ValueError:
|
||||
self.error(
|
||||
name, token, f"unable to process value {value!r} as percentage"
|
||||
)
|
||||
elif token_name == "number":
|
||||
try:
|
||||
opacity = clamp(float(value), 0, 1)
|
||||
self.styles.set_rule(name, opacity)
|
||||
except ValueError:
|
||||
self.error(
|
||||
name, token, f"unable to process value {value!r} as float"
|
||||
)
|
||||
else:
|
||||
self.error(
|
||||
name,
|
||||
token,
|
||||
f"expected a scalar percentage or float between 0 and 1; found {token.value!r}; example valid values: '0.4', '40%'",
|
||||
)
|
||||
|
||||
def _process_space(self, name: str, tokens: list[Token]) -> None:
|
||||
space: list[int] = []
|
||||
append = space.append
|
||||
|
||||
@@ -28,6 +28,7 @@ from ._style_properties import (
|
||||
StyleFlagsProperty,
|
||||
StyleProperty,
|
||||
TransitionsProperty,
|
||||
PercentageProperty,
|
||||
)
|
||||
from .constants import VALID_DISPLAY, VALID_VISIBILITY
|
||||
from .scalar import Scalar, ScalarOffset, Unit
|
||||
@@ -62,6 +63,8 @@ class RulesMap(TypedDict, total=False):
|
||||
text_background: Color
|
||||
text_style: Style
|
||||
|
||||
opacity: float
|
||||
|
||||
padding: Spacing
|
||||
margin: Spacing
|
||||
offset: ScalarOffset
|
||||
@@ -121,6 +124,8 @@ class StylesBase(ABC):
|
||||
text_background = ColorProperty()
|
||||
text_style = StyleFlagsProperty()
|
||||
|
||||
opacity = PercentageProperty()
|
||||
|
||||
padding = SpacingProperty()
|
||||
margin = SpacingProperty()
|
||||
offset = OffsetProperty()
|
||||
|
||||
@@ -4,7 +4,6 @@ import re
|
||||
from typing import NamedTuple
|
||||
|
||||
import rich.repr
|
||||
from rich.cells import cell_len
|
||||
|
||||
|
||||
class EOFError(Exception):
|
||||
|
||||
Reference in New Issue
Block a user