boolean to refresh

This commit is contained in:
Will McGugan
2022-02-16 13:41:07 +00:00
parent c71c337fb1
commit 9a70ea6c3d
5 changed files with 141 additions and 68 deletions

View File

@@ -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:

View File

@@ -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"

View File

@@ -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()

View File

@@ -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"""

View File

@@ -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__":