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 {
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;
}

View File

@@ -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)

View File

@@ -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

View File

@@ -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

View File

@@ -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

View File

@@ -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:

View File

@@ -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}")

View File

@@ -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(

View File

@@ -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))

View File

@@ -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)

View File

@@ -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: