From 57038c239374ddfae6823eac48d08df90669e25b Mon Sep 17 00:00:00 2001 From: Will McGugan Date: Sat, 10 Jul 2021 15:56:55 +0100 Subject: [PATCH] button widget --- examples/calculator.py | 10 +++++----- examples/grid.py | 20 ++++++++++++-------- examples/live.py | 0 src/textual/_layout_resolve.py | 2 +- src/textual/layout.py | 4 ++++ src/textual/layouts/dock.py | 13 +++++++------ src/textual/layouts/grid.py | 4 +++- src/textual/page.py | 1 + src/textual/scrollbar.py | 6 ++++-- src/textual/view.py | 7 +++++-- src/textual/widget.py | 2 +- src/textual/widgets/__init__.py | 1 + src/textual/widgets/_footer.py | 7 ++++--- src/textual/widgets/_scroll_view.py | 1 - src/textual/widgets/_static.py | 4 ++++ 15 files changed, 52 insertions(+), 30 deletions(-) create mode 100644 examples/live.py diff --git a/examples/calculator.py b/examples/calculator.py index bec719873..67b31a71e 100644 --- a/examples/calculator.py +++ b/examples/calculator.py @@ -1,7 +1,7 @@ from textual.app import App from textual import events from textual.view import View -from textual.widgets import Placeholder +from textual.widgets import Button, Placeholder from textual.layouts.grid import GridLayout @@ -15,7 +15,7 @@ class GridTest(App): await self.push_view(View(layout=layout)) layout.add_column("col", max_size=20, repeat=4) - layout.add_row("numbers", max_size=10) + layout.add_row("numbers", min_size=3, max_size=10) layout.add_row("row", max_size=10, repeat=4) layout.add_areas( @@ -27,9 +27,9 @@ class GridTest(App): layout.place( numbers=Placeholder(name="numbers"), - zero=Placeholder(name="0"), - dot=Placeholder(name="."), - equals=Placeholder(name="="), + zero=Button("0"), + dot=Button("."), + equals=Button("="), ) diff --git a/examples/grid.py b/examples/grid.py index 1f8582a4c..472cbdc4c 100644 --- a/examples/grid.py +++ b/examples/grid.py @@ -22,15 +22,19 @@ class GridTest(App): layout.add_row(fraction=2, name="middle") layout.add_row(fraction=1, name="bottom") - layout.add_area("area1", "left", "top") - layout.add_area("area2", "center", "middle") - layout.add_area("area3", ("left-start", "right-end"), "bottom") - layout.add_area("area4", "right", ("top-start", "middle-end")) + layout.add_areas( + area1="left,top", + area2="center,middle", + area3="left-start|right-end,bottom", + area4="right,top-start|middle-end", + ) - await view.mount(layout.add_widget(Placeholder(name="area1"), "area1")) - await view.mount(layout.add_widget(Placeholder(name="area2"), "area2")) - await view.mount(layout.add_widget(Placeholder(name="area3"), "area3")) - await view.mount(layout.add_widget(Placeholder(name="area4"), "area4")) + layout.place( + area1=Placeholder(name="area1"), + area2=Placeholder(name="area2"), + area3=Placeholder(name="area3"), + area4=Placeholder(name="area4"), + ) GridTest.run(title="Grid Test") \ No newline at end of file diff --git a/examples/live.py b/examples/live.py new file mode 100644 index 000000000..e69de29bb diff --git a/src/textual/_layout_resolve.py b/src/textual/_layout_resolve.py index 3655eb6bf..3228e9569 100644 --- a/src/textual/_layout_resolve.py +++ b/src/textual/_layout_resolve.py @@ -19,7 +19,7 @@ class Edge(Protocol): def layout_resolve(total: int, edges: Sequence[Edge]) -> List[int]: - """Divide total space to satisfy size, ratio, and minimum_size, constraints. + """Divide total space to satisfy size, fraction, and min_size, constraints. The returned list of integers should add up to total in most cases, unless it is impossible to satisfy all the constraints. For instance, if there are two edges diff --git a/src/textual/layout.py b/src/textual/layout.py index b67d5c4d8..b94c71703 100644 --- a/src/textual/layout.py +++ b/src/textual/layout.py @@ -122,6 +122,10 @@ class Layout(ABC): hidden=hidden_widgets, shown=shown_widgets, resized=resized_widgets ) + @abstractmethod + def get_widgets(self) -> Iterable[Widget]: + ... + @abstractmethod def generate_map( self, width: int, height: int, offset: Point = Point(0, 0) diff --git a/src/textual/layouts/dock.py b/src/textual/layouts/dock.py index 533d56301..5cc557b40 100644 --- a/src/textual/layouts/dock.py +++ b/src/textual/layouts/dock.py @@ -4,7 +4,7 @@ import sys from collections import defaultdict from dataclasses import dataclass import logging -from typing import TYPE_CHECKING, Sequence +from typing import Iterable, TYPE_CHECKING, Sequence from .._layout_resolve import layout_resolve from ..geometry import Region, Point @@ -29,8 +29,7 @@ DockEdge = Literal["top", "right", "bottom", "left"] class DockOptions: size: int | None = None fraction: int = 1 - minimum_size: int = 1 - maximim_size: int | None = None + min_size: int = 1 @dataclass @@ -45,6 +44,10 @@ class DockLayout(Layout): self.docks: list[Dock] = docks or [] super().__init__() + def get_widgets(self) -> Iterable[Widget]: + for dock in self.docks: + yield from dock.widgets + def generate_map( self, width: int, height: int, offset: Point = Point(0, 0) ) -> dict[Widget, OrderedRegion]: @@ -67,9 +70,7 @@ class DockLayout(Layout): for index, dock in enumerate(self.docks): dock_options = [ DockOptions( - widget.layout_size, - widget.layout_fraction, - widget.layout_minimim_size, + widget.layout_size, widget.layout_fraction, widget.layout_min_size ) for widget in dock.widgets ] diff --git a/src/textual/layouts/grid.py b/src/textual/layouts/grid.py index 855f679d3..a592a8495 100644 --- a/src/textual/layouts/grid.py +++ b/src/textual/layouts/grid.py @@ -210,13 +210,15 @@ class GridLayout(Layout): offset = (container - size) // 2 return offset - size = region.size offset_x = align(grid_size.width, container.width, col_align) offset_y = align(grid_size.height, container.height, row_align) region = region.translate(offset_x, offset_y) return region + def get_widgets(self) -> Iterable[Widget]: + return self.widgets.keys() + def generate_map( self, width: int, height: int, offset: Point = Point(0, 0) ) -> dict[Widget, OrderedRegion]: diff --git a/src/textual/page.py b/src/textual/page.py index b7837bdba..8595036e7 100644 --- a/src/textual/page.py +++ b/src/textual/page.py @@ -110,6 +110,7 @@ class Page(Widget): self._page.update(renderable) else: self._page.clear() + self.require_repaint() @property def virtual_size(self) -> Dimensions: diff --git a/src/textual/scrollbar.py b/src/textual/scrollbar.py index 7f3ab4095..2754b29c5 100644 --- a/src/textual/scrollbar.py +++ b/src/textual/scrollbar.py @@ -96,7 +96,6 @@ class ScrollBarRender: blank = " " * width_thickness foreground_meta = {"@mouse.up": "release", "@mouse.down": "grab"} - if window_size and size and virtual_size: step_size = virtual_size / size @@ -143,6 +142,7 @@ class ScrollBarRender: def __rich_console__( self, console: Console, options: ConsoleOptions ) -> RenderResult: + log.debug("SCROLLBAR RENDER") size = ( (options.height or console.height) if self.vertical @@ -254,4 +254,6 @@ if __name__ == "__main__": console = Console() bar = ScrollBarRender() - console.print(ScrollBarRender(position=15.3, thickness=5, vertical=False)) + console.print( + ScrollBarRender(position=15.3, window_size=100, thickness=5, vertical=True) + ) diff --git a/src/textual/view.py b/src/textual/view.py index 95efefe0d..abc38a0fd 100644 --- a/src/textual/view.py +++ b/src/textual/view.py @@ -97,6 +97,10 @@ class View(Widget): hidden, shown, resized = self.layout.reflow(width, height) self.app.refresh() + for widget in self.layout.get_widgets(): + if not self.is_mounted(widget): + await self.mount(widget) + for widget in hidden: widget.post_message_no_wait(events.Hide(self)) for widget in shown: @@ -104,9 +108,8 @@ class View(Widget): send_resize = shown send_resize.update(resized) + for widget, region in self.layout: - if not self.is_mounted(widget): - await self.mount(widget) if widget in send_resize: widget.post_message_no_wait( events.Resize(self, region.width, region.height) diff --git a/src/textual/widget.py b/src/textual/widget.py index d8a87c138..a4d3d38f5 100644 --- a/src/textual/widget.py +++ b/src/textual/widget.py @@ -66,7 +66,7 @@ class Widget(MessagePump): visible: Reactive[bool] = Reactive(True, layout=True) layout_size: Reactive[int | None] = Reactive(None) layout_fraction: Reactive[int] = Reactive(1) - layout_minimim_size: Reactive[int] = Reactive(1) + layout_min_size: Reactive[int] = Reactive(1) layout_offset_x: Reactive[float] = Reactive(0, layout=True) layout_offset_y: Reactive[float] = Reactive(0, layout=True) diff --git a/src/textual/widgets/__init__.py b/src/textual/widgets/__init__.py index 58bee8acb..257bc2dfd 100644 --- a/src/textual/widgets/__init__.py +++ b/src/textual/widgets/__init__.py @@ -1,5 +1,6 @@ from ._footer import Footer from ._header import Header +from ._button import Button from ._placeholder import Placeholder from ._scroll_view import ScrollView from ._static import Static diff --git a/src/textual/widgets/_footer.py b/src/textual/widgets/_footer.py index 0aea0def3..af06ceb7d 100644 --- a/src/textual/widgets/_footer.py +++ b/src/textual/widgets/_footer.py @@ -1,18 +1,19 @@ -from rich.console import Console, ConsoleOptions, RenderableType -from rich.repr import rich_repr, RichReprResult +from rich.console import RenderableType from rich.text import Text +import rich.repr from .. import events from ..widget import Widget +@rich.repr.auto class Footer(Widget): def __init__(self) -> None: self.keys: list[tuple[str, str]] = [] super().__init__() self.layout_size = 1 - def __rich_repr__(self) -> RichReprResult: + def __rich_repr__(self) -> rich.repr.RichReprResult: yield "footer" def add_key(self, key: str, label: str) -> None: diff --git a/src/textual/widgets/_scroll_view.py b/src/textual/widgets/_scroll_view.py index 5193a0b4c..914ff2794 100644 --- a/src/textual/widgets/_scroll_view.py +++ b/src/textual/widgets/_scroll_view.py @@ -7,7 +7,6 @@ from rich.style import StyleType from .. import events -from ..geometry import Dimensions from ..message import Message from ..scrollbar import ScrollTo, ScrollBar from ..geometry import clamp diff --git a/src/textual/widgets/_static.py b/src/textual/widgets/_static.py index bc29e5505..fe58422e0 100644 --- a/src/textual/widgets/_static.py +++ b/src/textual/widgets/_static.py @@ -11,3 +11,7 @@ class Static(Widget): def render(self) -> RenderableType: return self.renderable + + async def update(self, renderable: RenderableType) -> None: + self.renderable = renderable + self.require_repaint()