compositor nesting fix

This commit is contained in:
Will McGugan
2022-04-05 15:47:08 +01:00
parent ca77f7e24d
commit e44877c2af
11 changed files with 122 additions and 39 deletions

View File

@@ -3,8 +3,8 @@
App > Screen { App > Screen {
layout: dock; layout: dock;
docks: side=left/1; docks: side=left/1;
background: $background; background: $surface;
color: $on-background; color: $on-surface;
} }
#sidebar { #sidebar {
@@ -32,6 +32,7 @@ App > Screen {
height: 8; height: 8;
background: $secondary-darken1; background: $secondary-darken1;
color: $on-secondary-darken1; color: $on-secondary-darken1;
border-right: outer $secondary-darken3; border-right: outer $secondary-darken3;
} }
@@ -45,14 +46,38 @@ App > Screen {
color: $on-primary; color: $on-primary;
background: $primary; background: $primary;
height: 3; height: 3;
border-right: outer $secondary-darken3;
border: hkey $secondary-darken3;
} }
#content { #content {
color: $on-background; color: $on-surface;
background: $background; 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 { #footer {
color: $on-accent1; color: $on-accent1;
background: $accent1; background: $accent1;

View File

@@ -1,7 +1,29 @@
from rich.console import RenderableType
from rich.text import Text
from textual.app import App from textual.app import App
from textual.widget import Widget 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): class BasicApp(App):
"""A basic app demonstrating CSS""" """A basic app demonstrating CSS"""
@@ -13,7 +35,16 @@ class BasicApp(App):
"""Build layout here.""" """Build layout here."""
self.mount( self.mount(
header=Widget(), 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(), footer=Widget(),
sidebar=Widget( sidebar=Widget(
Widget(classes={"title"}), Widget(classes={"title"}),

View File

@@ -17,6 +17,7 @@ OUTER = 2
BORDER_CHARS: dict[EdgeType, tuple[str, str, str]] = { BORDER_CHARS: dict[EdgeType, tuple[str, str, str]] = {
"": (" ", " ", " "), "": (" ", " ", " "),
"none": (" ", " ", " "), "none": (" ", " ", " "),
"hidden": (" ", " ", " "),
"round": ("╭─╮", "│ │", "╰─╯"), "round": ("╭─╮", "│ │", "╰─╯"),
"solid": ("┌─┐", "│ │", "└─┘"), "solid": ("┌─┐", "│ │", "└─┘"),
"double": ("╔═╗", "║ ║", "╚═╝"), "double": ("╔═╗", "║ ║", "╚═╝"),
@@ -37,6 +38,7 @@ BORDER_LOCATIONS: dict[
] = { ] = {
"": ((0, 0, 0), (0, 0, 0), (0, 0, 0)), "": ((0, 0, 0), (0, 0, 0), (0, 0, 0)),
"none": ((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)), "round": ((0, 0, 0), (0, 0, 0), (0, 0, 0)),
"solid": ((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)), "double": ((0, 0, 0), (0, 0, 0), (0, 0, 0)),
@@ -118,9 +120,9 @@ class Border:
self, self,
renderable: RenderableType, renderable: RenderableType,
edge_styles: tuple[EdgeStyle, EdgeStyle, EdgeStyle, EdgeStyle], edge_styles: tuple[EdgeStyle, EdgeStyle, EdgeStyle, EdgeStyle],
inner_color: Color,
outer_color: Color,
outline: bool = False, outline: bool = False,
inner_color: Color | None = None,
outer_color: Color | None = None,
): ):
self.renderable = renderable self.renderable = renderable
self.edge_styles = edge_styles self.edge_styles = edge_styles
@@ -141,8 +143,8 @@ class Border:
from_color(bottom_color.rich_color), from_color(bottom_color.rich_color),
from_color(left_color.rich_color), from_color(left_color.rich_color),
) )
self.inner_style = from_color(bgcolor=inner_color) self.inner_style = from_color(bgcolor=inner_color.rich_color)
self.outer_style = from_color(bgcolor=outer_color) self.outer_style = from_color(bgcolor=outer_color.rich_color)
def __rich_repr__(self) -> rich.repr.Result: def __rich_repr__(self) -> rich.repr.Result:
yield self.renderable yield self.renderable
@@ -250,7 +252,7 @@ class Border:
yield new_line yield new_line
if has_bottom: 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: if has_left:
yield box1 if bottom == left else _Segment(" ", box1.style) yield box1 if bottom == left else _Segment(" ", box1.style)
yield _Segment(box2.text * width, box2.style) yield _Segment(box2.text * width, box2.style)
@@ -274,10 +276,10 @@ if __name__ == "__main__":
border = Border( border = Border(
Padding(text, 1, style="on #303F9F"), 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")), ("none", Color.parse("#C5CAE9")),
("wide", Color.parse("#C5CAE9")),
("wide", Color.parse("#C5CAE9")),
), ),
inner_color=inner, inner_color=inner,
outer_color=outer, outer_color=outer,

View File

@@ -178,6 +178,7 @@ class Compositor:
and the "virtual size" (scrollable region) and the "virtual size" (scrollable region)
""" """
indent = 0
ORIGIN = Offset(0, 0) ORIGIN = Offset(0, 0)
size = root.size size = root.size
map: RenderRegionMap = {} map: RenderRegionMap = {}
@@ -197,6 +198,8 @@ class Compositor:
order (tuple[int, ...]): A tuple of ints to define the order. order (tuple[int, ...]): A tuple of ints to define the order.
clip (Region): The clipping region (i.e. the viewport which contains it). clip (Region): The clipping region (i.e. the viewport which contains it).
""" """
nonlocal indent
indent += 1
widgets.add(widget) widgets.add(widget)
styles_offset = widget.styles.offset styles_offset = widget.styles.offset
layout_offset = ( layout_offset = (
@@ -205,6 +208,8 @@ class Compositor:
else ORIGIN else ORIGIN
) )
log(" " * indent, widget, region)
# region += layout_offset # region += layout_offset
# Container region is minus border # Container region is minus border
@@ -228,6 +233,10 @@ class Compositor:
widgets.update(arranged_widgets) widgets.update(arranged_widgets)
placements = sorted(placements, key=attrgetter("order")) 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 # Add all the widgets
for sub_region, sub_widget, z in placements: for sub_region, sub_widget, z in placements:
# Combine regions with children to calculate the "virtual size" # Combine regions with children to calculate the "virtual size"
@@ -235,15 +244,24 @@ class Compositor:
if sub_widget is not None: if sub_widget is not None:
add_widget( add_widget(
sub_widget, sub_widget,
( sub_region
sub_region + container_region.origin
+ child_region.origin + layout_offset
- scroll_offset - scroll_offset,
+ layout_offset
),
(z,) + sub_widget.z, (z,) + sub_widget.z,
sub_clip, sub_clip,
) )
# add_widget(
# sub_widget,
# (
# sub_region
# + child_region.origin
# - scroll_offset
# + layout_offset
# ),
# (z,) + sub_widget.z,
# sub_clip,
# )
# Add any scrollbars # Add any scrollbars
for chrome_widget, chrome_region in widget._arrange_scrollbars( for chrome_widget, chrome_region in widget._arrange_scrollbars(
@@ -275,6 +293,7 @@ class Compositor:
region.size, region.size,
container_region.size, container_region.size,
) )
indent -= 1
# Add top level (root) widget # Add top level (root) widget
add_widget(root, size.region, (), size.region) add_widget(root, size.region, (), size.region)

View File

@@ -316,7 +316,7 @@ class StyleProperty:
Returns: Returns:
A ``Style`` object. A ``Style`` object.
""" """
style = ColorPair(obj.color, obj.background).style style = ColorPair(obj.color, obj.background).style + obj.text_style
return style return style

View File

@@ -11,6 +11,7 @@ VALID_VISIBILITY: Final = {"visible", "hidden"}
VALID_DISPLAY: Final = {"block", "none"} VALID_DISPLAY: Final = {"block", "none"}
VALID_BORDER: Final = { VALID_BORDER: Final = {
"none", "none",
"hidden",
"round", "round",
"solid", "solid",
"double", "double",

View File

@@ -140,7 +140,6 @@ class StylesBase(ABC):
visibility = StringEnumProperty(VALID_VISIBILITY, "visible") visibility = StringEnumProperty(VALID_VISIBILITY, "visible")
layout = LayoutProperty() layout = LayoutProperty()
text = StyleProperty()
color = ColorProperty(Color(255, 255, 255)) color = ColorProperty(Color(255, 255, 255))
background = ColorProperty(Color(0, 0, 0)) background = ColorProperty(Color(0, 0, 0))
text_style = StyleFlagsProperty() text_style = StyleFlagsProperty()
@@ -181,6 +180,8 @@ class StylesBase(ABC):
layers = NameListProperty() layers = NameListProperty()
transitions = TransitionsProperty() transitions = TransitionsProperty()
rich_style = StyleProperty()
def __eq__(self, styles: object) -> bool: def __eq__(self, styles: object) -> bool:
"""Check that Styles containts the same rules.""" """Check that Styles containts the same rules."""
if not isinstance(styles, StylesBase): if not isinstance(styles, StylesBase):
@@ -371,7 +372,7 @@ class StylesBase(ABC):
if self.box_sizing == "content-box": if self.box_sizing == "content-box":
if has_rule("padding"): if has_rule("padding"):
size += self.padding size += self.padding.totals
if has_rule("border"): if has_rule("border"):
size += self.border.spacing.totals size += self.border.spacing.totals
if has_rule("margin"): if has_rule("margin"):
@@ -379,7 +380,7 @@ class StylesBase(ABC):
else: # border-box else: # border-box
if has_rule("padding"): if has_rule("padding"):
size -= self.padding size -= self.padding.totals
if has_rule("border"): if has_rule("border"):
size -= self.border.spacing.totals size -= self.border.spacing.totals
if has_rule("margin"): if has_rule("margin"):

View File

@@ -15,6 +15,7 @@ Edge = Literal["top", "right", "bottom", "left"]
EdgeType = Literal[ EdgeType = Literal[
"", "",
"none", "none",
"hidden",
"round", "round",
"solid", "solid",
"double", "double",

View File

@@ -245,16 +245,16 @@ class DOMNode(MessagePump):
return tuple(reversed(indexes)) return tuple(reversed(indexes))
@property @property
def text_style(self) -> Style: def rich_text_style(self) -> Style:
"""Get the text style (added to parent style). """Get the text style (added to parent style).
Returns: Returns:
Style: Rich Style object. Style: Rich Style object.
""" """
return ( return (
self.parent.text_style + self.styles.text self.parent.rich_text_style + self.styles.rich_style
if self.has_parent if self.has_parent
else self.styles.text else self.styles.rich_style
) )
@property @property

View File

@@ -438,14 +438,14 @@ class Region(NamedTuple):
Returns: Returns:
Region: The new, smaller region. Region: The new, smaller region.
""" """
_clamp = clamp
top, right, bottom, left = margin top, right, bottom, left = margin
x, y, width, height = self x, y, width, height = self
return Region( return Region(
x=_clamp(x + left, 0, width), x=x + left,
y=_clamp(y + top, 0, height), y=y + top,
width=_clamp(width - left - right, 0, width), width=max(0, width - left - right),
height=_clamp(height - top - bottom, 0, height), height=max(0, height - top - bottom),
) )
def intersection(self, region: Region) -> Region: def intersection(self, region: Region) -> Region:

View File

@@ -26,6 +26,7 @@ from . import events
from ._animator import BoundAnimator from ._animator import BoundAnimator
from ._border import Border from ._border import Border
from ._callback import invoke from ._callback import invoke
from .color import Color
from ._context import active_app from ._context import active_app
from ._types import Lines from ._types import Lines
from .dom import DOMNode from .dom import DOMNode
@@ -372,10 +373,11 @@ class Widget(DOMNode):
renderable = self.render() renderable = self.render()
styles = self.styles 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 renderable_text_style = parent_text_style + text_style
if renderable_text_style: if renderable_text_style:
renderable = Styled(renderable, renderable_text_style) renderable = Styled(renderable, renderable_text_style)
@@ -389,17 +391,17 @@ class Widget(DOMNode):
renderable = Border( renderable = Border(
renderable, renderable,
styles.border, styles.border,
inner_color=renderable_text_style.bgcolor, inner_color=styles.background,
outer_color=parent_text_style.bgcolor, outer_color=Color.from_rich_color(parent_text_style.bgcolor),
) )
if styles.outline: if styles.outline:
renderable = Border( renderable = Border(
renderable, renderable,
styles.outline, styles.outline,
inner_color=styles.background,
outer_color=parent_styles.background,
outline=True, outline=True,
inner_color=renderable_text_style.bgcolor,
outer_color=parent_text_style.bgcolor,
) )
if styles.opacity != 1.0: if styles.opacity != 1.0:
@@ -437,7 +439,8 @@ class Widget(DOMNode):
Returns: Returns:
bool: ``True`` if there is background color, otherwise ``False``. 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 @property
def console(self) -> Console: def console(self) -> Console: