fix basic

This commit is contained in:
Will McGugan
2022-07-27 15:16:48 +01:00
parent c98e1b9604
commit 06bc566fec
11 changed files with 114 additions and 109 deletions

View File

@@ -13,10 +13,15 @@
} }
App > Screen { App > Screen {
layout: dock;
docks: side=left/1;
background: $surface; background: $surface;
color: $text-surface; color: $text-surface;
layers: sidebar;
color: $text-background;
background: $background;
layout: vertical;
} }
DataTable { DataTable {
@@ -31,11 +36,12 @@ DataTable {
#sidebar { #sidebar {
color: $text-panel; color: $text-panel;
background: $panel; background: $panel;
dock: side; dock: left;
width: 30; width: 30;
offset-x: -100%; offset-x: -100%;
layout: dock;
transition: offset 500ms in_out_cubic; transition: offset 500ms in_out_cubic;
layer: sidebar;
} }
#sidebar.-active { #sidebar.-active {
@@ -71,14 +77,7 @@ DataTable {
height: 1; height: 1;
content-align: center middle; content-align: center middle;
dock: top;
}
#content {
color: $text-background;
background: $background;
layout: vertical;
overflow-y: scroll;
} }
@@ -168,6 +167,7 @@ Tweet.scroll-horizontal TweetBody {
height: 1; height: 1;
content-align: center middle; content-align: center middle;
dock:bottom;
} }

View File

@@ -1,9 +1,9 @@
from rich.console import RenderableType from rich.console import RenderableType
from rich.style import Style
from rich.syntax import Syntax from rich.syntax import Syntax
from rich.text import Text from rich.text import Text
from textual.app import App from textual.app import App, ComposeResult
from textual.reactive import Reactive from textual.reactive import Reactive
from textual.widget import Widget from textual.widget import Widget
from textual.widgets import Static, DataTable from textual.widgets import Static, DataTable
@@ -98,45 +98,45 @@ class BasicApp(App, css_path="basic.css"):
"""Bind keys here.""" """Bind keys here."""
self.bind("s", "toggle_class('#sidebar', '-active')") self.bind("s", "toggle_class('#sidebar', '-active')")
def on_mount(self): def compose(self) -> ComposeResult:
"""Build layout here."""
table = DataTable() table = DataTable()
self.scroll_to_target = Tweet(TweetBody()) self.scroll_to_target = Tweet(TweetBody())
self.mount(
header=Static( yield Static(
Text.from_markup( Text.from_markup(
"[b]This is a [u]Textual[/u] app, running in the terminal" "[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"),
), ),
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("Foo", width=20)
table.add_column("Bar", width=20) table.add_column("Bar", width=20)
table.add_column("Baz", width=20) table.add_column("Baz", width=20)

View File

@@ -41,8 +41,9 @@ class DockApp(App):
for n, color in zip(range(5), ["red", "green", "blue", "yellow", "magenta"]): for n, color in zip(range(5), ["red", "green", "blue", "yellow", "magenta"]):
thing = Static(f"Thing {n}", id=f"#thing{n}") 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.background = f"{color} 20%"
thing.styles.height = 5 thing.styles.height = 15
yield thing yield thing

View File

@@ -5,16 +5,25 @@ from fractions import Fraction
from typing import TYPE_CHECKING from typing import TYPE_CHECKING
from .geometry import Region, Size, Spacing from .geometry import Region, Size, Spacing
from ._layout import ArrangeResult, WidgetPlacement from ._layout import DockArrangeResult, WidgetPlacement
from ._partition import partition from ._partition import partition
if TYPE_CHECKING: if TYPE_CHECKING:
from ._layout import ArrangeResult
from .widget import Widget 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] display_children = [child for child in widget.children if child.display]
arrange_widgets: set[Widget] = set() arrange_widgets: set[Widget] = set()
@@ -31,7 +40,8 @@ def arrange(widget: Widget, size: Size, viewport: Size) -> ArrangeResult:
_WidgetPlacement = WidgetPlacement _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() scroll_spacing = Spacing()
@@ -47,23 +57,15 @@ def arrange(widget: Widget, size: Size, viewport: Size) -> ArrangeResult:
for dock_widget in dock_widgets: for dock_widget in dock_widgets:
edge = dock_widget.styles.dock edge = dock_widget.styles.dock
( fraction_unit = Fraction(
widget_width_fraction, size.height if edge in ("top", "bottom") else size.width
widget_height_fraction,
margin,
) = dock_widget.get_box_model(
size,
viewport,
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_width = int(widget_width_fraction) + margin.width
widget_height = int(widget_height_fraction) + margin.height widget_height = int(widget_height_fraction) + margin.height
align_offset = dock_widget.styles.align_size(
(widget_width, widget_height), size
)
if edge == "bottom": if edge == "bottom":
dock_region = Region( dock_region = Region(
0, height - widget_height, widget_width, widget_height 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 width - widget_width, 0, widget_width, widget_height
) )
right = max(right, dock_region.width) 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) dock_region = dock_region.shrink(margin).translate(align_offset)
add_placement(_WidgetPlacement(dock_region, dock_widget, top_z, True)) add_placement(_WidgetPlacement(dock_region, dock_widget, top_z, True))
dock_spacing = Spacing(top, right, bottom, left) dock_spacing = Spacing(top, right, bottom, left)
region = size.region.shrink(dock_spacing) 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 widget, layout_widgets, region.size
) )
if _layout_widgets: if _layout_widgets:
@@ -101,26 +108,4 @@ def arrange(widget: Widget, size: Size, viewport: Size) -> ArrangeResult:
placements.extend(layout_placements) placements.extend(layout_placements)
result = ArrangeResult(placements, arrange_widgets, scroll_spacing) return 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,
# )

