mirror of
https://github.com/Textualize/textual.git
synced 2025-10-17 02:38:12 +03:00
637 lines
24 KiB
Markdown
637 lines
24 KiB
Markdown
# Layout
|
|
|
|
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.
|
|
|
|
## Vertical
|
|
|
|
The `vertical` layout arranges child widgets vertically, from top to bottom.
|
|
|
|
<div class="excalidraw">
|
|
--8<-- "docs/images/layout/vertical.excalidraw.svg"
|
|
</div>
|
|
|
|
The example below demonstrates how children are arranged inside a container with the `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
|
|
|
|
The `horizontal` layout arranges child widgets horizontally, from left to right.
|
|
|
|
<div class="excalidraw">
|
|
--8<-- "docs/images/layout/horizontal.excalidraw.svg"
|
|
</div>
|
|
|
|
The example below shows how we can arrange widgets horizontally, with minimal changes to the vertical layout example above.
|
|
|
|
|
|
=== "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
|
|
|
|
The `center` layout will place a widget directly in the center of the container.
|
|
|
|
<div class="excalidraw">
|
|
--8<-- "docs/images/layout/center.excalidraw.svg"
|
|
</div>
|
|
|
|
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.
|
|
|
|
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
|
|
|
|
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`.
|
|
|
|
<div class="excalidraw">
|
|
--8<-- "docs/images/layout/grid.excalidraw.svg"
|
|
</div>
|
|
|
|
!!! note
|
|
|
|
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 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.
|
|
|
|
<div class="excalidraw">
|
|
--8<-- "docs/images/layout/dock.excalidraw.svg"
|
|
</div>
|
|
|
|
To dock a widget to an edge, add a `dock: <EDGE>;` declaration to it, where `<EDGE>` 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.
|
|
|
|
=== "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`.
|
|
|
|
[//]: # (NOTE: the example below also appears in the layers and layer style reference)
|
|
|
|
=== "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 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)`.
|
|
|
|
<div class="excalidraw">
|
|
--8<-- "docs/images/layout/offset.excalidraw.svg"
|
|
</div>
|
|
|
|
The offset of a widget can be set using the `offset` CSS property.
|
|
`offset` takes two values.
|
|
|
|
* 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!
|