mirror of
https://github.com/Textualize/textual.git
synced 2025-10-17 02:38:12 +03:00
boolean to refresh
This commit is contained in:
@@ -11,9 +11,9 @@ class BasicApp(App):
|
||||
"""Build layout here."""
|
||||
self.mount(
|
||||
header=Widget(),
|
||||
content=Placeholder(),
|
||||
footer=Widget(),
|
||||
sidebar=Widget(),
|
||||
# content=Placeholder(),
|
||||
# footer=Widget(),
|
||||
# sidebar=Widget(),
|
||||
)
|
||||
|
||||
async def on_key(self, event: events.Key) -> None:
|
||||
|
||||
@@ -45,6 +45,7 @@ class SimpleAnimation(Animation):
|
||||
duration: float
|
||||
start_value: float | Animatable
|
||||
end_value: float | Animatable
|
||||
final_value: float | Animatable
|
||||
easing: EasingFunction
|
||||
|
||||
def __call__(self, time: float) -> bool:
|
||||
@@ -62,7 +63,9 @@ class SimpleAnimation(Animation):
|
||||
factor = min(1.0, (time - self.start_time) / self.duration)
|
||||
eased_factor = self.easing(factor)
|
||||
|
||||
if isinstance(self.start_value, Animatable):
|
||||
if factor == 1.0:
|
||||
value = self.end_value
|
||||
elif isinstance(self.start_value, Animatable):
|
||||
assert isinstance(
|
||||
self.end_value, Animatable
|
||||
), "end_value must be animatable"
|
||||
@@ -148,11 +151,14 @@ class Animator:
|
||||
attribute: str,
|
||||
value: Any,
|
||||
*,
|
||||
final_value: Any = ...,
|
||||
duration: float | None = None,
|
||||
speed: float | None = None,
|
||||
easing: EasingFunction | str = DEFAULT_EASING,
|
||||
) -> None:
|
||||
|
||||
if final_value is ...:
|
||||
final_value = value
|
||||
start_time = time()
|
||||
|
||||
animation_key = (id(obj), attribute)
|
||||
@@ -190,6 +196,7 @@ class Animator:
|
||||
duration=animation_duration,
|
||||
start_value=start_value,
|
||||
end_value=value,
|
||||
final_value=final_value,
|
||||
easing=easing_function,
|
||||
)
|
||||
assert animation is not None, "animation expected to be non-None"
|
||||
|
||||
@@ -105,8 +105,8 @@ class ScalarProperty:
|
||||
)
|
||||
if new_value is not None and new_value.is_percent:
|
||||
new_value = Scalar(float(new_value.value), self.percent_unit, Unit.WIDTH)
|
||||
obj.set_rule(self.name, new_value)
|
||||
obj.refresh()
|
||||
if obj.set_rule(self.name, new_value):
|
||||
obj.refresh()
|
||||
|
||||
|
||||
class BoxProperty:
|
||||
@@ -151,7 +151,8 @@ class BoxProperty:
|
||||
StyleSyntaxError: If the string supplied for the color has invalid syntax.
|
||||
"""
|
||||
if border is None:
|
||||
obj.clear_rule(self.name)
|
||||
if obj.clear_rule(self.name):
|
||||
obj.refresh()
|
||||
else:
|
||||
_type, color = border
|
||||
new_value = border
|
||||
@@ -159,8 +160,8 @@ class BoxProperty:
|
||||
new_value = (_type, Color.parse(color))
|
||||
elif isinstance(color, Color):
|
||||
new_value = (_type, color)
|
||||
obj.set_rule(self.name, new_value)
|
||||
obj.refresh()
|
||||
if obj.set_rule(self.name, new_value):
|
||||
obj.refresh()
|
||||
|
||||
|
||||
@rich.repr.auto
|
||||
@@ -378,11 +379,13 @@ class SpacingProperty:
|
||||
ValueError: When the value is malformed, e.g. a ``tuple`` with a length that is
|
||||
not 1, 2, or 4.
|
||||
"""
|
||||
obj.refresh(layout=True)
|
||||
|
||||
if spacing is None:
|
||||
obj.clear_rule(self.name)
|
||||
if obj.clear_rule(self.name):
|
||||
obj.refresh(layout=True)
|
||||
else:
|
||||
obj.set_rule(self.name, Spacing.unpack(spacing))
|
||||
if obj.set_rule(self.name, Spacing.unpack(spacing)):
|
||||
obj.refresh(layout=True)
|
||||
|
||||
|
||||
class DocksProperty:
|
||||
@@ -411,12 +414,12 @@ class DocksProperty:
|
||||
obj (Styles): The ``Styles`` object.
|
||||
docks (Iterable[DockGroup]): Iterable of DockGroups
|
||||
"""
|
||||
obj.refresh(layout=True)
|
||||
if docks is None:
|
||||
obj.clear_rule("docks")
|
||||
|
||||
if obj.clear_rule("docks"):
|
||||
obj.refresh(layout=True)
|
||||
else:
|
||||
obj.set_rule("docks", tuple(docks))
|
||||
if obj.set_rule("docks", tuple(docks)):
|
||||
obj.refresh(layout=True)
|
||||
|
||||
|
||||
class DockProperty:
|
||||
@@ -445,8 +448,8 @@ class DockProperty:
|
||||
obj (Styles): The ``Styles`` object
|
||||
spacing (str | None): The spacing to use.
|
||||
"""
|
||||
obj.refresh(layout=True)
|
||||
obj.set_rule("dock", spacing)
|
||||
if obj.set_rule("dock", spacing):
|
||||
obj.refresh(layout=True)
|
||||
|
||||
|
||||
class LayoutProperty:
|
||||
@@ -477,14 +480,15 @@ class LayoutProperty:
|
||||
|
||||
from ..layouts.factory import get_layout, Layout # Prevents circular import
|
||||
|
||||
obj.refresh(layout=True)
|
||||
|
||||
if layout is None:
|
||||
obj.clear_rule("layout")
|
||||
if obj.clear_rule("layout"):
|
||||
obj.refresh(layout=True)
|
||||
elif isinstance(layout, Layout):
|
||||
obj.set_rule("layout", layout)
|
||||
if obj.set_rule("layout", layout):
|
||||
obj.refresh(layout=True)
|
||||
else:
|
||||
obj.set_rule("layout", get_layout(layout))
|
||||
if obj.set_rule("layout", get_layout(layout)):
|
||||
obj.refresh(layout=True)
|
||||
|
||||
|
||||
class OffsetProperty:
|
||||
@@ -525,11 +529,13 @@ class OffsetProperty:
|
||||
ScalarParseError: If any of the string values supplied in the 2-tuple cannot
|
||||
be parsed into a Scalar. For example, if you specify an non-existent unit.
|
||||
"""
|
||||
obj.refresh(layout=True)
|
||||
|
||||
if offset is None:
|
||||
obj.clear_rule(self.name)
|
||||
if obj.clear_rule(self.name):
|
||||
obj.refresh(layout=True)
|
||||
elif isinstance(offset, ScalarOffset):
|
||||
obj.set_rule(self.name, offset)
|
||||
if obj.set_rule(self.name, offset):
|
||||
obj.refresh(layout=True)
|
||||
else:
|
||||
x, y = offset
|
||||
scalar_x = (
|
||||
@@ -543,7 +549,8 @@ class OffsetProperty:
|
||||
else Scalar(float(y), Unit.CELLS, Unit.HEIGHT)
|
||||
)
|
||||
_offset = ScalarOffset(scalar_x, scalar_y)
|
||||
obj.set_rule(self.name, _offset)
|
||||
if obj.set_rule(self.name, _offset):
|
||||
obj.refresh(layout=True)
|
||||
|
||||
|
||||
class StringEnumProperty:
|
||||
@@ -580,15 +587,17 @@ class StringEnumProperty:
|
||||
Raises:
|
||||
StyleValueError: If the value is not in the set of valid values.
|
||||
"""
|
||||
obj.refresh()
|
||||
|
||||
if value is None:
|
||||
obj.clear_rule(self.name)
|
||||
if obj.clear_rule(self.name):
|
||||
obj.refresh()
|
||||
else:
|
||||
if value not in self._valid_values:
|
||||
raise StyleValueError(
|
||||
f"{self.name} must be one of {friendly_list(self._valid_values)}"
|
||||
)
|
||||
obj.set_rule(self.name, value)
|
||||
if obj.set_rule(self.name, value):
|
||||
obj.refresh()
|
||||
|
||||
|
||||
class NameProperty:
|
||||
@@ -619,13 +628,15 @@ class NameProperty:
|
||||
Raises:
|
||||
StyleTypeError: If the value is not a ``str``.
|
||||
"""
|
||||
obj.refresh(layout=True)
|
||||
|
||||
if name is None:
|
||||
obj.clear_rule(self.name)
|
||||
if obj.clear_rule(self.name):
|
||||
obj.refresh(layout=True)
|
||||
else:
|
||||
if not isinstance(name, str):
|
||||
raise StyleTypeError(f"{self.name} must be a str")
|
||||
obj.set_rule(self.name, name)
|
||||
if obj.set_rule(self.name, name):
|
||||
obj.refresh(layout=True)
|
||||
|
||||
|
||||
class NameListProperty:
|
||||
@@ -640,15 +651,18 @@ class NameListProperty:
|
||||
def __set__(
|
||||
self, obj: Styles, names: str | tuple[str] | None = None
|
||||
) -> str | tuple[str] | None:
|
||||
obj.refresh(layout=True)
|
||||
|
||||
if names is None:
|
||||
obj.clear_rule(self.name)
|
||||
if obj.clear_rule(self.name):
|
||||
obj.refresh(layout=True)
|
||||
elif isinstance(names, str):
|
||||
obj.set_rule(
|
||||
if obj.set_rule(
|
||||
self.name, tuple(name.strip().lower() for name in names.split(" "))
|
||||
)
|
||||
):
|
||||
obj.refresh(layout=True)
|
||||
elif isinstance(names, tuple):
|
||||
obj.set_rule(self.name, names)
|
||||
if obj.set_rule(self.name, names):
|
||||
obj.refresh(layout=True)
|
||||
|
||||
|
||||
class ColorProperty:
|
||||
@@ -681,13 +695,16 @@ class ColorProperty:
|
||||
Raises:
|
||||
ColorParseError: When the color string is invalid.
|
||||
"""
|
||||
obj.refresh()
|
||||
|
||||
if color is None:
|
||||
obj.clear_rule(self.name)
|
||||
if obj.clear_rule(self.name):
|
||||
obj.refresh()
|
||||
elif isinstance(color, Color):
|
||||
obj.set_rule(self.name, color)
|
||||
if obj.set_rule(self.name, color):
|
||||
obj.refresh()
|
||||
elif isinstance(color, str):
|
||||
obj.set_rule(self.name, Color.parse(color))
|
||||
if obj.set_rule(self.name, Color.parse(color)):
|
||||
obj.refresh()
|
||||
|
||||
|
||||
class StyleFlagsProperty:
|
||||
@@ -722,7 +739,7 @@ class StyleFlagsProperty:
|
||||
"""
|
||||
return obj.get_rule(self.name, Style.null())
|
||||
|
||||
def __set__(self, obj: Styles, style_flags: str | None):
|
||||
def __set__(self, obj: Styles, style_flags: Style | str | None):
|
||||
"""Set the style using a style flag string
|
||||
|
||||
Args:
|
||||
@@ -733,9 +750,12 @@ class StyleFlagsProperty:
|
||||
Raises:
|
||||
StyleValueError: If the value is an invalid style flag
|
||||
"""
|
||||
obj.refresh()
|
||||
if style_flags is None:
|
||||
obj.clear_rule(self.name)
|
||||
if obj.clear_rule(self.name):
|
||||
obj.refresh()
|
||||
elif isinstance(style_flags, Style):
|
||||
if obj.set_rule(self.name, style_flags):
|
||||
obj.refresh()
|
||||
else:
|
||||
words = [word.strip() for word in style_flags.split(" ")]
|
||||
valid_word = self._VALID_PROPERTIES.__contains__
|
||||
@@ -746,7 +766,8 @@ class StyleFlagsProperty:
|
||||
f"valid values are {friendly_list(self._VALID_PROPERTIES)}"
|
||||
)
|
||||
style = Style.parse(style_flags)
|
||||
obj.set_rule(self.name, style)
|
||||
if obj.set_rule(self.name, style):
|
||||
obj.refresh()
|
||||
|
||||
|
||||
class TransitionsProperty:
|
||||
@@ -806,10 +827,10 @@ class FractionalProperty:
|
||||
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)
|
||||
if obj.clear_rule(name):
|
||||
obj.refresh()
|
||||
return
|
||||
|
||||
if isinstance(value, float):
|
||||
@@ -820,4 +841,5 @@ class FractionalProperty:
|
||||
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))
|
||||
if obj.set_rule(name, clamp(float_value, 0, 1)):
|
||||
obj.refresh()
|
||||
|
||||
@@ -168,11 +168,14 @@ class StylesBase(ABC):
|
||||
"""
|
||||
|
||||
@abstractmethod
|
||||
def clear_rule(self, rule: str) -> None:
|
||||
def clear_rule(self, rule: str) -> bool:
|
||||
"""Removes the rule from the Styles object, as if it had never been set.
|
||||
|
||||
Args:
|
||||
rule (str): Rule name.
|
||||
|
||||
Returns:
|
||||
bool: ``True`` if a rules was clearled, or ``False`` if it was previously cleared.
|
||||
"""
|
||||
|
||||
@abstractmethod
|
||||
@@ -184,12 +187,15 @@ class StylesBase(ABC):
|
||||
"""
|
||||
|
||||
@abstractmethod
|
||||
def set_rule(self, rule: str, value: object | None) -> None:
|
||||
"""Set an individual rule.
|
||||
def set_rule(self, rule: str, value: object | None) -> bool:
|
||||
"""Set a rule.
|
||||
|
||||
Args:
|
||||
rule (str): Name of rule.
|
||||
value (object): Value of rule.
|
||||
rule (str): Rule name.
|
||||
value (object | None): New rule value.
|
||||
|
||||
Returns:
|
||||
bool: ``True`` of the rule changed, otherwise false.
|
||||
"""
|
||||
|
||||
@abstractmethod
|
||||
@@ -242,6 +248,7 @@ class StylesBase(ABC):
|
||||
|
||||
def get_render_rules(self) -> RulesMap:
|
||||
"""Get rules map with defaults."""
|
||||
# Get a dictionary of rules, going through the properties
|
||||
rules = dict(zip(RULE_NAMES, _rule_getter(self)))
|
||||
return cast(RulesMap, rules)
|
||||
|
||||
@@ -303,24 +310,35 @@ class Styles(StylesBase):
|
||||
def has_rule(self, rule: str) -> bool:
|
||||
return rule in self._rules
|
||||
|
||||
def clear_rule(self, rule: str) -> None:
|
||||
self._rules.pop(rule, None)
|
||||
def clear_rule(self, rule: str) -> bool:
|
||||
return self._rules.pop(rule, None) is not None
|
||||
|
||||
def get_rules(self) -> RulesMap:
|
||||
return self._rules.copy()
|
||||
|
||||
def set_rule(self, rule: str, value: object | None) -> None:
|
||||
def set_rule(self, rule: str, value: object | None) -> bool:
|
||||
"""Set a rule.
|
||||
|
||||
Args:
|
||||
rule (str): Rule name.
|
||||
value (object | None): New rule value.
|
||||
|
||||
Returns:
|
||||
bool: ``True`` of the rule changed, otherwise false.
|
||||
"""
|
||||
if value is None:
|
||||
self._rules.pop(rule, None)
|
||||
return self._rules.pop(rule, None) is not None
|
||||
else:
|
||||
current = self._rules.get(rule)
|
||||
self._rules[rule] = value
|
||||
return current != value
|
||||
|
||||
def get_rule(self, rule: str, default: object = None) -> object:
|
||||
return self._rules.get(rule, default)
|
||||
|
||||
def refresh(self, *, layout: bool = False) -> None:
|
||||
self._repaint_required = True
|
||||
self._layout_required = layout
|
||||
self._layout_required = self._layout_required or layout
|
||||
|
||||
def check_refresh(self) -> tuple[bool, bool]:
|
||||
"""Check if the Styles must be refreshed.
|
||||
@@ -615,9 +633,9 @@ class RenderStyles(StylesBase):
|
||||
return self._inline_styles.get_rule(rule, default)
|
||||
return self._base_styles.get_rule(rule, default)
|
||||
|
||||
def clear_rule(self, rule_name: str) -> None:
|
||||
def clear_rule(self, rule_name: str) -> bool:
|
||||
"""Clear a rule (from inline)."""
|
||||
self._inline_styles.clear_rule(rule_name)
|
||||
return self._inline_styles.clear_rule(rule_name)
|
||||
|
||||
def get_rules(self) -> RulesMap:
|
||||
"""Get rules as a dictionary"""
|
||||
|
||||
@@ -192,12 +192,38 @@ class Stylesheet:
|
||||
animate (bool, optional): Enable animation. Defaults to False.
|
||||
"""
|
||||
|
||||
new_styles = node._default_styles.copy()
|
||||
new_styles.merge_rules(rules)
|
||||
node.styles.base.reset()
|
||||
node.styles.base.merge(new_styles)
|
||||
styles = node.styles
|
||||
base_styles = styles.base
|
||||
|
||||
# current_rules = styles.get_render_rules()
|
||||
base_rules = list(base_styles.get_rules().keys())
|
||||
old_rules = {key: None for key in base_rules}
|
||||
rule_updates = {**old_rules, **rules}
|
||||
|
||||
set_rule = base_styles.set_rule
|
||||
base_styles.reset()
|
||||
base_styles.merge(node._default_styles)
|
||||
# base_styles.merge_rules(rules)
|
||||
|
||||
log("*", rule_updates)
|
||||
|
||||
for key, value in rule_updates.items():
|
||||
setattr(base_styles, key, value)
|
||||
|
||||
repaint, layout = styles.check_refresh()
|
||||
if repaint or layout:
|
||||
node.refresh(repaint=repaint, layout=layout)
|
||||
|
||||
return
|
||||
|
||||
# repaint = False
|
||||
# layout = False
|
||||
|
||||
# for (rule_name, rule1), (_, rule2) in zip(start_rules.items(), new_rules.items()):
|
||||
# if rule1 != rule2:
|
||||
|
||||
# return
|
||||
|
||||
styles = node.styles.base
|
||||
styles = Styles()
|
||||
current_styles = styles.get_render_rules()
|
||||
@@ -260,9 +286,9 @@ class Stylesheet:
|
||||
apply = self.apply
|
||||
for node in root.walk_children():
|
||||
apply(node, animate=animate)
|
||||
if hasattr(node, "clear_render_cache"):
|
||||
# TODO: Not ideal
|
||||
node.clear_render_cache()
|
||||
# if hasattr(node, "clear_render_cache"):
|
||||
# # TODO: Not ideal
|
||||
# node.clear_render_cache()
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
|
||||
Reference in New Issue
Block a user