mirror of
https://github.com/Textualize/textual.git
synced 2025-10-17 02:38:12 +03:00
new align
This commit is contained in:
@@ -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
|
||||
|
||||
|
||||
|
||||
@@ -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?"
|
||||
|
||||
|
||||
@@ -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?"
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
@@ -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()
|
||||
@@ -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()
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
Screen {
|
||||
layout: grid;
|
||||
grid-size: 3;
|
||||
grid-size: 3 2;
|
||||
}
|
||||
|
||||
.box {
|
||||
|
||||
@@ -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")
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
Screen {
|
||||
layout: center;
|
||||
align: center middle;
|
||||
layers: below above;
|
||||
}
|
||||
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
@@ -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()
|
||||
@@ -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",
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
Screen {
|
||||
layout: center;
|
||||
align: center middle;
|
||||
}
|
||||
|
||||
FizzBuzz {
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
Screen {
|
||||
layout: center;
|
||||
align: center middle;
|
||||
}
|
||||
|
||||
FizzBuzz {
|
||||
|
||||
@@ -1,3 +1,3 @@
|
||||
Screen {
|
||||
layout: center;
|
||||
align: center middle;
|
||||
}
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
Screen {
|
||||
layout: center;
|
||||
align: center middle;
|
||||
}
|
||||
|
||||
Hello {
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
Screen {
|
||||
layout: center;
|
||||
align: center middle;
|
||||
}
|
||||
|
||||
Hello {
|
||||
|
||||
@@ -1,3 +1,3 @@
|
||||
Screen {
|
||||
layout: center;
|
||||
align: center middle;
|
||||
}
|
||||
|
||||
@@ -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:
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
Screen {
|
||||
layout: center;
|
||||
align: center middle;
|
||||
}
|
||||
|
||||
Hello {
|
||||
|
||||
13
docs/examples/styles/align.css
Normal file
13
docs/examples/styles/align.css
Normal file
@@ -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;
|
||||
}
|
||||
11
docs/examples/styles/align.py
Normal file
11
docs/examples/styles/align.py
Normal file
@@ -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")
|
||||
@@ -10,12 +10,6 @@
|
||||
height: auto;
|
||||
}
|
||||
|
||||
#center-layout {
|
||||
layout: center;
|
||||
background: darkslateblue;
|
||||
height: 7;
|
||||
}
|
||||
|
||||
Static {
|
||||
margin: 1;
|
||||
width: 12;
|
||||
|
||||
@@ -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")
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -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")
|
||||
|
||||
@@ -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")
|
||||
|
||||
@@ -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()
|
||||
@@ -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()
|
||||
@@ -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
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
@@ -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),
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -88,10 +83,11 @@ This doesn't look much like a tree yet. Let's add a header and a footer to this
|
||||
|
||||
=== "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)
|
||||
|
||||
@@ -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"
|
||||
|
||||
@@ -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"
|
||||
|
||||
@@ -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.
|
||||
|
||||
|
||||
@@ -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.
|
||||
|
||||
<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.
|
||||
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.
|
||||
|
||||
@@ -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
|
||||
</div>
|
||||
|
||||
|
||||
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"
|
||||
```
|
||||
|
||||
|
||||
1
docs/reference/containers.md
Normal file
1
docs/reference/containers.md
Normal file
@@ -0,0 +1 @@
|
||||
::: textual.containers
|
||||
68
docs/styles/align.md
Normal file
68
docs/styles/align.md
Normal file
@@ -0,0 +1,68 @@
|
||||
# Align
|
||||
|
||||
The `align` style aligns children within a container.
|
||||
|
||||
## Syntax
|
||||
|
||||
```
|
||||
align: <HORIZONTAL> <VERTICAL>;
|
||||
align-horizontal: <HORIZONTAL>;
|
||||
align-vertical: <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")
|
||||
```
|
||||
@@ -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. |
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
|
||||
@@ -52,6 +52,7 @@ nav:
|
||||
- "events/show.md"
|
||||
- Styles:
|
||||
- "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"
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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"),
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
Screen {
|
||||
layout: center;
|
||||
align: center middle;
|
||||
background: darkslategrey;
|
||||
}
|
||||
|
||||
|
||||
@@ -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:
|
||||
|
||||
14
sandbox/will/align.css
Normal file
14
sandbox/will/align.css
Normal file
@@ -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;
|
||||
}
|
||||
19
sandbox/will/align.py
Normal file
19
sandbox/will/align.py
Normal file
@@ -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()
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
Screen {
|
||||
layout: center;
|
||||
align: center middle;
|
||||
}
|
||||
|
||||
#parent {
|
||||
|
||||
@@ -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")
|
||||
|
||||
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
from textual.app import App
|
||||
|
||||
from textual.layout import Container
|
||||
from textual.containers import Container
|
||||
from textual.widgets import DirectoryTree
|
||||
|
||||
|
||||
|
||||
@@ -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(
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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(),
|
||||
)
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
"""
|
||||
@@ -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),
|
||||
)
|
||||
|
||||
|
||||
|
||||
@@ -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(
|
||||
|
||||
@@ -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)
|
||||
@@ -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,
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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;
|
||||
background: $surface;
|
||||
}
|
||||
|
||||
@@ -47,9 +45,7 @@ class Welcome(Static):
|
||||
Welcome #close {
|
||||
dock: bottom;
|
||||
width: 100%;
|
||||
margin-top: 1;
|
||||
}
|
||||
|
||||
"""
|
||||
|
||||
def compose(self) -> ComposeResult:
|
||||
|
||||
File diff suppressed because one or more lines are too long
@@ -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")
|
||||
|
||||
|
||||
@@ -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
|
||||
Reference in New Issue
Block a user