diff --git a/docs/examples/guide/layout/center_layout.css b/docs/examples/guide/layout/center_layout.css new file mode 100644 index 000000000..793c7dddc --- /dev/null +++ b/docs/examples/guide/layout/center_layout.css @@ -0,0 +1,21 @@ +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 new file mode 100644 index 000000000..973a256f0 --- /dev/null +++ b/docs/examples/guide/layout/center_layout.py @@ -0,0 +1,14 @@ +from textual.app import App, ComposeResult +from textual.widgets import Static + + +class CenterLayoutExample(App): + def compose(self) -> ComposeResult: + yield Static("One", id="bottom") + yield Static("Two", id="middle") + yield Static("Three", id="top") + + +app = CenterLayoutExample(css_path="center_layout.css") +if __name__ == "__main__": + app.run() diff --git a/docs/examples/guide/layout/combining_layouts.css b/docs/examples/guide/layout/combining_layouts.css new file mode 100644 index 000000000..0681bb2fb --- /dev/null +++ b/docs/examples/guide/layout/combining_layouts.css @@ -0,0 +1,50 @@ +#app-grid { + layout: grid; + grid-size: 2; /* two columns */ + grid-columns: 1fr; + grid-rows: 1fr; +} + +#left-pane > Static { + background: $boost; + color: auto; + margin-bottom: 1; + padding: 1; +} + +#left-pane { + row-span: 2; + background: $panel; + border: dodgerblue; +} + +#top-right { + background: $panel; + border: mediumvioletred; +} + +#top-right > Static { + width: auto; + height: 100%; + margin-right: 1; + background: $boost; +} + +#bottom-right { + layout: grid; + grid-size: 3; + grid-columns: 1fr; + grid-rows: 1fr; + grid-gutter: 1; + background: $panel; + border: greenyellow; +} + +#bottom-right-final { + column-span: 2; +} + +#bottom-right > Static { + height: 100%; + background: $boost; +} diff --git a/docs/examples/guide/layout/combining_layouts.py b/docs/examples/guide/layout/combining_layouts.py new file mode 100644 index 000000000..396b207ec --- /dev/null +++ b/docs/examples/guide/layout/combining_layouts.py @@ -0,0 +1,43 @@ +from textual import layout +from textual.app import ComposeResult, App +from textual.widgets import Static, Header + + +class CombiningLayoutsExample(App): + def compose(self) -> ComposeResult: + yield Header() + yield layout.Container( + layout.Vertical( + *[Static(f"Vertical layout, child {number}") for number in range(15)], + id="left-pane", + ), + layout.Horizontal( + Static("Horizontally"), + Static("Positioned"), + Static("Children"), + Static("Here"), + id="top-right", + ), + layout.Container( + Static("This"), + Static("panel"), + Static("is"), + Static("using"), + Static("grid layout!", id="bottom-right-final"), + id="bottom-right", + ), + 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"]) + + +app = CombiningLayoutsExample(css_path="combining_layouts.css") +if __name__ == "__main__": + app.run() diff --git a/docs/examples/guide/layout/dock_layout1_sidebar.css b/docs/examples/guide/layout/dock_layout1_sidebar.css new file mode 100644 index 000000000..2d8d1e299 --- /dev/null +++ b/docs/examples/guide/layout/dock_layout1_sidebar.css @@ -0,0 +1,7 @@ +#sidebar { + dock: left; + width: 15; + height: 100%; + color: #0f2b41; + background: dodgerblue; +} diff --git a/docs/examples/guide/layout/dock_layout1_sidebar.py b/docs/examples/guide/layout/dock_layout1_sidebar.py new file mode 100644 index 000000000..9e0eced59 --- /dev/null +++ b/docs/examples/guide/layout/dock_layout1_sidebar.py @@ -0,0 +1,20 @@ +from textual.app import App, ComposeResult +from textual.widgets import Static + +TEXT = """\ +Docking a widget removes it from the layout and fixes its position, aligned to either the top, right, bottom, or left edges of a container. + +Docked widgets will not scroll out of view, making them ideal for sticky headers, footers, and sidebars. + +""" + + +class DockLayoutExample(App): + def compose(self) -> ComposeResult: + yield Static("Sidebar", id="sidebar") + yield Static(TEXT * 10, id="body") + + +app = DockLayoutExample(css_path="dock_layout1_sidebar.css") +if __name__ == "__main__": + app.run() diff --git a/docs/examples/guide/layout/dock_layout2_sidebar.css b/docs/examples/guide/layout/dock_layout2_sidebar.css new file mode 100644 index 000000000..b90fa8cd7 --- /dev/null +++ b/docs/examples/guide/layout/dock_layout2_sidebar.css @@ -0,0 +1,14 @@ +#another-sidebar { + dock: left; + width: 30; + height: 100%; + background: deeppink; +} + +#sidebar { + dock: left; + width: 15; + height: 100%; + color: #0f2b41; + background: dodgerblue; +} diff --git a/docs/examples/guide/layout/dock_layout2_sidebar.py b/docs/examples/guide/layout/dock_layout2_sidebar.py new file mode 100644 index 000000000..cec0f931e --- /dev/null +++ b/docs/examples/guide/layout/dock_layout2_sidebar.py @@ -0,0 +1,21 @@ +from textual.app import App, ComposeResult +from textual.widgets import Static + +TEXT = """\ +Docking a widget removes it from the layout and fixes its position, aligned to either the top, right, bottom, or left edges of a container. + +Docked widgets will not scroll out of view, making them ideal for sticky headers, footers, and sidebars. + +""" + + +class DockLayoutExample(App): + def compose(self) -> ComposeResult: + yield Static("Sidebar2", id="another-sidebar") + yield Static("Sidebar1", id="sidebar") + yield Static(TEXT * 10, id="body") + + +app = DockLayoutExample(css_path="dock_layout2_sidebar.css") +if __name__ == "__main__": + app.run() diff --git a/docs/examples/guide/layout/dock_layout3_sidebar_header.css b/docs/examples/guide/layout/dock_layout3_sidebar_header.css new file mode 100644 index 000000000..2d8d1e299 --- /dev/null +++ b/docs/examples/guide/layout/dock_layout3_sidebar_header.css @@ -0,0 +1,7 @@ +#sidebar { + dock: left; + width: 15; + height: 100%; + color: #0f2b41; + background: dodgerblue; +} diff --git a/docs/examples/guide/layout/dock_layout3_sidebar_header.py b/docs/examples/guide/layout/dock_layout3_sidebar_header.py new file mode 100644 index 000000000..eaa2a0b00 --- /dev/null +++ b/docs/examples/guide/layout/dock_layout3_sidebar_header.py @@ -0,0 +1,21 @@ +from textual.app import App, ComposeResult +from textual.widgets import Static, Header + +TEXT = """\ +Docking a widget removes it from the layout and fixes its position, aligned to either the top, right, bottom, or left edges of a container. + +Docked widgets will not scroll out of view, making them ideal for sticky headers, footers, and sidebars. + +""" + + +class DockLayoutExample(App): + def compose(self) -> ComposeResult: + yield Header(id="header") + yield Static("Sidebar1", id="sidebar") + yield Static(TEXT * 10, id="body") + + +app = DockLayoutExample(css_path="dock_layout3_sidebar_header.css") +if __name__ == "__main__": + app.run() diff --git a/docs/examples/guide/layout/grid_layout1.css b/docs/examples/guide/layout/grid_layout1.css new file mode 100644 index 000000000..1749a75c2 --- /dev/null +++ b/docs/examples/guide/layout/grid_layout1.css @@ -0,0 +1,9 @@ +Screen { + layout: grid; + grid-size: 3; +} + +.box { + height: 100%; + border: solid green; +} diff --git a/docs/examples/guide/layout/grid_layout1.py b/docs/examples/guide/layout/grid_layout1.py new file mode 100644 index 000000000..35ea2e384 --- /dev/null +++ b/docs/examples/guide/layout/grid_layout1.py @@ -0,0 +1,17 @@ +from textual.app import App, ComposeResult +from textual.widgets import Static + + +class GridLayoutExample(App): + def compose(self) -> ComposeResult: + yield Static("One", classes="box") + yield Static("Two", classes="box") + yield Static("Three", classes="box") + yield Static("Four", classes="box") + yield Static("Five", classes="box") + yield Static("Six", classes="box") + + +app = GridLayoutExample(css_path="grid_layout1.css") +if __name__ == "__main__": + app.run() diff --git a/docs/examples/guide/layout/grid_layout2.css b/docs/examples/guide/layout/grid_layout2.css new file mode 100644 index 000000000..1749a75c2 --- /dev/null +++ b/docs/examples/guide/layout/grid_layout2.css @@ -0,0 +1,9 @@ +Screen { + layout: grid; + grid-size: 3; +} + +.box { + height: 100%; + border: solid green; +} diff --git a/docs/examples/guide/layout/grid_layout2.py b/docs/examples/guide/layout/grid_layout2.py new file mode 100644 index 000000000..825d6e1c1 --- /dev/null +++ b/docs/examples/guide/layout/grid_layout2.py @@ -0,0 +1,18 @@ +from textual.app import App, ComposeResult +from textual.widgets import Static + + +class GridLayoutExample(App): + def compose(self) -> ComposeResult: + yield Static("One", classes="box") + yield Static("Two", classes="box") + yield Static("Three", classes="box") + yield Static("Four", classes="box") + yield Static("Five", classes="box") + yield Static("Six", classes="box") + yield Static("Seven", classes="box") + + +app = GridLayoutExample(css_path="grid_layout1.css") +if __name__ == "__main__": + app.run() diff --git a/docs/examples/guide/layout/grid_layout3_row_col_adjust.css b/docs/examples/guide/layout/grid_layout3_row_col_adjust.css new file mode 100644 index 000000000..af01d5137 --- /dev/null +++ b/docs/examples/guide/layout/grid_layout3_row_col_adjust.css @@ -0,0 +1,10 @@ +Screen { + layout: grid; + grid-size: 3; + grid-columns: 2fr 1fr 1fr; +} + +.box { + height: 100%; + border: solid green; +} diff --git a/docs/examples/guide/layout/grid_layout3_row_col_adjust.py b/docs/examples/guide/layout/grid_layout3_row_col_adjust.py new file mode 100644 index 000000000..e1a911005 --- /dev/null +++ b/docs/examples/guide/layout/grid_layout3_row_col_adjust.py @@ -0,0 +1,17 @@ +from textual.app import App, ComposeResult +from textual.widgets import Static + + +class GridLayoutExample(App): + def compose(self) -> ComposeResult: + yield Static("One", classes="box") + yield Static("Two", classes="box") + yield Static("Three", classes="box") + yield Static("Four", classes="box") + yield Static("Five", classes="box") + yield Static("Six", classes="box") + + +app = GridLayoutExample(css_path="grid_layout3_row_col_adjust.css") +if __name__ == "__main__": + app.run() diff --git a/docs/examples/guide/layout/grid_layout4_row_col_adjust.css b/docs/examples/guide/layout/grid_layout4_row_col_adjust.css new file mode 100644 index 000000000..7e377c941 --- /dev/null +++ b/docs/examples/guide/layout/grid_layout4_row_col_adjust.css @@ -0,0 +1,11 @@ +Screen { + layout: grid; + grid-size: 3; + grid-columns: 2fr 1fr 1fr; + grid-rows: 25% 75%; +} + +.box { + height: 100%; + border: solid green; +} diff --git a/docs/examples/guide/layout/grid_layout4_row_col_adjust.py b/docs/examples/guide/layout/grid_layout4_row_col_adjust.py new file mode 100644 index 000000000..62391b20f --- /dev/null +++ b/docs/examples/guide/layout/grid_layout4_row_col_adjust.py @@ -0,0 +1,17 @@ +from textual.app import App, ComposeResult +from textual.widgets import Static + + +class GridLayoutExample(App): + def compose(self) -> ComposeResult: + yield Static("One", classes="box") + yield Static("Two", classes="box") + yield Static("Three", classes="box") + yield Static("Four", classes="box") + yield Static("Five", classes="box") + yield Static("Six", classes="box") + + +app = GridLayoutExample(css_path="grid_layout4_row_col_adjust.css") +if __name__ == "__main__": + app.run() diff --git a/docs/examples/guide/layout/grid_layout5_col_span.css b/docs/examples/guide/layout/grid_layout5_col_span.css new file mode 100644 index 000000000..0c78063f6 --- /dev/null +++ b/docs/examples/guide/layout/grid_layout5_col_span.css @@ -0,0 +1,14 @@ +Screen { + layout: grid; + grid-size: 3; +} + +#two { + column-span: 2; + tint: magenta 40%; +} + +.box { + height: 100%; + border: solid green; +} diff --git a/docs/examples/guide/layout/grid_layout5_col_span.py b/docs/examples/guide/layout/grid_layout5_col_span.py new file mode 100644 index 000000000..f9302621a --- /dev/null +++ b/docs/examples/guide/layout/grid_layout5_col_span.py @@ -0,0 +1,17 @@ +from textual.app import App, ComposeResult +from textual.widgets import Static + + +class GridLayoutExample(App): + def compose(self) -> ComposeResult: + yield Static("One", classes="box") + yield Static("Two [b](column-span: 2)", classes="box", id="two") + yield Static("Three", classes="box") + yield Static("Four", classes="box") + yield Static("Five", classes="box") + yield Static("Six", classes="box") + + +app = GridLayoutExample(css_path="grid_layout5_col_span.css") +if __name__ == "__main__": + app.run() diff --git a/docs/examples/guide/layout/grid_layout6_row_span.css b/docs/examples/guide/layout/grid_layout6_row_span.css new file mode 100644 index 000000000..43084fedc --- /dev/null +++ b/docs/examples/guide/layout/grid_layout6_row_span.css @@ -0,0 +1,15 @@ +Screen { + layout: grid; + grid-size: 3; +} + +#two { + column-span: 2; + row-span: 2; + tint: magenta 40%; +} + +.box { + height: 100%; + border: solid green; +} diff --git a/docs/examples/guide/layout/grid_layout6_row_span.py b/docs/examples/guide/layout/grid_layout6_row_span.py new file mode 100644 index 000000000..19cabbe5e --- /dev/null +++ b/docs/examples/guide/layout/grid_layout6_row_span.py @@ -0,0 +1,17 @@ +from textual.app import App, ComposeResult +from textual.widgets import Static + + +class GridLayoutExample(App): + def compose(self) -> ComposeResult: + yield Static("One", classes="box") + yield Static("Two [b](column-span: 2 and row-span: 2)", classes="box", id="two") + yield Static("Three", classes="box") + yield Static("Four", classes="box") + yield Static("Five", classes="box") + yield Static("Six", classes="box") + + +app = GridLayoutExample(css_path="grid_layout6_row_span.css") +if __name__ == "__main__": + app.run() diff --git a/docs/examples/guide/layout/grid_layout7_gutter.css b/docs/examples/guide/layout/grid_layout7_gutter.css new file mode 100644 index 000000000..a495b2642 --- /dev/null +++ b/docs/examples/guide/layout/grid_layout7_gutter.css @@ -0,0 +1,11 @@ +Screen { + layout: grid; + grid-size: 3; + grid-gutter: 1; + background: lightgreen; +} + +.box { + background: darkmagenta; + height: 100%; +} diff --git a/docs/examples/guide/layout/grid_layout7_gutter.py b/docs/examples/guide/layout/grid_layout7_gutter.py new file mode 100644 index 000000000..00f98e112 --- /dev/null +++ b/docs/examples/guide/layout/grid_layout7_gutter.py @@ -0,0 +1,17 @@ +from textual.app import App, ComposeResult +from textual.widgets import Static + + +class GridLayoutExample(App): + def compose(self) -> ComposeResult: + yield Static("One", classes="box") + yield Static("Two", classes="box") + yield Static("Three", classes="box") + yield Static("Four", classes="box") + yield Static("Five", classes="box") + yield Static("Six", classes="box") + + +app = GridLayoutExample(css_path="grid_layout7_gutter.css") +if __name__ == "__main__": + app.run() diff --git a/docs/examples/guide/layout/horizontal_layout.css b/docs/examples/guide/layout/horizontal_layout.css new file mode 100644 index 000000000..3ea0c4e20 --- /dev/null +++ b/docs/examples/guide/layout/horizontal_layout.css @@ -0,0 +1,9 @@ +Screen { + layout: horizontal; +} + +.box { + height: 100%; + width: 1fr; + border: solid green; +} diff --git a/docs/examples/guide/layout/horizontal_layout.py b/docs/examples/guide/layout/horizontal_layout.py new file mode 100644 index 000000000..cada52668 --- /dev/null +++ b/docs/examples/guide/layout/horizontal_layout.py @@ -0,0 +1,14 @@ +from textual.app import App, ComposeResult +from textual.widgets import Static + + +class HorizontalLayoutExample(App): + def compose(self) -> ComposeResult: + yield Static("One", classes="box") + yield Static("Two", classes="box") + yield Static("Three", classes="box") + + +app = HorizontalLayoutExample(css_path="horizontal_layout.css") +if __name__ == "__main__": + app.run() diff --git a/docs/examples/guide/layout/horizontal_layout_overflow.css b/docs/examples/guide/layout/horizontal_layout_overflow.css new file mode 100644 index 000000000..6d52aa1f6 --- /dev/null +++ b/docs/examples/guide/layout/horizontal_layout_overflow.css @@ -0,0 +1,9 @@ +Screen { + layout: horizontal; + overflow-x: auto; +} + +.box { + height: 100%; + border: solid green; +} diff --git a/docs/examples/guide/layout/horizontal_layout_overflow.py b/docs/examples/guide/layout/horizontal_layout_overflow.py new file mode 100644 index 000000000..8520c2b8b --- /dev/null +++ b/docs/examples/guide/layout/horizontal_layout_overflow.py @@ -0,0 +1,14 @@ +from textual.app import App, ComposeResult +from textual.widgets import Static + + +class HorizontalLayoutExample(App): + def compose(self) -> ComposeResult: + yield Static("One", classes="box") + yield Static("Two", classes="box") + yield Static("Three", classes="box") + + +app = HorizontalLayoutExample(css_path="horizontal_layout_overflow.css") +if __name__ == "__main__": + app.run() diff --git a/docs/examples/guide/layout/layers.css b/docs/examples/guide/layout/layers.css new file mode 100644 index 000000000..7be3f8d83 --- /dev/null +++ b/docs/examples/guide/layout/layers.css @@ -0,0 +1,22 @@ +Screen { + layout: center; + layers: below above; +} + +Static { + width: 28; + height: 8; + color: auto; + content-align: center middle; +} + +#box1 { + background: darkcyan; + layer: above; +} + +#box2 { + layer: below; + background: orange; + offset: 12 6; +} diff --git a/docs/examples/guide/layout/layers.py b/docs/examples/guide/layout/layers.py new file mode 100644 index 000000000..d26b5f18a --- /dev/null +++ b/docs/examples/guide/layout/layers.py @@ -0,0 +1,13 @@ +from textual.app import App, ComposeResult +from textual.widgets import Static + + +class LayersExample(App): + def compose(self) -> ComposeResult: + yield Static("box1 (layer = above)", id="box1") + yield Static("box2 (layer = below)", id="box2") + + +app = LayersExample(css_path="layers.css") +if __name__ == "__main__": + app.run() diff --git a/docs/examples/guide/layout/offset.css b/docs/examples/guide/layout/offset.css new file mode 100644 index 000000000..4a64c7115 --- /dev/null +++ b/docs/examples/guide/layout/offset.css @@ -0,0 +1,36 @@ +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 new file mode 100644 index 000000000..c55362ca4 --- /dev/null +++ b/docs/examples/guide/layout/offset.py @@ -0,0 +1,27 @@ +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): + def compose(self) -> ComposeResult: + yield layout.Container( + Box(id="box1"), + Box(id="box2"), + Box(id="box3"), + Box(id="box4"), + id="parent", + ) + + +app = OffsetExample(css_path="offset.css") +if __name__ == "__main__": + app.run() diff --git a/docs/examples/guide/layout/utility_containers.css b/docs/examples/guide/layout/utility_containers.css new file mode 100644 index 000000000..006604130 --- /dev/null +++ b/docs/examples/guide/layout/utility_containers.css @@ -0,0 +1,10 @@ +Static { + content-align: center middle; + background: crimson; + border: solid darkred; + height: 1fr; +} + +.column { + width: 1fr; +} diff --git a/docs/examples/guide/layout/utility_containers.py b/docs/examples/guide/layout/utility_containers.py new file mode 100644 index 000000000..2ae40a2cc --- /dev/null +++ b/docs/examples/guide/layout/utility_containers.py @@ -0,0 +1,24 @@ +from textual import layout +from textual.app import App, ComposeResult +from textual.widgets import Static + + +class UtilityContainersExample(App): + def compose(self) -> ComposeResult: + yield layout.Horizontal( + layout.Vertical( + Static("One"), + Static("Two"), + classes="column", + ), + layout.Vertical( + Static("Three"), + Static("Four"), + classes="column", + ), + ) + + +app = UtilityContainersExample(css_path="utility_containers.css") +if __name__ == "__main__": + app.run() diff --git a/docs/examples/guide/layout/vertical_layout.css b/docs/examples/guide/layout/vertical_layout.css new file mode 100644 index 000000000..30d557c60 --- /dev/null +++ b/docs/examples/guide/layout/vertical_layout.css @@ -0,0 +1,8 @@ +Screen { + layout: vertical; +} + +.box { + height: 1fr; + border: solid green; +} diff --git a/docs/examples/guide/layout/vertical_layout.py b/docs/examples/guide/layout/vertical_layout.py new file mode 100644 index 000000000..7c75dd592 --- /dev/null +++ b/docs/examples/guide/layout/vertical_layout.py @@ -0,0 +1,14 @@ +from textual.app import App, ComposeResult +from textual.widgets import Static + + +class VerticalLayoutExample(App): + def compose(self) -> ComposeResult: + yield Static("One", classes="box") + yield Static("Two", classes="box") + yield Static("Three", classes="box") + + +app = VerticalLayoutExample(css_path="vertical_layout.css") +if __name__ == "__main__": + app.run() diff --git a/docs/examples/guide/layout/vertical_layout_scrolled.css b/docs/examples/guide/layout/vertical_layout_scrolled.css new file mode 100644 index 000000000..3dafcd616 --- /dev/null +++ b/docs/examples/guide/layout/vertical_layout_scrolled.css @@ -0,0 +1,8 @@ +Screen { + layout: vertical; +} + +.box { + height: 14; + border: solid green; +} diff --git a/docs/examples/guide/layout/vertical_layout_scrolled.py b/docs/examples/guide/layout/vertical_layout_scrolled.py new file mode 100644 index 000000000..7adb2cc81 --- /dev/null +++ b/docs/examples/guide/layout/vertical_layout_scrolled.py @@ -0,0 +1,14 @@ +from textual.app import App, ComposeResult +from textual.widgets import Static + + +class VerticalLayoutScrolledExample(App): + def compose(self) -> ComposeResult: + yield Static("One", classes="box") + yield Static("Two", classes="box") + yield Static("Three", classes="box") + + +app = VerticalLayoutScrolledExample(css_path="vertical_layout_scrolled.css") +if __name__ == "__main__": + app.run() diff --git a/docs/guide/layout.md b/docs/guide/layout.md index 051f303ee..534355643 100644 --- a/docs/guide/layout.md +++ b/docs/guide/layout.md @@ -1,78 +1,634 @@ # Layout -In textual the *layout* defines how widgets will be arranged (or *layed out*) on the screen. Textual supports a number of layouts which can be set either via a widgets `styles` object or via CSS. +In Textual, the *layout* defines how widgets will be arranged (or *laid out*) inside a container. +Textual supports a number of layouts which can be set either via a widgets `styles` object or via CSS. +Layouts can be used for both high-level positioning of widgets on screen, and for positioning of nested widgets. -TODO: layout docs +## Vertical -## Vertical - -A vertical layout will place new widgets below previous widgets, starting from the top of the screen. +The `vertical` layout arranges child widgets vertically, from top to bottom.
--8<-- "docs/images/layout/vertical.excalidraw.svg"
+The example below demonstrates how children are arranged inside a container with the `vertical` layout. -TODO: Explanation of vertical layout +=== "Output" + ```{.textual path="docs/examples/guide/layout/vertical_layout.py"} + ``` + +=== "vertical_layout.py" + + ```python + --8<-- "docs/examples/guide/layout/vertical_layout.py" + ``` + +=== "vertical_layout.css" + + ```sass hl_lines="2" + --8<-- "docs/examples/guide/layout/vertical_layout.css" + ``` + +Notice that the first widget yielded from the `compose` method appears at the top of the display, +the second widget appears below it, and so on. +Inside `vertical_layout.css`, we've assigned `layout: vertical` to `Screen`. +`Screen` is the parent container of the widgets yielded from the `App.compose` method, and can be thought of as the terminal window itself. + +!!! note + + The `layout: vertical` CSS isn't *strictly* necessary in this case, since Screens use a `vertical` layout by default. + +We've assigned each child `.box` a height of `1fr`, which ensures they're each allocated an equal portion of the available height. + +You might also have noticed that the child widgets are the same width as the screen, despite nothing in our CSS file suggesting this. +This is because widgets expand to the width of their parent container (in this case, the `Screen`). + +Just like other styles, `layout` can be adjusted at runtime by modifying the `styles` of a `Widget` instance: + +```python +widget.styles.layout = "vertical" +``` + +Using `fr` units guarantees that the children fill the available height of the parent. +However, if the total height of the children exceeds the available space, then Textual will automatically add +a scrollbar to the parent `Screen`. + +!!! note + + A scrollbar is added automatically because `Screen` contains the declaration `overflow-y: auto;`. + +For example, if we swap out `height: 1fr;` for `height: 10;` in the example above, the child widgets become a fixed height of 10, and a scrollbar appears (assuming our terminal window is sufficiently small): + +```{.textual path="docs/examples/guide/layout/vertical_layout_scrolled.py"} +``` + +[//]: # (TODO: Add link to "focus" docs in paragraph below.) + +With the parent container in focus, we can use our mouse wheel, trackpad, or keyboard to scroll it. ## Horizontal -A horizontal layout will place the first widget at the top left of the screen, and new widgets will be place directly to the right of the previous widget. +The `horizontal` layout arranges child widgets horizontally, from left to right.
--8<-- "docs/images/layout/horizontal.excalidraw.svg"
+The example below shows how we can arrange widgets horizontally, with minimal changes to the vertical layout example above. -TODO: Explantion of horizontal layout + +=== "Output" + + ```{.textual path="docs/examples/guide/layout/horizontal_layout.py"} + ``` + +=== "horizontal_layout.py" + + ```python + --8<-- "docs/examples/guide/layout/horizontal_layout.py" + ``` + +=== "horizontal_layout.css" + + ```sass hl_lines="2" + --8<-- "docs/examples/guide/layout/horizontal_layout.css" + ``` + + +We've changed the `layout` to `horizontal` inside our CSS file. +As a result, the widgets are now arranged from left to right instead of top to bottom. + +We also adjusted the height of the child `.box` widgets to `100%`. +As mentioned earlier, widgets expand to fill the _width_ of their parent container. +They do not, however, expand to fill the container's height. +Thus, we need explicitly assign `height: 100%` to achieve this. + +A consequence of this "horizontal growth" behaviour is that if we remove the width restriction from the above example (by deleting `width: 1fr;`), each child widget will grow to fit the width of the screen, +and only the first widget will be visible. +The other two widgets in our layout are offscreen, to the right-hand side of the screen. +In the case of `horizontal` layout, Textual will _not_ automatically add a scrollbar. + +To enable horizontal scrolling, we can use the `overflow-x: auto;` declaration: + +=== "Output" + + ```{.textual path="docs/examples/guide/layout/horizontal_layout_overflow.py"} + ``` + +=== "horizontal_layout_overflow.py" + + ```python + --8<-- "docs/examples/guide/layout/horizontal_layout_overflow.py" + ``` + +=== "horizontal_layout_overflow.css" + + ```sass hl_lines="3" + --8<-- "docs/examples/guide/layout/horizontal_layout_overflow.css" + ``` + +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 -A center widget will place the widget directly in the center of the screen. New widgets will also be placed in the center of the screen, overlapping previous widgets. - -There probably isn't a practical use for such overlapping widgets. In practice this layout is probably only useful where you have a single child widget. +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. -TODO: Explanation of center layout +=== "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. + +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. +In other words, we have a single row containing two columns. + +=== "Output" + + ```{.textual path="docs/examples/guide/layout/utility_containers.py"} + ``` + +=== "utility_containers.py" + + ```python + --8<-- "docs/examples/guide/layout/utility_containers.py" + ``` + +=== "utility_containers.css" + + ```sass hl_lines="2" + --8<-- "docs/examples/guide/layout/utility_containers.css" + ``` + +You may be tempted to use many levels of nested utility containers in order to build advanced, grid-like layouts. +However, Textual comes with a more powerful mechanism for achieving this known as _grid layout_, which we'll discuss next. ## Grid -A grid layout arranges widgets within a grid composed of columns and rows. Widgets can span multiple rows or columns to create more complex layouts. +The `grid` layout arranges widgets within a grid. +Widgets can span multiple rows and columns to create complex layouts. +The diagram below hints at what can be achieved using `layout: grid`.
--8<-- "docs/images/layout/grid.excalidraw.svg"
+!!! note -TODO: Explanation of grid layout + Grid layouts in Textual have very 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. + +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. + +=== "Output" + + ```{.textual path="docs/examples/guide/layout/grid_layout1.py"} + ``` + +=== "grid_layout1.py" + + ```python + --8<-- "docs/examples/guide/layout/grid_layout1.py" + ``` + +=== "grid_layout1.css" + + ```sass hl_lines="2 3" + --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"} +``` + +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. + +Now that we know how to define a simple uniform grid, let's look at how we can +customize it to create more complex layouts. + +### Row and column sizes + +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`. +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"} + ``` + +=== "grid_layout3_row_col_adjust.py" + + ```python + --8<-- "docs/examples/guide/layout/grid_layout3_row_col_adjust.py" + ``` + +=== "grid_layout3_row_col_adjust.css" + + ```sass hl_lines="4" + --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. +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`. +As a result, the first column is allocated twice the width of the other columns. + +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"} + ``` + +=== "grid_layout4_row_col_adjust.py" + + ```python + --8<-- "docs/examples/guide/layout/grid_layout4_row_col_adjust.py" + ``` + +=== "grid_layout4_row_col_adjust.css" + + ```sass hl_lines="5" + --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"} +``` + +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. + +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. +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" + + ```{.textual path="docs/examples/guide/layout/grid_layout5_col_span.py"} + ``` + +=== "grid_layout5_col_span.py" + + ```python hl_lines="8" + --8<-- "docs/examples/guide/layout/grid_layout5_col_span.py" + ``` + +=== "grid_layout5_col_span.css" + + ```sass hl_lines="6-9" + --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. + +!!! note + + In the example above, setting the `column-span` of `#two` to be 3 (instead of 2) would have the same effect, since there are only 2 columns available (including `#two`'s original column). + +We can similarly adjust the `row-span` of a cell to have it span multiple rows. +This can be used in conjunction with `column-span`, meaning one cell may span multiple rows and columns. +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"} + ``` + +=== "grid_layout6_row_span.py" + + ```python + --8<-- "docs/examples/guide/layout/grid_layout6_row_span.py" + ``` + +=== "grid_layout6_row_span.css" + + ```sass hl_lines="8" + --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. + +### Gutter + +The spacing between cells in the grid can be adjusted using the `grid-gutter` CSS property. +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`. +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" + + ```{.textual path="docs/examples/guide/layout/grid_layout7_gutter.py"} + ``` + +=== "grid_layout7_gutter.py" + + ```python + --8<-- "docs/examples/guide/layout/grid_layout7_gutter.py" + ``` + +=== "grid_layout7_gutter.css" + + ```sass hl_lines="4" + --8<-- "docs/examples/guide/layout/grid_layout7_gutter.css" + ``` + +Notice that gutter only applies _between_ the cells in a grid, pushing them away from each other. +It doesn't add any spacing between cells and the edges of the parent container. + +!!! tip + + You can also supply two values to the `grid-gutter` property to set vertical and horizontal gutters respectively. + Since terminal cells are typically two times taller than they are wide, + it's common to set the horizontal gutter equal to double the vertical gutter (e.g. `grid-gutter: 1 2;`) in order to achieve visually consistent spacing around grid cells. ## Docking -Widgets may be *docked*. Docking a widget removes it from the layout and fixes it position, aligned to either the top, right, bottom, or left edges of the screen. Docked widgets will not scroll, making them ideal for fixed headers / footers / sidebars. +Widgets may be *docked*. +Docking a widget removes it from the layout and fixes its position, aligned to either the top, right, bottom, or left edges of a container. +Docked widgets will not scroll out of view, making them ideal for sticky headers, footers, and sidebars.
--8<-- "docs/images/layout/dock.excalidraw.svg"
+To dock a widget to an edge, add a `dock: ;` declaration to it, where `` is one of `top`, `right`, `bottom`, or `left`. +For example, a sidebar similar to that shown in the diagram above can be achieved using `dock: left;`. +The code below shows a simple sidebar implementation. -TODO: Diagram -TODO: Explanation of dock +=== "Output" + + ```{.textual path="docs/examples/guide/layout/dock_layout1_sidebar.py" press="pagedown,down,down,_,_,_,_,_"} + ``` + +=== "dock_layout1_sidebar.py" + + ```python + --8<-- "docs/examples/guide/layout/dock_layout1_sidebar.py" + ``` + +=== "dock_layout1_sidebar.css" + + ```sass hl_lines="2" + --8<-- "docs/examples/guide/layout/dock_layout1_sidebar.css" + ``` + +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. +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. + +=== "Output" + + ```{.textual path="docs/examples/guide/layout/dock_layout2_sidebar.py" press="pagedown,down,down,_,_,_,_,_"} + ``` + +=== "dock_layout2_sidebar.py" + + ```python hl_lines="14" + --8<-- "docs/examples/guide/layout/dock_layout2_sidebar.py" + ``` + +=== "dock_layout2_sidebar.css" + + ```sass hl_lines="1-6" + --8<-- "docs/examples/guide/layout/dock_layout2_sidebar.css" + ``` + +Notice that the original sidebar (`#sidebar`) appears on top of the newly docked widget. +This is because `#sidebar` was yielded _after_ `#another-sidebar` inside the `compose` method. + +Of course, we can also dock widgets to multiple edges within the same container. +The built-in `Header` widget contains some internal CSS which docks it to the top. +We can yield it inside `compose`, and without any additional CSS, we get a header fixed to the top of the screen. + +=== "Output" + + ```{.textual path="docs/examples/guide/layout/dock_layout3_sidebar_header.py"} + ``` + +=== "dock_layout3_sidebar_header.py" + + ```python hl_lines="14" + --8<-- "docs/examples/guide/layout/dock_layout3_sidebar_header.py" + ``` + +=== "dock_layout3_sidebar_header.css" + + ```sass + --8<-- "docs/examples/guide/layout/dock_layout3_sidebar_header.css" + ``` + +If we wished for the sidebar to appear below the header, it'd simply be a case of yielding the sidebar before we yield the header. + +## 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. + +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. + +The `layers` declaration 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. + +An example `layers` declaration looks like: `layers: one two three;`. +To add a widget to the topmost layer in this case, you'd add a declaration of `layer: three;` to it. + +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`. + +=== "Output" + + ```{.textual path="docs/examples/guide/layout/layers.py"} + ``` + +=== "layers.py" + + ```python + --8<-- "docs/examples/guide/layout/layers.py" + ``` + +=== "layers.css" + + ```sass hl_lines="3 15 19" + --8<-- "docs/examples/guide/layout/layers.css" + ``` ## Offsets -Widgets have a relative offset which is added to the widget's location, after its location has been determined via its layout. +Widgets have a relative offset which is added to the widget's location, _after_ its location has been determined via its parent's layout. +This means that if a widget hasn't had its offset modified using CSS or Python code, it will have an offset of `(0, 0)`.
--8<-- "docs/images/layout/offset.excalidraw.svg"
+The offset of a widget can be set using the `offset` CSS property. +`offset` takes two values. -TODO: Diagram -TODO: Offsets +* 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. + +=== "Output" + + ```{.textual path="docs/examples/guide/layout/combining_layouts.py"} + ``` + +=== "combining_layouts.py" + + ```python + --8<-- "docs/examples/guide/layout/combining_layouts.py" + ``` + +=== "combining_layouts.css" + + ```sass hl_lines="4" + --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! diff --git a/examples/calculator.css b/examples/calculator.css index ce9458a10..adbabebe7 100644 --- a/examples/calculator.css +++ b/examples/calculator.css @@ -9,13 +9,13 @@ Screen { grid-columns: 1fr; grid-rows: 2fr 1fr 1fr 1fr 1fr 1fr; margin: 1 2; - min-height:25; - min-width: 26; + min-height: 25; + min-width: 26; } Button { width: 100%; - height: 100%; + height: 100%; } #numbers { diff --git a/mkdocs.yml b/mkdocs.yml index fa7ba2f05..a2c957d2f 100644 --- a/mkdocs.yml +++ b/mkdocs.yml @@ -23,12 +23,12 @@ nav: - "how-to/index.md" - "how-to/animation.md" - "how-to/mouse-and-keyboard.md" - - "how-to/scroll.md" + - "how-to/scroll.md" - Events: - "events/index.md" - "events/blur.md" - "events/descendant_blur.md" - - "events/descendant_focus.md" + - "events/descendant_focus.md" - "events/enter.md" - "events/focus.md" - "events/hide.md" @@ -78,7 +78,7 @@ nav: - "styles/tint.md" - "styles/visibility.md" - "styles/width.md" - - Widgets: + - Widgets: - "widgets/index.md" - "widgets/button.md" - "widgets/data_table.md" @@ -142,6 +142,7 @@ theme: features: - navigation.tabs - navigation.indexes + - navigation.tabs.sticky palette: - media: "(prefers-color-scheme: light)" scheme: default diff --git a/src/textual/css/_style_properties.py b/src/textual/css/_style_properties.py index b8d6cef22..4a45cfbbf 100644 --- a/src/textual/css/_style_properties.py +++ b/src/textual/css/_style_properties.py @@ -552,9 +552,7 @@ class DocksProperty: class DockProperty: """Descriptor for getting and setting the dock property. The dock property - allows you to specify which dock you wish a Widget to be attached to. This - should be used in conjunction with the "docks" property which lets you define - the docks themselves, and where they are located on screen. + allows you to specify which edge you want to fix a Widget to. """ def __get__(