From 45d1e1c7e1a473302eedff4c8b3dde5d64cbbf9c Mon Sep 17 00:00:00 2001 From: Will McGugan Date: Tue, 13 Sep 2022 14:27:29 +0100 Subject: [PATCH] border improvement --- offset.css | 21 ++++++++++++++++++++ offset.py | 17 +++++++++++++++++ sandbox/will/offset.css | 21 ++++++++++++++++++++ sandbox/will/offset.py | 17 +++++++++++++++++ src/textual/_border.py | 39 +++++++++++++++++++++++--------------- src/textual/_compositor.py | 18 ++++++++++++------ src/textual/design.py | 6 +++--- 7 files changed, 115 insertions(+), 24 deletions(-) create mode 100644 offset.css create mode 100644 offset.py create mode 100644 sandbox/will/offset.css create mode 100644 sandbox/will/offset.py diff --git a/offset.css b/offset.css new file mode 100644 index 000000000..97b98a271 --- /dev/null +++ b/offset.css @@ -0,0 +1,21 @@ +Screen { + layout: center; +} + +#parent { + width: 32; + height: 8; + background: $panel; +} + +#tag { + color: $text; + background: $success; + padding: 1 2; + width: auto; + /* offset: -8 -4; */ +} + +#child { + background: red; +} diff --git a/offset.py b/offset.py new file mode 100644 index 000000000..d4a9dfd20 --- /dev/null +++ b/offset.py @@ -0,0 +1,17 @@ +from textual import layout +from textual.app import App, ComposeResult +from textual.widgets import Static + + +class OffsetExample(App): + def compose(self) -> ComposeResult: + yield layout.Vertical( + Static("Child", id="child"), + id="parent" + ) + yield Static("Tag", id="tag") + + +app = OffsetExample(css_path="offset.css") +if __name__ == "__main__": + app.run() diff --git a/sandbox/will/offset.css b/sandbox/will/offset.css new file mode 100644 index 000000000..ddcb18e49 --- /dev/null +++ b/sandbox/will/offset.css @@ -0,0 +1,21 @@ +Screen { + layout: center; +} + +#parent { + width: 32; + height: 8; + background: $panel; +} + +#tag { + color: $text; + background: $success; + padding: 2 4; + width: auto; + offset: -8 -4; +} + +#child { + background: red; +} diff --git a/sandbox/will/offset.py b/sandbox/will/offset.py new file mode 100644 index 000000000..d4a9dfd20 --- /dev/null +++ b/sandbox/will/offset.py @@ -0,0 +1,17 @@ +from textual import layout +from textual.app import App, ComposeResult +from textual.widgets import Static + + +class OffsetExample(App): + def compose(self) -> ComposeResult: + yield layout.Vertical( + Static("Child", id="child"), + id="parent" + ) + yield Static("Tag", id="tag") + + +app = OffsetExample(css_path="offset.css") +if __name__ == "__main__": + app.run() diff --git a/src/textual/_border.py b/src/textual/_border.py index 43d70f11d..7b1e7a9d5 100644 --- a/src/textual/_border.py +++ b/src/textual/_border.py @@ -39,8 +39,8 @@ BORDER_CHARS: dict[EdgeType, tuple[str, str, str]] = { "outer": ("▛▀▜", "▌ ▐", "▙▄▟"), "hkey": ("▔▔▔", " ", "▁▁▁"), "vkey": ("▏ ▕", "▏ ▕", "▏ ▕"), - "tall": ("▕▔▏", "▕ ▏", "▕▁▏"), - "wide": ("▁▁▁", "▏ ▕", "▔▔▔"), + "tall": ("▊▔▎", "▊ ▎", "▊▁▎"), + "wide": ("▁▁▁", "▎ ▋", "▔▔▔"), } # Some of the borders are on the widget background and some are on the background of the parent @@ -62,8 +62,8 @@ BORDER_LOCATIONS: dict[ "outer": ((0, 0, 0), (0, 0, 0), (0, 0, 0)), "hkey": ((0, 0, 0), (0, 0, 0), (0, 0, 0)), "vkey": ((0, 0, 0), (0, 0, 0), (0, 0, 0)), - "tall": ((1, 0, 1), (1, 0, 1), (1, 0, 1)), - "wide": ((1, 1, 1), (0, 1, 0), (1, 1, 1)), + "tall": ((2, 0, 1), (2, 0, 1), (2, 0, 1)), + "wide": ((1, 1, 1), (3, 1, 4), (1, 1, 1)), } INVISIBLE_EDGE_TYPES = cast("frozenset[EdgeType]", frozenset(("", "none", "hidden"))) @@ -81,7 +81,10 @@ Borders: TypeAlias = Tuple[EdgeStyle, EdgeStyle, EdgeStyle, EdgeStyle] @lru_cache(maxsize=1024) def get_box( - name: EdgeType, inner_style: Style, outer_style: Style, style: Style + name: EdgeType, + inner_style: Style, + outer_style: Style, + style: Style, ) -> BoxSegments: """Get segments used to render a box. @@ -107,23 +110,29 @@ def get_box( (lbottom1, lbottom2, lbottom3), ) = BORDER_LOCATIONS[name] - styles = (inner_style, outer_style) + styles: tuple[Style, ...] = (inner_style + style, outer_style + style) + + styles += ( + Style.from_color(styles[1].bgcolor, styles[0].color), + Style.from_color(styles[0].color, styles[0].bgcolor), + Style.from_color(styles[0].bgcolor, styles[1].color), + ) return ( ( - _Segment(top1, styles[ltop1] + style), - _Segment(top2, styles[ltop2] + style), - _Segment(top3, styles[ltop3] + style), + _Segment(top1, styles[ltop1]), + _Segment(top2, styles[ltop2]), + _Segment(top3, styles[ltop3]), ), ( - _Segment(mid1, styles[lmid1] + style), - _Segment(mid2, styles[lmid2] + style), - _Segment(mid3, styles[lmid3] + style), + _Segment(mid1, styles[lmid1]), + _Segment(mid2, styles[lmid2]), + _Segment(mid3, styles[lmid3]), ), ( - _Segment(bottom1, styles[lbottom1] + style), - _Segment(bottom2, styles[lbottom2] + style), - _Segment(bottom3, styles[lbottom3] + style), + _Segment(bottom1, styles[lbottom1]), + _Segment(bottom2, styles[lbottom2]), + _Segment(bottom3, styles[lbottom3]), ), ) diff --git a/src/textual/_compositor.py b/src/textual/_compositor.py index 0eeac1fe0..5632f50b5 100644 --- a/src/textual/_compositor.py +++ b/src/textual/_compositor.py @@ -14,7 +14,7 @@ without having to render the entire screen. from __future__ import annotations from itertools import chain -from operator import attrgetter, itemgetter +from operator import itemgetter import sys from typing import Callable, cast, Iterator, Iterable, NamedTuple, TYPE_CHECKING @@ -26,7 +26,7 @@ from rich.segment import Segment from rich.style import Style from . import errors -from .geometry import Region, Offset, Size, Spacing +from .geometry import Region, Offset, Size from ._cells import cell_len from ._profile import timer @@ -55,7 +55,7 @@ class MapGeometry(NamedTuple): """Defines the absolute location of a Widget.""" region: Region # The (screen) region occupied by the widget - order: tuple[int, ...] # A tuple of ints defining the painting order + order: tuple[tuple[int, ...], ...] # A tuple of ints defining the painting order clip: Region # A region to clip the widget by (if a Widget is within a container) virtual_size: Size # The virtual size (scrollable region) of a widget if it is a container container_size: Size # The container size (area not occupied by scrollbars) @@ -344,12 +344,14 @@ class Compositor: map: CompositorMap = {} widgets: set[Widget] = set() + layer_order: int = 0 def add_widget( widget: Widget, virtual_region: Region, region: Region, - order: tuple[int, ...], + order: tuple[tuple[int, ...], ...], + layer_order: int, clip: Region, ) -> None: """Called recursively to place a widget and its children in the map. @@ -413,15 +415,19 @@ class Compositor: ) widget_region = sub_region + placement_scroll_offset - widget_order = order + (get_layer_index(sub_widget.layer, 0), z) + widget_order = order + ( + (get_layer_index(sub_widget.layer, 0), z, layer_order), + ) add_widget( sub_widget, sub_region, widget_region, widget_order, + layer_order, sub_clip, ) + layer_order -= 1 # Add any scrollbars for chrome_widget, chrome_region in widget._arrange_scrollbars( @@ -457,7 +463,7 @@ class Compositor: ) # Add top level (root) widget - add_widget(root, size.region, size.region, (0,), size.region) + add_widget(root, size.region, size.region, ((0,),), layer_order, size.region) return map, widgets @property diff --git a/src/textual/design.py b/src/textual/design.py index 5de30f4dd..cd9747534 100644 --- a/src/textual/design.py +++ b/src/textual/design.py @@ -124,13 +124,13 @@ class ColorSystem: background = self.background or Color.parse(DEFAULT_LIGHT_BACKGROUND) surface = self.surface or Color.parse(DEFAULT_LIGHT_SURFACE) + boost = self.boost or background.get_contrast_text(1.0).with_alpha(0.07) + if self.panel is None: - panel = surface.blend(primary, luminosity_spread) + panel = surface.blend(primary, luminosity_spread) + boost else: panel = self.panel - boost = self.boost or background.get_contrast_text(1.0).with_alpha(0.07) - colors: dict[str, str] = {} def luminosity_range(spread) -> Iterable[tuple[str, float]]: