mirror of
https://github.com/Textualize/textual.git
synced 2025-10-17 02:38:12 +03:00
fix basic
This commit is contained in:
@@ -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;
|
||||
}
|
||||
|
||||
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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:
|
||||
|
||||
@@ -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}")
|
||||
|
||||
|
||||
@@ -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(
|
||||
|
||||
@@ -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))
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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:
|
||||
|
||||
Reference in New Issue
Block a user