mirror of
https://github.com/Textualize/textual.git
synced 2025-10-17 02:38:12 +03:00
compositor nesting fix
This commit is contained in:
@@ -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;
|
||||
|
||||
@@ -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"}),
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
|
||||
@@ -11,6 +11,7 @@ VALID_VISIBILITY: Final = {"visible", "hidden"}
|
||||
VALID_DISPLAY: Final = {"block", "none"}
|
||||
VALID_BORDER: Final = {
|
||||
"none",
|
||||
"hidden",
|
||||
"round",
|
||||
"solid",
|
||||
"double",
|
||||
|
||||
@@ -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"):
|
||||
|
||||
@@ -15,6 +15,7 @@ Edge = Literal["top", "right", "bottom", "left"]
|
||||
EdgeType = Literal[
|
||||
"",
|
||||
"none",
|
||||
"hidden",
|
||||
"round",
|
||||
"solid",
|
||||
"double",
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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:
|
||||
|
||||
@@ -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:
|
||||
|
||||
Reference in New Issue
Block a user