mirror of
https://github.com/Textualize/textual.git
synced 2025-10-17 02:38:12 +03:00
22
CHANGELOG.md
22
CHANGELOG.md
@@ -11,6 +11,21 @@ and this project adheres to [Semantic Versioning](http://semver.org/).
|
|||||||
|
|
||||||
- Dropped "loading-indicator--dot" component style from LoadingIndicator https://github.com/Textualize/textual/pull/2050
|
- Dropped "loading-indicator--dot" component style from LoadingIndicator https://github.com/Textualize/textual/pull/2050
|
||||||
|
|
||||||
|
## Unreleased
|
||||||
|
|
||||||
|
### Changed
|
||||||
|
|
||||||
|
- Breaking change: changed default behaviour of `Vertical` (see `VerticalScroll`) https://github.com/Textualize/textual/issues/1957
|
||||||
|
- The default `overflow` style for `Horizontal` was changed to `hidden hidden` https://github.com/Textualize/textual/issues/1957
|
||||||
|
|
||||||
|
### Added
|
||||||
|
|
||||||
|
- Added `HorizontalScroll` https://github.com/Textualize/textual/issues/1957
|
||||||
|
- Added `Center` https://github.com/Textualize/textual/issues/1957
|
||||||
|
- Added `Middle` https://github.com/Textualize/textual/issues/1957
|
||||||
|
- Added `VerticalScroll` (mimicking the old behaviour of `Vertical`) https://github.com/Textualize/textual/issues/1957
|
||||||
|
|
||||||
|
|
||||||
## [0.15.1] - 2023-03-14
|
## [0.15.1] - 2023-03-14
|
||||||
|
|
||||||
### Fixed
|
### Fixed
|
||||||
@@ -22,6 +37,13 @@ and this project adheres to [Semantic Versioning](http://semver.org/).
|
|||||||
|
|
||||||
### Fixed
|
### Fixed
|
||||||
|
|
||||||
|
- Fixed container not resizing when a widget is removed https://github.com/Textualize/textual/issues/2007
|
||||||
|
- Fixes issue where the horizontal scrollbar would be incorrectly enabled https://github.com/Textualize/textual/pull/2024
|
||||||
|
|
||||||
|
## [0.15.0] - 2023-03-13
|
||||||
|
|
||||||
|
### Changed
|
||||||
|
|
||||||
- Fixed container not resizing when a widget is removed https://github.com/Textualize/textual/issues/2007
|
- Fixed container not resizing when a widget is removed https://github.com/Textualize/textual/issues/2007
|
||||||
- Fixed issue where the horizontal scrollbar would be incorrectly enabled https://github.com/Textualize/textual/pull/2024
|
- Fixed issue where the horizontal scrollbar would be incorrectly enabled https://github.com/Textualize/textual/pull/2024
|
||||||
- Fixed `Pilot.click` not correctly creating the mouse events https://github.com/Textualize/textual/issues/2022
|
- Fixed `Pilot.click` not correctly creating the mouse events https://github.com/Textualize/textual/issues/2022
|
||||||
|
|||||||
@@ -1,9 +1,9 @@
|
|||||||
from random import randint
|
|
||||||
import time
|
import time
|
||||||
|
from random import randint
|
||||||
|
|
||||||
from textual.app import App, ComposeResult
|
from textual.app import App, ComposeResult
|
||||||
from textual.color import Color
|
from textual.color import Color
|
||||||
from textual.containers import Grid, Vertical
|
from textual.containers import Grid, VerticalScroll
|
||||||
from textual.widget import Widget
|
from textual.widget import Widget
|
||||||
from textual.widgets import Footer, Label
|
from textual.widgets import Footer, Label
|
||||||
|
|
||||||
@@ -28,7 +28,7 @@ class MyApp(App[None]):
|
|||||||
def compose(self) -> ComposeResult:
|
def compose(self) -> ComposeResult:
|
||||||
yield Grid(
|
yield Grid(
|
||||||
ColourChanger(),
|
ColourChanger(),
|
||||||
Vertical(id="log"),
|
VerticalScroll(id="log"),
|
||||||
)
|
)
|
||||||
yield Footer()
|
yield Footer()
|
||||||
|
|
||||||
|
|||||||
@@ -1,10 +1,10 @@
|
|||||||
import asyncio
|
import asyncio
|
||||||
from random import randint
|
|
||||||
import time
|
import time
|
||||||
|
from random import randint
|
||||||
|
|
||||||
from textual.app import App, ComposeResult
|
from textual.app import App, ComposeResult
|
||||||
from textual.color import Color
|
from textual.color import Color
|
||||||
from textual.containers import Grid, Vertical
|
from textual.containers import Grid, VerticalScroll
|
||||||
from textual.widget import Widget
|
from textual.widget import Widget
|
||||||
from textual.widgets import Footer, Label
|
from textual.widgets import Footer, Label
|
||||||
|
|
||||||
@@ -29,7 +29,7 @@ class MyApp(App[None]):
|
|||||||
def compose(self) -> ComposeResult:
|
def compose(self) -> ComposeResult:
|
||||||
yield Grid(
|
yield Grid(
|
||||||
ColourChanger(),
|
ColourChanger(),
|
||||||
Vertical(id="log"),
|
VerticalScroll(id="log"),
|
||||||
)
|
)
|
||||||
yield Footer()
|
yield Footer()
|
||||||
|
|
||||||
|
|||||||
@@ -1,10 +1,10 @@
|
|||||||
import asyncio
|
import asyncio
|
||||||
from random import randint
|
|
||||||
import time
|
import time
|
||||||
|
from random import randint
|
||||||
|
|
||||||
from textual.app import App, ComposeResult
|
from textual.app import App, ComposeResult
|
||||||
from textual.color import Color
|
from textual.color import Color
|
||||||
from textual.containers import Grid, Vertical
|
from textual.containers import Grid, VerticalScroll
|
||||||
from textual.widget import Widget
|
from textual.widget import Widget
|
||||||
from textual.widgets import Footer, Label
|
from textual.widgets import Footer, Label
|
||||||
|
|
||||||
@@ -29,7 +29,7 @@ class MyApp(App[None]):
|
|||||||
def compose(self) -> ComposeResult:
|
def compose(self) -> ComposeResult:
|
||||||
yield Grid(
|
yield Grid(
|
||||||
ColourChanger(),
|
ColourChanger(),
|
||||||
Vertical(id="log"),
|
VerticalScroll(id="log"),
|
||||||
)
|
)
|
||||||
yield Footer()
|
yield Footer()
|
||||||
|
|
||||||
|
|||||||
@@ -6,9 +6,10 @@ except ImportError:
|
|||||||
raise ImportError("Please install httpx with 'pip install httpx' ")
|
raise ImportError("Please install httpx with 'pip install httpx' ")
|
||||||
|
|
||||||
from rich.json import JSON
|
from rich.json import JSON
|
||||||
|
|
||||||
from textual.app import App, ComposeResult
|
from textual.app import App, ComposeResult
|
||||||
from textual.containers import Vertical
|
from textual.containers import VerticalScroll
|
||||||
from textual.widgets import Static, Input
|
from textual.widgets import Input, Static
|
||||||
|
|
||||||
|
|
||||||
class DictionaryApp(App):
|
class DictionaryApp(App):
|
||||||
@@ -18,7 +19,7 @@ class DictionaryApp(App):
|
|||||||
|
|
||||||
def compose(self) -> ComposeResult:
|
def compose(self) -> ComposeResult:
|
||||||
yield Input(placeholder="Search for a word")
|
yield Input(placeholder="Search for a word")
|
||||||
yield Vertical(Static(id="results"), id="results-container")
|
yield VerticalScroll(Static(id="results"), id="results-container")
|
||||||
|
|
||||||
async def on_input_changed(self, message: Input.Changed) -> None:
|
async def on_input_changed(self, message: Input.Changed) -> None:
|
||||||
"""A coroutine to handle a text changed message."""
|
"""A coroutine to handle a text changed message."""
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
from textual.app import App, ComposeResult
|
from textual.app import App, ComposeResult
|
||||||
from textual.containers import Container, Horizontal, Vertical
|
from textual.containers import Container, Horizontal, VerticalScroll
|
||||||
from textual.widgets import Header, Static
|
from textual.widgets import Header, Static
|
||||||
|
|
||||||
|
|
||||||
@@ -9,7 +9,7 @@ class CombiningLayoutsExample(App):
|
|||||||
def compose(self) -> ComposeResult:
|
def compose(self) -> ComposeResult:
|
||||||
yield Header()
|
yield Header()
|
||||||
with Container(id="app-grid"):
|
with Container(id="app-grid"):
|
||||||
with Vertical(id="left-pane"):
|
with VerticalScroll(id="left-pane"):
|
||||||
for number in range(15):
|
for number in range(15):
|
||||||
yield Static(f"Vertical layout, child {number}")
|
yield Static(f"Vertical layout, child {number}")
|
||||||
with Horizontal(id="top-right"):
|
with Horizontal(id="top-right"):
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
from textual.app import App
|
from textual.app import App
|
||||||
from textual.containers import Vertical
|
from textual.containers import VerticalScroll
|
||||||
from textual.widgets import Placeholder, Label, Static
|
from textual.widgets import Label, Placeholder, Static
|
||||||
|
|
||||||
|
|
||||||
class Ruler(Static):
|
class Ruler(Static):
|
||||||
@@ -11,7 +11,7 @@ class Ruler(Static):
|
|||||||
|
|
||||||
class HeightComparisonApp(App):
|
class HeightComparisonApp(App):
|
||||||
def compose(self):
|
def compose(self):
|
||||||
yield Vertical(
|
yield VerticalScroll(
|
||||||
Placeholder(id="cells"), # (1)!
|
Placeholder(id="cells"), # (1)!
|
||||||
Placeholder(id="percent"),
|
Placeholder(id="percent"),
|
||||||
Placeholder(id="w"),
|
Placeholder(id="w"),
|
||||||
|
|||||||
@@ -1,11 +1,11 @@
|
|||||||
from textual.app import App
|
from textual.app import App
|
||||||
from textual.containers import Vertical
|
from textual.containers import VerticalScroll
|
||||||
from textual.widgets import Placeholder
|
from textual.widgets import Placeholder
|
||||||
|
|
||||||
|
|
||||||
class MaxWidthApp(App):
|
class MaxWidthApp(App):
|
||||||
def compose(self):
|
def compose(self):
|
||||||
yield Vertical(
|
yield VerticalScroll(
|
||||||
Placeholder("max-width: 50h", id="p1"),
|
Placeholder("max-width: 50h", id="p1"),
|
||||||
Placeholder("max-width: 999", id="p2"),
|
Placeholder("max-width: 999", id="p2"),
|
||||||
Placeholder("max-width: 50%", id="p3"),
|
Placeholder("max-width: 50%", id="p3"),
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
Vertical {
|
VerticalScroll {
|
||||||
height: 100%;
|
height: 100%;
|
||||||
width: 100%;
|
width: 100%;
|
||||||
overflow-x: auto;
|
overflow-x: auto;
|
||||||
@@ -10,7 +10,8 @@ Placeholder {
|
|||||||
}
|
}
|
||||||
|
|
||||||
#p1 {
|
#p1 {
|
||||||
min-width: 25%; /* (1)! */
|
min-width: 25%;
|
||||||
|
/* (1)! */
|
||||||
}
|
}
|
||||||
|
|
||||||
#p2 {
|
#p2 {
|
||||||
|
|||||||
@@ -1,11 +1,11 @@
|
|||||||
from textual.app import App
|
from textual.app import App
|
||||||
from textual.containers import Vertical
|
from textual.containers import VerticalScroll
|
||||||
from textual.widgets import Placeholder
|
from textual.widgets import Placeholder
|
||||||
|
|
||||||
|
|
||||||
class MinWidthApp(App):
|
class MinWidthApp(App):
|
||||||
def compose(self):
|
def compose(self):
|
||||||
yield Vertical(
|
yield VerticalScroll(
|
||||||
Placeholder("min-width: 25%", id="p1"),
|
Placeholder("min-width: 25%", id="p1"),
|
||||||
Placeholder("min-width: 75%", id="p2"),
|
Placeholder("min-width: 75%", id="p2"),
|
||||||
Placeholder("min-width: 100", id="p3"),
|
Placeholder("min-width: 100", id="p3"),
|
||||||
|
|||||||
@@ -3,7 +3,7 @@ Screen {
|
|||||||
color: black;
|
color: black;
|
||||||
}
|
}
|
||||||
|
|
||||||
Vertical {
|
VerticalScroll {
|
||||||
width: 1fr;
|
width: 1fr;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
from textual.app import App
|
from textual.app import App
|
||||||
|
from textual.containers import Horizontal, VerticalScroll
|
||||||
from textual.widgets import Static
|
from textual.widgets import Static
|
||||||
from textual.containers import Horizontal, Vertical
|
|
||||||
|
|
||||||
TEXT = """I must not fear.
|
TEXT = """I must not fear.
|
||||||
Fear is the mind-killer.
|
Fear is the mind-killer.
|
||||||
@@ -14,8 +14,8 @@ Where the fear has gone there will be nothing. Only I will remain."""
|
|||||||
class OverflowApp(App):
|
class OverflowApp(App):
|
||||||
def compose(self):
|
def compose(self):
|
||||||
yield Horizontal(
|
yield Horizontal(
|
||||||
Vertical(Static(TEXT), Static(TEXT), Static(TEXT), id="left"),
|
VerticalScroll(Static(TEXT), Static(TEXT), Static(TEXT), id="left"),
|
||||||
Vertical(Static(TEXT), Static(TEXT), Static(TEXT), id="right"),
|
VerticalScroll(Static(TEXT), Static(TEXT), Static(TEXT), id="right"),
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -1,11 +1,11 @@
|
|||||||
from textual.app import App
|
from textual.app import App
|
||||||
from textual.containers import Horizontal, Vertical
|
from textual.containers import Horizontal, VerticalScroll
|
||||||
from textual.widgets import Placeholder
|
from textual.widgets import Placeholder
|
||||||
|
|
||||||
|
|
||||||
class VisibilityContainersApp(App):
|
class VisibilityContainersApp(App):
|
||||||
def compose(self):
|
def compose(self):
|
||||||
yield Vertical(
|
yield VerticalScroll(
|
||||||
Horizontal(
|
Horizontal(
|
||||||
Placeholder(),
|
Placeholder(),
|
||||||
Placeholder(),
|
Placeholder(),
|
||||||
|
|||||||
@@ -2,7 +2,7 @@ Button {
|
|||||||
margin: 1 2;
|
margin: 1 2;
|
||||||
}
|
}
|
||||||
|
|
||||||
Horizontal > Vertical {
|
Horizontal > VerticalScroll {
|
||||||
width: 24;
|
width: 24;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
from textual.app import App, ComposeResult
|
from textual.app import App, ComposeResult
|
||||||
from textual.containers import Horizontal, Vertical
|
from textual.containers import Horizontal, VerticalScroll
|
||||||
from textual.widgets import Button, Static
|
from textual.widgets import Button, Static
|
||||||
|
|
||||||
|
|
||||||
@@ -8,7 +8,7 @@ class ButtonsApp(App[str]):
|
|||||||
|
|
||||||
def compose(self) -> ComposeResult:
|
def compose(self) -> ComposeResult:
|
||||||
yield Horizontal(
|
yield Horizontal(
|
||||||
Vertical(
|
VerticalScroll(
|
||||||
Static("Standard Buttons", classes="header"),
|
Static("Standard Buttons", classes="header"),
|
||||||
Button("Default"),
|
Button("Default"),
|
||||||
Button("Primary!", variant="primary"),
|
Button("Primary!", variant="primary"),
|
||||||
@@ -16,7 +16,7 @@ class ButtonsApp(App[str]):
|
|||||||
Button.warning("Warning!"),
|
Button.warning("Warning!"),
|
||||||
Button.error("Error!"),
|
Button.error("Error!"),
|
||||||
),
|
),
|
||||||
Vertical(
|
VerticalScroll(
|
||||||
Static("Disabled Buttons", classes="header"),
|
Static("Disabled Buttons", classes="header"),
|
||||||
Button("Default", disabled=True),
|
Button("Default", disabled=True),
|
||||||
Button("Primary!", variant="primary", disabled=True),
|
Button("Primary!", variant="primary", disabled=True),
|
||||||
|
|||||||
@@ -2,7 +2,7 @@ Screen {
|
|||||||
align: center middle;
|
align: center middle;
|
||||||
}
|
}
|
||||||
|
|
||||||
Vertical {
|
VerticalScroll {
|
||||||
width: auto;
|
width: auto;
|
||||||
height: auto;
|
height: auto;
|
||||||
border: solid $primary;
|
border: solid $primary;
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
from textual.app import App, ComposeResult
|
from textual.app import App, ComposeResult
|
||||||
from textual.containers import Vertical
|
from textual.containers import VerticalScroll
|
||||||
from textual.widgets import Checkbox
|
from textual.widgets import Checkbox
|
||||||
|
|
||||||
|
|
||||||
@@ -7,7 +7,7 @@ class CheckboxApp(App[None]):
|
|||||||
CSS_PATH = "checkbox.css"
|
CSS_PATH = "checkbox.css"
|
||||||
|
|
||||||
def compose(self) -> ComposeResult:
|
def compose(self) -> ComposeResult:
|
||||||
with Vertical():
|
with VerticalScroll():
|
||||||
yield Checkbox("Arrakis :sweat:")
|
yield Checkbox("Arrakis :sweat:")
|
||||||
yield Checkbox("Caladan")
|
yield Checkbox("Caladan")
|
||||||
yield Checkbox("Chusuk")
|
yield Checkbox("Chusuk")
|
||||||
|
|||||||
@@ -1,14 +1,13 @@
|
|||||||
from textual.app import App, ComposeResult
|
from textual.app import App, ComposeResult
|
||||||
from textual.containers import Container, Horizontal, Vertical
|
from textual.containers import Container, Horizontal, VerticalScroll
|
||||||
from textual.widgets import Placeholder
|
from textual.widgets import Placeholder
|
||||||
|
|
||||||
|
|
||||||
class PlaceholderApp(App):
|
class PlaceholderApp(App):
|
||||||
|
|
||||||
CSS_PATH = "placeholder.css"
|
CSS_PATH = "placeholder.css"
|
||||||
|
|
||||||
def compose(self) -> ComposeResult:
|
def compose(self) -> ComposeResult:
|
||||||
yield Vertical(
|
yield VerticalScroll(
|
||||||
Container(
|
Container(
|
||||||
Placeholder("This is a custom label for p1.", id="p1"),
|
Placeholder("This is a custom label for p1.", id="p1"),
|
||||||
Placeholder("Placeholder p2 here!", id="p2"),
|
Placeholder("Placeholder p2 here!", id="p2"),
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
Vertical {
|
VerticalScroll {
|
||||||
align: center middle;
|
align: center middle;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
from textual.app import App, ComposeResult
|
from textual.app import App, ComposeResult
|
||||||
from textual.containers import Horizontal, Vertical
|
from textual.containers import Horizontal, VerticalScroll
|
||||||
from textual.widgets import Label, RadioButton, RadioSet
|
from textual.widgets import Label, RadioButton, RadioSet
|
||||||
|
|
||||||
|
|
||||||
@@ -7,7 +7,7 @@ class RadioSetChangedApp(App[None]):
|
|||||||
CSS_PATH = "radio_set_changed.css"
|
CSS_PATH = "radio_set_changed.css"
|
||||||
|
|
||||||
def compose(self) -> ComposeResult:
|
def compose(self) -> ComposeResult:
|
||||||
with Vertical():
|
with VerticalScroll():
|
||||||
with Horizontal():
|
with Horizontal():
|
||||||
with RadioSet():
|
with RadioSet():
|
||||||
yield RadioButton("Battlestar Galactica")
|
yield RadioButton("Battlestar Galactica")
|
||||||
|
|||||||
@@ -134,8 +134,8 @@ exceeds the available horizontal space in the parent container.
|
|||||||
|
|
||||||
## Utility containers
|
## Utility containers
|
||||||
|
|
||||||
Textual comes with several "container" widgets.
|
Textual comes with [several "container" widgets][textual.containers].
|
||||||
These are [Vertical][textual.containers.Vertical], [Horizontal][textual.containers.Horizontal], and [Grid][textual.containers.Grid] which have the corresponding layout.
|
Among them, we have [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.
|
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.
|
Inside a single `Horizontal` container, we place two `Vertical` containers.
|
||||||
@@ -163,8 +163,8 @@ However, Textual comes with a more powerful mechanism for achieving this known a
|
|||||||
|
|
||||||
## Composing with context managers
|
## Composing with context managers
|
||||||
|
|
||||||
In the previous section we've show how you add children to a container (such as `Horizontal` and `Vertical`) using positional arguments.
|
In the previous section, we've shown how you add children to a container (such as `Horizontal` and `Vertical`) using positional arguments.
|
||||||
It's fine to do it this way, but Textual offers a simplified syntax using [context managers](https://docs.python.org/3/reference/datamodel.html#context-managers) which is generally easier to write and edit.
|
It's fine to do it this way, but Textual offers a simplified syntax using [context managers](https://docs.python.org/3/reference/datamodel.html#context-managers), which is generally easier to write and edit.
|
||||||
|
|
||||||
When composing a widget, you can introduce a container using Python's `with` statement.
|
When composing a widget, you can introduce a container using Python's `with` statement.
|
||||||
Any widgets yielded within that block are added as a child of the container.
|
Any widgets yielded within that block are added as a child of the container.
|
||||||
@@ -202,7 +202,7 @@ Let's update the [utility containers](#utility-containers) example to use the co
|
|||||||
```{.textual path="docs/examples/guide/layout/utility_containers_using_with.py"}
|
```{.textual path="docs/examples/guide/layout/utility_containers_using_with.py"}
|
||||||
```
|
```
|
||||||
|
|
||||||
Note how the end result is the same, but the code with context managers is a little easer to read. It is up to you which method you want to use, and you can mix context managers with positional arguments if you like!
|
Note how the end result is the same, but the code with context managers is a little easier to read. It is up to you which method you want to use, and you can mix context managers with positional arguments if you like!
|
||||||
|
|
||||||
## Grid
|
## Grid
|
||||||
|
|
||||||
|
|||||||
@@ -61,8 +61,8 @@ Open the CSS file tab to see the comments that explain how each height is comput
|
|||||||
|
|
||||||
1. This sets the height to 2 lines.
|
1. This sets the height to 2 lines.
|
||||||
2. This sets the height to 12.5% of the space made available by the container. The container is 24 lines tall, so 12.5% of 24 is 3.
|
2. This sets the height to 12.5% of the space made available by the container. The container is 24 lines tall, so 12.5% of 24 is 3.
|
||||||
3. This sets the height to 5% of the width of the direct container, which is the `Vertical` container. Because it expands to fit all of the terminal, the width of the `Vertical` is 80 and 5% of 80 is 4.
|
3. This sets the height to 5% of the width of the direct container, which is the `VerticalScroll` container. Because it expands to fit all of the terminal, the width of the `VerticalScroll` is 80 and 5% of 80 is 4.
|
||||||
4. This sets the height to 12.5% of the height of the direct container, which is the `Vertical` container. Because it expands to fit all of the terminal, the height of the `Vertical` is 24 and 12.5% of 24 is 3.
|
4. This sets the height to 12.5% of the height of the direct container, which is the `VerticalScroll` container. Because it expands to fit all of the terminal, the height of the `VerticalScroll` is 24 and 12.5% of 24 is 3.
|
||||||
5. This sets the height to 6.25% of the viewport width, which is 80. 6.25% of 80 is 5.
|
5. This sets the height to 6.25% of the viewport width, which is 80. 6.25% of 80 is 5.
|
||||||
6. This sets the height to 12.5% of the viewport height, which is 24. 12.5% of 24 is 3.
|
6. This sets the height to 12.5% of the viewport height, which is 24. 12.5% of 24 is 3.
|
||||||
7. This sets the height of the placeholder to be the optimal size that fits the content without scrolling.
|
7. This sets the height of the placeholder to be the optimal size that fits the content without scrolling.
|
||||||
|
|||||||
@@ -25,7 +25,7 @@ The default setting for containers is `overflow: auto auto`.
|
|||||||
|
|
||||||
!!! warning
|
!!! warning
|
||||||
|
|
||||||
Some built-in containers like `Horizontal` and `Vertical` override these defaults.
|
Some built-in containers like `Horizontal` and `VerticalScroll` override these defaults.
|
||||||
|
|
||||||
## Example
|
## Example
|
||||||
|
|
||||||
|
|||||||
@@ -14,7 +14,7 @@ from rich.traceback import Traceback
|
|||||||
|
|
||||||
from textual import events
|
from textual import events
|
||||||
from textual.app import App, ComposeResult
|
from textual.app import App, ComposeResult
|
||||||
from textual.containers import Container, Vertical
|
from textual.containers import Container, VerticalScroll
|
||||||
from textual.reactive import var
|
from textual.reactive import var
|
||||||
from textual.widgets import DirectoryTree, Footer, Header, Static
|
from textual.widgets import DirectoryTree, Footer, Header, Static
|
||||||
|
|
||||||
@@ -40,7 +40,7 @@ class CodeBrowser(App):
|
|||||||
yield Header()
|
yield Header()
|
||||||
with Container():
|
with Container():
|
||||||
yield DirectoryTree(path, id="tree-view")
|
yield DirectoryTree(path, id="tree-view")
|
||||||
with Vertical(id="code-view"):
|
with VerticalScroll(id="code-view"):
|
||||||
yield Static(id="code", expand=True)
|
yield Static(id="code", expand=True)
|
||||||
yield Footer()
|
yield Footer()
|
||||||
|
|
||||||
|
|||||||
@@ -192,11 +192,10 @@ def align_lines(
|
|||||||
style: Background style.
|
style: Background style.
|
||||||
size: Size of container.
|
size: Size of container.
|
||||||
horizontal: Horizontal alignment.
|
horizontal: Horizontal alignment.
|
||||||
vertical: Vertical alignment
|
vertical: Vertical alignment.
|
||||||
|
|
||||||
Returns:
|
Returns:
|
||||||
Aligned lines.
|
Aligned lines.
|
||||||
|
|
||||||
"""
|
"""
|
||||||
|
|
||||||
width, height = size
|
width, height = size
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
from textual.app import App, ComposeResult
|
from textual.app import App, ComposeResult
|
||||||
from textual.constants import BORDERS
|
from textual.constants import BORDERS
|
||||||
from textual.containers import Vertical
|
from textual.containers import VerticalScroll
|
||||||
from textual.widgets import Button, Label
|
from textual.widgets import Button, Label
|
||||||
|
|
||||||
TEXT = """I must not fear.
|
TEXT = """I must not fear.
|
||||||
@@ -12,7 +12,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."""
|
Where the fear has gone there will be nothing. Only I will remain."""
|
||||||
|
|
||||||
|
|
||||||
class BorderButtons(Vertical):
|
class BorderButtons(VerticalScroll):
|
||||||
DEFAULT_CSS = """
|
DEFAULT_CSS = """
|
||||||
BorderButtons {
|
BorderButtons {
|
||||||
dock: left;
|
dock: left;
|
||||||
|
|||||||
@@ -1,11 +1,11 @@
|
|||||||
from textual.app import App, ComposeResult
|
from textual.app import App, ComposeResult
|
||||||
from textual.containers import Horizontal, Vertical
|
from textual.containers import Horizontal, VerticalScroll
|
||||||
from textual.design import ColorSystem
|
from textual.design import ColorSystem
|
||||||
from textual.widget import Widget
|
from textual.widget import Widget
|
||||||
from textual.widgets import Button, Footer, Label, Static
|
from textual.widgets import Button, Footer, Label, Static
|
||||||
|
|
||||||
|
|
||||||
class ColorButtons(Vertical):
|
class ColorButtons(VerticalScroll):
|
||||||
def compose(self) -> ComposeResult:
|
def compose(self) -> ComposeResult:
|
||||||
for border in ColorSystem.COLOR_NAMES:
|
for border in ColorSystem.COLOR_NAMES:
|
||||||
if border:
|
if border:
|
||||||
@@ -20,15 +20,15 @@ class ColorItem(Horizontal):
|
|||||||
pass
|
pass
|
||||||
|
|
||||||
|
|
||||||
class ColorGroup(Vertical):
|
class ColorGroup(VerticalScroll):
|
||||||
pass
|
pass
|
||||||
|
|
||||||
|
|
||||||
class Content(Vertical):
|
class Content(VerticalScroll):
|
||||||
pass
|
pass
|
||||||
|
|
||||||
|
|
||||||
class ColorsView(Vertical):
|
class ColorsView(VerticalScroll):
|
||||||
def compose(self) -> ComposeResult:
|
def compose(self) -> ComposeResult:
|
||||||
LEVELS = [
|
LEVELS = [
|
||||||
"darken-3",
|
"darken-3",
|
||||||
|
|||||||
@@ -5,7 +5,7 @@ from rich.console import RenderableType
|
|||||||
from textual._easing import EASING
|
from textual._easing import EASING
|
||||||
from textual.app import App, ComposeResult
|
from textual.app import App, ComposeResult
|
||||||
from textual.cli.previews.borders import TEXT
|
from textual.cli.previews.borders import TEXT
|
||||||
from textual.containers import Container, Horizontal, Vertical
|
from textual.containers import Container, Horizontal, VerticalScroll
|
||||||
from textual.reactive import reactive, var
|
from textual.reactive import reactive, var
|
||||||
from textual.scrollbar import ScrollBarRender
|
from textual.scrollbar import ScrollBarRender
|
||||||
from textual.widget import Widget
|
from textual.widget import Widget
|
||||||
@@ -73,7 +73,7 @@ class EasingApp(App):
|
|||||||
)
|
)
|
||||||
|
|
||||||
yield EasingButtons()
|
yield EasingButtons()
|
||||||
with Vertical():
|
with VerticalScroll():
|
||||||
with Horizontal(id="inputs"):
|
with Horizontal(id="inputs"):
|
||||||
yield Label("Animation Duration:", id="label")
|
yield Label("Animation Duration:", id="label")
|
||||||
yield duration_input
|
yield duration_input
|
||||||
|
|||||||
@@ -14,11 +14,23 @@ class Container(Widget):
|
|||||||
|
|
||||||
|
|
||||||
class Vertical(Widget):
|
class Vertical(Widget):
|
||||||
"""A container widget which aligns children vertically."""
|
"""A container which arranges children vertically."""
|
||||||
|
|
||||||
DEFAULT_CSS = """
|
DEFAULT_CSS = """
|
||||||
Vertical {
|
Vertical {
|
||||||
height: 1fr;
|
width: 1fr;
|
||||||
|
layout: vertical;
|
||||||
|
overflow: hidden hidden;
|
||||||
|
}
|
||||||
|
"""
|
||||||
|
|
||||||
|
|
||||||
|
class VerticalScroll(Widget):
|
||||||
|
"""A container which arranges children vertically, with an automatic vertical scrollbar."""
|
||||||
|
|
||||||
|
DEFAULT_CSS = """
|
||||||
|
VerticalScroll {
|
||||||
|
width: 1fr;
|
||||||
layout: vertical;
|
layout: vertical;
|
||||||
overflow-y: auto;
|
overflow-y: auto;
|
||||||
}
|
}
|
||||||
@@ -26,19 +38,55 @@ class Vertical(Widget):
|
|||||||
|
|
||||||
|
|
||||||
class Horizontal(Widget):
|
class Horizontal(Widget):
|
||||||
"""A container widget which aligns children horizontally."""
|
"""A container which arranges children horizontally."""
|
||||||
|
|
||||||
DEFAULT_CSS = """
|
DEFAULT_CSS = """
|
||||||
Horizontal {
|
Horizontal {
|
||||||
height: 1fr;
|
height: 1fr;
|
||||||
layout: horizontal;
|
layout: horizontal;
|
||||||
overflow-x: hidden;
|
overflow: hidden hidden;
|
||||||
|
}
|
||||||
|
"""
|
||||||
|
|
||||||
|
|
||||||
|
class HorizontalScroll(Widget):
|
||||||
|
"""A container which arranges children horizontally, with an automatic horizontal scrollbar."""
|
||||||
|
|
||||||
|
DEFAULT_CSS = """
|
||||||
|
HorizontalScroll {
|
||||||
|
height: 1fr;
|
||||||
|
layout: horizontal;
|
||||||
|
overflow-x: auto;
|
||||||
|
}
|
||||||
|
"""
|
||||||
|
|
||||||
|
|
||||||
|
class Center(Widget):
|
||||||
|
"""A container which centers children horizontally."""
|
||||||
|
|
||||||
|
DEFAULT_CSS = """
|
||||||
|
Center {
|
||||||
|
align-horizontal: center;
|
||||||
|
height: auto;
|
||||||
|
width: 1fr;
|
||||||
|
}
|
||||||
|
"""
|
||||||
|
|
||||||
|
|
||||||
|
class Middle(Widget):
|
||||||
|
"""A container which aligns children vertically in the middle."""
|
||||||
|
|
||||||
|
DEFAULT_CSS = """
|
||||||
|
Middle {
|
||||||
|
align-vertical: middle;
|
||||||
|
width: auto;
|
||||||
|
height: 1fr;
|
||||||
}
|
}
|
||||||
"""
|
"""
|
||||||
|
|
||||||
|
|
||||||
class Grid(Widget):
|
class Grid(Widget):
|
||||||
"""A container widget with grid alignment."""
|
"""A container with grid alignment."""
|
||||||
|
|
||||||
DEFAULT_CSS = """
|
DEFAULT_CSS = """
|
||||||
Grid {
|
Grid {
|
||||||
@@ -52,7 +100,7 @@ class Content(Widget, can_focus=True, can_focus_children=False):
|
|||||||
"""A container for content such as text."""
|
"""A container for content such as text."""
|
||||||
|
|
||||||
DEFAULT_CSS = """
|
DEFAULT_CSS = """
|
||||||
Vertical {
|
VerticalScroll {
|
||||||
height: 1fr;
|
height: 1fr;
|
||||||
layout: vertical;
|
layout: vertical;
|
||||||
overflow-y: auto;
|
overflow-y: auto;
|
||||||
|
|||||||
@@ -4,7 +4,7 @@ from typing import ClassVar, Optional
|
|||||||
|
|
||||||
from textual.await_remove import AwaitRemove
|
from textual.await_remove import AwaitRemove
|
||||||
from textual.binding import Binding, BindingType
|
from textual.binding import Binding, BindingType
|
||||||
from textual.containers import Vertical
|
from textual.containers import VerticalScroll
|
||||||
from textual.geometry import clamp
|
from textual.geometry import clamp
|
||||||
from textual.message import Message
|
from textual.message import Message
|
||||||
from textual.reactive import reactive
|
from textual.reactive import reactive
|
||||||
@@ -12,7 +12,7 @@ from textual.widget import AwaitMount, Widget
|
|||||||
from textual.widgets._list_item import ListItem
|
from textual.widgets._list_item import ListItem
|
||||||
|
|
||||||
|
|
||||||
class ListView(Vertical, can_focus=True, can_focus_children=False):
|
class ListView(VerticalScroll, can_focus=True, can_focus_children=False):
|
||||||
"""A vertical list view widget.
|
"""A vertical list view widget.
|
||||||
|
|
||||||
Displays a vertical list of `ListItem`s which can be highlighted and
|
Displays a vertical list of `ListItem`s which can be highlighted and
|
||||||
|
|||||||
@@ -10,7 +10,7 @@ from rich.text import Text
|
|||||||
from typing_extensions import TypeAlias
|
from typing_extensions import TypeAlias
|
||||||
|
|
||||||
from ..app import ComposeResult
|
from ..app import ComposeResult
|
||||||
from ..containers import Horizontal, Vertical
|
from ..containers import Horizontal, VerticalScroll
|
||||||
from ..message import Message
|
from ..message import Message
|
||||||
from ..reactive import reactive, var
|
from ..reactive import reactive, var
|
||||||
from ..widget import Widget
|
from ..widget import Widget
|
||||||
@@ -266,7 +266,7 @@ class MarkdownBulletList(MarkdownList):
|
|||||||
width: 1fr;
|
width: 1fr;
|
||||||
}
|
}
|
||||||
|
|
||||||
MarkdownBulletList Vertical {
|
MarkdownBulletList VerticalScroll {
|
||||||
height: auto;
|
height: auto;
|
||||||
width: 1fr;
|
width: 1fr;
|
||||||
}
|
}
|
||||||
@@ -277,7 +277,7 @@ class MarkdownBulletList(MarkdownList):
|
|||||||
if isinstance(block, MarkdownListItem):
|
if isinstance(block, MarkdownListItem):
|
||||||
bullet = MarkdownBullet()
|
bullet = MarkdownBullet()
|
||||||
bullet.symbol = block.bullet
|
bullet.symbol = block.bullet
|
||||||
yield Horizontal(bullet, Vertical(*block._blocks))
|
yield Horizontal(bullet, VerticalScroll(*block._blocks))
|
||||||
self._blocks.clear()
|
self._blocks.clear()
|
||||||
|
|
||||||
|
|
||||||
@@ -295,7 +295,7 @@ class MarkdownOrderedList(MarkdownList):
|
|||||||
width: 1fr;
|
width: 1fr;
|
||||||
}
|
}
|
||||||
|
|
||||||
MarkdownOrderedList Vertical {
|
MarkdownOrderedList VerticalScroll {
|
||||||
height: auto;
|
height: auto;
|
||||||
width: 1fr;
|
width: 1fr;
|
||||||
}
|
}
|
||||||
@@ -311,7 +311,7 @@ class MarkdownOrderedList(MarkdownList):
|
|||||||
if isinstance(block, MarkdownListItem):
|
if isinstance(block, MarkdownListItem):
|
||||||
bullet = MarkdownBullet()
|
bullet = MarkdownBullet()
|
||||||
bullet.symbol = block.bullet.rjust(symbol_size + 1)
|
bullet.symbol = block.bullet.rjust(symbol_size + 1)
|
||||||
yield Horizontal(bullet, Vertical(*block._blocks))
|
yield Horizontal(bullet, VerticalScroll(*block._blocks))
|
||||||
|
|
||||||
self._blocks.clear()
|
self._blocks.clear()
|
||||||
|
|
||||||
@@ -410,7 +410,7 @@ class MarkdownListItem(MarkdownBlock):
|
|||||||
height: auto;
|
height: auto;
|
||||||
}
|
}
|
||||||
|
|
||||||
MarkdownListItem > Vertical {
|
MarkdownListItem > VerticalScroll {
|
||||||
width: 1fr;
|
width: 1fr;
|
||||||
height: auto;
|
height: auto;
|
||||||
}
|
}
|
||||||
@@ -761,7 +761,7 @@ class MarkdownTableOfContents(Widget, can_focus_children=True):
|
|||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
class MarkdownViewer(Vertical, can_focus=True, can_focus_children=True):
|
class MarkdownViewer(VerticalScroll, can_focus=True, can_focus_children=True):
|
||||||
"""A Markdown viewer widget."""
|
"""A Markdown viewer widget."""
|
||||||
|
|
||||||
DEFAULT_CSS = """
|
DEFAULT_CSS = """
|
||||||
|
|||||||
File diff suppressed because one or more lines are too long
29
tests/snapshot_tests/snapshot_apps/alignment_containers.py
Normal file
29
tests/snapshot_tests/snapshot_apps/alignment_containers.py
Normal file
@@ -0,0 +1,29 @@
|
|||||||
|
"""
|
||||||
|
App to test alignment containers.
|
||||||
|
"""
|
||||||
|
|
||||||
|
from textual.app import App, ComposeResult
|
||||||
|
from textual.containers import Center, Middle
|
||||||
|
from textual.widgets import Button
|
||||||
|
|
||||||
|
|
||||||
|
class AlignContainersApp(App[None]):
|
||||||
|
CSS = """
|
||||||
|
Center {
|
||||||
|
tint: $primary 10%;
|
||||||
|
}
|
||||||
|
Middle {
|
||||||
|
tint: $secondary 10%;
|
||||||
|
}
|
||||||
|
"""
|
||||||
|
|
||||||
|
def compose(self) -> ComposeResult:
|
||||||
|
with Center():
|
||||||
|
yield Button.success("center")
|
||||||
|
with Middle():
|
||||||
|
yield Button.error("middle")
|
||||||
|
|
||||||
|
|
||||||
|
app = AlignContainersApp()
|
||||||
|
if __name__ == "__main__":
|
||||||
|
app.run()
|
||||||
@@ -1,10 +1,9 @@
|
|||||||
from textual.app import App, ComposeResult
|
from textual.app import App, ComposeResult
|
||||||
from textual.containers import Vertical
|
from textual.containers import VerticalScroll
|
||||||
from textual.widgets import Header, Footer, Label, Input
|
from textual.widgets import Header, Footer, Label, Input
|
||||||
|
|
||||||
|
|
||||||
class InputWidthAutoApp(App[None]):
|
class InputWidthAutoApp(App[None]):
|
||||||
|
|
||||||
CSS = """
|
CSS = """
|
||||||
Input.auto {
|
Input.auto {
|
||||||
width: auto;
|
width: auto;
|
||||||
|
|||||||
@@ -2,6 +2,7 @@ from textual.app import App, ComposeResult
|
|||||||
from textual.containers import Horizontal
|
from textual.containers import Horizontal
|
||||||
from textual.widgets import Button
|
from textual.widgets import Button
|
||||||
|
|
||||||
|
|
||||||
class WidgetDisableTestApp(App[None]):
|
class WidgetDisableTestApp(App[None]):
|
||||||
CSS = """
|
CSS = """
|
||||||
Horizontal {
|
Horizontal {
|
||||||
@@ -28,5 +29,6 @@ class WidgetDisableTestApp(App[None]):
|
|||||||
yield Button(variant="warning")
|
yield Button(variant="warning")
|
||||||
yield Button(variant="error")
|
yield Button(variant="error")
|
||||||
|
|
||||||
|
|
||||||
if __name__ == "__main__":
|
if __name__ == "__main__":
|
||||||
WidgetDisableTestApp().run()
|
WidgetDisableTestApp().run()
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
from rich.text import Text
|
from rich.text import Text
|
||||||
|
|
||||||
from textual.app import App, ComposeResult, RenderResult
|
from textual.app import App, ComposeResult, RenderResult
|
||||||
from textual.containers import Vertical
|
from textual.containers import VerticalScroll
|
||||||
from textual.widgets import Header, Footer
|
from textual.widgets import Header, Footer
|
||||||
from textual.widget import Widget
|
from textual.widget import Widget
|
||||||
|
|
||||||
@@ -32,7 +32,7 @@ class Tester(Widget, can_focus=True):
|
|||||||
class StyleBugApp(App[None]):
|
class StyleBugApp(App[None]):
|
||||||
def compose(self) -> ComposeResult:
|
def compose(self) -> ComposeResult:
|
||||||
yield Header()
|
yield Header()
|
||||||
with Vertical():
|
with VerticalScroll():
|
||||||
for n in range(40):
|
for n in range(40):
|
||||||
yield Tester(n)
|
yield Tester(n)
|
||||||
yield Footer()
|
yield Footer()
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
from textual.app import App, ComposeResult
|
from textual.app import App, ComposeResult
|
||||||
from textual.containers import Horizontal, Vertical
|
from textual.containers import Horizontal, VerticalScroll
|
||||||
from textual.widgets import Static
|
from textual.widgets import Static
|
||||||
|
|
||||||
|
|
||||||
@@ -8,7 +8,6 @@ class StaticText(Static):
|
|||||||
|
|
||||||
|
|
||||||
class FRApp(App):
|
class FRApp(App):
|
||||||
|
|
||||||
CSS = """
|
CSS = """
|
||||||
StaticText {
|
StaticText {
|
||||||
height: 1fr;
|
height: 1fr;
|
||||||
@@ -39,7 +38,7 @@ class FRApp(App):
|
|||||||
"""
|
"""
|
||||||
|
|
||||||
def compose(self) -> ComposeResult:
|
def compose(self) -> ComposeResult:
|
||||||
yield Vertical(
|
yield VerticalScroll(
|
||||||
StaticText("HEADER", id="header"),
|
StaticText("HEADER", id="header"),
|
||||||
Horizontal(
|
Horizontal(
|
||||||
StaticText("foo", id="foo"),
|
StaticText("foo", id="foo"),
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
from textual.app import App, ComposeResult
|
from textual.app import App, ComposeResult
|
||||||
from textual.screen import Screen
|
from textual.screen import Screen
|
||||||
from textual.widgets import Header, Footer, Label
|
from textual.widgets import Header, Footer, Label
|
||||||
from textual.containers import Vertical, Container
|
from textual.containers import VerticalScroll, Container
|
||||||
|
|
||||||
|
|
||||||
class Overlay(Container):
|
class Overlay(Container):
|
||||||
@@ -9,12 +9,12 @@ class Overlay(Container):
|
|||||||
yield Label("This should float over the top")
|
yield Label("This should float over the top")
|
||||||
|
|
||||||
|
|
||||||
class Body1(Vertical):
|
class Body1(VerticalScroll):
|
||||||
def compose(self) -> ComposeResult:
|
def compose(self) -> ComposeResult:
|
||||||
yield Label("My God! It's full of stars! " * 300)
|
yield Label("My God! It's full of stars! " * 300)
|
||||||
|
|
||||||
|
|
||||||
class Body2(Vertical):
|
class Body2(VerticalScroll):
|
||||||
def compose(self) -> ComposeResult:
|
def compose(self) -> ComposeResult:
|
||||||
yield Label("My God! It's full of stars! " * 300)
|
yield Label("My God! It's full of stars! " * 300)
|
||||||
|
|
||||||
@@ -36,7 +36,6 @@ class Bad(Screen):
|
|||||||
|
|
||||||
|
|
||||||
class Layers(App[None]):
|
class Layers(App[None]):
|
||||||
|
|
||||||
CSS = """
|
CSS = """
|
||||||
Screen {
|
Screen {
|
||||||
layers: base higher;
|
layers: base higher;
|
||||||
|
|||||||
56
tests/snapshot_tests/snapshot_apps/layout_containers.py
Normal file
56
tests/snapshot_tests/snapshot_apps/layout_containers.py
Normal file
@@ -0,0 +1,56 @@
|
|||||||
|
"""
|
||||||
|
App to test layout containers.
|
||||||
|
"""
|
||||||
|
|
||||||
|
from typing import Iterable
|
||||||
|
|
||||||
|
from textual.app import App, ComposeResult
|
||||||
|
from textual.containers import (
|
||||||
|
Grid,
|
||||||
|
Horizontal,
|
||||||
|
HorizontalScroll,
|
||||||
|
Vertical,
|
||||||
|
VerticalScroll,
|
||||||
|
)
|
||||||
|
from textual.widget import Widget
|
||||||
|
from textual.widgets import Button, Input, Label
|
||||||
|
|
||||||
|
|
||||||
|
def sub_compose() -> Iterable[Widget]:
|
||||||
|
yield Button.success("Accept")
|
||||||
|
yield Button.error("Decline")
|
||||||
|
yield Input()
|
||||||
|
yield Label("\n\n".join([str(n * 1_000_000) for n in range(10)]))
|
||||||
|
|
||||||
|
|
||||||
|
class MyApp(App[None]):
|
||||||
|
CSS = """
|
||||||
|
Grid {
|
||||||
|
grid-size: 2 2;
|
||||||
|
grid-rows: 1fr;
|
||||||
|
grid-columns: 1fr;
|
||||||
|
}
|
||||||
|
Grid > Widget {
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
}
|
||||||
|
Input {
|
||||||
|
width: 80;
|
||||||
|
}
|
||||||
|
"""
|
||||||
|
|
||||||
|
def compose(self) -> ComposeResult:
|
||||||
|
with Grid():
|
||||||
|
with Horizontal():
|
||||||
|
yield from sub_compose()
|
||||||
|
with HorizontalScroll():
|
||||||
|
yield from sub_compose()
|
||||||
|
with Vertical():
|
||||||
|
yield from sub_compose()
|
||||||
|
with VerticalScroll():
|
||||||
|
yield from sub_compose()
|
||||||
|
|
||||||
|
|
||||||
|
app = MyApp()
|
||||||
|
if __name__ == "__main__":
|
||||||
|
app.run()
|
||||||
@@ -1,7 +1,7 @@
|
|||||||
from rich.text import Text
|
from rich.text import Text
|
||||||
|
|
||||||
from textual.app import App, ComposeResult
|
from textual.app import App, ComposeResult
|
||||||
from textual.containers import Vertical
|
from textual.containers import VerticalScroll
|
||||||
from textual.widget import Widget
|
from textual.widget import Widget
|
||||||
from textual.widgets import TextLog
|
from textual.widgets import TextLog
|
||||||
|
|
||||||
@@ -27,26 +27,26 @@ class ScrollViewApp(App):
|
|||||||
height:10;
|
height:10;
|
||||||
}
|
}
|
||||||
|
|
||||||
Vertical{
|
VerticalScroll {
|
||||||
width:13;
|
width:13;
|
||||||
height: 10;
|
height: 10;
|
||||||
overflow: scroll;
|
overflow: scroll;
|
||||||
overflow-x: auto;
|
overflow-x: auto;
|
||||||
}
|
}
|
||||||
|
|
||||||
MyWidget {
|
MyWidget {
|
||||||
width:13;
|
width:13;
|
||||||
height:auto;
|
height:auto;
|
||||||
}
|
}
|
||||||
|
|
||||||
"""
|
"""
|
||||||
|
|
||||||
def compose(self) -> ComposeResult:
|
def compose(self) -> ComposeResult:
|
||||||
yield TextLog()
|
yield TextLog()
|
||||||
yield Vertical(MyWidget())
|
yield VerticalScroll(MyWidget())
|
||||||
|
|
||||||
def on_ready(self) -> None:
|
def on_ready(self) -> None:
|
||||||
self.query_one(TextLog).write("\n".join(f"{n} 0123456789" for n in range(20)))
|
self.query_one(TextLog).write("\n".join(f"{n} 0123456789" for n in range(20)))
|
||||||
self.query_one(Vertical).scroll_end(animate=False)
|
self.query_one(VerticalScroll).scroll_end(animate=False)
|
||||||
|
|
||||||
|
|
||||||
if __name__ == "__main__":
|
if __name__ == "__main__":
|
||||||
|
|||||||
@@ -3,7 +3,7 @@
|
|||||||
from __future__ import annotations
|
from __future__ import annotations
|
||||||
|
|
||||||
from textual.app import App, ComposeResult
|
from textual.app import App, ComposeResult
|
||||||
from textual.containers import Vertical
|
from textual.containers import VerticalScroll
|
||||||
from textual.widgets import Static
|
from textual.widgets import Static
|
||||||
|
|
||||||
|
|
||||||
@@ -42,8 +42,8 @@ class NestedAutoApp(App[None]):
|
|||||||
|
|
||||||
def compose(self) -> ComposeResult:
|
def compose(self) -> ComposeResult:
|
||||||
self._static = Static("", id="my-static")
|
self._static = Static("", id="my-static")
|
||||||
yield Vertical(
|
yield VerticalScroll(
|
||||||
Vertical(
|
VerticalScroll(
|
||||||
self._static,
|
self._static,
|
||||||
id="my-static-wrapper",
|
id="my-static-wrapper",
|
||||||
),
|
),
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
from textual.app import App, ComposeResult
|
from textual.app import App, ComposeResult
|
||||||
from textual.containers import Vertical
|
from textual.containers import VerticalScroll
|
||||||
from textual.widgets import Label, Static
|
from textual.widgets import Label, Static
|
||||||
|
|
||||||
|
|
||||||
@@ -18,7 +18,6 @@ class Box(Static):
|
|||||||
|
|
||||||
|
|
||||||
class OffsetsApp(App):
|
class OffsetsApp(App):
|
||||||
|
|
||||||
CSS = """
|
CSS = """
|
||||||
|
|
||||||
#box1 {
|
#box1 {
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
from textual.app import App
|
from textual.app import App
|
||||||
from textual.containers import Vertical
|
from textual.containers import VerticalScroll
|
||||||
from textual.widgets import Static
|
from textual.widgets import Static
|
||||||
|
|
||||||
|
|
||||||
@@ -10,7 +10,7 @@ class Visibility(App):
|
|||||||
Screen {
|
Screen {
|
||||||
layout: horizontal;
|
layout: horizontal;
|
||||||
}
|
}
|
||||||
Vertical {
|
VerticalScroll {
|
||||||
width: 1fr;
|
width: 1fr;
|
||||||
border: solid red;
|
border: solid red;
|
||||||
}
|
}
|
||||||
@@ -30,13 +30,12 @@ class Visibility(App):
|
|||||||
"""
|
"""
|
||||||
|
|
||||||
def compose(self):
|
def compose(self):
|
||||||
|
yield VerticalScroll(
|
||||||
yield Vertical(
|
|
||||||
Static("foo"),
|
Static("foo"),
|
||||||
Static("float", classes="float"),
|
Static("float", classes="float"),
|
||||||
id="container1",
|
id="container1",
|
||||||
)
|
)
|
||||||
yield Vertical(
|
yield VerticalScroll(
|
||||||
Static("bar"),
|
Static("bar"),
|
||||||
Static("float", classes="float"),
|
Static("float", classes="float"),
|
||||||
id="container2",
|
id="container2",
|
||||||
|
|||||||
@@ -47,6 +47,14 @@ def test_dock_layout_sidebar(snap_compare):
|
|||||||
assert snap_compare(LAYOUT_EXAMPLES_DIR / "dock_layout2_sidebar.py")
|
assert snap_compare(LAYOUT_EXAMPLES_DIR / "dock_layout2_sidebar.py")
|
||||||
|
|
||||||
|
|
||||||
|
def test_layout_containers(snap_compare):
|
||||||
|
assert snap_compare(SNAPSHOT_APPS_DIR / "layout_containers.py")
|
||||||
|
|
||||||
|
|
||||||
|
def test_alignment_containers(snap_compare):
|
||||||
|
assert snap_compare(SNAPSHOT_APPS_DIR / "alignment_containers.py")
|
||||||
|
|
||||||
|
|
||||||
# --- Widgets - rendering and basic interactions ---
|
# --- Widgets - rendering and basic interactions ---
|
||||||
# Each widget should have a canonical example that is display in the docs.
|
# Each widget should have a canonical example that is display in the docs.
|
||||||
# When adding a new widget, ideally we should also create a snapshot test
|
# When adding a new widget, ideally we should also create a snapshot test
|
||||||
|
|||||||
100
tests/test_containers.py
Normal file
100
tests/test_containers.py
Normal file
@@ -0,0 +1,100 @@
|
|||||||
|
"""Test basic functioning of some containers."""
|
||||||
|
|
||||||
|
from textual.app import App, ComposeResult
|
||||||
|
from textual.containers import (
|
||||||
|
Center,
|
||||||
|
Horizontal,
|
||||||
|
HorizontalScroll,
|
||||||
|
Middle,
|
||||||
|
Vertical,
|
||||||
|
VerticalScroll,
|
||||||
|
)
|
||||||
|
from textual.widgets import Label
|
||||||
|
|
||||||
|
|
||||||
|
async def test_horizontal_vs_horizontalscroll_scrolling():
|
||||||
|
"""Check the default scrollbar behaviours for `Horizontal` and `HorizontalScroll`."""
|
||||||
|
|
||||||
|
class HorizontalsApp(App[None]):
|
||||||
|
CSS = """
|
||||||
|
Screen {
|
||||||
|
layout: vertical;
|
||||||
|
}
|
||||||
|
"""
|
||||||
|
|
||||||
|
def compose(self) -> ComposeResult:
|
||||||
|
with Horizontal():
|
||||||
|
for _ in range(10):
|
||||||
|
yield Label("How is life going? " * 3 + " | ")
|
||||||
|
with HorizontalScroll():
|
||||||
|
for _ in range(10):
|
||||||
|
yield Label("How is life going? " * 3 + " | ")
|
||||||
|
|
||||||
|
WIDTH = 80
|
||||||
|
HEIGHT = 24
|
||||||
|
app = HorizontalsApp()
|
||||||
|
async with app.run_test(size=(WIDTH, HEIGHT)):
|
||||||
|
horizontal = app.query_one(Horizontal)
|
||||||
|
horizontal_scroll = app.query_one(HorizontalScroll)
|
||||||
|
assert horizontal.size.height == horizontal_scroll.size.height
|
||||||
|
assert horizontal.scrollbars_enabled == (False, False)
|
||||||
|
assert horizontal_scroll.scrollbars_enabled == (False, True)
|
||||||
|
|
||||||
|
|
||||||
|
async def test_vertical_vs_verticalscroll_scrolling():
|
||||||
|
"""Check the default scrollbar behaviours for `Vertical` and `VerticalScroll`."""
|
||||||
|
|
||||||
|
class VerticalsApp(App[None]):
|
||||||
|
CSS = """
|
||||||
|
Screen {
|
||||||
|
layout: horizontal;
|
||||||
|
}
|
||||||
|
"""
|
||||||
|
|
||||||
|
def compose(self) -> ComposeResult:
|
||||||
|
with Vertical():
|
||||||
|
for _ in range(10):
|
||||||
|
yield Label("How is life going?\n" * 3 + "\n\n")
|
||||||
|
with VerticalScroll():
|
||||||
|
for _ in range(10):
|
||||||
|
yield Label("How is life going?\n" * 3 + "\n\n")
|
||||||
|
|
||||||
|
WIDTH = 80
|
||||||
|
HEIGHT = 24
|
||||||
|
app = VerticalsApp()
|
||||||
|
async with app.run_test(size=(WIDTH, HEIGHT)):
|
||||||
|
vertical = app.query_one(Vertical)
|
||||||
|
vertical_scroll = app.query_one(VerticalScroll)
|
||||||
|
assert vertical.size.width == vertical_scroll.size.width
|
||||||
|
assert vertical.scrollbars_enabled == (False, False)
|
||||||
|
assert vertical_scroll.scrollbars_enabled == (True, False)
|
||||||
|
|
||||||
|
|
||||||
|
async def test_center_container():
|
||||||
|
"""Check the size of the container `Center`."""
|
||||||
|
|
||||||
|
class CenterApp(App[None]):
|
||||||
|
def compose(self) -> ComposeResult:
|
||||||
|
with Center():
|
||||||
|
yield Label("<>\n<>\n<>")
|
||||||
|
|
||||||
|
app = CenterApp()
|
||||||
|
async with app.run_test():
|
||||||
|
center = app.query_one(Center)
|
||||||
|
assert center.size.width == app.size.width
|
||||||
|
assert center.size.height == 3
|
||||||
|
|
||||||
|
|
||||||
|
async def test_middle_container():
|
||||||
|
"""Check the size of the container `Middle`."""
|
||||||
|
|
||||||
|
class MiddleApp(App[None]):
|
||||||
|
def compose(self) -> ComposeResult:
|
||||||
|
with Middle():
|
||||||
|
yield Label("1234")
|
||||||
|
|
||||||
|
app = MiddleApp()
|
||||||
|
async with app.run_test():
|
||||||
|
middle = app.query_one(Middle)
|
||||||
|
assert middle.size.width == 4
|
||||||
|
assert middle.size.height == app.size.height
|
||||||
@@ -1,7 +1,7 @@
|
|||||||
"""Test Widget.disabled."""
|
"""Test Widget.disabled."""
|
||||||
|
|
||||||
from textual.app import App, ComposeResult
|
from textual.app import App, ComposeResult
|
||||||
from textual.containers import Vertical
|
from textual.containers import VerticalScroll
|
||||||
from textual.widgets import (
|
from textual.widgets import (
|
||||||
Button,
|
Button,
|
||||||
DataTable,
|
DataTable,
|
||||||
@@ -21,7 +21,7 @@ class DisableApp(App[None]):
|
|||||||
|
|
||||||
def compose(self) -> ComposeResult:
|
def compose(self) -> ComposeResult:
|
||||||
"""Compose the child widgets."""
|
"""Compose the child widgets."""
|
||||||
yield Vertical(
|
yield VerticalScroll(
|
||||||
Button(),
|
Button(),
|
||||||
DataTable(),
|
DataTable(),
|
||||||
DirectoryTree("."),
|
DirectoryTree("."),
|
||||||
@@ -56,7 +56,7 @@ async def test_enabled_widgets_have_enabled_pseudo_class() -> None:
|
|||||||
async def test_all_individually_disabled() -> None:
|
async def test_all_individually_disabled() -> None:
|
||||||
"""Post-disable all widgets should report being disabled."""
|
"""Post-disable all widgets should report being disabled."""
|
||||||
async with DisableApp().run_test() as pilot:
|
async with DisableApp().run_test() as pilot:
|
||||||
for node in pilot.app.screen.query("Vertical > *"):
|
for node in pilot.app.screen.query("VerticalScroll > *"):
|
||||||
node.disabled = True
|
node.disabled = True
|
||||||
assert all(
|
assert all(
|
||||||
node.disabled for node in pilot.app.screen.query("#test-container > *")
|
node.disabled for node in pilot.app.screen.query("#test-container > *")
|
||||||
@@ -77,7 +77,7 @@ async def test_disabled_widgets_have_disabled_pseudo_class() -> None:
|
|||||||
async def test_disable_via_container() -> None:
|
async def test_disable_via_container() -> None:
|
||||||
"""All child widgets should appear (to CSS) as disabled by a container being disabled."""
|
"""All child widgets should appear (to CSS) as disabled by a container being disabled."""
|
||||||
async with DisableApp().run_test() as pilot:
|
async with DisableApp().run_test() as pilot:
|
||||||
pilot.app.screen.query_one("#test-container", Vertical).disabled = True
|
pilot.app.screen.query_one("#test-container", VerticalScroll).disabled = True
|
||||||
assert all(
|
assert all(
|
||||||
node.has_pseudo_class("disabled") and not node.has_pseudo_class("enabled")
|
node.has_pseudo_class("disabled") and not node.has_pseudo_class("enabled")
|
||||||
for node in pilot.app.screen.query("#test-container > *")
|
for node in pilot.app.screen.query("#test-container > *")
|
||||||
|
|||||||
@@ -151,11 +151,11 @@ def test_focus_next_and_previous_with_type_selector_without_self():
|
|||||||
|
|
||||||
screen = app.screen
|
screen = app.screen
|
||||||
|
|
||||||
from textual.containers import Horizontal, Vertical
|
from textual.containers import Horizontal, VerticalScroll
|
||||||
from textual.widgets import Button, Input, Switch
|
from textual.widgets import Button, Input, Switch
|
||||||
|
|
||||||
screen._add_children(
|
screen._add_children(
|
||||||
Vertical(
|
VerticalScroll(
|
||||||
Horizontal(
|
Horizontal(
|
||||||
Input(id="w3"),
|
Input(id="w3"),
|
||||||
Switch(id="w4"),
|
Switch(id="w4"),
|
||||||
|
|||||||
@@ -1,19 +1,18 @@
|
|||||||
"""Regression test for #1616 https://github.com/Textualize/textual/issues/1616"""
|
"""Regression test for #1616 https://github.com/Textualize/textual/issues/1616"""
|
||||||
import pytest
|
|
||||||
|
|
||||||
from textual.app import App
|
from textual.app import App
|
||||||
from textual.containers import Vertical
|
from textual.containers import VerticalScroll
|
||||||
|
|
||||||
|
|
||||||
async def test_overflow_change_updates_virtual_size_appropriately():
|
async def test_overflow_change_updates_virtual_size_appropriately():
|
||||||
class MyApp(App):
|
class MyApp(App):
|
||||||
def compose(self):
|
def compose(self):
|
||||||
yield Vertical()
|
yield VerticalScroll()
|
||||||
|
|
||||||
app = MyApp()
|
app = MyApp()
|
||||||
|
|
||||||
async with app.run_test() as pilot:
|
async with app.run_test() as pilot:
|
||||||
vertical = app.query_one(Vertical)
|
vertical = app.query_one(VerticalScroll)
|
||||||
|
|
||||||
height = vertical.virtual_size.height
|
height = vertical.virtual_size.height
|
||||||
|
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
"""See https://github.com/Textualize/textual/issues/1355 as the motivation for these tests."""
|
"""See https://github.com/Textualize/textual/issues/1355 as the motivation for these tests."""
|
||||||
|
|
||||||
from textual.app import App, ComposeResult
|
from textual.app import App, ComposeResult
|
||||||
from textual.containers import Vertical
|
from textual.containers import VerticalScroll
|
||||||
from textual.widget import Widget
|
from textual.widget import Widget
|
||||||
|
|
||||||
|
|
||||||
@@ -18,7 +18,7 @@ class VisibleTester(App[None]):
|
|||||||
"""
|
"""
|
||||||
|
|
||||||
def compose(self) -> ComposeResult:
|
def compose(self) -> ComposeResult:
|
||||||
yield Vertical(
|
yield VerticalScroll(
|
||||||
Widget(id="keep"), Widget(id="hide-via-code"), Widget(id="hide-via-css")
|
Widget(id="keep"), Widget(id="hide-via-code"), Widget(id="hide-via-css")
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user