From 5773e39845839146bef5d1a94c13da2661736ee0 Mon Sep 17 00:00:00 2001 From: Darren Burns Date: Fri, 21 Jan 2022 12:33:35 +0000 Subject: [PATCH] Move LayoutProperty into styles, add some comments --- examples/basic.css | 1 + src/textual/app.py | 2 +- src/textual/css/_style_properties.py | 17 +++++++++++++++++ src/textual/css/styles.py | 7 ++++++- src/textual/css/stylesheet.py | 12 ++++++++++-- src/textual/view.py | 19 ++----------------- 6 files changed, 37 insertions(+), 21 deletions(-) diff --git a/examples/basic.css b/examples/basic.css index efafe797e..279f6e520 100644 --- a/examples/basic.css +++ b/examples/basic.css @@ -1,6 +1,7 @@ /* CSS file for basic.py */ App > View { + layout: dock; docks: side=left/1; text: on #20639b; } diff --git a/src/textual/app.py b/src/textual/app.py index 0e6dee254..6f181cf89 100644 --- a/src/textual/app.py +++ b/src/textual/app.py @@ -510,7 +510,7 @@ class App(DOMNode): # Handle input events that haven't been forwarded # If the event has been forwarded it may have bubbled up back to the App if isinstance(event, events.Mount): - view = View() + view = View(id="_root") self.register(self, view) await self.push_view(view) await super().on_event(event) diff --git a/src/textual/css/_style_properties.py b/src/textual/css/_style_properties.py index 3ed32ee8b..fdb9b31a9 100644 --- a/src/textual/css/_style_properties.py +++ b/src/textual/css/_style_properties.py @@ -28,6 +28,7 @@ from .constants import NULL_SPACING from .errors import StyleTypeError, StyleValueError from .transition import Transition from ._error_tools import friendly_list +from ..layouts.factory import get_layout if TYPE_CHECKING: from .styles import Styles @@ -290,6 +291,22 @@ class DockProperty: return spacing +class LayoutProperty: + def __set_name__(self, owner: Styles, name: str) -> None: + self._internal_name = f"_rule_{name}" + + def __get__(self, obj: Styles, objtype: type[Styles] | None = None) -> str: + return getattr(obj, self._internal_name) or "" + + def __set__(self, obj: Styles, layout: str | Styles): + obj.refresh(True) + if isinstance(layout, str): + new_layout = get_layout(layout) + else: + new_layout = layout + self._layout = new_layout + + class OffsetProperty: def __set_name__(self, owner: Styles, name: str) -> None: self._internal_name = f"_rule_{name}" diff --git a/src/textual/css/styles.py b/src/textual/css/styles.py index 533167347..09510ae78 100644 --- a/src/textual/css/styles.py +++ b/src/textual/css/styles.py @@ -42,6 +42,7 @@ from ._style_properties import ( StyleProperty, StyleFlagsProperty, TransitionsProperty, + LayoutProperty, ) from .types import Display, Edge, Visibility @@ -110,7 +111,7 @@ class Styles: display = StringProperty(VALID_DISPLAY, "block") visibility = StringProperty(VALID_VISIBILITY, "visible") - layout = StringProperty(VALID_LAYOUT, "dock") + layout = LayoutProperty() text = StyleProperty() text_color = ColorProperty() @@ -279,6 +280,10 @@ class Styles: ) else: setattr(styles, f"_rule_{key}", value) + + if self.node.id == "_root": + log("_root.styles.layout =", self.node.styles.layout) + if self.node is not None: self.node.on_style_change() diff --git a/src/textual/css/stylesheet.py b/src/textual/css/stylesheet.py index 05704b782..802c86fbb 100644 --- a/src/textual/css/stylesheet.py +++ b/src/textual/css/stylesheet.py @@ -108,18 +108,24 @@ class Stylesheet: yield selector_set.specificity def apply(self, node: DOMNode) -> None: + # Dictionary of rule attribute names e.g. "text_background" to list of tuples. + # The tuples contain the rule specificity, and the value for that rule. + # We can use this to determine, for a given rule, whether we should apply it + # or not by examining the specificity. If we have two rules for the + # same attribute, then we can choose the most specific rule and use that. rule_attributes: dict[str, list[tuple[Specificity4, object]]] rule_attributes = defaultdict(list) _check_rule = self._check_rule + # TODO: The line below breaks inline styles and animations node.styles.reset() - # Get the default node CSS rules + # Collect default node CSS rules for key, default_specificity, value in node._default_rules: rule_attributes[key].append((default_specificity, value)) - # Apply styles on top of the default node CSS rules + # Collect the rules defined in the stylesheet for rule in self.rules: for specificity in _check_rule(rule, node): for key, rule_specificity, value in rule.styles.extract_rules( @@ -127,12 +133,14 @@ class Stylesheet: ): rule_attributes[key].append((rule_specificity, value)) + # For each rule declared for this node, keep only the most specific one get_first_item = itemgetter(0) node_rules = [ (name, max(specificity_rules, key=get_first_item)[1]) for name, specificity_rules in rule_attributes.items() ] + log(node.id, node_rules) node.styles.apply_rules(node_rules) def update(self, root: DOMNode) -> None: diff --git a/src/textual/view.py b/src/textual/view.py index d6cd03784..f4f17e92f 100644 --- a/src/textual/view.py +++ b/src/textual/view.py @@ -16,26 +16,13 @@ from .layouts.factory import get_layout from .geometry import Size, Offset, Region from .reactive import Reactive, watch -from .widget import Widget, Widget +from .widget import Widget if TYPE_CHECKING: from .app import App -class LayoutProperty: - def __get__(self, obj: View, objtype: type[View] | None = None) -> str: - return obj._layout.name - - def __set__(self, obj: View, layout: str | Layout) -> str: - if isinstance(layout, str): - new_layout = get_layout(layout) - else: - new_layout = layout - self._layout = new_layout - return self._layout.name - - @rich.repr.auto class View(Widget): @@ -45,7 +32,7 @@ class View(Widget): """ def __init__(self, name: str | None = None, id: str | None = None) -> None: - + # TODO: Get rid of this, replace usages with layout from Styles object from .layouts.dock import DockLayout self._layout: Layout = DockLayout() @@ -69,8 +56,6 @@ class View(Widget): cls.layout_factory = layout super().__init_subclass__(**kwargs) - layout = LayoutProperty() - background: Reactive[str] = Reactive("") scroll_x: Reactive[int] = Reactive(0) scroll_y: Reactive[int] = Reactive(0)