diff --git a/examples/basic.py b/examples/basic.py index 6cfa20e5b..2764bb323 100644 --- a/examples/basic.py +++ b/examples/basic.py @@ -9,7 +9,7 @@ class BasicApp(App): App > DockView { layout: dock; - docks: sidebar=left widgets=top; + docks: sidebar=left/1 widgets=top; layers: base panels; } diff --git a/src/textual/css/_style_properties.py b/src/textual/css/_style_properties.py index 77b1fcf94..e24b46a2d 100644 --- a/src/textual/css/_style_properties.py +++ b/src/textual/css/_style_properties.py @@ -14,6 +14,7 @@ from ._error_tools import friendly_list if TYPE_CHECKING: from .styles import Styles + from .styles import DockSpecification class ScalarProperty: @@ -223,14 +224,14 @@ class SpacingProperty: class DocksProperty: def __get__( self, obj: Styles, objtype: type[Styles] | None = None - ) -> tuple[tuple[str, str], ...]: + ) -> tuple[DockSpecification, ...]: return obj._rule_docks or () def __set__( - self, obj: Styles, docks: Iterable[tuple[str, str]] | None - ) -> Iterable[tuple[str, str]] | None: + self, obj: Styles, docks: Iterable[DockSpecification] | None + ) -> Iterable[DockSpecification] | None: if docks is None: - obj._rule_docks = docks + obj._rule_docks = None else: obj._rule_docks = tuple(docks) return docks @@ -258,17 +259,6 @@ class OffsetProperty: return offset -class DockEdgeProperty: - def __get__(self, obj: Styles, objtype: type[Styles] | None = None) -> str: - return obj._rule_dock_edge or "" - - def __set__(self, obj: Styles, edge: str | None) -> str | None: - if isinstance(edge, str) and edge not in VALID_EDGE: - raise ValueError(f"dock edge must be one of {friendly_list(VALID_EDGE)}") - obj._rule_dock_edge = edge - return edge - - class IntegerProperty: def __set_name__(self, owner: Styles, name: str) -> None: self._name = name diff --git a/src/textual/css/_styles_builder.py b/src/textual/css/_styles_builder.py index f7e112f40..eb6c03d41 100644 --- a/src/textual/css/_styles_builder.py +++ b/src/textual/css/_styles_builder.py @@ -12,8 +12,8 @@ from ._error_tools import friendly_list from ..geometry import Offset, Spacing, SpacingDimensions from .model import Declaration from .scalar import Scalar -from .styles import Styles -from .types import Display, Visibility +from .styles import DockSpecification, Styles +from .types import Edge, Display, Visibility from .tokenize import Token @@ -289,20 +289,26 @@ class StylesBuilder: self.styles._rule_dock_group = tokens[0].value if tokens else "" def process_docks(self, name: str, tokens: list[Token]) -> None: - docks: list[tuple[str, str]] = [] + docks: list[DockSpecification] = [] for token in tokens: - if token.name == "token": - docks.append((token.value, "")) - elif token.name == "key_value": - key, group_name = token.value.split("=") - group_name = group_name.strip().lower() - if group_name not in VALID_EDGE: + if token.name == "key_value": + key, edge_name = token.value.split("=") + edge_name = edge_name.strip().lower() + edge_name, _, number = edge_name.partition("/") + z = 0 + if number: + if not number.isdigit(): + self.error( + name, token, f"expected integer after /, found {number!r}" + ) + z = int(number) + if edge_name not in VALID_EDGE: self.error( name, token, - f"edge must be one of 'top', 'right', 'bottom', or 'left'; found {group_name!r}", + f"edge must be one of 'top', 'right', 'bottom', or 'left'; found {edge_name!r}", ) - docks.append((key.strip(), group_name)) + docks.append(DockSpecification(key.strip(), cast(Edge, edge_name), z)) elif token.name == "bar": pass else: @@ -313,14 +319,6 @@ class StylesBuilder: ) self.styles._rule_docks = tuple(docks) - def process_dock_edge(self, name: str, tokens: list[Token]) -> None: - if len(tokens) > 1: - self.error(name, tokens[1], f"unexpected tokens in dock-edge declaration") - try: - self.styles.dock_edge = tokens[0].value if tokens else "" - except StyleValueError as error: - self.error(name, tokens[0], str(error)) - def process_layer(self, name: str, tokens: list[Token]) -> None: if len(tokens) > 1: self.error(name, tokens[1], f"unexpected tokens in dock-edge declaration") diff --git a/src/textual/css/styles.py b/src/textual/css/styles.py index 54f4bdea2..b49413bcb 100644 --- a/src/textual/css/styles.py +++ b/src/textual/css/styles.py @@ -1,7 +1,8 @@ from __future__ import annotations from dataclasses import dataclass, field -from typing import Any, Iterable +import sys +from typing import Any, Iterable, NamedTuple from rich import print from rich.color import Color @@ -22,7 +23,6 @@ from ._style_properties import ( BorderProperty, BoxProperty, ColorProperty, - DockEdgeProperty, DocksProperty, DockGroupProperty, OffsetProperty, @@ -34,7 +34,19 @@ from ._style_properties import ( StyleProperty, StyleFlagsProperty, ) -from .types import Display, Visibility +from .types import Display, Edge, Visibility + + +if sys.version_info >= (3, 8): + from typing import Literal +else: + from typing_extensions import Literal + + +class DockSpecification(NamedTuple): + name: str + edge: Edge + z: int @rich.repr.auto @@ -71,8 +83,7 @@ class Styles: _rule_layout: str | None = None _rule_dock_group: str | None = None - _rule_dock_edge: str | None = None - _rule_docks: tuple[tuple[str, str], ...] | None = None + _rule_docks: tuple[DockSpecification, ...] | None = None _rule_layers: tuple[str, ...] | None = None _rule_layer: str | None = None @@ -111,7 +122,6 @@ class Styles: dock_group = DockGroupProperty() docks = DocksProperty() - dock_edge = DockEdgeProperty() layer = NameProperty() layers = NameListProperty() @@ -241,12 +251,10 @@ class Styles: append_declaration( "docks", " ".join( - (f"{key}={value}" if value else key) - for key, value in self._rule_docks + (f"{key}={value}/{z}" if z else f"{key}={value}") + for key, value, z in self._rule_docks ), ) - if self._rule_dock_edge: - append_declaration("dock-edge", self._rule_dock_edge) if self._rule_layers is not None: append_declaration("layers", " ".join(self.layers)) if self._rule_layer is not None: diff --git a/src/textual/css/tokenize.py b/src/textual/css/tokenize.py index 15e6c13e3..8e78d7317 100644 --- a/src/textual/css/tokenize.py +++ b/src/textual/css/tokenize.py @@ -47,10 +47,9 @@ expect_declaration_content = Expect( percentage=r"\d+\%", scalar=r"\d+\.?\d*(?:fr|%)?", color=r"\#[0-9a-f]{6}|color\([0-9]{1,3}\)|rgb\(\d{1,3}\,\s?\d{1,3}\,\s?\d{1,3}\)", - key_value=r"[a-zA-Z_-][a-zA-Z0-9_-]*=[a-zA-Z_-]+", + key_value=r"[a-zA-Z_-][a-zA-Z0-9_-]*=[0-9a-zA-Z_\-\/]+", token="[a-zA-Z_-]+", string=r"\".*?\"", - bar=r"\|", important=r"\!important", declaration_set_end=r"\}", ) diff --git a/src/textual/css/types.py b/src/textual/css/types.py index 2e1748e74..8443ffd97 100644 --- a/src/textual/css/types.py +++ b/src/textual/css/types.py @@ -12,6 +12,7 @@ else: from typing_extensions import Literal +Edge = Literal["top", "right", "bottom", "left"] Visibility = Literal["visible", "hidden", "initial", "inherit"] Display = Literal["block", "none"] EdgeStyle = Tuple[str, Style] diff --git a/src/textual/dom.py b/src/textual/dom.py index b0597d81d..46be2bcba 100644 --- a/src/textual/dom.py +++ b/src/textual/dom.py @@ -105,14 +105,15 @@ class DOMNode(MessagePump): indexes: list[int] = [] append = indexes.append node = self + layer: str = node.styles.layer while node._parent: - styles = node.styles parent_styles = node.parent.styles - append( - parent_styles.layers.index(styles.layer) - if styles.layer in parent_styles.layers - else 0 - ) + layer = layer or node.styles.layer + if layer in parent_styles.layers: + append(parent_styles.layers.index(layer)) + layer = "" + else: + append(0) node = node.parent return tuple(reversed(indexes)) diff --git a/src/textual/layout.py b/src/textual/layout.py index 9fc827aaf..026290d94 100644 --- a/src/textual/layout.py +++ b/src/textual/layout.py @@ -53,7 +53,7 @@ class WidgetPlacement(NamedTuple): region: Region widget: Widget | None = None - order: tuple[int, ...] = () + order: int = 0 @rich.repr.auto diff --git a/src/textual/layout_map.py b/src/textual/layout_map.py index 916c6a116..dcf93cf2a 100644 --- a/src/textual/layout_map.py +++ b/src/textual/layout_map.py @@ -1,12 +1,11 @@ from __future__ import annotations -from rich.console import Console from typing import ItemsView, KeysView, ValuesView, NamedTuple from . import log from .geometry import Region, Size - +from operator import attrgetter from .widget import Widget @@ -56,14 +55,16 @@ class LayoutMap: total_region = region.size.region sub_clip = clip.intersection(region) - arrangement = view.get_arrangement(region.size, scroll) - for sub_region, sub_widget, sub_order in arrangement: + arrangement = sorted( + view.get_arrangement(region.size, scroll), key=attrgetter("order") + ) + for sub_region, sub_widget, z in arrangement: total_region = total_region.union(sub_region) if sub_widget is not None: self.add_widget( sub_widget, sub_region + region.origin - scroll, - sub_order, + sub_widget.z, sub_clip, ) view.virtual_size = total_region.size diff --git a/src/textual/layouts/dock.py b/src/textual/layouts/dock.py index 4dd981cd2..c0b4a3a47 100644 --- a/src/textual/layouts/dock.py +++ b/src/textual/layouts/dock.py @@ -3,7 +3,7 @@ from __future__ import annotations import sys from collections import defaultdict from dataclasses import dataclass -from typing import Iterable, TYPE_CHECKING, Sequence +from typing import Iterable, TYPE_CHECKING, NamedTuple, Sequence from .. import log @@ -54,8 +54,8 @@ class DockLayout(Layout): groups[child.styles.dock_group].append(child) docks: list[Dock] = [] append_dock = docks.append - for name, edge in view.styles.docks: - append_dock(Dock(edge, groups[name], 0)) + for name, edge, z in view.styles.docks: + append_dock(Dock(edge, groups[name], z)) return docks def get_widgets(self, view: View) -> Iterable[DOMNode]: @@ -90,7 +90,7 @@ class DockLayout(Layout): ) ) - for index, dock in enumerate(docks): + for dock in docks: dock_options = [make_dock_options(widget) for widget in dock.widgets] region = layers[dock.z] @@ -98,7 +98,6 @@ class DockLayout(Layout): # No space left continue - order = (dock.z, index) x, y, width, height = region if dock.edge == "top": @@ -114,7 +113,7 @@ class DockLayout(Layout): break total += layout_size yield WidgetPlacement( - Region(x, render_y, width, layout_size), widget, order + Region(x, render_y, width, layout_size), widget, dock.z ) render_y += layout_size remaining = max(0, remaining - layout_size) @@ -135,7 +134,7 @@ class DockLayout(Layout): yield WidgetPlacement( Region(x, render_y - layout_size, width, layout_size), widget, - order, + dock.z, ) render_y -= layout_size remaining = max(0, remaining - layout_size) @@ -154,9 +153,7 @@ class DockLayout(Layout): break total += layout_size yield WidgetPlacement( - Region(render_x, y, layout_size, height), - widget, - order, + Region(render_x, y, layout_size, height), widget, dock.z ) render_x += layout_size remaining = max(0, remaining - layout_size) @@ -177,7 +174,7 @@ class DockLayout(Layout): yield WidgetPlacement( Region(render_x - layout_size, y, layout_size, height), widget, - order, + dock.z, ) render_x -= layout_size remaining = max(0, remaining - layout_size)