From e44877c2af5f0509557fa439f69d7bc7b6d0deb5 Mon Sep 17 00:00:00 2001 From: Will McGugan Date: Tue, 5 Apr 2022 15:47:08 +0100 Subject: [PATCH] compositor nesting fix --- examples/basic.css | 35 ++++++++++++++++++++++++---- examples/basic.py | 33 +++++++++++++++++++++++++- src/textual/_border.py | 18 +++++++------- src/textual/_compositor.py | 31 +++++++++++++++++++----- src/textual/css/_style_properties.py | 2 +- src/textual/css/constants.py | 1 + src/textual/css/styles.py | 7 +++--- src/textual/css/types.py | 1 + src/textual/dom.py | 6 ++--- src/textual/geometry.py | 10 ++++---- src/textual/widget.py | 17 ++++++++------ 11 files changed, 122 insertions(+), 39 deletions(-) diff --git a/examples/basic.css b/examples/basic.css index 74d41cf8c..f95a83534 100644 --- a/examples/basic.css +++ b/examples/basic.css @@ -3,8 +3,8 @@ App > Screen { layout: dock; docks: side=left/1; - background: $background; - color: $on-background; + background: $surface; + color: $on-surface; } #sidebar { @@ -32,6 +32,7 @@ App > Screen { height: 8; background: $secondary-darken1; color: $on-secondary-darken1; + border-right: outer $secondary-darken3; } @@ -45,14 +46,38 @@ App > Screen { color: $on-primary; background: $primary; height: 3; - border-right: outer $secondary-darken3; + + border: hkey $secondary-darken3; } #content { - color: $on-background; - background: $background; + color: $on-surface; + background: $surface; + layout: vertical; } +Tweet { + height: 7; + max-width: 80; + + margin: 1 2; + background: $background; + color: $on-background; + layout: vertical +} + +TweetHeader { + height:1 + background: $secondary + color: $on-secondary +} + +TweetBody { + background: $background + color: $on-background +} + + #footer { color: $on-accent1; background: $accent1; diff --git a/examples/basic.py b/examples/basic.py index 94968ef64..25c34570c 100644 --- a/examples/basic.py +++ b/examples/basic.py @@ -1,7 +1,29 @@ +from rich.console import RenderableType +from rich.text import Text + from textual.app import App from textual.widget import Widget +lorem = Text.from_markup( + """Lorem ipsum dolor sit amet, consectetur adipiscing elit. In velit libero, volutpat nec hendrerit at, faucibus in odio. Aliquam hendrerit nibh sed quam volutpat maximus. Nullam suscipit convallis lorem quis sodales. In tristique lobortis ante et dictum. Ut at finibus ipsum. In urna dolor, placerat et mi facilisis, congue sollicitudin massa. Phasellus felis turpis, cursus eu lectus et, porttitor malesuada augue. Sed feugiat volutpat velit, sollicitudin fringilla velit bibendum faucibus. Lorem ipsum dolor sit amet, consectetur adipiscing elit. In velit libero, volutpat nec hendrerit at, faucibus in odio. Aliquam hendrerit nibh sed quam volutpat maximus. Nullam suscipit convallis lorem quis sodales. In tristique lobortis ante et dictum. Ut at finibus ipsum. In urna dolor, placerat et mi facilisis, congue sollicitudin massa. Phasellus felis turpis, cursus eu lectus et, porttitor malesuada augue. Sed feugiat volutpat velit, sollicitudin fringilla velit bibendum faucibus. """, +) + + +class TweetHeader(Widget): + def render(self) -> RenderableType: + return Text("Lorem Impsum", justify="center") + + +class TweetBody(Widget): + def render(self) -> Text: + return lorem + + +class Tweet(Widget): + pass + + class BasicApp(App): """A basic app demonstrating CSS""" @@ -13,7 +35,16 @@ class BasicApp(App): """Build layout here.""" self.mount( header=Widget(), - content=Widget(), + content=Widget( + Tweet(TweetHeader(), TweetBody()), + Tweet(TweetHeader(), TweetBody()), + Tweet(TweetHeader(), TweetBody()) + # Tweet(TweetHeader(), TweetBody()), + # Tweet(TweetHeader(), TweetBody()), + # Tweet(TweetHeader(), TweetBody()), + # Tweet(TweetHeader(), TweetBody()), + # Tweet(TweetHeader(), TweetBody()), + ), footer=Widget(), sidebar=Widget( Widget(classes={"title"}), diff --git a/src/textual/_border.py b/src/textual/_border.py index 262ee2d87..a25a69dfe 100644 --- a/src/textual/_border.py +++ b/src/textual/_border.py @@ -17,6 +17,7 @@ OUTER = 2 BORDER_CHARS: dict[EdgeType, tuple[str, str, str]] = { "": (" ", " ", " "), "none": (" ", " ", " "), + "hidden": (" ", " ", " "), "round": ("╭─╮", "│ │", "╰─╯"), "solid": ("┌─┐", "│ │", "└─┘"), "double": ("╔═╗", "║ ║", "╚═╝"), @@ -37,6 +38,7 @@ BORDER_LOCATIONS: dict[ ] = { "": ((0, 0, 0), (0, 0, 0), (0, 0, 0)), "none": ((0, 0, 0), (0, 0, 0), (0, 0, 0)), + "hidden": ((0, 0, 0), (0, 0, 0), (0, 0, 0)), "round": ((0, 0, 0), (0, 0, 0), (0, 0, 0)), "solid": ((0, 0, 0), (0, 0, 0), (0, 0, 0)), "double": ((0, 0, 0), (0, 0, 0), (0, 0, 0)), @@ -118,9 +120,9 @@ class Border: self, renderable: RenderableType, edge_styles: tuple[EdgeStyle, EdgeStyle, EdgeStyle, EdgeStyle], + inner_color: Color, + outer_color: Color, outline: bool = False, - inner_color: Color | None = None, - outer_color: Color | None = None, ): self.renderable = renderable self.edge_styles = edge_styles @@ -141,8 +143,8 @@ class Border: from_color(bottom_color.rich_color), from_color(left_color.rich_color), ) - self.inner_style = from_color(bgcolor=inner_color) - self.outer_style = from_color(bgcolor=outer_color) + self.inner_style = from_color(bgcolor=inner_color.rich_color) + self.outer_style = from_color(bgcolor=outer_color.rich_color) def __rich_repr__(self) -> rich.repr.Result: yield self.renderable @@ -250,7 +252,7 @@ class Border: yield new_line if has_bottom: - box1, box2, box3 = get_box(top, style, outer_style, bottom_style)[2] + box1, box2, box3 = get_box(bottom, style, outer_style, bottom_style)[2] if has_left: yield box1 if bottom == left else _Segment(" ", box1.style) yield _Segment(box2.text * width, box2.style) @@ -274,10 +276,10 @@ if __name__ == "__main__": border = Border( Padding(text, 1, style="on #303F9F"), ( + ("none", Color.parse("#C5CAE9")), + ("none", Color.parse("#C5CAE9")), ("wide", Color.parse("#C5CAE9")), - ("wide", Color.parse("#C5CAE9")), - ("wide", Color.parse("#C5CAE9")), - ("wide", Color.parse("#C5CAE9")), + ("none", Color.parse("#C5CAE9")), ), inner_color=inner, outer_color=outer, diff --git a/src/textual/_compositor.py b/src/textual/_compositor.py index b4897f300..149a82c05 100644 --- a/src/textual/_compositor.py +++ b/src/textual/_compositor.py @@ -178,6 +178,7 @@ class Compositor: and the "virtual size" (scrollable region) """ + indent = 0 ORIGIN = Offset(0, 0) size = root.size map: RenderRegionMap = {} @@ -197,6 +198,8 @@ class Compositor: order (tuple[int, ...]): A tuple of ints to define the order. clip (Region): The clipping region (i.e. the viewport which contains it). """ + nonlocal indent + indent += 1 widgets.add(widget) styles_offset = widget.styles.offset layout_offset = ( @@ -205,6 +208,8 @@ class Compositor: else ORIGIN ) + log(" " * indent, widget, region) + # region += layout_offset # Container region is minus border @@ -228,6 +233,10 @@ class Compositor: widgets.update(arranged_widgets) placements = sorted(placements, key=attrgetter("order")) + for sub_region, sub_widget, z in placements: + if sub_widget: + log(" " * indent, sub_region) + # Add all the widgets for sub_region, sub_widget, z in placements: # Combine regions with children to calculate the "virtual size" @@ -235,15 +244,24 @@ class Compositor: if sub_widget is not None: add_widget( sub_widget, - ( - sub_region - + child_region.origin - - scroll_offset - + layout_offset - ), + sub_region + + container_region.origin + + layout_offset + - scroll_offset, (z,) + sub_widget.z, sub_clip, ) + # add_widget( + # sub_widget, + # ( + # sub_region + # + child_region.origin + # - scroll_offset + # + layout_offset + # ), + # (z,) + sub_widget.z, + # sub_clip, + # ) # Add any scrollbars for chrome_widget, chrome_region in widget._arrange_scrollbars( @@ -275,6 +293,7 @@ class Compositor: region.size, container_region.size, ) + indent -= 1 # Add top level (root) widget add_widget(root, size.region, (), size.region) diff --git a/src/textual/css/_style_properties.py b/src/textual/css/_style_properties.py index 52405160b..1e64d32fe 100644 --- a/src/textual/css/_style_properties.py +++ b/src/textual/css/_style_properties.py @@ -316,7 +316,7 @@ class StyleProperty: Returns: A ``Style`` object. """ - style = ColorPair(obj.color, obj.background).style + style = ColorPair(obj.color, obj.background).style + obj.text_style return style diff --git a/src/textual/css/constants.py b/src/textual/css/constants.py index 5022bddd9..d738a8341 100644 --- a/src/textual/css/constants.py +++ b/src/textual/css/constants.py @@ -11,6 +11,7 @@ VALID_VISIBILITY: Final = {"visible", "hidden"} VALID_DISPLAY: Final = {"block", "none"} VALID_BORDER: Final = { "none", + "hidden", "round", "solid", "double", diff --git a/src/textual/css/styles.py b/src/textual/css/styles.py index 0ff7939cd..125bab664 100644 --- a/src/textual/css/styles.py +++ b/src/textual/css/styles.py @@ -140,7 +140,6 @@ class StylesBase(ABC): visibility = StringEnumProperty(VALID_VISIBILITY, "visible") layout = LayoutProperty() - text = StyleProperty() color = ColorProperty(Color(255, 255, 255)) background = ColorProperty(Color(0, 0, 0)) text_style = StyleFlagsProperty() @@ -181,6 +180,8 @@ class StylesBase(ABC): layers = NameListProperty() transitions = TransitionsProperty() + rich_style = StyleProperty() + def __eq__(self, styles: object) -> bool: """Check that Styles containts the same rules.""" if not isinstance(styles, StylesBase): @@ -371,7 +372,7 @@ class StylesBase(ABC): if self.box_sizing == "content-box": if has_rule("padding"): - size += self.padding + size += self.padding.totals if has_rule("border"): size += self.border.spacing.totals if has_rule("margin"): @@ -379,7 +380,7 @@ class StylesBase(ABC): else: # border-box if has_rule("padding"): - size -= self.padding + size -= self.padding.totals if has_rule("border"): size -= self.border.spacing.totals if has_rule("margin"): diff --git a/src/textual/css/types.py b/src/textual/css/types.py index 80e05751c..a74ceb75f 100644 --- a/src/textual/css/types.py +++ b/src/textual/css/types.py @@ -15,6 +15,7 @@ Edge = Literal["top", "right", "bottom", "left"] EdgeType = Literal[ "", "none", + "hidden", "round", "solid", "double", diff --git a/src/textual/dom.py b/src/textual/dom.py index d6d4de60a..dae5bb238 100644 --- a/src/textual/dom.py +++ b/src/textual/dom.py @@ -245,16 +245,16 @@ class DOMNode(MessagePump): return tuple(reversed(indexes)) @property - def text_style(self) -> Style: + def rich_text_style(self) -> Style: """Get the text style (added to parent style). Returns: Style: Rich Style object. """ return ( - self.parent.text_style + self.styles.text + self.parent.rich_text_style + self.styles.rich_style if self.has_parent - else self.styles.text + else self.styles.rich_style ) @property diff --git a/src/textual/geometry.py b/src/textual/geometry.py index 3d5db30ad..5fccc2728 100644 --- a/src/textual/geometry.py +++ b/src/textual/geometry.py @@ -438,14 +438,14 @@ class Region(NamedTuple): Returns: Region: The new, smaller region. """ - _clamp = clamp + top, right, bottom, left = margin x, y, width, height = self return Region( - x=_clamp(x + left, 0, width), - y=_clamp(y + top, 0, height), - width=_clamp(width - left - right, 0, width), - height=_clamp(height - top - bottom, 0, height), + x=x + left, + y=y + top, + width=max(0, width - left - right), + height=max(0, height - top - bottom), ) def intersection(self, region: Region) -> Region: diff --git a/src/textual/widget.py b/src/textual/widget.py index fbc0bd676..136d300f3 100644 --- a/src/textual/widget.py +++ b/src/textual/widget.py @@ -26,6 +26,7 @@ from . import events from ._animator import BoundAnimator from ._border import Border from ._callback import invoke +from .color import Color from ._context import active_app from ._types import Lines from .dom import DOMNode @@ -372,10 +373,11 @@ class Widget(DOMNode): renderable = self.render() styles = self.styles + parent_styles = self.parent.styles - parent_text_style = self.parent.text_style + parent_text_style = self.parent.rich_text_style + text_style = styles.rich_style - text_style = styles.text renderable_text_style = parent_text_style + text_style if renderable_text_style: renderable = Styled(renderable, renderable_text_style) @@ -389,17 +391,17 @@ class Widget(DOMNode): renderable = Border( renderable, styles.border, - inner_color=renderable_text_style.bgcolor, - outer_color=parent_text_style.bgcolor, + inner_color=styles.background, + outer_color=Color.from_rich_color(parent_text_style.bgcolor), ) if styles.outline: renderable = Border( renderable, styles.outline, + inner_color=styles.background, + outer_color=parent_styles.background, outline=True, - inner_color=renderable_text_style.bgcolor, - outer_color=parent_text_style.bgcolor, ) if styles.opacity != 1.0: @@ -437,7 +439,8 @@ class Widget(DOMNode): Returns: bool: ``True`` if there is background color, otherwise ``False``. """ - return self.layout is not None and self.styles.text.bgcolor is None + return False + return self.layout is not None @property def console(self) -> Console: