mirror of
https://github.com/Textualize/textual.git
synced 2025-10-17 02:38:12 +03:00
reset css plan
This commit is contained in:
@@ -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")
|
||||
@@ -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;
|
||||
}
|
||||
@@ -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.
|
||||
|
||||
@@ -5,3 +5,7 @@ App > View {
|
||||
Widget {
|
||||
text: on blue;
|
||||
}
|
||||
|
||||
Widget.-highlight {
|
||||
outline: heavy red;
|
||||
}
|
||||
|
||||
@@ -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")
|
||||
|
||||
@@ -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()
|
||||
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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__":
|
||||
|
||||
@@ -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)"""
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
Reference in New Issue
Block a user