From d962dcd49c86c404b661fc298663a7cc5671feb2 Mon Sep 17 00:00:00 2001 From: Will McGugan Date: Tue, 27 Sep 2022 16:35:40 +0100 Subject: [PATCH] new align --- docs/examples/events/dictionary.py | 3 +- docs/examples/guide/dom3.py | 4 +- docs/examples/guide/dom4.py | 2 +- docs/examples/guide/input/mouse01.py | 2 +- docs/examples/guide/layout/center_layout.css | 21 -- docs/examples/guide/layout/center_layout.py | 16 -- .../guide/layout/combining_layouts.py | 18 +- docs/examples/guide/layout/grid_layout1.css | 2 +- docs/examples/guide/layout/grid_layout2.py | 2 +- docs/examples/guide/layout/layers.css | 2 +- docs/examples/guide/layout/offset.css | 36 ---- docs/examples/guide/layout/offset.py | 29 --- .../guide/layout/utility_containers.py | 8 +- docs/examples/guide/widgets/fizzbuzz01.css | 2 +- docs/examples/guide/widgets/fizzbuzz02.css | 2 +- docs/examples/guide/widgets/hello01.css | 2 +- docs/examples/guide/widgets/hello02.css | 2 +- docs/examples/guide/widgets/hello03.css | 2 +- docs/examples/guide/widgets/hello04.css | 2 +- docs/examples/guide/widgets/hello04.py | 4 +- docs/examples/guide/widgets/hello05.css | 2 +- docs/examples/styles/align.css | 13 ++ docs/examples/styles/align.py | 11 ++ docs/examples/styles/layout.css | 6 - docs/examples/styles/layout.py | 11 +- docs/examples/styles/overflow.py | 2 +- docs/examples/styles/scrollbar_size.py | 4 +- docs/examples/styles/scrollbars.py | 6 +- docs/examples/tutorial/clock01.py | 22 --- docs/examples/tutorial/clock02.py | 22 --- docs/examples/tutorial/stopwatch.py | 2 +- docs/examples/tutorial/stopwatch02.py | 2 +- docs/examples/tutorial/stopwatch03.py | 2 +- docs/examples/tutorial/stopwatch04.py | 2 +- docs/examples/tutorial/stopwatch05.py | 2 +- docs/examples/tutorial/stopwatch06.py | 2 +- docs/examples/widgets/button.py | 8 +- docs/guide/CSS.md | 23 +-- docs/guide/app.md | 12 +- docs/guide/events.md | 2 +- docs/guide/input.md | 2 +- docs/guide/layout.md | 185 +++++------------- docs/guide/styles.md | 14 +- docs/reference/containers.md | 1 + docs/styles/align.md | 68 +++++++ docs/styles/layout.md | 3 +- e2e_tests/test_apps/basic.py | 2 +- examples/calculator.py | 2 +- examples/code_browser.py | 3 +- examples/dictionary.py | 2 +- mkdocs.yml | 4 +- sandbox/darren/basic.py | 2 +- sandbox/darren/buttons.py | 5 +- sandbox/darren/just_a_box.css | 2 +- sandbox/will/add_remove.py | 8 +- sandbox/will/align.css | 14 ++ sandbox/will/align.py | 19 ++ sandbox/will/basic.css | 10 +- sandbox/will/basic.py | 2 +- sandbox/will/calculator.py | 2 +- sandbox/will/center2.py | 2 +- sandbox/will/design.py | 2 +- sandbox/will/just_a_box.py | 2 +- sandbox/will/offset.css | 2 +- sandbox/will/offset.py | 7 +- sandbox/will/tree.py | 2 +- src/textual/_arrange.py | 13 +- src/textual/cli/previews/borders.py | 4 +- src/textual/cli/previews/easing.py | 16 +- src/textual/{layout.py => containers.py} | 14 +- src/textual/css/styles.py | 20 +- src/textual/css/stylesheet.py | 2 +- src/textual/layouts/center.py | 36 ---- src/textual/layouts/factory.py | 2 - src/textual/layouts/horizontal.py | 18 +- src/textual/layouts/vertical.py | 14 +- src/textual/widgets/_welcome.py | 10 +- .../__snapshots__/test_snapshots.ambr | 158 --------------- tests/snapshot_tests/test_snapshots.py | 4 - tests/test_layouts_center.py | 28 --- 80 files changed, 336 insertions(+), 687 deletions(-) delete mode 100644 docs/examples/guide/layout/center_layout.css delete mode 100644 docs/examples/guide/layout/center_layout.py delete mode 100644 docs/examples/guide/layout/offset.css delete mode 100644 docs/examples/guide/layout/offset.py create mode 100644 docs/examples/styles/align.css create mode 100644 docs/examples/styles/align.py delete mode 100644 docs/examples/tutorial/clock01.py delete mode 100644 docs/examples/tutorial/clock02.py create mode 100644 docs/reference/containers.md create mode 100644 docs/styles/align.md create mode 100644 sandbox/will/align.css create mode 100644 sandbox/will/align.py rename src/textual/{layout.py => containers.py} (67%) delete mode 100644 src/textual/layouts/center.py delete mode 100644 tests/test_layouts_center.py diff --git a/docs/examples/events/dictionary.py b/docs/examples/events/dictionary.py index 483abed2d..392070624 100644 --- a/docs/examples/events/dictionary.py +++ b/docs/examples/events/dictionary.py @@ -6,9 +6,8 @@ except ImportError: raise ImportError("Please install httpx with 'pip install httpx' ") from rich.json import JSON - from textual.app import App, ComposeResult -from textual.layout import Vertical +from textual.containers import Vertical from textual.widgets import Static, TextInput diff --git a/docs/examples/guide/dom3.py b/docs/examples/guide/dom3.py index e78ee5c05..bd68f24b4 100644 --- a/docs/examples/guide/dom3.py +++ b/docs/examples/guide/dom3.py @@ -1,6 +1,6 @@ from textual.app import App, ComposeResult -from textual.layout import Container, Horizontal -from textual.widgets import Header, Footer, Static, Button +from textual.containers import Container, Horizontal +from textual.widgets import Button, Footer, Header, Static QUESTION = "Do you want to learn about Textual CSS?" diff --git a/docs/examples/guide/dom4.py b/docs/examples/guide/dom4.py index dd12a4ea8..3191138d4 100644 --- a/docs/examples/guide/dom4.py +++ b/docs/examples/guide/dom4.py @@ -1,5 +1,5 @@ from textual.app import App, ComposeResult -from textual.layout import Container, Horizontal +from textual.containers import Container, Horizontal from textual.widgets import Header, Footer, Static, Button QUESTION = "Do you want to learn about Textual CSS?" diff --git a/docs/examples/guide/input/mouse01.py b/docs/examples/guide/input/mouse01.py index 7c0de3f27..cd2b3621b 100644 --- a/docs/examples/guide/input/mouse01.py +++ b/docs/examples/guide/input/mouse01.py @@ -1,6 +1,6 @@ from textual import events from textual.app import App, ComposeResult -from textual.layout import Container +from textual.containers import Container from textual.widgets import Static, TextLog diff --git a/docs/examples/guide/layout/center_layout.css b/docs/examples/guide/layout/center_layout.css deleted file mode 100644 index 793c7dddc..000000000 --- a/docs/examples/guide/layout/center_layout.css +++ /dev/null @@ -1,21 +0,0 @@ -Screen { - layout: center; -} - -#bottom { - width: 20; - height: 20; - background: red; -} - -#middle { - width: 26; - height: 12; - background: green; -} - -#top { - width: 32; - height: 6; - background: blue; -} diff --git a/docs/examples/guide/layout/center_layout.py b/docs/examples/guide/layout/center_layout.py deleted file mode 100644 index 0cf31c615..000000000 --- a/docs/examples/guide/layout/center_layout.py +++ /dev/null @@ -1,16 +0,0 @@ -from textual.app import App, ComposeResult -from textual.widgets import Static - - -class CenterLayoutExample(App): - CSS_PATH = "center_layout.css" - - def compose(self) -> ComposeResult: - yield Static("One", id="bottom") - yield Static("Two", id="middle") - yield Static("Three", id="top") - - -if __name__ == "__main__": - app = CenterLayoutExample() - app.run() diff --git a/docs/examples/guide/layout/combining_layouts.py b/docs/examples/guide/layout/combining_layouts.py index 9dd34ab96..d832bd628 100644 --- a/docs/examples/guide/layout/combining_layouts.py +++ b/docs/examples/guide/layout/combining_layouts.py @@ -1,4 +1,4 @@ -from textual import layout +from textual.containers import Container, Horizontal, Vertical from textual.app import ComposeResult, App from textual.widgets import Static, Header @@ -8,19 +8,19 @@ class CombiningLayoutsExample(App): def compose(self) -> ComposeResult: yield Header() - yield layout.Container( - layout.Vertical( + yield Container( + Vertical( *[Static(f"Vertical layout, child {number}") for number in range(15)], id="left-pane", ), - layout.Horizontal( + Horizontal( Static("Horizontally"), Static("Positioned"), Static("Children"), Static("Here"), id="top-right", ), - layout.Container( + Container( Static("This"), Static("panel"), Static("is"), @@ -31,14 +31,6 @@ class CombiningLayoutsExample(App): id="app-grid", ) - async def on_key(self, event) -> None: - await self.dispatch_key(event) - - def key_a(self): - print(self.stylesheet.variables["boost"]) - print(self.stylesheet.variables["boost-lighten-1"]) - print(self.stylesheet.variables["boost-lighten-2"]) - if __name__ == "__main__": app = CombiningLayoutsExample() diff --git a/docs/examples/guide/layout/grid_layout1.css b/docs/examples/guide/layout/grid_layout1.css index 1749a75c2..3324c7400 100644 --- a/docs/examples/guide/layout/grid_layout1.css +++ b/docs/examples/guide/layout/grid_layout1.css @@ -1,6 +1,6 @@ Screen { layout: grid; - grid-size: 3; + grid-size: 3 2; } .box { diff --git a/docs/examples/guide/layout/grid_layout2.py b/docs/examples/guide/layout/grid_layout2.py index fa0094073..407c081e1 100644 --- a/docs/examples/guide/layout/grid_layout2.py +++ b/docs/examples/guide/layout/grid_layout2.py @@ -3,7 +3,7 @@ from textual.widgets import Static class GridLayoutExample(App): - CSS_PATH = "grid_layout1.css" + CSS_PATH = "grid_layout2.css" def compose(self) -> ComposeResult: yield Static("One", classes="box") diff --git a/docs/examples/guide/layout/layers.css b/docs/examples/guide/layout/layers.css index 7be3f8d83..4465b2d91 100644 --- a/docs/examples/guide/layout/layers.css +++ b/docs/examples/guide/layout/layers.css @@ -1,5 +1,5 @@ Screen { - layout: center; + align: center middle; layers: below above; } diff --git a/docs/examples/guide/layout/offset.css b/docs/examples/guide/layout/offset.css deleted file mode 100644 index 4a64c7115..000000000 --- a/docs/examples/guide/layout/offset.css +++ /dev/null @@ -1,36 +0,0 @@ -Screen { - layout: center; -} - -#parent { - layout: center; - background: #9e9e9e; - width: 60; - height: 20; -} - -Box { - color: auto; - width: auto; - height: auto; - padding: 1 2; -} - -#box1 { - background: $primary; -} - -#box2 { - background: $secondary; - offset: 12 4; -} - -#box3 { - background: lightseagreen; - offset: -12 -4; -} - -#box4 { - background: darkred; - offset: -26 10; -} diff --git a/docs/examples/guide/layout/offset.py b/docs/examples/guide/layout/offset.py deleted file mode 100644 index 738297307..000000000 --- a/docs/examples/guide/layout/offset.py +++ /dev/null @@ -1,29 +0,0 @@ -from rich.console import RenderableType - -from textual import layout -from textual.app import App, ComposeResult -from textual.widgets import Static - - -class Box(Static): - def render(self) -> RenderableType: - x, y = self.styles.offset - return f"{self.id}: offset = ({x}, {y})" - - -class OffsetExample(App): - CSS_PATH = "offset.css" - - def compose(self) -> ComposeResult: - yield layout.Container( - Box(id="box1"), - Box(id="box2"), - Box(id="box3"), - Box(id="box4"), - id="parent", - ) - - -if __name__ == "__main__": - app = OffsetExample() - app.run() diff --git a/docs/examples/guide/layout/utility_containers.py b/docs/examples/guide/layout/utility_containers.py index fa44d0088..eadf58b4c 100644 --- a/docs/examples/guide/layout/utility_containers.py +++ b/docs/examples/guide/layout/utility_containers.py @@ -1,5 +1,5 @@ -from textual import layout from textual.app import App, ComposeResult +from textual.containers import Horizontal, Vertical from textual.widgets import Static @@ -7,13 +7,13 @@ class UtilityContainersExample(App): CSS_PATH = "utility_containers.css" def compose(self) -> ComposeResult: - yield layout.Horizontal( - layout.Vertical( + yield Horizontal( + Vertical( Static("One"), Static("Two"), classes="column", ), - layout.Vertical( + Vertical( Static("Three"), Static("Four"), classes="column", diff --git a/docs/examples/guide/widgets/fizzbuzz01.css b/docs/examples/guide/widgets/fizzbuzz01.css index 0893c4dbe..ed041d2dc 100644 --- a/docs/examples/guide/widgets/fizzbuzz01.css +++ b/docs/examples/guide/widgets/fizzbuzz01.css @@ -1,5 +1,5 @@ Screen { - layout: center; + align: center middle; } FizzBuzz { diff --git a/docs/examples/guide/widgets/fizzbuzz02.css b/docs/examples/guide/widgets/fizzbuzz02.css index d5c2e0c28..a8fe581c1 100644 --- a/docs/examples/guide/widgets/fizzbuzz02.css +++ b/docs/examples/guide/widgets/fizzbuzz02.css @@ -1,5 +1,5 @@ Screen { - layout: center; + align: center middle; } FizzBuzz { diff --git a/docs/examples/guide/widgets/hello01.css b/docs/examples/guide/widgets/hello01.css index 8fd85db4d..87b9fc77f 100644 --- a/docs/examples/guide/widgets/hello01.css +++ b/docs/examples/guide/widgets/hello01.css @@ -1,3 +1,3 @@ Screen { - layout: center; + align: center middle; } diff --git a/docs/examples/guide/widgets/hello02.css b/docs/examples/guide/widgets/hello02.css index 307261e85..6a9503d69 100644 --- a/docs/examples/guide/widgets/hello02.css +++ b/docs/examples/guide/widgets/hello02.css @@ -1,5 +1,5 @@ Screen { - layout: center; + align: center middle; } Hello { diff --git a/docs/examples/guide/widgets/hello03.css b/docs/examples/guide/widgets/hello03.css index 44847f8ac..1e46fd415 100644 --- a/docs/examples/guide/widgets/hello03.css +++ b/docs/examples/guide/widgets/hello03.css @@ -1,5 +1,5 @@ Screen { - layout: center; + align: center middle; } Hello { diff --git a/docs/examples/guide/widgets/hello04.css b/docs/examples/guide/widgets/hello04.css index 8fd85db4d..87b9fc77f 100644 --- a/docs/examples/guide/widgets/hello04.css +++ b/docs/examples/guide/widgets/hello04.css @@ -1,3 +1,3 @@ Screen { - layout: center; + align: center middle; } diff --git a/docs/examples/guide/widgets/hello04.py b/docs/examples/guide/widgets/hello04.py index f026b5880..450f26697 100644 --- a/docs/examples/guide/widgets/hello04.py +++ b/docs/examples/guide/widgets/hello04.py @@ -25,14 +25,14 @@ class Hello(Static): """Display a greeting.""" DEFAULT_CSS = """ - Hello { + Hello { width: 40; height: 9; padding: 1 2; background: $panel; border: $secondary tall; content-align: center middle; - } + } """ def on_mount(self) -> None: diff --git a/docs/examples/guide/widgets/hello05.css b/docs/examples/guide/widgets/hello05.css index 44847f8ac..1e46fd415 100644 --- a/docs/examples/guide/widgets/hello05.css +++ b/docs/examples/guide/widgets/hello05.css @@ -1,5 +1,5 @@ Screen { - layout: center; + align: center middle; } Hello { diff --git a/docs/examples/styles/align.css b/docs/examples/styles/align.css new file mode 100644 index 000000000..a49af0571 --- /dev/null +++ b/docs/examples/styles/align.css @@ -0,0 +1,13 @@ +Screen { + align: center middle; +} + +.box { + width: 40; + height: 5; + margin: 1; + padding: 1; + background: green; + color: white 90%; + border: heavy white; +} diff --git a/docs/examples/styles/align.py b/docs/examples/styles/align.py new file mode 100644 index 000000000..6abee37ce --- /dev/null +++ b/docs/examples/styles/align.py @@ -0,0 +1,11 @@ +from textual.app import App +from textual.widgets import Static + + +class AlignApp(App): + def compose(self): + yield Static("Vertical alignment with [b]Textual[/]", classes="box") + yield Static("Take note, browsers.", classes="box") + + +app = AlignApp(css_path="align.css") diff --git a/docs/examples/styles/layout.css b/docs/examples/styles/layout.css index 1b8cacd13..b0f7e3385 100644 --- a/docs/examples/styles/layout.css +++ b/docs/examples/styles/layout.css @@ -10,12 +10,6 @@ height: auto; } -#center-layout { - layout: center; - background: darkslateblue; - height: 7; -} - Static { margin: 1; width: 12; diff --git a/docs/examples/styles/layout.py b/docs/examples/styles/layout.py index be91681f6..f7c04e984 100644 --- a/docs/examples/styles/layout.py +++ b/docs/examples/styles/layout.py @@ -1,27 +1,22 @@ -from textual import layout from textual.app import App -from textual.widget import Widget +from textual.containers import Container from textual.widgets import Static class LayoutApp(App): def compose(self): - yield layout.Container( + yield Container( Static("Layout"), Static("Is"), Static("Vertical"), id="vertical-layout", ) - yield layout.Container( + yield Container( Static("Layout"), Static("Is"), Static("Horizontal"), id="horizontal-layout", ) - yield layout.Container( - Static("Center"), - id="center-layout", - ) app = LayoutApp(css_path="layout.css") diff --git a/docs/examples/styles/overflow.py b/docs/examples/styles/overflow.py index d3ea7c5ca..b9a6c3383 100644 --- a/docs/examples/styles/overflow.py +++ b/docs/examples/styles/overflow.py @@ -1,6 +1,6 @@ from textual.app import App from textual.widgets import Static -from textual.layout import Horizontal, Vertical +from textual.containers import Horizontal, Vertical TEXT = """I must not fear. Fear is the mind-killer. diff --git a/docs/examples/styles/scrollbar_size.py b/docs/examples/styles/scrollbar_size.py index 0f8b34082..97facad70 100644 --- a/docs/examples/styles/scrollbar_size.py +++ b/docs/examples/styles/scrollbar_size.py @@ -1,5 +1,5 @@ from textual.app import App -from textual import layout +from textual.containers import Vertical from textual.widgets import Static TEXT = """I must not fear. @@ -14,7 +14,7 @@ Where the fear has gone there will be nothing. Only I will remain. class ScrollbarApp(App): def compose(self): - yield layout.Vertical(Static(TEXT * 5), classes="panel") + yield Vertical(Static(TEXT * 5), classes="panel") app = ScrollbarApp(css_path="scrollbar_size.css") diff --git a/docs/examples/styles/scrollbars.py b/docs/examples/styles/scrollbars.py index 5f50ee208..f426c0d57 100644 --- a/docs/examples/styles/scrollbars.py +++ b/docs/examples/styles/scrollbars.py @@ -1,5 +1,5 @@ from textual.app import App -from textual import layout +from textual.containers import Vertical from textual.widgets import Static TEXT = """I must not fear. @@ -14,8 +14,8 @@ Where the fear has gone there will be nothing. Only I will remain. class ScrollbarApp(App): def compose(self): - yield layout.Vertical(Static(TEXT * 5), classes="panel1") - yield layout.Vertical(Static(TEXT * 5), classes="panel2") + yield Vertical(Static(TEXT * 5), classes="panel1") + yield Vertical(Static(TEXT * 5), classes="panel2") app = ScrollbarApp(css_path="scrollbars.css") diff --git a/docs/examples/tutorial/clock01.py b/docs/examples/tutorial/clock01.py deleted file mode 100644 index af5f33194..000000000 --- a/docs/examples/tutorial/clock01.py +++ /dev/null @@ -1,22 +0,0 @@ -from datetime import datetime - -from textual.app import App -from textual.widget import Widget - - -class Clock(Widget): - def on_mount(self): - self.styles.content_align = ("center", "middle") - self.auto_refresh = 1.0 - - def render(self): - return datetime.now().strftime("%X") - - -class ClockApp(App): - def compose(self): - yield Clock() - - -app = ClockApp() -app.run() diff --git a/docs/examples/tutorial/clock02.py b/docs/examples/tutorial/clock02.py deleted file mode 100644 index 3bfd17637..000000000 --- a/docs/examples/tutorial/clock02.py +++ /dev/null @@ -1,22 +0,0 @@ -from datetime import datetime - -from textual.app import App -from textual.widget import Widget - - -class Clock(Widget): - def on_mount(self): - self.styles.content_align = ("center", "middle") - self.auto_refresh = 1.0 - - def render(self): - return datetime.now().strftime("%c") - - -class ClockApp(App): - def compose(self): - yield Clock() - - -app = ClockApp() -app.run() diff --git a/docs/examples/tutorial/stopwatch.py b/docs/examples/tutorial/stopwatch.py index 72e9e7139..ffb87ea4c 100644 --- a/docs/examples/tutorial/stopwatch.py +++ b/docs/examples/tutorial/stopwatch.py @@ -1,7 +1,7 @@ from time import monotonic from textual.app import App, ComposeResult -from textual.layout import Container +from textual.containers import Container from textual.reactive import reactive from textual.widgets import Button, Header, Footer, Static diff --git a/docs/examples/tutorial/stopwatch02.py b/docs/examples/tutorial/stopwatch02.py index ab1e843e1..8baa3831e 100644 --- a/docs/examples/tutorial/stopwatch02.py +++ b/docs/examples/tutorial/stopwatch02.py @@ -1,5 +1,5 @@ from textual.app import App, ComposeResult -from textual.layout import Container +from textual.containers import Container from textual.widgets import Button, Header, Footer, Static diff --git a/docs/examples/tutorial/stopwatch03.py b/docs/examples/tutorial/stopwatch03.py index 7b879673a..0c455fecb 100644 --- a/docs/examples/tutorial/stopwatch03.py +++ b/docs/examples/tutorial/stopwatch03.py @@ -1,5 +1,5 @@ from textual.app import App, ComposeResult -from textual.layout import Container +from textual.containers import Container from textual.widgets import Button, Header, Footer, Static diff --git a/docs/examples/tutorial/stopwatch04.py b/docs/examples/tutorial/stopwatch04.py index 8ad6e7cc3..2394fd20c 100644 --- a/docs/examples/tutorial/stopwatch04.py +++ b/docs/examples/tutorial/stopwatch04.py @@ -1,5 +1,5 @@ from textual.app import App, ComposeResult -from textual.layout import Container +from textual.containers import Container from textual.widgets import Button, Header, Footer, Static diff --git a/docs/examples/tutorial/stopwatch05.py b/docs/examples/tutorial/stopwatch05.py index b2281515b..6543ac5eb 100644 --- a/docs/examples/tutorial/stopwatch05.py +++ b/docs/examples/tutorial/stopwatch05.py @@ -1,7 +1,7 @@ from time import monotonic from textual.app import App, ComposeResult -from textual.layout import Container +from textual.containers import Container from textual.reactive import reactive from textual.widgets import Button, Header, Footer, Static diff --git a/docs/examples/tutorial/stopwatch06.py b/docs/examples/tutorial/stopwatch06.py index 04a609322..e446d9798 100644 --- a/docs/examples/tutorial/stopwatch06.py +++ b/docs/examples/tutorial/stopwatch06.py @@ -1,7 +1,7 @@ from time import monotonic from textual.app import App, ComposeResult -from textual.layout import Container +from textual.containers import Container from textual.reactive import reactive from textual.widgets import Button, Header, Footer, Static diff --git a/docs/examples/widgets/button.py b/docs/examples/widgets/button.py index aa152a921..b7958e18b 100644 --- a/docs/examples/widgets/button.py +++ b/docs/examples/widgets/button.py @@ -1,5 +1,5 @@ -from textual import layout from textual.app import App, ComposeResult +from textual.containers import Horizontal, Vertical from textual.widgets import Button, Static @@ -7,8 +7,8 @@ class ButtonsApp(App[str]): CSS_PATH = "button.css" def compose(self) -> ComposeResult: - yield layout.Horizontal( - layout.Vertical( + yield Horizontal( + Vertical( Static("Standard Buttons", classes="header"), Button("Default"), Button("Primary!", variant="primary"), @@ -16,7 +16,7 @@ class ButtonsApp(App[str]): Button.warning("Warning!"), Button.error("Error!"), ), - layout.Vertical( + Vertical( Static("Disabled Buttons", classes="header"), Button("Default", disabled=True), Button("Primary!", variant="primary", disabled=True), diff --git a/docs/guide/CSS.md b/docs/guide/CSS.md index ded358811..7bca457f8 100644 --- a/docs/guide/CSS.md +++ b/docs/guide/CSS.md @@ -1,17 +1,12 @@ # Textual CSS -Textual uses CSS to apply style to widgets. If you have any exposure to web development you will have encountered CSS, but don't worry if you haven't: this section will get you up to speed. +Textual uses CSS to apply style to widgets. If you have any exposure to web development you will have encountered CSS, but don't worry if you haven't: this chapter will get you up to speed. ## Stylesheets -CSS stands for _Cascading Stylesheets_. A stylesheet is a list of styles and rules about how those styles should be applied to a page. In the case of Textual, the stylesheet applies [styles](./styles.md) to widgets but otherwise it is the same idea. +CSS stands for _Cascading Stylesheets_. A stylesheet is a list of styles and rules about how those styles should be applied to a web page. In the case of Textual, the stylesheet applies [styles](./styles.md) to widgets but otherwise it is the same idea. -!!! note - - Depending on what you want to build with Textual, you may not need to learn Textual CSS at all. Widgets are packaged with CSS styles so apps may not need any additional CSS. - - -When Textual loads CSS it sets attributes of your widgets's `style` object. The effect is the same as if you had set attributes in Python. +When Textual loads CSS it sets attributes on your widgets' `style` object. The effect is the same as if you had set attributes in Python. CSS is typically stored in an external file with the extension `.css` alongside your Python code. @@ -87,11 +82,12 @@ With the above example, the DOM will look like the following: This doesn't look much like a tree yet. Let's add a header and a footer to this application, which will create more _branches_ of the tree: === "dom2.py" - - ```python + + ```python hl_lines="7 8" --8<-- "docs/examples/guide/dom2.py" ``` + === "Output" ```{.textual path="docs/examples/guide/dom2.py"} @@ -105,7 +101,7 @@ With a header and a footer widget the DOM looks the this: !!! note - We've simplified the above example somewhat. Both the Header and Footer widgets contain children of their own. When building an app with pre-built widgets you rarely need to know how they are constructed unless you plan on changing the styles for the individual components. + We've simplified the above example somewhat. Both the Header and Footer widgets contain children of their own. When building an app with pre-built widgets you rarely need to know how they are constructed unless you plan on changing the styles of individual components. Both Header and Footer are children of the Screen object. @@ -432,8 +428,3 @@ Let's say we define a variable `$success: lime;`. Our `$border` variable could then be updated to `$border: wide $success;`, which will be translated to `$border: wide lime;`. -Textual CSS ships with a number of builtin variables. -These can be used in CSS without any additional imports or declarations. -For more information on these builtin variables, see [this page](#). - -[//]: # (TODO: Fill in the link above when builtin style variables are documented) diff --git a/docs/guide/app.md b/docs/guide/app.md index 7e828cf4e..8038e387c 100644 --- a/docs/guide/app.md +++ b/docs/guide/app.md @@ -40,7 +40,7 @@ If you hit ++ctrl+c++ Textual will exit application mode and return you to the c ## Events -Textual has an event system you can use to respond to key presses, mouse actions, and internal state changes. Event handlers are methods which are prefixed with `on_` followed by the name of the event. +Textual has an event system you can use to respond to key presses, mouse actions, and internal state changes. Event handlers are methods prefixed with `on_` followed by the name of the event. One such event is the *mount* event which is sent to an application after it enters application mode. You can respond to this event by defining a method called `on_mount`. @@ -59,15 +59,15 @@ The `on_mount` handler sets the `self.screen.styles.background` attribute to `"d ```{.textual path="docs/examples/app/event01.py" hl_lines="23-25"} ``` -The key event handler (`on_key`) specifies an `event` parameter which will receive a [Key][textual.events.Key] instance. Every event has an associated event object which will be passed to the handler method if it is present in the method's parameter list. +The key event handler (`on_key`) has an `event` parameter which will receive a [Key][textual.events.Key] instance. Every event has an associated event object which will be passed to the handler method if it is present in the method's parameter list. !!! note It is unusual (but not unprecedented) for a method's parameters to affect how it is called. Textual accomplishes this by inspecting the method prior to calling it. -For some events, such as the key event, the event object contains additional information. In the case of [Key][textual.events.Key] it will contain the key that was pressed. +For some events contains additional information. In the case of [Key][textual.events.Key] it will contain the key that was pressed. -The `on_key` method above uses the `key` attribute on the Key event to change the background color if any of the keys ++0++ to ++9++ are pressed. +The `on_key` method above changes the background color if any of the keys from ++0++ to ++9++ are pressed. ### Async events @@ -81,7 +81,7 @@ Textual knows to *await* your event handlers if they are coroutines (i.e. prefix ## Widgets -Widgets are self-contained components responsible for generating the output for a portion of the screen and can respond to events in much the same way as the App. Most apps that do anything interesting will contain at least one (and probably many) widgets which together form a User Interface. +Widgets are self-contained components responsible for generating the output for a portion of the screen. Widgets respond to events in much the same way as the App. Most apps that do anything interesting will contain at least one (and probably many) widgets which together form a User Interface. Widgets can be as simple as a piece of text, a button, or a fully-fledge component like a text editor or file browser (which may contain widgets of their own). @@ -106,7 +106,7 @@ Notice the `on_button_pressed` method which handles the [Button.Pressed][textual While composing is the preferred way of adding widgets when your app starts it is sometimes necessary to add new widget(s) in response to events. You can do this by calling [mount()][textual.widget.Widget.mount] which will add a new widget to the UI. -Here's an app which adds the welcome widget in response to any key press: +Here's an app which adds a welcome widget in response to any key press: ```python title="widgets02.py" --8<-- "docs/examples/app/widgets02.py" diff --git a/docs/guide/events.md b/docs/guide/events.md index 2098f5cfe..242c34cfd 100644 --- a/docs/guide/events.md +++ b/docs/guide/events.md @@ -172,7 +172,7 @@ Let's look at an example which looks up word definitions from an [api](https://d === "dictionary.py" - ```python title="dictionary.py" hl_lines="28" + ```python title="dictionary.py" hl_lines="27" --8<-- "docs/examples/events/dictionary.py" ``` === "dictionary.css" diff --git a/docs/guide/input.md b/docs/guide/input.md index 1d24da5f3..2fab4eeac 100644 --- a/docs/guide/input.md +++ b/docs/guide/input.md @@ -188,7 +188,7 @@ Textual will send a [Enter](../events/enter.md) event to a widget when the mouse ### Click events -There are three events associated with clicking a button on your mouse. When the button is initially pressed, Textual sends a [MouseDown](../events/mouse_down.md) event, followed by [MouseUp](../events/mouse_up.md) when the button is released. Textual then sends a final [Click](../events/mouse_click.md) event. +There are three events associated with clicking a button on your mouse. When the button is initially pressed, Textual sends a [MouseDown](../events/mouse_down.md) event, followed by [MouseUp](../events/mouse_up.md) when the button is released. Textual then sends a final [Click](../events/click.md) event. If you want your app to respond to a mouse click you should prefer the Click event (and not MouseDown or MouseUp). This is because a future version of Textual may support other pointing devices which don't have up and down states. diff --git a/docs/guide/layout.md b/docs/guide/layout.md index 8275b9e65..adbe5c16f 100644 --- a/docs/guide/layout.md +++ b/docs/guide/layout.md @@ -132,42 +132,10 @@ To enable horizontal scrolling, we can use the `overflow-x: auto;` declaration: With `overflow-x: auto;`, Textual automatically adds a horizontal scrollbar since the width of the children exceeds the available horizontal space in the parent container. -## Center - -The `center` layout will place a widget directly in the center of the container. - -
---8<-- "docs/images/layout/center.excalidraw.svg" -
- -If there's more than one child widget inside a container using `center` layout, -the child widgets will be "stacked" on top of each other, as demonstrated below. - -=== "Output" - - ```{.textual path="docs/examples/guide/layout/center_layout.py"} - ``` - -=== "center_layout.py" - - ```python - --8<-- "docs/examples/guide/layout/center_layout.py" - ``` - -=== "center_layout.css" - - ```sass hl_lines="2" - --8<-- "docs/examples/guide/layout/center_layout.css" - ``` - -Widgets are drawn in the order they are yielded from `compose`. -The first yielded widget appears at the bottom, and widgets yielded after it are stacked on top. - ## Utility containers Textual comes with several "container" widgets. -These are `layout.Vertical`, `layout.Horizontal`, and `layout.Center`. -Internally, these widgets contain some default CSS containing a `layout` declaration. +These are [Vertical][textual.containers.Vertical], [Horizontal][textual.containers.Horizontal], and [Grid][textual.containers.Grid] which have the corresponding layout. The example below shows how we can combine these containers to create a simple 2x2 grid. Inside a single `Horizontal` container, we place two `Vertical` containers. @@ -205,24 +173,11 @@ The diagram below hints at what can be achieved using `layout: grid`. !!! note - Grid layouts in Textual have very little in common with browser-based CSS Grid. + Grid layouts in Textual have little in common with browser-based CSS Grid. -To get started with grid layout, you define the number of columns and rows in your grid using the `grid-size` CSS property and set `layout: grid`. -When you yield widgets from the `compose` method, they're inserted into the "cells" of your grid in left-to-right, top-to-bottom order. +To get started with grid layout, define the number of columns and rows in your grid with the `grid-size` CSS property and set `layout: grid`. Widgets are inserted into the "cells" of the grid from left-to-right and top-to-bottom order. -For example, `grid-size: 3 2;` defines a grid with 3 columns and 2 rows. -We can now yield 6 widgets from our `compose` method, and they'll be inserted into all available cells in the grid. - -```{.textual path="docs/examples/guide/layout/grid_layout1.py"} -``` - -If we were to yield a seventh widget from our `compose` method, it would not be visible as the grid does not contain enough cells to accommodate it. - -We can optionally omit the number of rows from `grid-size`, and Textual will create them "on-demand" based on the number of widgets yielded from `compose`. -Widgets will be inserted into the grid in the order they're yielded, and when all cells in a row become occupied, a new row will be created to accommodate the next widget. - -Let's create a simple grid with three columns. In our CSS, we'll specify this using `grid-size: 3`. -Then, we'll yield six widgets from `compose`, in order to fully occupy two rows in the grid. +The following example creates a 3 x 2 grid and adds six widgets to it === "Output" @@ -241,11 +196,26 @@ Then, we'll yield six widgets from `compose`, in order to fully occupy two rows --8<-- "docs/examples/guide/layout/grid_layout1.css" ``` -To further illustrate the "on-demand" nature of `layout: grid` when the number of rows is omitted, here's what happens when you modify the example -above to yield an additional widget (for a total of seven widgets). -```{.textual path="docs/examples/guide/layout/grid_layout2.py"} -``` +If we were to yield a seventh widget from our `compose` method, it would not be visible as the grid does not contain enough cells to accommodate it. We can tell Textual to add new rows on demand to fit the number of widgets, by omitting the number of rows from `grid-size`. The following example creates a grid with three columns, with rows created on demand: + + +=== "Output" + + ```{.textual path="docs/examples/guide/layout/grid_layout2.py"} + ``` + +=== "grid_layout2.py" + + ```python + --8<-- "docs/examples/guide/layout/grid_layout2.py" + ``` + +=== "grid_layout2.css" + + ```sass hl_lines="3" + --8<-- "docs/examples/guide/layout/grid_layout2.css" + ``` Since we specified that our grid has three columns (`grid-size: 3`), and we've yielded seven widgets in total, a third row has been created to accommodate the seventh widget. @@ -258,9 +228,10 @@ customize it to create more complex layouts. You can adjust the width of columns and the height of rows in your grid using the `grid-columns` and `grid-rows` properties. These properties can take multiple values, letting you specify dimensions on a column-by-column or row-by-row basis. -Continuing on from our earlier 2x3 example grid, let's adjust the width of the columns using `grid-columns`. +Continuing on from our earlier 3x2 example grid, let's adjust the width of the columns using `grid-columns`. We'll make the first column take up half of the screen width, with the other two columns sharing the remaining space equally. + === "Output" ```{.textual path="docs/examples/guide/layout/grid_layout3_row_col_adjust.py"} @@ -278,7 +249,8 @@ We'll make the first column take up half of the screen width, with the other two --8<-- "docs/examples/guide/layout/grid_layout3_row_col_adjust.css" ``` -Since our `grid-size` is three (meaning it has three columns), our `grid-columns` declaration has three space-separated values. + +Since our `grid-size` is 3 (meaning it has three columns), our `grid-columns` declaration has three space-separated values. Each of these values sets the width of a column. The first value refers to the left-most column, the second value refers to the next column, and so on. In the example above, we've given the left-most column a width of `2fr` and the other columns widths of `1fr`. @@ -288,6 +260,7 @@ Similarly, we can adjust the height of a row using `grid-rows`. In the following example, we use `%` units to adjust the first row of our grid to `25%` height, and the second row to `75%` height (while retaining the `grid-columns` change from above). + === "Output" ```{.textual path="docs/examples/guide/layout/grid_layout4_row_col_adjust.py"} @@ -305,26 +278,21 @@ and the second row to `75%` height (while retaining the `grid-columns` change fr --8<-- "docs/examples/guide/layout/grid_layout4_row_col_adjust.css" ``` + If you don't specify enough values in a `grid-columns` or `grid-rows` declaration, the values you _have_ provided will be "repeated". For example, if your grid has four columns (i.e. `grid-size: 4;`), then `grid-columns: 2 4;` is equivalent to `grid-columns: 2 4 2 4;`. If it instead had three columns, then `grid-columns: 2 4;` would be equivalent to `grid-columns: 2 4 2;`. ### Cell spans -You can adjust the number of rows and columns an individual cell spans across. - -Let's return to our original, uniform, 2x3 grid to more clearly illustrate the effect of modifying the row spans and column spans of cells: - -```{.textual path="docs/examples/guide/layout/grid_layout1.py"} -``` +Cells may _span_ multiple rows or columns, to create more interesting grid arrangements. To make a single cell span multiple rows or columns in the grid, we need to be able to select it using CSS. -To do this, we'll add an ID to the widget inside our `compose` method. -Then, we can set the `row-span` and `column-span` properties on this ID using CSS. +To do this, we'll add an ID to the widget inside our `compose` method so we can set the `row-span` and `column-span` properties using CSS. -Let's add an ID of `#two` to the second widget yielded from `compose`, and give it a `column-span` of 2 in our CSS to make that widget span across two columns. +Let's add an ID of `#two` to the second widget yielded from `compose`, and give it a `column-span` of 2 to make that widget span two columns. We'll also add a slight tint using `tint: magenta 40%;` to draw attention to it. -The relevant changes are highlighted in the Python and CSS files below. + === "Output" @@ -333,7 +301,7 @@ The relevant changes are highlighted in the Python and CSS files below. === "grid_layout5_col_span.py" - ```python hl_lines="8" + ```python --8<-- "docs/examples/guide/layout/grid_layout5_col_span.py" ``` @@ -343,6 +311,8 @@ The relevant changes are highlighted in the Python and CSS files below. --8<-- "docs/examples/guide/layout/grid_layout5_col_span.css" ``` + + Notice that the widget expands to fill columns to the _right_ of its original position. Since `#two` now spans two cells instead of one, all widgets that follow it are shifted along one cell in the grid to accommodate. As a result, the final widget wraps on to a new row at the bottom of the grid. @@ -356,6 +326,7 @@ This can be used in conjunction with `column-span`, meaning one cell may span mu The example below shows `row-span` in action. We again target widget `#two` in our CSS, and add a `row-span: 2;` declaration to it. + === "Output" ```{.textual path="docs/examples/guide/layout/grid_layout6_row_span.py"} @@ -373,6 +344,8 @@ We again target widget `#two` in our CSS, and add a `row-span: 2;` declaration t --8<-- "docs/examples/guide/layout/grid_layout6_row_span.css" ``` + + Widget `#two` now spans two columns and two rows, covering a total of four cells. Notice how the other cells are moved to accommodate this change. The widget that previously occupied a single cell now occupies four cells, thus displacing three cells to a new row. @@ -383,7 +356,7 @@ The spacing between cells in the grid can be adjusted using the `grid-gutter` CS By default, cells have no gutter, meaning their edges touch each other. Gutter is applied across every cell in the grid, so `grid-gutter` must be used on a widget with `layout: grid` (_not_ on a child/cell widget). -To better illustrate gutter, let's set our `Screen` background color to `lightgreen`, and the background color of the widgets we yield to `darkmagenta`. +To illustrate gutter let's set our `Screen` background color to `lightgreen`, and the background color of the widgets we yield to `darkmagenta`. Now if we add `grid-gutter: 1;` to our grid, one cell of spacing appears between the cells and reveals the light green background of the `Screen`. === "Output" @@ -446,7 +419,7 @@ The code below shows a simple sidebar implementation. If we run the app above and scroll down, the body text will scroll but the sidebar does not (note the position of the scrollbar in the output shown above). Docking multiple widgets to the same edge will result in overlap. -Just like in the `center` layout, the first widget yielded from `compose` will appear on below widgets yielded after it. +The first widget yielded from `compose` will appear below widgets yielded after it. Let's dock a second sidebar, `#another-sidebar`, to the left of the screen. This new sidebar is double the width of the one previous one, and has a `deeppink` background. @@ -457,7 +430,7 @@ This new sidebar is double the width of the one previous one, and has a `deeppin === "dock_layout2_sidebar.py" - ```python hl_lines="14" + ```python hl_lines="16" --8<-- "docs/examples/guide/layout/dock_layout2_sidebar.py" ``` @@ -495,17 +468,15 @@ If we wished for the sidebar to appear below the header, it'd simply be a case o ## Layers -The order which widgets are yielded isn't the only thing that affects the order in which they're painted. -Textual also has the concept of _layers_, which you may be familiar with if you've ever used image editing software. +Textual has a concept of _layers_ which gives you finely grained control over the order widgets are place. When drawing widgets, Textual will first draw on _lower_ layers, working its way up to higher layers. As such, widgets on higher layers will be drawn on top of those on lower layers. -Layers take precedence over yield order. -Layer names need to be defined in advance, using a `layers` CSS declaration on a widget. -Descendants of this widget can then be assigned to one of these layers using a `layer` declaration. +Layer names are defined with a `layers` style on a container (parent) widget. +Descendants of this widget can then be assigned to one of these layers using a `layer` style. -The `layers` declaration takes a space-separated list of layer names. +The `layers` style takes a space-separated list of layer names. The leftmost name is the lowest layer, and the rightmost is the highest layer. Therefore, if you assign a descendant to the rightmost layer name, it'll be drawn on the top layer and will be visible above all other descendants. @@ -514,9 +485,8 @@ To add a widget to the topmost layer in this case, you'd add a declaration of `l In the example below, `#box1` is yielded before `#box2`. Given our earlier discussion on yield order, you'd expect `#box2` to appear on top. -However, in this case, both `#box1` and `#box2` are assigned to layers. -From the `layers: below above;` declaration inside `layers.css`, we can see that the layer named `above` is on top of the `below` layer. -Since `#box1` is on the higher layer, it is drawn on top of `#box2`. +However, in this case, both `#box1` and `#box2` are assigned to layers which define the reverse order, so `#box1` is on top of `#box2` + [//]: # (NOTE: the example below also appears in the layers and layer style reference) @@ -552,46 +522,12 @@ The offset of a widget can be set using the `offset` CSS property. * The first value defines the `x` (horizontal) offset. Positive values will shift the widget to the right. Negative values will shift the widget to the left. * The second value defines the `y` (vertical) offset. Positive values will shift the widget down. Negative values will shift the widget up. -For example, `offset: 4 -2;` will shift the target widget 4 terminal cells to the right, and 2 terminal cells up. - -The example below illustrates `offset` further. -The `#parent` container has `layout: center`, meaning all four of the widgets we yield from `compose` have an origin in the center of it. - -* We make no adjustments to the offset of `#box1` in the CSS - it remains in its original position, and thus has offset `(0, 0)`. -* We apply and offset of `12 4` to `#box2`, moving it to the right and down a little. -* In the case of `#box3` we apply and offset of `-12 -4`, which shifts it to the left and up. -* `#box4` at the bottom left of the screen illustrates clipping. A child widget will be clipped by its parent's region, meaning any part of the child which extends beyond the parent region will not be visible. - -=== "Output" - - ```{.textual path="docs/examples/guide/layout/offset.py"} - ``` - -=== "offset.py" - - ```python - --8<-- "docs/examples/guide/layout/offset.py" - ``` - -=== "offset.css" - - ```sass hl_lines="25 30 35" - --8<-- "docs/examples/guide/layout/offset.css" - ``` - - [//]: # (TODO Link the word animation below to animation docs) -Offset is commonly used with animation. -You may have a sidebar, for example, with its initial offset set such that it is hidden off to the left of the screen. -On pressing a button, the offset can be eased to `(0, 0)`, animating the sidebar in from the left, back to its origin position as defined by the layout. - ## Putting it all together The sections above show how the various layouts in Textual can be used to position widgets on screen. In a real application, you'll make use of several layouts. -You might choose to build the high-level structure of your app using `layout: grid;`, with individual widgets laying out their children using `horizontal` or `vertical` layouts. -If one of your widgets is particularly complex, perhaps it'll use `layout: grid;` itself. The example below shows how an advanced layout can be built by combining the various techniques described on this page. @@ -608,29 +544,8 @@ The example below shows how an advanced layout can be built by combining the var === "combining_layouts.css" - ```sass hl_lines="4" + ```sass --8<-- "docs/examples/guide/layout/combining_layouts.css" ``` -At the top of the application we have a header. -This header is yielded from our `compose` method using `yield Header()`. -As mentioned earlier, `Header` is a builtin Textual widget which internally contains a `dock: top;`. -Since it's yielded directly from `compose`, it gets docked to the top of `Screen` (the terminal window). - -The body of the application is contained within the widget `#app-grid` which uses a grid layout. -The cells of the grid have been given blue, pink, and green borders. - -This grid consists of two columns (`grid-size: 2`). -The left pane (with the blue border) is the first cell within our grid. -It has ID `#left-pane`, and is set to span two rows using `row-span: 2;`. - -The left pane `#left-pane` itself is a `layout.Vertical` container widget. -This widget internally contains some CSS which sets `layout: vertical`, resulting in vertically arranged children. - -The next cell in the grid layout is `#top-right`, which has the pink-red border. -This grid cell makes use of a horizontal layout. - -The final cell in our grid is located at the bottom right of the screen. -It has a green border, and this cell itself uses a grid layout. - -As you can see, combining layouts lets you design complex apps with very little code! +Textual layouts make it easy design build real-life applications with relatively little code. diff --git a/docs/guide/styles.md b/docs/guide/styles.md index f2b12e39a..1b382d1cf 100644 --- a/docs/guide/styles.md +++ b/docs/guide/styles.md @@ -43,7 +43,9 @@ Widgets will occupy the full width of their container and as many lines as requi Note how the combined height of the widget is three rows in the terminal. This is because a border adds two rows (and two columns). If you were to remove the line that sets the border style, the widget would occupy a single row. -Widgets will wrap text by default. If you were to replace `"Textual"` with a long paragraph of text, the widget will expand downwards to fit. +!!! information + + Widgets will wrap text by default. If you were to replace `"Textual"` with a long paragraph of text, the widget will expand downwards to fit. ## Colors @@ -184,7 +186,7 @@ With the width set to `"50%"` and the height set to `"80%"`, the widget will kee ```{.textual path="docs/examples/guide/styles/dimensions03.py" columns="120" lines="40"} ``` -Percentage units are useful for widgets that occupy a relative portion of the screen, but they can be problematic for some proportions. For instance, if we want to divide the screen into thirds, we would have to set a dimension to `33.3333333333%` which is awkward. Textual supports `fr` units which are often better than percentage-based units for these situations. +Percentage units can be problematic for some relative values. For instance, if we want to divide the screen into thirds, we would have to set a dimension to `33.3333333333%` which is awkward. Textual supports `fr` units which are often better than percentage-based units for these situations. When specifying `fr` units for a given dimension, Textual will divide the available space by the sum of the `fr` units on that dimension. That space will then be divided amongst the widgets as a proportion of their individual `fr` values. @@ -292,7 +294,9 @@ If you set `box_sizing` to `"content-box"` then space required for padding and b -The following example creates two widgets which have a width of 30, a height of 6, and a border and padding of 1. The second widget has `box_sizing` set to `"content-box"`. +The following example creates two widgets with a width of 30, a height of 6, and a border and padding of 1. +The first widget has the default `box_sizing` (`"border-box"`). +The second widget sets `box_sizing` to `"content-box"`. ```python title="box_sizing01.py" hl_lines="33" --8<-- "docs/examples/guide/styles/box_sizing01.py" @@ -307,9 +311,9 @@ The padding and border of the first widget is subtracted from the height leaving Margin is similar to padding in that it adds space, but unlike padding, [margin](../styles/margin.md) is outside of the widget's border. It is used to add space between widgets. -The following example creates two widgets, each with a padding of 2. +The following example creates two widgets, each with a margin of 2. -```python title="margin01.py" hl_lines="33" +```python title="margin01.py" hl_lines="26-27" --8<-- "docs/examples/guide/styles/margin01.py" ``` diff --git a/docs/reference/containers.md b/docs/reference/containers.md new file mode 100644 index 000000000..f65b50868 --- /dev/null +++ b/docs/reference/containers.md @@ -0,0 +1 @@ +::: textual.containers diff --git a/docs/styles/align.md b/docs/styles/align.md new file mode 100644 index 000000000..15bbcbed9 --- /dev/null +++ b/docs/styles/align.md @@ -0,0 +1,68 @@ +# Align + +The `align` style aligns children within a container. + +## Syntax + +``` +align: ; +align-horizontal: ; +align-vertical: ; +``` + + +### Values + +#### `HORIZONTAL` + +| Value | Description | +| ---------------- | -------------------------------------------------- | +| `left` (default) | Align content on the left of the horizontal axis | +| `center` | Align content in the center of the horizontal axis | +| `right` | Align content on the right of the horizontal axis | + +#### `VERTICAL` + +| Value | Description | +| --------------- | ------------------------------------------------ | +| `top` (default) | Align content at the top of the vertical axis | +| `middle` | Align content in the middle of the vertical axis | +| `bottom` | Align content at the bottom of the vertical axis | + + +## Example + +=== "align.py" + + ```python + --8<-- "docs/examples/styles/align.py" + ``` + +=== "align.css" + + ```scss hl_lines="2" + --8<-- "docs/examples/styles/align.css" + ``` + +=== "Output" + + ```{.textual path="docs/examples/styles/align.py"} + + ``` + +## CSS + +```sass +/* Align child widgets to the center. */ +align: center middle; +/* Align child widget to th top right */ +align: right top; +``` + +## Python +```python +# Align child widgets to the center +widget.styles.align = ("center", "middle") +# Align child widgets to the top right +widget.styles.align = ("right", "top") +``` diff --git a/docs/styles/layout.md b/docs/styles/layout.md index 1ce87bf23..332209219 100644 --- a/docs/styles/layout.md +++ b/docs/styles/layout.md @@ -13,8 +13,7 @@ layout: [center|grid|horizontal|vertical]; ### Values | Value | Description | -|----------------------|-------------------------------------------------------------------------------| -| `center` | A single child widget will be placed in the center. | +| -------------------- | ----------------------------------------------------------------------------- | | `grid` | Child widgets will be arranged in a grid. | | `horizontal` | Child widgets will be arranged along the horizontal axis, from left to right. | | `vertical` (default) | Child widgets will be arranged along the vertical axis, from top to bottom. | diff --git a/e2e_tests/test_apps/basic.py b/e2e_tests/test_apps/basic.py index 3391e68e5..4b44a4f6e 100644 --- a/e2e_tests/test_apps/basic.py +++ b/e2e_tests/test_apps/basic.py @@ -7,7 +7,7 @@ from textual.app import App, ComposeResult from textual.reactive import Reactive from textual.widget import Widget from textual.widgets import Static, DataTable, DirectoryTree, Header, Footer -from textual.layout import Container +from textual.containers import Container CODE = ''' from __future__ import annotations diff --git a/examples/calculator.py b/examples/calculator.py index a1f1061f9..9477b6cdd 100644 --- a/examples/calculator.py +++ b/examples/calculator.py @@ -2,7 +2,7 @@ from decimal import Decimal from textual.app import App, ComposeResult from textual import events -from textual.layout import Container +from textual.containers import Container from textual.reactive import var from textual.widgets import Button, Static diff --git a/examples/code_browser.py b/examples/code_browser.py index b97ccc268..86b54665a 100644 --- a/examples/code_browser.py +++ b/examples/code_browser.py @@ -11,9 +11,8 @@ import sys from rich.syntax import Syntax from rich.traceback import Traceback - from textual.app import App, ComposeResult -from textual.layout import Container, Vertical +from textual.containers import Container, Vertical from textual.reactive import var from textual.widgets import DirectoryTree, Footer, Header, Static diff --git a/examples/dictionary.py b/examples/dictionary.py index e0d756d11..4fa7d22bc 100644 --- a/examples/dictionary.py +++ b/examples/dictionary.py @@ -11,7 +11,7 @@ except ImportError: from rich.markdown import Markdown from textual.app import App, ComposeResult -from textual.layout import Vertical +from textual.containers import Vertical from textual.widgets import Static, TextInput diff --git a/mkdocs.yml b/mkdocs.yml index 1179dda6d..2a9a020a8 100644 --- a/mkdocs.yml +++ b/mkdocs.yml @@ -51,7 +51,8 @@ nav: - "events/screen_suspend.md" - "events/show.md" - Styles: - - "styles/index.md" + - "styles/index.md" + - "styles/align.md" - "styles/background.md" - "styles/border.md" - "styles/box_sizing.md" @@ -96,6 +97,7 @@ nav: - "reference/app.md" - "reference/button.md" - "reference/color.md" + - "reference/containers.md" - "reference/dom_node.md" - "reference/events.md" - "reference/geometry.md" diff --git a/sandbox/darren/basic.py b/sandbox/darren/basic.py index 061609238..4c70cdd69 100644 --- a/sandbox/darren/basic.py +++ b/sandbox/darren/basic.py @@ -7,7 +7,7 @@ from textual.app import App, ComposeResult from textual.reactive import Reactive from textual.widget import Widget from textual.widgets import Static, DataTable, DirectoryTree, Header, Footer -from textual.layout import Container +from textual.containers import Container CODE = ''' from __future__ import annotations diff --git a/sandbox/darren/buttons.py b/sandbox/darren/buttons.py index 883a457e2..65d9e3ffc 100644 --- a/sandbox/darren/buttons.py +++ b/sandbox/darren/buttons.py @@ -1,11 +1,12 @@ -from textual import layout, events +from textual import events from textual.app import App, ComposeResult +from textual.containers import Vertical from textual.widgets import Button class ButtonsApp(App[str]): def compose(self) -> ComposeResult: - yield layout.Vertical( + yield Vertical( Button("default", id="foo"), Button.success("success", id="bar"), Button.warning("warning", id="baz"), diff --git a/sandbox/darren/just_a_box.css b/sandbox/darren/just_a_box.css index 9f9d7da7e..f3d70cebd 100644 --- a/sandbox/darren/just_a_box.css +++ b/sandbox/darren/just_a_box.css @@ -1,5 +1,5 @@ Screen { - layout: center; + align: center middle; background: darkslategrey; } diff --git a/sandbox/will/add_remove.py b/sandbox/will/add_remove.py index a34ddd617..f3777b555 100644 --- a/sandbox/will/add_remove.py +++ b/sandbox/will/add_remove.py @@ -1,6 +1,6 @@ import random -from textual import layout +from textual.containers import Horizontal, Vertical from textual.app import App, ComposeResult from textual.widgets import Button, Static @@ -36,14 +36,14 @@ class AddRemoveApp(App): self.count = 0 def compose(self) -> ComposeResult: - yield layout.Vertical( - layout.Horizontal( + yield Vertical( + Horizontal( Button("Add", variant="success", id="add"), Button("Remove", variant="error", id="remove"), Button("Remove random", variant="warning", id="remove_random"), id="buttons", ), - layout.Vertical(id="items"), + Vertical(id="items"), ) def on_button_pressed(self, event: Button.Pressed) -> None: diff --git a/sandbox/will/align.css b/sandbox/will/align.css new file mode 100644 index 000000000..ec40f8346 --- /dev/null +++ b/sandbox/will/align.css @@ -0,0 +1,14 @@ +Screen { + align: center middle; +} + +Label { + + width: 20; + height: 5; + background: blue; + color: white; + border: tall white; + margin: 1; + content-align: center middle; +} diff --git a/sandbox/will/align.py b/sandbox/will/align.py new file mode 100644 index 000000000..864f6eb95 --- /dev/null +++ b/sandbox/will/align.py @@ -0,0 +1,19 @@ +from textual.app import App, ComposeResult +from textual.widgets import Static + + +class Label(Static): + pass + + +class AlignApp(App): + CSS_PATH = "align.css" + + def compose(self) -> ComposeResult: + yield Label("Hello") + yield Label("World!") + + +if __name__ == "__main__": + app = AlignApp() + app.run() diff --git a/sandbox/will/basic.css b/sandbox/will/basic.css index 51db0cbc4..e113d23fc 100644 --- a/sandbox/will/basic.css +++ b/sandbox/will/basic.css @@ -112,7 +112,7 @@ Tweet { border: wide $panel; /* scrollbar-gutter: stable; */ - align-horizontal: center; + box-sizing: border-box; } @@ -124,7 +124,7 @@ Tweet { padding: 0 2; margin: 1 2; height: 24; - align-horizontal: center; + layout: vertical; } @@ -228,7 +228,7 @@ Error { padding: 0; text-style: bold; - align-horizontal: center; + content-align: center middle; } Warning { @@ -240,7 +240,7 @@ Warning { border-bottom: tall $warning-darken-2; text-style: bold; - align-horizontal: center; + content-align: center middle; } Success { @@ -256,7 +256,7 @@ Success { text-style: bold ; - align-horizontal: center; + content-align: center middle; } diff --git a/sandbox/will/basic.py b/sandbox/will/basic.py index 7545a4d47..d50509b9d 100644 --- a/sandbox/will/basic.py +++ b/sandbox/will/basic.py @@ -7,7 +7,7 @@ from textual.app import App, ComposeResult from textual.reactive import Reactive from textual.widget import Widget from textual.widgets import Static, DataTable, DirectoryTree, Header, Footer -from textual.layout import Container, Vertical +from textual.containers import Container, Vertical CODE = ''' from __future__ import annotations diff --git a/sandbox/will/calculator.py b/sandbox/will/calculator.py index dabf99376..972202b17 100644 --- a/sandbox/will/calculator.py +++ b/sandbox/will/calculator.py @@ -2,7 +2,7 @@ from decimal import Decimal from textual.app import App, ComposeResult from textual import events -from textual.layout import Container +from textual.containers import Container from textual.reactive import Reactive from textual.widgets import Button, Static diff --git a/sandbox/will/center2.py b/sandbox/will/center2.py index 0ec20f61c..7abbefe29 100644 --- a/sandbox/will/center2.py +++ b/sandbox/will/center2.py @@ -1,5 +1,5 @@ from textual.app import App -from textual.layout import Vertical, Center +from textual.containers import Vertical, Center from textual.widgets import Static diff --git a/sandbox/will/design.py b/sandbox/will/design.py index 48cb7e3ae..56452147f 100644 --- a/sandbox/will/design.py +++ b/sandbox/will/design.py @@ -1,5 +1,5 @@ from textual.app import App -from textual.layout import Container +from textual.containers import Container from textual.widgets import Header, Footer, Static diff --git a/sandbox/will/just_a_box.py b/sandbox/will/just_a_box.py index ea35fa128..df19e259a 100644 --- a/sandbox/will/just_a_box.py +++ b/sandbox/will/just_a_box.py @@ -5,7 +5,7 @@ from rich.panel import Panel from textual import events from textual.app import App, ComposeResult -from textual.layout import Container, Horizontal, Vertical +from textual.containers import Container, Horizontal, Vertical from textual.widget import Widget diff --git a/sandbox/will/offset.css b/sandbox/will/offset.css index ddcb18e49..349f23a5e 100644 --- a/sandbox/will/offset.css +++ b/sandbox/will/offset.css @@ -1,5 +1,5 @@ Screen { - layout: center; + align: center middle; } #parent { diff --git a/sandbox/will/offset.py b/sandbox/will/offset.py index d4a9dfd20..3fbd7a57c 100644 --- a/sandbox/will/offset.py +++ b/sandbox/will/offset.py @@ -1,14 +1,11 @@ -from textual import layout from textual.app import App, ComposeResult +from textual.containers import Vertical from textual.widgets import Static class OffsetExample(App): def compose(self) -> ComposeResult: - yield layout.Vertical( - Static("Child", id="child"), - id="parent" - ) + yield Vertical(Static("Child", id="child"), id="parent") yield Static("Tag", id="tag") diff --git a/sandbox/will/tree.py b/sandbox/will/tree.py index 13df2604c..8f5a9c46b 100644 --- a/sandbox/will/tree.py +++ b/sandbox/will/tree.py @@ -1,6 +1,6 @@ from textual.app import App -from textual.layout import Container +from textual.containers import Container from textual.widgets import DirectoryTree diff --git a/src/textual/_arrange.py b/src/textual/_arrange.py index 8c1032594..4d4e489ee 100644 --- a/src/textual/_arrange.py +++ b/src/textual/_arrange.py @@ -49,6 +49,7 @@ def arrange( scroll_spacing = Spacing() null_spacing = Spacing() get_dock = attrgetter("styles.dock") + styles = widget.styles for widgets in dock_layers.values(): @@ -89,7 +90,7 @@ def arrange( # Should not occur, mainly to keep Mypy happy raise AssertionError("invalid value for edge") # pragma: no-cover - align_offset = dock_widget.styles.align_size( + align_offset = dock_widget.styles._align_size( (widget_width, widget_height), size ) dock_region = dock_region.shrink(margin).translate(align_offset) @@ -105,7 +106,17 @@ def arrange( if arranged_layout_widgets: scroll_spacing = scroll_spacing.grow_maximum(dock_spacing) arrange_widgets.update(arranged_layout_widgets) + placement_offset = region.offset + if styles.align_horizontal != "left" or styles.align_vertical != "top": + placement_size = Region.from_union( + [ + placement.region.grow(placement.margin) + for placement in layout_placements + ] + ).size + placement_offset += styles._align_size(placement_size, size) + if placement_offset: layout_placements = [ _WidgetPlacement( diff --git a/src/textual/cli/previews/borders.py b/src/textual/cli/previews/borders.py index 53772d754..613343443 100644 --- a/src/textual/cli/previews/borders.py +++ b/src/textual/cli/previews/borders.py @@ -1,7 +1,7 @@ from textual.app import App, ComposeResult from textual.constants import BORDERS from textual.widgets import Button, Static -from textual import layout +from textual.containers import Vertical TEXT = """I must not fear. @@ -13,7 +13,7 @@ And when it has gone past, I will turn the inner eye to see its path. Where the fear has gone there will be nothing. Only I will remain.""" -class BorderButtons(layout.Vertical): +class BorderButtons(Vertical): DEFAULT_CSS = """ BorderButtons { dock: left; diff --git a/src/textual/cli/previews/easing.py b/src/textual/cli/previews/easing.py index 56399ddde..3fadf7f35 100644 --- a/src/textual/cli/previews/easing.py +++ b/src/textual/cli/previews/easing.py @@ -1,16 +1,14 @@ from __future__ import annotations from rich.console import RenderableType - -from textual import layout from textual._easing import EASING -from textual.app import ComposeResult, App +from textual.app import App, ComposeResult from textual.cli.previews.borders import TEXT +from textual.containers import Container, Horizontal, Vertical from textual.reactive import Reactive from textual.scrollbar import ScrollBarRender from textual.widget import Widget -from textual.widgets import Button, Static, Footer -from textual.widgets import TextInput +from textual.widgets import Button, Footer, Static, TextInput from textual.widgets._text_input import TextWidgetBase VIRTUAL_SIZE = 100 @@ -78,13 +76,13 @@ class EasingApp(App): ) yield EasingButtons() - yield layout.Vertical( - layout.Horizontal( + yield Vertical( + Horizontal( Static("Animation Duration:", id="label"), duration_input, id="inputs" ), - layout.Horizontal( + Horizontal( self.animated_bar, - layout.Container(self.opacity_widget, id="other"), + Container(self.opacity_widget, id="other"), ), Footer(), ) diff --git a/src/textual/layout.py b/src/textual/containers.py similarity index 67% rename from src/textual/layout.py rename to src/textual/containers.py index ba5ad9143..331d040df 100644 --- a/src/textual/layout.py +++ b/src/textual/containers.py @@ -13,7 +13,7 @@ class Container(Widget): class Vertical(Widget): - """A container widget to align children vertically.""" + """A container widget which aligns children vertically.""" DEFAULT_CSS = """ Vertical { @@ -24,7 +24,7 @@ class Vertical(Widget): class Horizontal(Widget): - """A container widget to align children horizontally.""" + """A container widget which aligns children horizontally.""" DEFAULT_CSS = """ Horizontal { @@ -34,11 +34,11 @@ class Horizontal(Widget): """ -class Center(Widget): - """A container widget to align children in the center.""" +class Grid(Widget): + """A container widget with grid alignment.""" DEFAULT_CSS = """ - Center { - layout: center; - } + Grid { + layout: grid; + } """ diff --git a/src/textual/css/styles.py b/src/textual/css/styles.py index f5ef209c6..ebc59df8c 100644 --- a/src/textual/css/styles.py +++ b/src/textual/css/styles.py @@ -449,13 +449,21 @@ class StylesBase(ABC): styles.node = node return styles - def get_transition(self, key: str) -> Transition | None: + def _get_transition(self, key: str) -> Transition | None: + """Get a transition. + + Args: + key (str): Transition key. + + Returns: + Transition | None: Transition object or None it no transition exists. + """ if key in self.ANIMATABLE: return self.transitions.get(key, None) else: return None - def align_width(self, width: int, parent_width: int) -> int: + def _align_width(self, width: int, parent_width: int) -> int: """Align the width dimension. Args: @@ -474,7 +482,7 @@ class StylesBase(ABC): offset_x = parent_width - width return offset_x - def align_height(self, height: int, parent_height: int) -> int: + def _align_height(self, height: int, parent_height: int) -> int: """Align the height dimensions Args: @@ -493,7 +501,7 @@ class StylesBase(ABC): offset_y = parent_height - height 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: @@ -506,8 +514,8 @@ class StylesBase(ABC): width, height = child parent_width, parent_height = parent return Offset( - self.align_width(width, parent_width), - self.align_height(height, parent_height), + self._align_width(width, parent_width), + self._align_height(height, parent_height), ) diff --git a/src/textual/css/stylesheet.py b/src/textual/css/stylesheet.py index ef4ae6490..5255de7e4 100644 --- a/src/textual/css/stylesheet.py +++ b/src/textual/css/stylesheet.py @@ -444,7 +444,7 @@ class Stylesheet: # Check if this can / should be animated if is_animatable(key) and new_render_value != old_render_value: - transition = new_styles.get_transition(key) + transition = new_styles._get_transition(key) if transition is not None: duration, easing, delay = transition node.app.animator.animate( diff --git a/src/textual/layouts/center.py b/src/textual/layouts/center.py deleted file mode 100644 index 288c2d38d..000000000 --- a/src/textual/layouts/center.py +++ /dev/null @@ -1,36 +0,0 @@ -from __future__ import annotations - -from fractions import Fraction - -from .._layout import ArrangeResult, Layout, WidgetPlacement -from ..geometry import Region, Size -from ..widget import Widget - - -class CenterLayout(Layout): - """Positions widgets in the center of the screen.""" - - name = "center" - - def arrange( - self, parent: Widget, children: list[Widget], size: Size - ) -> ArrangeResult: - - placements: list[WidgetPlacement] = [] - - parent_size = parent.outer_size - container_width, container_height = size - fraction_unit = Fraction(size.width) - - for widget in children: - width, height, margin = widget._get_box_model( - size, parent_size, fraction_unit - ) - margin_width = width + margin.width - margin_height = height + margin.height - x = margin.left + max(0, (container_width - margin_width) // 2) - y = margin.top + max(0, (container_height - margin_height) // 2) - region = Region(x, y, int(width), int(height)) - placements.append(WidgetPlacement(region, margin, widget, 0)) - - return placements, set(children) diff --git a/src/textual/layouts/factory.py b/src/textual/layouts/factory.py index 2e1320fc4..a5f89d843 100644 --- a/src/textual/layouts/factory.py +++ b/src/textual/layouts/factory.py @@ -1,13 +1,11 @@ from __future__ import annotations from .._layout import Layout -from .center import CenterLayout from .horizontal import HorizontalLayout from .grid import GridLayout from .vertical import VerticalLayout LAYOUT_MAP: dict[str, type[Layout]] = { - "center": CenterLayout, "horizontal": HorizontalLayout, "grid": GridLayout, "vertical": VerticalLayout, diff --git a/src/textual/layouts/horizontal.py b/src/textual/layouts/horizontal.py index fd03b09b7..9261f2476 100644 --- a/src/textual/layouts/horizontal.py +++ b/src/textual/layouts/horizontal.py @@ -23,7 +23,7 @@ class HorizontalLayout(Layout): placements: list[WidgetPlacement] = [] add_placement = placements.append - x = max_width = max_height = Fraction(0) + x = max_height = Fraction(0) parent_size = parent.outer_size styles = [child.styles for child in children if child.styles.width is not None] @@ -48,21 +48,19 @@ class HorizontalLayout(Layout): displayed_children = [child for child in children if child.display] + _Region = Region + _WidgetPlacement = WidgetPlacement for widget, box_model, margin in zip(children, box_models, margins): content_width, content_height, box_margin = box_model - offset_y = ( - widget.styles.align_height( - int(content_height), size.height - box_margin.height - ) - + box_model.margin.top - ) + offset_y = box_margin.top next_x = x + content_width - region = Region(int(x), offset_y, int(next_x - int(x)), int(content_height)) + region = _Region( + int(x), offset_y, int(next_x - int(x)), int(content_height) + ) max_height = max( max_height, content_height + offset_y + box_model.margin.bottom ) - add_placement(WidgetPlacement(region, box_model.margin, widget, 0)) + add_placement(_WidgetPlacement(region, box_model.margin, widget, 0)) x = next_x + margin - max_width = x return placements, set(displayed_children) diff --git a/src/textual/layouts/vertical.py b/src/textual/layouts/vertical.py index 266dfd74d..d7afced19 100644 --- a/src/textual/layouts/vertical.py +++ b/src/textual/layouts/vertical.py @@ -44,17 +44,15 @@ class VerticalLayout(Layout): y = Fraction(box_models[0].margin.top if box_models else 0) + _Region = Region + _WidgetPlacement = WidgetPlacement for widget, box_model, margin in zip(children, box_models, margins): content_width, content_height, box_margin = box_model - offset_x = ( - widget.styles.align_width( - int(content_width), size.width - box_margin.width - ) - + box_model.margin.left - ) next_y = y + content_height - region = Region(offset_x, int(y), int(content_width), int(next_y) - int(y)) - add_placement(WidgetPlacement(region, box_model.margin, widget, 0)) + region = _Region( + box_margin.left, int(y), int(content_width), int(next_y) - int(y) + ) + add_placement(_WidgetPlacement(region, box_model.margin, widget, 0)) y = next_y + margin return placements, set(children) diff --git a/src/textual/widgets/_welcome.py b/src/textual/widgets/_welcome.py index 4b21a8830..3c8a6d1be 100644 --- a/src/textual/widgets/_welcome.py +++ b/src/textual/widgets/_welcome.py @@ -1,7 +1,7 @@ from ..app import ComposeResult from ._static import Static from ._button import Button -from ..layout import Container +from ..containers import Container from rich.markdown import Markdown @@ -26,11 +26,9 @@ Where the fear has gone there will be nothing. Only I will remain." class Welcome(Static): DEFAULT_CSS = """ - Welcome { width: 100%; - height: 100%; - padding: 1 2; + height: 100%; background: $surface; } @@ -46,10 +44,8 @@ class Welcome(Static): Welcome #close { dock: bottom; - width: 100%; - margin-top: 1; + width: 100%; } - """ def compose(self) -> ComposeResult: diff --git a/tests/snapshot_tests/__snapshots__/test_snapshots.ambr b/tests/snapshot_tests/__snapshots__/test_snapshots.ambr index 57f073c10..6c6f3cd1c 100644 --- a/tests/snapshot_tests/__snapshots__/test_snapshots.ambr +++ b/tests/snapshot_tests/__snapshots__/test_snapshots.ambr @@ -1,161 +1,3 @@ -# name: test_center_layout - ''' - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - Textual - - - - - - - - - - - - One - - - - Two - - - Three - - - - - - - - - - - - - - - - - - - ''' -# --- # name: test_dock_layout_sidebar ''' diff --git a/tests/snapshot_tests/test_snapshots.py b/tests/snapshot_tests/test_snapshots.py index 0c95f103c..34cb261b9 100644 --- a/tests/snapshot_tests/test_snapshots.py +++ b/tests/snapshot_tests/test_snapshots.py @@ -14,10 +14,6 @@ def test_layers(snap_compare): assert snap_compare("docs/examples/guide/layout/layers.py") -def test_center_layout(snap_compare): - assert snap_compare("docs/examples/guide/layout/center_layout.py") - - def test_horizontal_layout(snap_compare): assert snap_compare("docs/examples/guide/layout/horizontal_layout.py") diff --git a/tests/test_layouts_center.py b/tests/test_layouts_center.py deleted file mode 100644 index fecd97dcd..000000000 --- a/tests/test_layouts_center.py +++ /dev/null @@ -1,28 +0,0 @@ -from textual._layout import WidgetPlacement -from textual.geometry import Region, Size, Spacing -from textual.layouts.center import CenterLayout -from textual.widget import Widget - - -def test_center_layout(): - - widget = Widget() - widget._size = Size(80, 24) - child = Widget() - child.styles.width = 10 - child.styles.height = 5 - layout = CenterLayout() - - placements, widgets = layout.arrange(widget, [child], Size(60, 20)) - assert widgets == {child} - - expected = [ - WidgetPlacement( - region=Region(x=25, y=7, width=10, height=5), - margin=Spacing(), - widget=child, - order=0, - fixed=False, - ), - ] - assert placements == expected