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