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
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 {
text: on blue;
}
Widget.-highlight {
outline: heavy red;
}

View File

@@ -25,5 +25,9 @@ class BasicApp(App):
def key_b(self) -> None:
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")

View File

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

View File

@@ -4,6 +4,7 @@ import sys
from abc import ABC, abstractmethod
from dataclasses import dataclass, field
from functools import lru_cache
from operator import attrgetter
from typing import TYPE_CHECKING, Any, Iterable, NamedTuple, cast
import rich.repr
@@ -94,6 +95,7 @@ class RulesMap(TypedDict, total=False):
RULE_NAMES = list(RulesMap.__annotations__.keys())
_rule_getter = attrgetter(*RULE_NAMES)
class DockGroup(NamedTuple):
@@ -238,6 +240,11 @@ class StylesBase(ABC):
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
def is_animatable(cls, rule: str) -> bool:
"""Check if a given rule may be animated.
@@ -289,6 +296,10 @@ class Styles(StylesBase):
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:
return rule in self._rules
@@ -307,7 +318,7 @@ class Styles(StylesBase):
def get_rule(self, rule: str, default: object = None) -> object:
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._layout_required = layout
@@ -561,10 +572,6 @@ class RenderStyles(StylesBase):
if self.has_rule(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:
self._inline_styles.refresh(layout=layout)
@@ -590,6 +597,12 @@ class RenderStyles(StylesBase):
result = (base_repaint or inline_repaint, base_layout or inline_layout)
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:
"""Check if a rule has been set."""
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 textual._loop import loop_last
from .._context import active_app
from .errors import StylesheetError
from .match import _check_selectors
from .model import RuleSet
from .parse import parse
from .styles import RulesMap
from .styles import RULE_NAMES, Styles, RulesMap
from .types import Specificity3, Specificity4
from ..dom import DOMNode
from .. import log
@@ -190,12 +191,39 @@ class Stylesheet:
rules (RulesMap): Mapping of rules.
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
set_rule = styles.base.set_rule
current_rules = styles.get_rules()
styles.reset()
if animate:
is_animatable = styles.is_animatable
current_rules = styles.get_rules()
set_rule = styles.base.set_rule
for key, value in rules.items():
current = current_rules.get(key)
@@ -204,7 +232,7 @@ class Stylesheet:
if is_animatable(key):
transition = styles.get_transition(key)
if transition is None:
styles.base.set_rule(key, value)
set_rule(key, value)
else:
duration, easing, delay = transition
node.app.animator.animate(
@@ -217,7 +245,13 @@ class Stylesheet:
else:
set_rule(key, value)
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()
@@ -226,6 +260,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 __name__ == "__main__":

View File

@@ -45,8 +45,8 @@ class DOMNode(MessagePump):
self.INLINE_STYLES, repr(self), node=self
)
self.styles = RenderStyles(self, self._css_styles, self._inline_styles)
default_styles = Styles.parse(self.DEFAULT_STYLES, repr(self))
self._default_rules = default_styles.extract_rules((0, 0, 0))
self._default_styles = Styles.parse(self.DEFAULT_STYLES, repr(self))
self._default_rules = self._default_styles.extract_rules((0, 0, 0))
super().__init__()
def __rich_repr__(self) -> rich.repr.Result:
@@ -344,6 +344,7 @@ class DOMNode(MessagePump):
"""
self._classes.symmetric_difference_update(class_names)
self.app.stylesheet.update(self.app, animate=True)
self.app.refresh()
def has_pseudo_class(self, *class_names: str) -> bool:
"""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.style import Style
from . import log
from ._loop import loop_last
from ._types import Lines
from .geometry import Region, Offset, Size
@@ -425,4 +426,5 @@ class Layout(ABC):
update_region = region.intersection(clip)
update_lines = self.render(console, crop=update_region).lines
update = LayoutUpdate(update_lines, update_region)
log(update)
return update

View File

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