View File

@@ -17,12 +17,8 @@ if TYPE_CHECKING:
from .widget import Widget from .widget import Widget
class ArrangeResult(NamedTuple): ArrangeResult: TypeAlias = "tuple[list[WidgetPlacement], set[Widget]]"
"""The result of an arrange operation.""" DockArrangeResult: TypeAlias = "tuple[list[WidgetPlacement], set[Widget], Spacing]"
placements: list[WidgetPlacement]
widgets: set[Widget]
spacing: Spacing = Spacing()
class WidgetPlacement(NamedTuple): class WidgetPlacement(NamedTuple):
@@ -93,6 +89,6 @@ class Layout(ABC):
if not widget.displayed_children: if not widget.displayed_children:
height = container.height height = container.height
else: 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) height = max(placement.region.bottom for placement in placements)
return height return height

View File

@@ -246,7 +246,9 @@ class StylesCache:
line: Iterable[Segment] line: Iterable[Segment]
# Draw top or bottom borders (A) # Draw top or bottom borders (A)
if (border_top and y == 0) or (border_bottom and y == height - 1): 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( box_segments = get_box(
border_top if y == 0 else border_bottom, border_top if y == 0 else border_bottom,
inner, inner,
@@ -290,9 +292,9 @@ class StylesCache:
if border_left or border_right: if border_left or border_right:
# Add left / right border # 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] 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] right = get_box(border_right, inner, outer, right_style)[1][2]
if border_left and border_right: if border_left and border_right:
@@ -321,9 +323,9 @@ class StylesCache:
elif outline_left or outline_right: elif outline_left or outline_right:
# Lines in side outline # 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] 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] right = get_box(outline_right, inner, outer, right_style)[1][2]
line = line_trim(list(line), outline_left != "", outline_right != "") line = line_trim(list(line), outline_left != "", outline_right != "")
if outline_left and outline_right: if outline_left and outline_right:

View File

@@ -802,8 +802,9 @@ class NameListProperty:
class ColorProperty: class ColorProperty:
"""Descriptor for getting and setting color properties.""" """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._default_color = Color.parse(default_color)
self._is_background = background
def __set_name__(self, owner: StylesBase, name: str) -> None: def __set_name__(self, owner: StylesBase, name: str) -> None:
self.name = name self.name = name
@@ -837,10 +838,10 @@ class ColorProperty:
_rich_traceback_omit = True _rich_traceback_omit = True
if color is None: if color is None:
if obj.clear_rule(self.name): if obj.clear_rule(self.name):
obj.refresh(children=True) obj.refresh(children=self._is_background)
elif isinstance(color, Color): elif isinstance(color, Color):
if obj.set_rule(self.name, color): if obj.set_rule(self.name, color):
obj.refresh(children=True) obj.refresh(children=self._is_background)
elif isinstance(color, str): elif isinstance(color, str):
alpha = 1.0 alpha = 1.0
parsed_color = Color(255, 255, 255) parsed_color = Color(255, 255, 255)
@@ -862,7 +863,7 @@ class ColorProperty:
) )
parsed_color = parsed_color.with_alpha(alpha) parsed_color = parsed_color.with_alpha(alpha)
if obj.set_rule(self.name, parsed_color): if obj.set_rule(self.name, parsed_color):
obj.refresh(children=True) obj.refresh(children=self._is_background)
else: else:
raise StyleValueError(f"Invalid color value {color}") raise StyleValueError(f"Invalid color value {color}")

View File

@@ -185,7 +185,7 @@ class StylesBase(ABC):
layout = LayoutProperty() layout = LayoutProperty()
color = ColorProperty(Color(255, 255, 255)) 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() text_style = StyleFlagsProperty()
opacity = FractionalProperty() opacity = FractionalProperty()
@@ -437,6 +437,15 @@ class StylesBase(ABC):
return offset_y return offset_y
def align_size(self, child: tuple[int, int], parent: tuple[int, int]) -> Offset: 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 width, height = child
parent_width, parent_height = parent parent_width, parent_height = parent
return Offset( return Offset(

View File

@@ -304,6 +304,8 @@ class Stylesheet:
animate (bool, optional): Animate changed rules. Defaults to ``False``. animate (bool, optional): Animate changed rules. Defaults to ``False``.
""" """
print(node)
# Dictionary of rule attribute names e.g. "text_background" to list of tuples. # 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. # 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 # We can use this to determine, for a given rule, whether we should apply it
@@ -405,7 +407,12 @@ class Stylesheet:
else: else:
# Not animated, so we apply the rules directly # Not animated, so we apply the rules directly
get_rule = rules.get get_rule = rules.get
from ..screen import Screen
for key in modified_rule_keys: for key in modified_rule_keys:
if isinstance(node, Screen):
print(node, key, get_rule(key))
setattr(base_styles, key, get_rule(key)) setattr(base_styles, key, get_rule(key))
node.post_message_no_wait(messages.StylesUpdated(sender=node)) node.post_message_no_wait(messages.StylesUpdated(sender=node))

View File

@@ -60,4 +60,4 @@ class VerticalLayout(Layout):
total_region = Region(0, 0, size.width, int(y)) total_region = Region(0, 0, size.width, int(y))
add_placement(WidgetPlacement(total_region, None, 0)) add_placement(WidgetPlacement(total_region, None, 0))
return ArrangeResult(placements, set(children)) return placements, set(children)

View File

@@ -117,7 +117,11 @@ class Screen(Widget):
self._refresh_layout() self._refresh_layout()
self._layout_required = False self._layout_required = False
self._dirty_widgets.clear() 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() self.update_timer.resume()
def _on_update(self) -> None: def _on_update(self) -> None: