From 45d1e1c7e1a473302eedff4c8b3dde5d64cbbf9c Mon Sep 17 00:00:00 2001 From: Will McGugan Date: Tue, 13 Sep 2022 14:27:29 +0100 Subject: [PATCH 1/7] 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]]: From e27f9f97c7d560a8e30a5dd7ce4b9b3d21a8ce43 Mon Sep 17 00:00:00 2001 From: Will McGugan Date: Tue, 13 Sep 2022 14:33:06 +0100 Subject: [PATCH 2/7] optimize box --- src/textual/_border.py | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/src/textual/_border.py b/src/textual/_border.py index 7b1e7a9d5..dabb6581f 100644 --- a/src/textual/_border.py +++ b/src/textual/_border.py @@ -111,11 +111,13 @@ def get_box( ) = BORDER_LOCATIONS[name] styles: tuple[Style, ...] = (inner_style + style, outer_style + style) + inner, outer = styles + from_color = Style.from_color 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), + from_color(outer.bgcolor, inner.color), + from_color(inner.color, inner.bgcolor), + from_color(inner.bgcolor, outer.color), ) return ( From 16f75a42702a959beeb5ed5e140b09ff3cdb9fdb Mon Sep 17 00:00:00 2001 From: Will McGugan Date: Tue, 13 Sep 2022 14:41:04 +0100 Subject: [PATCH 3/7] boost dark --- src/textual/design.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/textual/design.py b/src/textual/design.py index cd9747534..4100fa25f 100644 --- a/src/textual/design.py +++ b/src/textual/design.py @@ -127,7 +127,9 @@ class ColorSystem: 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) + boost + panel = surface.blend(primary, luminosity_spread) + if dark: + panel += boost else: panel = self.panel From 57ffe5b01417d15d16ed3bc4981920c1df6e4ffa Mon Sep 17 00:00:00 2001 From: Will McGugan Date: Tue, 13 Sep 2022 14:47:11 +0100 Subject: [PATCH 4/7] remove from integration test --- tests/test_integration_layout.py | 2 -- 1 file changed, 2 deletions(-) diff --git a/tests/test_integration_layout.py b/tests/test_integration_layout.py index 46106dd5f..c95847ab0 100644 --- a/tests/test_integration_layout.py +++ b/tests/test_integration_layout.py @@ -189,8 +189,6 @@ async def test_composition_of_vertical_container_with_children( "outer", "hkey", "vkey", - "tall", - "wide", ] ], ), From 7c95a0e75a0aa2ff535f85d4a38ce034ae528c13 Mon Sep 17 00:00:00 2001 From: Will McGugan Date: Tue, 13 Sep 2022 14:54:55 +0100 Subject: [PATCH 5/7] simplify border lookup --- src/textual/_border.py | 17 ++++++++--------- 1 file changed, 8 insertions(+), 9 deletions(-) diff --git a/src/textual/_border.py b/src/textual/_border.py index dabb6581f..fbc5ef7e9 100644 --- a/src/textual/_border.py +++ b/src/textual/_border.py @@ -63,7 +63,7 @@ BORDER_LOCATIONS: dict[ "hkey": ((0, 0, 0), (0, 0, 0), (0, 0, 0)), "vkey": ((0, 0, 0), (0, 0, 0), (0, 0, 0)), "tall": ((2, 0, 1), (2, 0, 1), (2, 0, 1)), - "wide": ((1, 1, 1), (3, 1, 4), (1, 1, 1)), + "wide": ((1, 1, 1), (0, 1, 3), (1, 1, 1)), } INVISIBLE_EDGE_TYPES = cast("frozenset[EdgeType]", frozenset(("", "none", "hidden"))) @@ -110,14 +110,13 @@ def get_box( (lbottom1, lbottom2, lbottom3), ) = BORDER_LOCATIONS[name] - styles: tuple[Style, ...] = (inner_style + style, outer_style + style) - inner, outer = styles - - from_color = Style.from_color - styles += ( - from_color(outer.bgcolor, inner.color), - from_color(inner.color, inner.bgcolor), - from_color(inner.bgcolor, outer.color), + inner = inner_style + style + outer = outer_style + style + styles = ( + inner_style + style, + outer_style + style, + Style.from_color(outer.bgcolor, inner.color), + Style.from_color(inner.bgcolor, outer.color), ) return ( From f8f20a0aeef38f18bfd560535660151e9fe7ed51 Mon Sep 17 00:00:00 2001 From: Will McGugan Date: Tue, 13 Sep 2022 14:56:28 +0100 Subject: [PATCH 6/7] wrong place --- offset.css | 21 --------------------- offset.py | 17 ----------------- 2 files changed, 38 deletions(-) delete mode 100644 offset.css delete mode 100644 offset.py diff --git a/offset.css b/offset.css deleted file mode 100644 index 97b98a271..000000000 --- a/offset.css +++ /dev/null @@ -1,21 +0,0 @@ -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 deleted file mode 100644 index d4a9dfd20..000000000 --- a/offset.py +++ /dev/null @@ -1,17 +0,0 @@ -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() From fbec608198311101cf3ecf078ba26f2d49b44715 Mon Sep 17 00:00:00 2001 From: Will McGugan Date: Tue, 13 Sep 2022 14:57:25 +0100 Subject: [PATCH 7/7] faster --- src/textual/_border.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/textual/_border.py b/src/textual/_border.py index fbc5ef7e9..7294e4fc0 100644 --- a/src/textual/_border.py +++ b/src/textual/_border.py @@ -113,8 +113,8 @@ def get_box( inner = inner_style + style outer = outer_style + style styles = ( - inner_style + style, - outer_style + style, + inner, + outer, Style.from_color(outer.bgcolor, inner.color), Style.from_color(inner.bgcolor, outer.color), )