reset css plan

This commit is contained in:
Will McGugan
2022-02-14 21:00:26 +00:00
parent 0c7c7ac964
commit 0d74197b52
11 changed files with 76 additions and 109 deletions

View File

@@ -1,33 +0,0 @@
from rich.console import RenderableType
from rich.panel import Panel
from textual.app import App
from textual.widget import Widget
class PanelWidget(Widget):
def render(self) -> RenderableType:
return Panel("hello world!", title="Title")
class BasicApp(App):
"""Sandbox application used for testing/development by Textual developers"""
def on_load(self):
"""Bind keys here."""
self.bind("tab", "toggle_class('#sidebar', '-active')")
self.bind("a", "toggle_class('#header', '-visible')")
self.bind("c", "toggle_class('#content', '-content-visible')")
self.bind("d", "toggle_class('#footer', 'dim')")
def on_mount(self):
"""Build layout here."""
self.mount(
header=Widget(),
content=PanelWidget(),
footer=Widget(),
sidebar=Widget(),
)
BasicApp.run(css_file="dev_sandbox.scss", watch_css=True, log="textual.log")

View File

@@ -1,63 +0,0 @@
/* CSS file for dev_sandbox.py */
$text: #f0f0f0;
$primary: #021720;
$secondary:#95d52a;
$background: #262626;
$primary-style: $text on $background;
$animation-speed: 500ms;
$animation: offset $animation-speed in_out_cubic;
App > View {
docks: side=left/1;
text: on $background;
}
Widget:hover {
outline: heavy;
text: bold !important;
}
#sidebar {
text: $primary-style;
dock: side;
width: 30;
offset-x: -100%;
transition: $animation;
border-right: outer $secondary;
}
#sidebar.-active {
offset-x: 0;
}
#header {
text: $text on $primary;
height: 3;
border-bottom: hkey $secondary;
}
#header.-visible {
visibility: hidden;
}
#content {
text: $text on $background;
offset-y: -3;
}
#content.-content-visible {
visibility: hidden;
}
#footer {
opacity: 1;
text: $text on $primary;
height: 3;
border-top: hkey $secondary;
}
#footer.dim {
opacity: 0.5;
}

View File

@@ -1,3 +1,3 @@
# Dev Sandbox # Dev Sandbox
This directory contains test code. None of the .py files here are guaranteed to run or do anything useful, but you are welcome to look around. This directory contains test code for Textual devs to experiment with new features. None of the .py files here are guaranteed to run or do anything useful, but you are welcome to look around.

View File

@@ -5,3 +5,7 @@ App > View {
Widget { Widget {
text: on blue; text: on blue;
} }
Widget.-highlight {
outline: heavy red;
}

View File

@@ -25,5 +25,9 @@ class BasicApp(App):
def key_b(self) -> None: def key_b(self) -> None:
self["#footer"].set_styles("text: on green") self["#footer"].set_styles("text: on green")
def key_c(self) -> None:
self["#header"].toggle_class("-highlight")
self.log(self["#header"].styles)
BasicApp.run(css_file="local_styles.css", log="textual.log") BasicApp.run(css_file="local_styles.css", log="textual.log")

View File

@@ -508,6 +508,7 @@ class App(DOMNode):
if sync_available: if sync_available:
console.file.write("\x1bP=2s\x1b\\") console.file.write("\x1bP=2s\x1b\\")
console.file.flush() console.file.flush()
self.log("APP REFRESH")
except Exception: except Exception:
self.panic() self.panic()

View File

@@ -4,6 +4,7 @@ import sys
from abc import ABC, abstractmethod from abc import ABC, abstractmethod
from dataclasses import dataclass, field from dataclasses import dataclass, field
from functools import lru_cache from functools import lru_cache
from operator import attrgetter
from typing import TYPE_CHECKING, Any, Iterable, NamedTuple, cast from typing import TYPE_CHECKING, Any, Iterable, NamedTuple, cast
import rich.repr import rich.repr
@@ -94,6 +95,7 @@ class RulesMap(TypedDict, total=False):
RULE_NAMES = list(RulesMap.__annotations__.keys()) RULE_NAMES = list(RulesMap.__annotations__.keys())
_rule_getter = attrgetter(*RULE_NAMES)
class DockGroup(NamedTuple): class DockGroup(NamedTuple):
@@ -238,6 +240,11 @@ class StylesBase(ABC):
rules (RulesMap): A mapping of rules. rules (RulesMap): A mapping of rules.
""" """
def get_render_rules(self) -> RulesMap:
"""Get rules map with defaults."""
rules = dict(zip(RULE_NAMES, _rule_getter(self)))
return cast(RulesMap, rules)
@classmethod @classmethod
def is_animatable(cls, rule: str) -> bool: def is_animatable(cls, rule: str) -> bool:
"""Check if a given rule may be animated. """Check if a given rule may be animated.
@@ -289,6 +296,10 @@ class Styles(StylesBase):
important: set[str] = field(default_factory=set) important: set[str] = field(default_factory=set)
def copy(self) -> Styles:
"""Get a copy of this Styles object."""
return Styles(node=self.node, _rules=self.get_rules(), important=self.important)
def has_rule(self, rule: str) -> bool: def has_rule(self, rule: str) -> bool:
return rule in self._rules return rule in self._rules
@@ -307,7 +318,7 @@ class Styles(StylesBase):
def get_rule(self, rule: str, default: object = None) -> object: def get_rule(self, rule: str, default: object = None) -> object:
return self._rules.get(rule, default) return self._rules.get(rule, default)
def refresh(self, layout: bool = False) -> None: def refresh(self, *, layout: bool = False) -> None:
self._repaint_required = True self._repaint_required = True
self._layout_required = layout self._layout_required = layout
@@ -561,10 +572,6 @@ class RenderStyles(StylesBase):
if self.has_rule(rule_name): if self.has_rule(rule_name):
yield rule_name, getattr(self, rule_name) yield rule_name, getattr(self, rule_name)
def reset(self) -> None:
"""Reset the inline styles."""
self._inline_styles.reset()
def refresh(self, layout: bool = False) -> None: def refresh(self, layout: bool = False) -> None:
self._inline_styles.refresh(layout=layout) self._inline_styles.refresh(layout=layout)
@@ -590,6 +597,12 @@ class RenderStyles(StylesBase):
result = (base_repaint or inline_repaint, base_layout or inline_layout) result = (base_repaint or inline_repaint, base_layout or inline_layout)
return result return result
def reset(self) -> None:
"""
Reset internal style rules to ``None``, reverting to default styles.
"""
self._inline_styles.reset()
def has_rule(self, rule: str) -> bool: def has_rule(self, rule: str) -> bool:
"""Check if a rule has been set.""" """Check if a rule has been set."""
return self._inline_styles.has_rule(rule) or self._base_styles.has_rule(rule) return self._inline_styles.has_rule(rule) or self._base_styles.has_rule(rule)

View File

@@ -16,11 +16,12 @@ from rich.syntax import Syntax
from rich.text import Text from rich.text import Text
from textual._loop import loop_last from textual._loop import loop_last
from .._context import active_app
from .errors import StylesheetError from .errors import StylesheetError
from .match import _check_selectors from .match import _check_selectors
from .model import RuleSet from .model import RuleSet
from .parse import parse from .parse import parse
from .styles import RulesMap from .styles import RULE_NAMES, Styles, RulesMap
from .types import Specificity3, Specificity4 from .types import Specificity3, Specificity4
from ..dom import DOMNode from ..dom import DOMNode
from .. import log from .. import log
@@ -190,12 +191,39 @@ class Stylesheet:
rules (RulesMap): Mapping of rules. rules (RulesMap): Mapping of rules.
animate (bool, optional): Enable animation. Defaults to False. 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)
return
styles = node.styles.base
styles = Styles()
current_styles = styles.get_render_rules()
styles.reset()
styles.merge_rules(rules)
new_styles = styles.get_render_rules()
for (key, value1), (_, value2) in zip(
current_styles.items(), new_styles.items()
):
if value1 != value2:
setattr(styles, key, value1)
return
current_styles = styles.get_render_rules()
styles = node._default_styles.copy()
styles = node.styles styles = node.styles
set_rule = styles.base.set_rule
current_rules = styles.get_rules()
styles.reset()
if animate: if animate:
is_animatable = styles.is_animatable is_animatable = styles.is_animatable
current_rules = styles.get_rules()
set_rule = styles.base.set_rule
for key, value in rules.items(): for key, value in rules.items():
current = current_rules.get(key) current = current_rules.get(key)
@@ -204,7 +232,7 @@ class Stylesheet:
if is_animatable(key): if is_animatable(key):
transition = styles.get_transition(key) transition = styles.get_transition(key)
if transition is None: if transition is None:
styles.base.set_rule(key, value) set_rule(key, value)
else: else:
duration, easing, delay = transition duration, easing, delay = transition
node.app.animator.animate( node.app.animator.animate(
@@ -217,7 +245,13 @@ class Stylesheet:
else: else:
set_rule(key, value) set_rule(key, value)
else: else:
styles.base.merge_rules(rules) for key, value in rules.items():
current = current_rules.get(key)
if current == value:
continue
set_rule(key, value)
# styles.base.merge_rules(rules)
# styles.refresh()
node.on_style_change() node.on_style_change()
@@ -226,6 +260,9 @@ class Stylesheet:
apply = self.apply apply = self.apply
for node in root.walk_children(): for node in root.walk_children():
apply(node, animate=animate) apply(node, animate=animate)
if hasattr(node, "clear_render_cache"):
# TODO: Not ideal
node.clear_render_cache()
if __name__ == "__main__": if __name__ == "__main__":

View File

@@ -45,8 +45,8 @@ class DOMNode(MessagePump):
self.INLINE_STYLES, repr(self), node=self self.INLINE_STYLES, repr(self), node=self
) )
self.styles = RenderStyles(self, self._css_styles, self._inline_styles) self.styles = RenderStyles(self, self._css_styles, self._inline_styles)
default_styles = Styles.parse(self.DEFAULT_STYLES, repr(self)) self._default_styles = Styles.parse(self.DEFAULT_STYLES, repr(self))
self._default_rules = default_styles.extract_rules((0, 0, 0)) self._default_rules = self._default_styles.extract_rules((0, 0, 0))
super().__init__() super().__init__()
def __rich_repr__(self) -> rich.repr.Result: def __rich_repr__(self) -> rich.repr.Result:
@@ -344,6 +344,7 @@ class DOMNode(MessagePump):
""" """
self._classes.symmetric_difference_update(class_names) self._classes.symmetric_difference_update(class_names)
self.app.stylesheet.update(self.app, animate=True) self.app.stylesheet.update(self.app, animate=True)
self.app.refresh()
def has_pseudo_class(self, *class_names: str) -> bool: def has_pseudo_class(self, *class_names: str) -> bool:
"""Check for pseudo class (such as hover, focus etc)""" """Check for pseudo class (such as hover, focus etc)"""

View File

@@ -12,6 +12,7 @@ from rich.control import Control
from rich.segment import Segment, SegmentLines from rich.segment import Segment, SegmentLines
from rich.style import Style from rich.style import Style
from . import log
from ._loop import loop_last from ._loop import loop_last
from ._types import Lines from ._types import Lines
from .geometry import Region, Offset, Size from .geometry import Region, Offset, Size
@@ -425,4 +426,5 @@ class Layout(ABC):
update_region = region.intersection(clip) update_region = region.intersection(clip)
update_lines = self.render(console, crop=update_region).lines update_lines = self.render(console, crop=update_region).lines
update = LayoutUpdate(update_lines, update_region) update = LayoutUpdate(update_lines, update_region)
log(update)
return update return update

View File

@@ -217,6 +217,7 @@ class Widget(DOMNode):
def on_style_change(self) -> None: def on_style_change(self) -> None:
self.clear_render_cache() self.clear_render_cache()
self.refresh()
def _update_size(self, size: Size) -> None: def _update_size(self, size: Size) -> None:
self._size = size self._size = size