From 06bc566fec924610f311a6c4924ee40c19808714 Mon Sep 17 00:00:00 2001 From: Will McGugan Date: Wed, 27 Jul 2022 15:16:48 +0100 Subject: [PATCH] fix basic --- sandbox/will/basic.css | 24 ++++----- sandbox/will/basic.py | 74 ++++++++++++++-------------- sandbox/will/dock.py | 3 +- src/textual/_arrange.py | 65 ++++++++++-------------- src/textual/_layout.py | 10 ++-- src/textual/_styles_cache.py | 12 +++-- src/textual/css/_style_properties.py | 9 ++-- src/textual/css/styles.py | 11 ++++- src/textual/css/stylesheet.py | 7 +++ src/textual/layouts/vertical.py | 2 +- src/textual/screen.py | 6 ++- 11 files changed, 114 insertions(+), 109 deletions(-) diff --git a/sandbox/will/basic.css b/sandbox/will/basic.css index dedc7d161..3c9043b6b 100644 --- a/sandbox/will/basic.css +++ b/sandbox/will/basic.css @@ -13,10 +13,15 @@ } App > Screen { - layout: dock; - docks: side=left/1; + background: $surface; color: $text-surface; + layers: sidebar; + + color: $text-background; + background: $background; + layout: vertical; + } DataTable { @@ -31,11 +36,12 @@ DataTable { #sidebar { color: $text-panel; background: $panel; - dock: side; + dock: left; width: 30; offset-x: -100%; - layout: dock; + transition: offset 500ms in_out_cubic; + layer: sidebar; } #sidebar.-active { @@ -71,14 +77,7 @@ DataTable { height: 1; content-align: center middle; - -} - -#content { - color: $text-background; - background: $background; - layout: vertical; - overflow-y: scroll; + dock: top; } @@ -168,6 +167,7 @@ Tweet.scroll-horizontal TweetBody { height: 1; content-align: center middle; + dock:bottom; } diff --git a/sandbox/will/basic.py b/sandbox/will/basic.py index c376d5c12..dee825f0e 100644 --- a/sandbox/will/basic.py +++ b/sandbox/will/basic.py @@ -1,9 +1,9 @@ from rich.console import RenderableType -from rich.style import Style + from rich.syntax import Syntax from rich.text import Text -from textual.app import App +from textual.app import App, ComposeResult from textual.reactive import Reactive from textual.widget import Widget from textual.widgets import Static, DataTable @@ -98,45 +98,45 @@ class BasicApp(App, css_path="basic.css"): """Bind keys here.""" self.bind("s", "toggle_class('#sidebar', '-active')") - def on_mount(self): - """Build layout here.""" - + def compose(self) -> ComposeResult: table = DataTable() self.scroll_to_target = Tweet(TweetBody()) - self.mount( - header=Static( - Text.from_markup( - "[b]This is a [u]Textual[/u] app, running in the terminal" - ), - ), - content=Widget( - Tweet(TweetBody()), - Widget( - Static(Syntax(CODE, "python"), classes="code"), - classes="scrollable", - ), - table, - Error(), - Tweet(TweetBody(), classes="scrollbar-size-custom"), - Warning(), - Tweet(TweetBody(), classes="scroll-horizontal"), - Success(), - Tweet(TweetBody(), classes="scroll-horizontal"), - Tweet(TweetBody(), classes="scroll-horizontal"), - Tweet(TweetBody(), classes="scroll-horizontal"), - Tweet(TweetBody(), classes="scroll-horizontal"), - Tweet(TweetBody(), classes="scroll-horizontal"), - ), - footer=Widget(), - sidebar=Widget( - Widget(classes="title"), - Widget(classes="user"), - OptionItem(), - OptionItem(), - OptionItem(), - Widget(classes="content"), + + yield Static( + Text.from_markup( + "[b]This is a [u]Textual[/u] app, running in the terminal" ), + id="header", ) + yield from ( + Tweet(TweetBody()), + Widget( + Static(Syntax(CODE, "python"), classes="code"), + classes="scrollable", + ), + table, + Error(), + Tweet(TweetBody(), classes="scrollbar-size-custom"), + Warning(), + Tweet(TweetBody(), classes="scroll-horizontal"), + Success(), + Tweet(TweetBody(), classes="scroll-horizontal"), + Tweet(TweetBody(), classes="scroll-horizontal"), + Tweet(TweetBody(), classes="scroll-horizontal"), + Tweet(TweetBody(), classes="scroll-horizontal"), + Tweet(TweetBody(), classes="scroll-horizontal"), + ) + yield Widget(id="footer") + yield Widget( + Widget(classes="title"), + Widget(classes="user"), + OptionItem(), + OptionItem(), + OptionItem(), + Widget(classes="content"), + id="sidebar", + ) + table.add_column("Foo", width=20) table.add_column("Bar", width=20) table.add_column("Baz", width=20) diff --git a/sandbox/will/dock.py b/sandbox/will/dock.py index f9eaac67c..7f358aa0a 100644 --- a/sandbox/will/dock.py +++ b/sandbox/will/dock.py @@ -41,8 +41,9 @@ class DockApp(App): for n, color in zip(range(5), ["red", "green", "blue", "yellow", "magenta"]): thing = Static(f"Thing {n}", id=f"#thing{n}") + thing.styles.border = ("heavy", "rgba(0,0,0,0.2)") thing.styles.background = f"{color} 20%" - thing.styles.height = 5 + thing.styles.height = 15 yield thing diff --git a/src/textual/_arrange.py b/src/textual/_arrange.py index 537371912..666b96e3e 100644 --- a/src/textual/_arrange.py +++ b/src/textual/_arrange.py @@ -5,16 +5,25 @@ from fractions import Fraction from typing import TYPE_CHECKING from .geometry import Region, Size, Spacing -from ._layout import ArrangeResult, WidgetPlacement +from ._layout import DockArrangeResult, WidgetPlacement from ._partition import partition if TYPE_CHECKING: - from ._layout import ArrangeResult from .widget import Widget -def arrange(widget: Widget, size: Size, viewport: Size) -> ArrangeResult: +def arrange(widget: Widget, size: Size, viewport: Size) -> DockArrangeResult: + """Arrange widgets by applying docks and calling layouts + + Args: + widget (Widget): The parent (container) widget. + size (Size): The size of the available area. + viewport (Size): The size of the viewport (terminal). + + Returns: + tuple[list[WidgetPlacement], set[Widget], Spacing]: Widget arrangement information. + """ display_children = [child for child in widget.children if child.display] arrange_widgets: set[Widget] = set() @@ -31,7 +40,8 @@ def arrange(widget: Widget, size: Size, viewport: Size) -> ArrangeResult: _WidgetPlacement = WidgetPlacement - top_z = 2**32 - 1 + # TODO: This is a bit of a fudge, need to ensure it is impossible for layouts to generate this value + top_z = 2**31 - 1 scroll_spacing = Spacing() @@ -47,23 +57,15 @@ def arrange(widget: Widget, size: Size, viewport: Size) -> ArrangeResult: for dock_widget in dock_widgets: edge = dock_widget.styles.dock - ( - widget_width_fraction, - widget_height_fraction, - margin, - ) = dock_widget.get_box_model( - size, - viewport, - Fraction(size.height if edge in ("top", "bottom") else size.width), + fraction_unit = Fraction( + size.height if edge in ("top", "bottom") else size.width ) + box_model = dock_widget.get_box_model(size, viewport, fraction_unit) + widget_width_fraction, widget_height_fraction, margin = box_model widget_width = int(widget_width_fraction) + margin.width widget_height = int(widget_height_fraction) + margin.height - align_offset = dock_widget.styles.align_size( - (widget_width, widget_height), size - ) - if edge == "bottom": dock_region = Region( 0, height - widget_height, widget_width, widget_height @@ -80,13 +82,18 @@ def arrange(widget: Widget, size: Size, viewport: Size) -> ArrangeResult: width - widget_width, 0, widget_width, widget_height ) right = max(right, dock_region.width) + else: + raise AssertionError("invalid value for edge") + align_offset = dock_widget.styles.align_size( + (widget_width, widget_height), size + ) dock_region = dock_region.shrink(margin).translate(align_offset) add_placement(_WidgetPlacement(dock_region, dock_widget, top_z, True)) dock_spacing = Spacing(top, right, bottom, left) region = size.region.shrink(dock_spacing) - layout_placements, _layout_widgets, spacing = widget.layout.arrange( + layout_placements, _layout_widgets = widget.layout.arrange( widget, layout_widgets, region.size ) if _layout_widgets: @@ -101,26 +108,4 @@ def arrange(widget: Widget, size: Size, viewport: Size) -> ArrangeResult: placements.extend(layout_placements) - result = ArrangeResult(placements, arrange_widgets, scroll_spacing) - return result - - # dock_spacing = Spacing(top, right, bottom, left) - # region = region.shrink(dock_spacing) - - # placements, placement_widgets, spacing = widget.layout.arrange( - # widget, layout_widgets, region.size - # ) - # dock_spacing += spacing - - # placement_offset = region.offset - # if placement_offset: - # placements = [ - # _WidgetPlacement(_region + placement_offset, widget, order, fixed) - # for _region, widget, order, fixed in placements - # ] - - # return ArrangeResult( - # (dock_placements + placements), - # placement_widgets.union(layout_widgets), - # dock_spacing, - # ) + return placements, arrange_widgets, scroll_spacing diff --git a/src/textual/_layout.py b/src/textual/_layout.py index b8e13d663..04c56e00f 100644 --- a/src/textual/_layout.py +++ b/src/textual/_layout.py @@ -17,12 +17,8 @@ if TYPE_CHECKING: from .widget import Widget -class ArrangeResult(NamedTuple): - """The result of an arrange operation.""" - - placements: list[WidgetPlacement] - widgets: set[Widget] - spacing: Spacing = Spacing() +ArrangeResult: TypeAlias = "tuple[list[WidgetPlacement], set[Widget]]" +DockArrangeResult: TypeAlias = "tuple[list[WidgetPlacement], set[Widget], Spacing]" class WidgetPlacement(NamedTuple): @@ -93,6 +89,6 @@ class Layout(ABC): if not widget.displayed_children: height = container.height else: - placements, widgets = widget._arrange(Size(width, container.height)) + placements, *_ = widget._arrange(Size(width, container.height)) height = max(placement.region.bottom for placement in placements) return height diff --git a/src/textual/_styles_cache.py b/src/textual/_styles_cache.py index e9b467166..2d414f352 100644 --- a/src/textual/_styles_cache.py +++ b/src/textual/_styles_cache.py @@ -246,7 +246,9 @@ class StylesCache: line: Iterable[Segment] # Draw top or bottom borders (A) if (border_top and y == 0) or (border_bottom and y == height - 1): - border_color = border_top_color if y == 0 else border_bottom_color + border_color = background + ( + border_top_color if y == 0 else border_bottom_color + ) box_segments = get_box( border_top if y == 0 else border_bottom, inner, @@ -290,9 +292,9 @@ class StylesCache: if border_left or border_right: # Add left / right border - left_style = from_color(border_left_color.rich_color) + left_style = from_color((background + border_left_color).rich_color) left = get_box(border_left, inner, outer, left_style)[1][0] - right_style = from_color(border_right_color.rich_color) + right_style = from_color((background + border_right_color).rich_color) right = get_box(border_right, inner, outer, right_style)[1][2] if border_left and border_right: @@ -321,9 +323,9 @@ class StylesCache: elif outline_left or outline_right: # Lines in side outline - left_style = from_color(outline_left_color.rich_color) + left_style = from_color((background + outline_left_color).rich_color) left = get_box(outline_left, inner, outer, left_style)[1][0] - right_style = from_color(outline_right_color.rich_color) + right_style = from_color((background + outline_right_color).rich_color) right = get_box(outline_right, inner, outer, right_style)[1][2] line = line_trim(list(line), outline_left != "", outline_right != "") if outline_left and outline_right: diff --git a/src/textual/css/_style_properties.py b/src/textual/css/_style_properties.py index 2f285d52f..0f38ae03e 100644 --- a/src/textual/css/_style_properties.py +++ b/src/textual/css/_style_properties.py @@ -802,8 +802,9 @@ class NameListProperty: class ColorProperty: """Descriptor for getting and setting color properties.""" - def __init__(self, default_color: Color | str) -> None: + def __init__(self, default_color: Color | str, background: bool = False) -> None: self._default_color = Color.parse(default_color) + self._is_background = background def __set_name__(self, owner: StylesBase, name: str) -> None: self.name = name @@ -837,10 +838,10 @@ class ColorProperty: _rich_traceback_omit = True if color is None: if obj.clear_rule(self.name): - obj.refresh(children=True) + obj.refresh(children=self._is_background) elif isinstance(color, Color): if obj.set_rule(self.name, color): - obj.refresh(children=True) + obj.refresh(children=self._is_background) elif isinstance(color, str): alpha = 1.0 parsed_color = Color(255, 255, 255) @@ -862,7 +863,7 @@ class ColorProperty: ) parsed_color = parsed_color.with_alpha(alpha) if obj.set_rule(self.name, parsed_color): - obj.refresh(children=True) + obj.refresh(children=self._is_background) else: raise StyleValueError(f"Invalid color value {color}") diff --git a/src/textual/css/styles.py b/src/textual/css/styles.py index 7e21786bd..311903a0a 100644 --- a/src/textual/css/styles.py +++ b/src/textual/css/styles.py @@ -185,7 +185,7 @@ class StylesBase(ABC): layout = LayoutProperty() color = ColorProperty(Color(255, 255, 255)) - background = ColorProperty(Color(0, 0, 0, 0)) + background = ColorProperty(Color(0, 0, 0, 0), background=True) text_style = StyleFlagsProperty() opacity = FractionalProperty() @@ -437,6 +437,15 @@ class StylesBase(ABC): return offset_y def align_size(self, child: tuple[int, int], parent: tuple[int, int]) -> Offset: + """Align a size according to alignment rules. + + Args: + child (tuple[int, int]): The size of the child (width, height) + parent (tuple[int, int]): The size of the parent (width, height) + + Returns: + Offset: Offset required to align the child. + """ width, height = child parent_width, parent_height = parent return Offset( diff --git a/src/textual/css/stylesheet.py b/src/textual/css/stylesheet.py index c7bb8b543..784b16cdd 100644 --- a/src/textual/css/stylesheet.py +++ b/src/textual/css/stylesheet.py @@ -304,6 +304,8 @@ class Stylesheet: animate (bool, optional): Animate changed rules. Defaults to ``False``. """ + print(node) + # Dictionary of rule attribute names e.g. "text_background" to list of tuples. # The tuples contain the rule specificity, and the value for that rule. # We can use this to determine, for a given rule, whether we should apply it @@ -405,7 +407,12 @@ class Stylesheet: else: # Not animated, so we apply the rules directly get_rule = rules.get + from ..screen import Screen + for key in modified_rule_keys: + if isinstance(node, Screen): + + print(node, key, get_rule(key)) setattr(base_styles, key, get_rule(key)) node.post_message_no_wait(messages.StylesUpdated(sender=node)) diff --git a/src/textual/layouts/vertical.py b/src/textual/layouts/vertical.py index 93f14ff24..e896319a6 100644 --- a/src/textual/layouts/vertical.py +++ b/src/textual/layouts/vertical.py @@ -60,4 +60,4 @@ class VerticalLayout(Layout): total_region = Region(0, 0, size.width, int(y)) add_placement(WidgetPlacement(total_region, None, 0)) - return ArrangeResult(placements, set(children)) + return placements, set(children) diff --git a/src/textual/screen.py b/src/textual/screen.py index 7ad6d3e02..07d189bb1 100644 --- a/src/textual/screen.py +++ b/src/textual/screen.py @@ -117,7 +117,11 @@ class Screen(Widget): self._refresh_layout() self._layout_required = False self._dirty_widgets.clear() - elif self._dirty_widgets: + if self._repaint_required: + self._dirty_widgets.add(self) + self._repaint_required = False + + if self._dirty_widgets: self.update_timer.resume() def _on_update(self) -> None: