mirror of
https://github.com/Textualize/textual.git
synced 2025-10-17 02:38:12 +03:00
default styles
This commit is contained in:
@@ -1,15 +1,11 @@
|
||||
/* CSS file for basic.py */
|
||||
|
||||
App > View {
|
||||
layout: dock;
|
||||
docks: side=left/1;
|
||||
text: on #20639b;
|
||||
}
|
||||
|
||||
Widget:hover {
|
||||
outline: heavy;
|
||||
text: bold !important;
|
||||
}
|
||||
|
||||
#sidebar {
|
||||
text: #09312e on #3caea3;
|
||||
dock: side;
|
||||
@@ -29,10 +25,6 @@ Widget:hover {
|
||||
border: hkey;
|
||||
}
|
||||
|
||||
#header.-visible {
|
||||
visibility: hidden;
|
||||
}
|
||||
|
||||
#content {
|
||||
text: white on #20639b;
|
||||
border-bottom: hkey #0f2b41;
|
||||
|
||||
@@ -8,7 +8,6 @@ class BasicApp(App):
|
||||
def on_load(self):
|
||||
"""Bind keys here."""
|
||||
self.bind("tab", "toggle_class('#sidebar', '-active')")
|
||||
self.bind("a", "toggle_class('#header', '-visible')")
|
||||
|
||||
def on_mount(self):
|
||||
"""Build layout here."""
|
||||
|
||||
@@ -16,18 +16,11 @@ class BasicApp(App):
|
||||
sidebar=Widget(),
|
||||
)
|
||||
|
||||
async def on_key(self, event: events.Key) -> None:
|
||||
await self.dispatch_key(event)
|
||||
def key_a(self) -> None:
|
||||
self.query("#footer").set_styles(text="on magenta")
|
||||
|
||||
def key_a(self) -> bool | None:
|
||||
self.query("#footer").set_styles(text="on magenta").refresh()
|
||||
|
||||
self.log(self["#footer"].styles.css)
|
||||
self.bell()
|
||||
self.refresh()
|
||||
|
||||
def key_b(self) -> bool | None:
|
||||
self["#content"].set_styles("text: on magenta")
|
||||
def key_b(self) -> None:
|
||||
self["#footer"].set_styles("text: on green")
|
||||
|
||||
|
||||
BasicApp.run(css_file="local_styles.css", log="textual.log")
|
||||
|
||||
@@ -130,7 +130,10 @@ class Animator:
|
||||
self._timer.start()
|
||||
|
||||
async def stop(self) -> None:
|
||||
await self._timer.stop()
|
||||
try:
|
||||
await self._timer.stop()
|
||||
except asyncio.CancelledError:
|
||||
pass
|
||||
|
||||
def bind(self, obj: object) -> BoundAnimator:
|
||||
return BoundAnimator(self, obj)
|
||||
|
||||
@@ -5,6 +5,7 @@ from asyncio import (
|
||||
get_event_loop,
|
||||
CancelledError,
|
||||
Event,
|
||||
shield,
|
||||
sleep,
|
||||
Task,
|
||||
)
|
||||
|
||||
@@ -135,7 +135,6 @@ class DOMQuery:
|
||||
return self
|
||||
|
||||
def refresh(self, repaint: bool = True, layout: bool = False) -> DOMQuery:
|
||||
|
||||
"""Refresh matched nodes.
|
||||
|
||||
Args:
|
||||
|
||||
@@ -2,6 +2,7 @@ from __future__ import annotations
|
||||
|
||||
from dataclasses import dataclass, field
|
||||
from functools import lru_cache
|
||||
from operator import attrgetter
|
||||
from typing import Any, Iterable, NamedTuple, TYPE_CHECKING
|
||||
|
||||
import rich.repr
|
||||
@@ -49,6 +50,22 @@ if TYPE_CHECKING:
|
||||
from ..dom import DOMNode
|
||||
|
||||
|
||||
_text_getter = attrgetter(
|
||||
"_rule_text_color", "_rule_text_background", "_rule_text_style"
|
||||
)
|
||||
|
||||
_border_getter = attrgetter(
|
||||
"_rule_border_top", "_rule_border_right", "_rule_border_bottom", "_rule_border_left"
|
||||
)
|
||||
|
||||
_outline_getter = attrgetter(
|
||||
"_rule_outline_top",
|
||||
"_rule_outline_right",
|
||||
"_rule_outline_bottom",
|
||||
"_rule_outline_left",
|
||||
)
|
||||
|
||||
|
||||
class DockGroup(NamedTuple):
|
||||
name: str
|
||||
edge: Edge
|
||||
@@ -103,7 +120,15 @@ class Styles:
|
||||
|
||||
def has_rule(self, rule: str) -> bool:
|
||||
"""Check if a rule has been set."""
|
||||
return getattr(self, f"_rule_{rule}") != None
|
||||
if rule in RULE_NAMES and getattr(self, f"_rule_{rule}") is not None:
|
||||
return True
|
||||
if rule == "text":
|
||||
return not all(rule is None for rule in _text_getter(self))
|
||||
if rule == "border" and any(self.border):
|
||||
return not all(rule is None for rule in _border_getter(self))
|
||||
if rule == "outline" and any(self.outline):
|
||||
return not all(rule is None for rule in _outline_getter(self))
|
||||
return False
|
||||
|
||||
display = StringEnumProperty(VALID_DISPLAY, "block")
|
||||
visibility = StringEnumProperty(VALID_VISIBILITY, "visible")
|
||||
@@ -164,10 +189,11 @@ class Styles:
|
||||
|
||||
@classmethod
|
||||
@lru_cache(maxsize=1024)
|
||||
def parse(cls, css: str, path: str) -> Styles:
|
||||
def parse(cls, css: str, path: str, *, node: DOMNode = None) -> Styles:
|
||||
from .parse import parse_declarations
|
||||
|
||||
styles = parse_declarations(css, path)
|
||||
styles.node = node
|
||||
return styles
|
||||
|
||||
def __textual_animation__(
|
||||
@@ -345,11 +371,11 @@ class Styles:
|
||||
_type, style = self._rule_outline_left
|
||||
append_declaration("outline-left", f"{_type} {style}")
|
||||
|
||||
if self.offset:
|
||||
if self._rule_offset is not None:
|
||||
x, y = self.offset
|
||||
append_declaration("offset", f"{x} {y}")
|
||||
if self._rule_dock:
|
||||
append_declaration("dock-group", self._rule_dock)
|
||||
append_declaration("dock", self._rule_dock)
|
||||
if self._rule_docks:
|
||||
append_declaration(
|
||||
"docks",
|
||||
@@ -363,6 +389,7 @@ class Styles:
|
||||
if self._rule_layer is not None:
|
||||
append_declaration("layer", self.layer)
|
||||
if self._rule_layout is not None:
|
||||
assert self.layout is not None
|
||||
append_declaration("layout", self.layout.name)
|
||||
if self._rule_text_color or self._rule_text_background or self._rule_text_style:
|
||||
append_declaration("text", str(self.text))
|
||||
@@ -415,10 +442,9 @@ class StyleViewProperty(Generic[GetType, SetType]):
|
||||
def __get__(
|
||||
self, obj: StylesView, objtype: type[StylesView] | None = None
|
||||
) -> GetType:
|
||||
styles_value = getattr(obj._inline_styles, self._internal_name, None)
|
||||
if styles_value is None:
|
||||
return getattr(obj._base_styles, self._name)
|
||||
return styles_value
|
||||
if obj._inline_styles.has_rule(self._name):
|
||||
return getattr(obj._inline_styles, self._name)
|
||||
return getattr(obj._base_styles, self._name)
|
||||
|
||||
|
||||
@rich.repr.auto
|
||||
@@ -448,6 +474,17 @@ class StylesView:
|
||||
"""Reset the inline styles."""
|
||||
self._inline_styles.reset()
|
||||
|
||||
def refresh(self, layout: bool = False) -> None:
|
||||
self._inline_styles.refresh(layout=layout)
|
||||
|
||||
def merge(self, other: Styles) -> None:
|
||||
"""Merge values from another Styles.
|
||||
|
||||
Args:
|
||||
other (Styles): A Styles object.
|
||||
"""
|
||||
self._inline_styles.merge(other)
|
||||
|
||||
def check_refresh(self) -> tuple[bool, bool]:
|
||||
"""Check if the Styles must be refreshed.
|
||||
|
||||
@@ -475,10 +512,11 @@ class StylesView:
|
||||
display: StyleViewProperty[str, str | None] = StyleViewProperty()
|
||||
visibility: StyleViewProperty[str, str | None] = StyleViewProperty()
|
||||
layout: StyleViewProperty[Layout | None, str | Layout] = StyleViewProperty()
|
||||
|
||||
text: StyleViewProperty[Style, Style | str | None] = StyleViewProperty()
|
||||
color: StyleViewProperty[Color, Color | str | None] = StyleViewProperty()
|
||||
background: StyleViewProperty[Color, Color | str | None] = StyleViewProperty()
|
||||
style: StyleViewProperty[Style, str | None] = StyleViewProperty()
|
||||
text_color: StyleViewProperty[Color, Color | str | None] = StyleViewProperty()
|
||||
text_background: StyleViewProperty[Color, Color | str | None] = StyleViewProperty()
|
||||
text_style: StyleViewProperty[Style, str | None] = StyleViewProperty()
|
||||
|
||||
padding: StyleViewProperty[Spacing, SpacingDimensions] = StyleViewProperty()
|
||||
margin: StyleViewProperty[Spacing, SpacingDimensions] = StyleViewProperty()
|
||||
@@ -537,6 +575,8 @@ class StylesView:
|
||||
tuple[str, ...], str | tuple[str] | None
|
||||
] = StyleViewProperty()
|
||||
|
||||
transitions: StyleViewProperty[dict[str, Transition], None] = StyleViewProperty()
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
styles = Styles()
|
||||
|
||||
@@ -135,8 +135,8 @@ class Stylesheet:
|
||||
node._css_styles.reset()
|
||||
|
||||
# Collect default node CSS rules
|
||||
# for key, default_specificity, value in node._default_rules:
|
||||
# rule_attributes[key].append((default_specificity, value))
|
||||
for key, default_specificity, value in node._default_rules:
|
||||
rule_attributes[key].append((default_specificity, value))
|
||||
|
||||
# Collect the rules defined in the stylesheet
|
||||
for rule in self.rules:
|
||||
|
||||
@@ -32,6 +32,7 @@ class DOMNode(MessagePump):
|
||||
|
||||
"""
|
||||
|
||||
DEFAULT_STYLES = ""
|
||||
STYLES = ""
|
||||
|
||||
def __init__(self, name: str | None = None, id: str | None = None) -> None:
|
||||
@@ -40,9 +41,11 @@ class DOMNode(MessagePump):
|
||||
self._classes: set[str] = set()
|
||||
self.children = NodeList()
|
||||
self._css_styles: Styles = Styles(self)
|
||||
self._inline_styles: Styles = Styles.parse(self.STYLES, repr(self))
|
||||
self._inline_styles: Styles = Styles.parse(self.STYLES, repr(self), node=self)
|
||||
self.styles = StylesView(self._css_styles, self._inline_styles)
|
||||
super().__init__()
|
||||
self.default_styles = Styles.parse(self.DEFAULT_STYLES, repr(self))
|
||||
self._default_rules = self.default_styles.extract_rules((0, 0, 0))
|
||||
|
||||
def __rich_repr__(self) -> rich.repr.Result:
|
||||
yield "name", self._name, None
|
||||
@@ -301,7 +304,9 @@ class DOMNode(MessagePump):
|
||||
)
|
||||
apply_css = f"{css or ''}\n{kwarg_css}\n"
|
||||
new_styles = parse_declarations(apply_css, f"<custom styles for ${self!r}>")
|
||||
self._inline_styles.merge(new_styles)
|
||||
self.styles.merge(new_styles)
|
||||
self.log(repr(self.styles))
|
||||
self.log(self._inline_styles, self._css_styles, self.styles.text)
|
||||
self.refresh()
|
||||
|
||||
def has_class(self, *class_names: str) -> bool:
|
||||
|
||||
@@ -36,6 +36,7 @@ class Dock(NamedTuple):
|
||||
|
||||
|
||||
class DockLayout(Layout):
|
||||
"""Dock Widgets to edge of screen."""
|
||||
|
||||
name = "dock"
|
||||
|
||||
|
||||
@@ -311,7 +311,7 @@ class MessagePump:
|
||||
else:
|
||||
return False
|
||||
|
||||
async def dispatch_key(self, event: events.Key) -> None:
|
||||
async def dispatch_key(self, event: events.Key) -> bool:
|
||||
"""Dispatch a key event to method.
|
||||
|
||||
This method will call the method named 'key_<event.key>' if it exists. This key method
|
||||
@@ -323,9 +323,9 @@ class MessagePump:
|
||||
|
||||
key_method = getattr(self, f"key_{event.key}", None)
|
||||
if key_method is not None:
|
||||
key_result = await invoke(key_method, event)
|
||||
if key_result is None or key_result:
|
||||
event.prevent_default()
|
||||
await invoke(key_method, event)
|
||||
return True
|
||||
return False
|
||||
|
||||
async def on_timer(self, event: events.Timer) -> None:
|
||||
event.prevent_default()
|
||||
|
||||
@@ -15,9 +15,8 @@ from .widget import Widget
|
||||
|
||||
@rich.repr.auto
|
||||
class View(Widget):
|
||||
STYLES = """
|
||||
DEFAULT_STYLES = """
|
||||
layout: dock;
|
||||
docks: main=top;
|
||||
"""
|
||||
|
||||
def __init__(self, name: str | None = None, id: str | None = None) -> None:
|
||||
@@ -55,9 +54,7 @@ class View(Widget):
|
||||
|
||||
Returns: The Layout associated with this view
|
||||
"""
|
||||
# self.log("I", self._inline_styles)
|
||||
# self.log("C", self._css_styles)
|
||||
# self.log("S", self.styles)
|
||||
|
||||
assert self.styles.layout
|
||||
return self.styles.layout
|
||||
|
||||
|
||||
@@ -58,7 +58,7 @@ class Widget(DOMNode):
|
||||
_counts: ClassVar[dict[str, int]] = {}
|
||||
can_focus: bool = False
|
||||
|
||||
STYLES = """
|
||||
DEFAULT_STYLES = """
|
||||
dock: _default;
|
||||
"""
|
||||
|
||||
@@ -360,3 +360,7 @@ class Widget(DOMNode):
|
||||
async def on_leave(self, event: events.Leave) -> None:
|
||||
self._mouse_over = False
|
||||
self.app.update_styles()
|
||||
|
||||
async def on_key(self, event: events.Key) -> None:
|
||||
if await self.dispatch_key(event):
|
||||
event.prevent_default()
|
||||
|
||||
Reference in New Issue
Block a user