diff --git a/CHANGELOG.md b/CHANGELOG.md
index 1d3a6a573..fc686b822 100644
--- a/CHANGELOG.md
+++ b/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
+## 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
### Fixed
@@ -22,6 +37,13 @@ and this project adheres to [Semantic Versioning](http://semver.org/).
### 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 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
diff --git a/docs/blog/snippets/2022-12-07-responsive-app-background-task/blocking01.py b/docs/blog/snippets/2022-12-07-responsive-app-background-task/blocking01.py
index 6e0ab0949..9773da0c9 100644
--- a/docs/blog/snippets/2022-12-07-responsive-app-background-task/blocking01.py
+++ b/docs/blog/snippets/2022-12-07-responsive-app-background-task/blocking01.py
@@ -1,9 +1,9 @@
-from random import randint
import time
+from random import randint
from textual.app import App, ComposeResult
from textual.color import Color
-from textual.containers import Grid, Vertical
+from textual.containers import Grid, VerticalScroll
from textual.widget import Widget
from textual.widgets import Footer, Label
@@ -28,7 +28,7 @@ class MyApp(App[None]):
def compose(self) -> ComposeResult:
yield Grid(
ColourChanger(),
- Vertical(id="log"),
+ VerticalScroll(id="log"),
)
yield Footer()
diff --git a/docs/blog/snippets/2022-12-07-responsive-app-background-task/blocking02.py b/docs/blog/snippets/2022-12-07-responsive-app-background-task/blocking02.py
index 693bde821..0edefbeff 100644
--- a/docs/blog/snippets/2022-12-07-responsive-app-background-task/blocking02.py
+++ b/docs/blog/snippets/2022-12-07-responsive-app-background-task/blocking02.py
@@ -1,10 +1,10 @@
import asyncio
-from random import randint
import time
+from random import randint
from textual.app import App, ComposeResult
from textual.color import Color
-from textual.containers import Grid, Vertical
+from textual.containers import Grid, VerticalScroll
from textual.widget import Widget
from textual.widgets import Footer, Label
@@ -29,7 +29,7 @@ class MyApp(App[None]):
def compose(self) -> ComposeResult:
yield Grid(
ColourChanger(),
- Vertical(id="log"),
+ VerticalScroll(id="log"),
)
yield Footer()
diff --git a/docs/blog/snippets/2022-12-07-responsive-app-background-task/nonblocking01.py b/docs/blog/snippets/2022-12-07-responsive-app-background-task/nonblocking01.py
index 2c96e5ed6..20f2daba8 100644
--- a/docs/blog/snippets/2022-12-07-responsive-app-background-task/nonblocking01.py
+++ b/docs/blog/snippets/2022-12-07-responsive-app-background-task/nonblocking01.py
@@ -1,10 +1,10 @@
import asyncio
-from random import randint
import time
+from random import randint
from textual.app import App, ComposeResult
from textual.color import Color
-from textual.containers import Grid, Vertical
+from textual.containers import Grid, VerticalScroll
from textual.widget import Widget
from textual.widgets import Footer, Label
@@ -29,7 +29,7 @@ class MyApp(App[None]):
def compose(self) -> ComposeResult:
yield Grid(
ColourChanger(),
- Vertical(id="log"),
+ VerticalScroll(id="log"),
)
yield Footer()
diff --git a/docs/examples/events/dictionary.py b/docs/examples/events/dictionary.py
index cf85c2bf6..51421ad39 100644
--- a/docs/examples/events/dictionary.py
+++ b/docs/examples/events/dictionary.py
@@ -6,9 +6,10 @@ except ImportError:
raise ImportError("Please install httpx with 'pip install httpx' ")
from rich.json import JSON
+
from textual.app import App, ComposeResult
-from textual.containers import Vertical
-from textual.widgets import Static, Input
+from textual.containers import VerticalScroll
+from textual.widgets import Input, Static
class DictionaryApp(App):
@@ -18,7 +19,7 @@ class DictionaryApp(App):
def compose(self) -> ComposeResult:
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:
"""A coroutine to handle a text changed message."""
diff --git a/docs/examples/guide/layout/combining_layouts.py b/docs/examples/guide/layout/combining_layouts.py
index c52ecce0d..e608152cd 100644
--- a/docs/examples/guide/layout/combining_layouts.py
+++ b/docs/examples/guide/layout/combining_layouts.py
@@ -1,5 +1,5 @@
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
@@ -9,7 +9,7 @@ class CombiningLayoutsExample(App):
def compose(self) -> ComposeResult:
yield Header()
with Container(id="app-grid"):
- with Vertical(id="left-pane"):
+ with VerticalScroll(id="left-pane"):
for number in range(15):
yield Static(f"Vertical layout, child {number}")
with Horizontal(id="top-right"):
diff --git a/docs/examples/styles/height_comparison.py b/docs/examples/styles/height_comparison.py
index c679a68e6..41d8f0a76 100644
--- a/docs/examples/styles/height_comparison.py
+++ b/docs/examples/styles/height_comparison.py
@@ -1,6 +1,6 @@
from textual.app import App
-from textual.containers import Vertical
-from textual.widgets import Placeholder, Label, Static
+from textual.containers import VerticalScroll
+from textual.widgets import Label, Placeholder, Static
class Ruler(Static):
@@ -11,7 +11,7 @@ class Ruler(Static):
class HeightComparisonApp(App):
def compose(self):
- yield Vertical(
+ yield VerticalScroll(
Placeholder(id="cells"), # (1)!
Placeholder(id="percent"),
Placeholder(id="w"),
diff --git a/docs/examples/styles/max_width.py b/docs/examples/styles/max_width.py
index 0ee440a92..7ea482dc7 100644
--- a/docs/examples/styles/max_width.py
+++ b/docs/examples/styles/max_width.py
@@ -1,11 +1,11 @@
from textual.app import App
-from textual.containers import Vertical
+from textual.containers import VerticalScroll
from textual.widgets import Placeholder
class MaxWidthApp(App):
def compose(self):
- yield Vertical(
+ yield VerticalScroll(
Placeholder("max-width: 50h", id="p1"),
Placeholder("max-width: 999", id="p2"),
Placeholder("max-width: 50%", id="p3"),
diff --git a/docs/examples/styles/min_width.css b/docs/examples/styles/min_width.css
index 43dd8400a..ba2dd81c6 100644
--- a/docs/examples/styles/min_width.css
+++ b/docs/examples/styles/min_width.css
@@ -1,4 +1,4 @@
-Vertical {
+VerticalScroll {
height: 100%;
width: 100%;
overflow-x: auto;
@@ -10,7 +10,8 @@ Placeholder {
}
#p1 {
- min-width: 25%; /* (1)! */
+ min-width: 25%;
+ /* (1)! */
}
#p2 {
diff --git a/docs/examples/styles/min_width.py b/docs/examples/styles/min_width.py
index 25195c61d..b00881266 100644
--- a/docs/examples/styles/min_width.py
+++ b/docs/examples/styles/min_width.py
@@ -1,11 +1,11 @@
from textual.app import App
-from textual.containers import Vertical
+from textual.containers import VerticalScroll
from textual.widgets import Placeholder
class MinWidthApp(App):
def compose(self):
- yield Vertical(
+ yield VerticalScroll(
Placeholder("min-width: 25%", id="p1"),
Placeholder("min-width: 75%", id="p2"),
Placeholder("min-width: 100", id="p3"),
diff --git a/docs/examples/styles/overflow.css b/docs/examples/styles/overflow.css
index 3b68440c7..527429097 100644
--- a/docs/examples/styles/overflow.css
+++ b/docs/examples/styles/overflow.css
@@ -3,7 +3,7 @@ Screen {
color: black;
}
-Vertical {
+VerticalScroll {
width: 1fr;
}
@@ -13,7 +13,7 @@ Static {
border: green wide;
color: white 90%;
height: auto;
-}
+}
#right {
overflow-y: hidden;
diff --git a/docs/examples/styles/overflow.py b/docs/examples/styles/overflow.py
index b9a6c3383..debe0252d 100644
--- a/docs/examples/styles/overflow.py
+++ b/docs/examples/styles/overflow.py
@@ -1,6 +1,6 @@
from textual.app import App
+from textual.containers import Horizontal, VerticalScroll
from textual.widgets import Static
-from textual.containers import Horizontal, Vertical
TEXT = """I must not fear.
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):
def compose(self):
yield Horizontal(
- Vertical(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="left"),
+ VerticalScroll(Static(TEXT), Static(TEXT), Static(TEXT), id="right"),
)
diff --git a/docs/examples/styles/visibility_containers.py b/docs/examples/styles/visibility_containers.py
index 879b916fb..a94de145d 100644
--- a/docs/examples/styles/visibility_containers.py
+++ b/docs/examples/styles/visibility_containers.py
@@ -1,11 +1,11 @@
from textual.app import App
-from textual.containers import Horizontal, Vertical
+from textual.containers import Horizontal, VerticalScroll
from textual.widgets import Placeholder
class VisibilityContainersApp(App):
def compose(self):
- yield Vertical(
+ yield VerticalScroll(
Horizontal(
Placeholder(),
Placeholder(),
diff --git a/docs/examples/widgets/button.css b/docs/examples/widgets/button.css
index 5f1c906da..cb07fcaa1 100644
--- a/docs/examples/widgets/button.css
+++ b/docs/examples/widgets/button.css
@@ -2,7 +2,7 @@ Button {
margin: 1 2;
}
-Horizontal > Vertical {
+Horizontal > VerticalScroll {
width: 24;
}
diff --git a/docs/examples/widgets/button.py b/docs/examples/widgets/button.py
index 4c3509c32..09339ccb0 100644
--- a/docs/examples/widgets/button.py
+++ b/docs/examples/widgets/button.py
@@ -1,5 +1,5 @@
from textual.app import App, ComposeResult
-from textual.containers import Horizontal, Vertical
+from textual.containers import Horizontal, VerticalScroll
from textual.widgets import Button, Static
@@ -8,7 +8,7 @@ class ButtonsApp(App[str]):
def compose(self) -> ComposeResult:
yield Horizontal(
- Vertical(
+ VerticalScroll(
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!"),
),
- Vertical(
+ VerticalScroll(
Static("Disabled Buttons", classes="header"),
Button("Default", disabled=True),
Button("Primary!", variant="primary", disabled=True),
diff --git a/docs/examples/widgets/checkbox.css b/docs/examples/widgets/checkbox.css
index 4e80b6685..b6e17c093 100644
--- a/docs/examples/widgets/checkbox.css
+++ b/docs/examples/widgets/checkbox.css
@@ -2,7 +2,7 @@ Screen {
align: center middle;
}
-Vertical {
+VerticalScroll {
width: auto;
height: auto;
border: solid $primary;
diff --git a/docs/examples/widgets/checkbox.py b/docs/examples/widgets/checkbox.py
index 5ad6ca1ff..75eadda0c 100644
--- a/docs/examples/widgets/checkbox.py
+++ b/docs/examples/widgets/checkbox.py
@@ -1,5 +1,5 @@
from textual.app import App, ComposeResult
-from textual.containers import Vertical
+from textual.containers import VerticalScroll
from textual.widgets import Checkbox
@@ -7,7 +7,7 @@ class CheckboxApp(App[None]):
CSS_PATH = "checkbox.css"
def compose(self) -> ComposeResult:
- with Vertical():
+ with VerticalScroll():
yield Checkbox("Arrakis :sweat:")
yield Checkbox("Caladan")
yield Checkbox("Chusuk")
diff --git a/docs/examples/widgets/placeholder.py b/docs/examples/widgets/placeholder.py
index c09e9c412..9c7d6eb0e 100644
--- a/docs/examples/widgets/placeholder.py
+++ b/docs/examples/widgets/placeholder.py
@@ -1,14 +1,13 @@
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
class PlaceholderApp(App):
-
CSS_PATH = "placeholder.css"
def compose(self) -> ComposeResult:
- yield Vertical(
+ yield VerticalScroll(
Container(
Placeholder("This is a custom label for p1.", id="p1"),
Placeholder("Placeholder p2 here!", id="p2"),
diff --git a/docs/examples/widgets/radio_set_changed.css b/docs/examples/widgets/radio_set_changed.css
index 47583c1d8..fc1299294 100644
--- a/docs/examples/widgets/radio_set_changed.css
+++ b/docs/examples/widgets/radio_set_changed.css
@@ -1,4 +1,4 @@
-Vertical {
+VerticalScroll {
align: center middle;
}
diff --git a/docs/examples/widgets/radio_set_changed.py b/docs/examples/widgets/radio_set_changed.py
index c360cb868..c817b6e6f 100644
--- a/docs/examples/widgets/radio_set_changed.py
+++ b/docs/examples/widgets/radio_set_changed.py
@@ -1,5 +1,5 @@
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
@@ -7,7 +7,7 @@ class RadioSetChangedApp(App[None]):
CSS_PATH = "radio_set_changed.css"
def compose(self) -> ComposeResult:
- with Vertical():
+ with VerticalScroll():
with Horizontal():
with RadioSet():
yield RadioButton("Battlestar Galactica")
diff --git a/docs/guide/layout.md b/docs/guide/layout.md
index ba142f9ad..3564208a9 100644
--- a/docs/guide/layout.md
+++ b/docs/guide/layout.md
@@ -134,8 +134,8 @@ exceeds the available horizontal space in the parent container.
## Utility containers
-Textual comes with several "container" widgets.
-These are [Vertical][textual.containers.Vertical], [Horizontal][textual.containers.Horizontal], and [Grid][textual.containers.Grid] which have the corresponding layout.
+Textual comes with [several "container" widgets][textual.containers].
+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.
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
-In the previous section we've show 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.
+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.
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.
@@ -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"}
```
-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
diff --git a/docs/styles/height.md b/docs/styles/height.md
index 15d5399a3..bd3fe3512 100644
--- a/docs/styles/height.md
+++ b/docs/styles/height.md
@@ -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.
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.
- 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.
+ 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 `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.
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.
diff --git a/docs/styles/overflow.md b/docs/styles/overflow.md
index 12edaac15..d3f0ece5f 100644
--- a/docs/styles/overflow.md
+++ b/docs/styles/overflow.md
@@ -25,7 +25,7 @@ The default setting for containers is `overflow: auto auto`.
!!! warning
- Some built-in containers like `Horizontal` and `Vertical` override these defaults.
+ Some built-in containers like `Horizontal` and `VerticalScroll` override these defaults.
## Example
diff --git a/examples/code_browser.py b/examples/code_browser.py
index 4616be4f7..13fdf3c6d 100644
--- a/examples/code_browser.py
+++ b/examples/code_browser.py
@@ -14,7 +14,7 @@ from rich.traceback import Traceback
from textual import events
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.widgets import DirectoryTree, Footer, Header, Static
@@ -40,7 +40,7 @@ class CodeBrowser(App):
yield Header()
with Container():
yield DirectoryTree(path, id="tree-view")
- with Vertical(id="code-view"):
+ with VerticalScroll(id="code-view"):
yield Static(id="code", expand=True)
yield Footer()
diff --git a/src/textual/_segment_tools.py b/src/textual/_segment_tools.py
index 5f9df333b..79f3bc077 100644
--- a/src/textual/_segment_tools.py
+++ b/src/textual/_segment_tools.py
@@ -192,11 +192,10 @@ def align_lines(
style: Background style.
size: Size of container.
horizontal: Horizontal alignment.
- vertical: Vertical alignment
+ vertical: Vertical alignment.
Returns:
Aligned lines.
-
"""
width, height = size
diff --git a/src/textual/cli/previews/borders.py b/src/textual/cli/previews/borders.py
index a33320a0a..09dbcaab4 100644
--- a/src/textual/cli/previews/borders.py
+++ b/src/textual/cli/previews/borders.py
@@ -1,6 +1,6 @@
from textual.app import App, ComposeResult
from textual.constants import BORDERS
-from textual.containers import Vertical
+from textual.containers import VerticalScroll
from textual.widgets import Button, Label
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."""
-class BorderButtons(Vertical):
+class BorderButtons(VerticalScroll):
DEFAULT_CSS = """
BorderButtons {
dock: left;
diff --git a/src/textual/cli/previews/colors.py b/src/textual/cli/previews/colors.py
index b9d8da3eb..d8028f7c5 100644
--- a/src/textual/cli/previews/colors.py
+++ b/src/textual/cli/previews/colors.py
@@ -1,11 +1,11 @@
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.widget import Widget
from textual.widgets import Button, Footer, Label, Static
-class ColorButtons(Vertical):
+class ColorButtons(VerticalScroll):
def compose(self) -> ComposeResult:
for border in ColorSystem.COLOR_NAMES:
if border:
@@ -20,15 +20,15 @@ class ColorItem(Horizontal):
pass
-class ColorGroup(Vertical):
+class ColorGroup(VerticalScroll):
pass
-class Content(Vertical):
+class Content(VerticalScroll):
pass
-class ColorsView(Vertical):
+class ColorsView(VerticalScroll):
def compose(self) -> ComposeResult:
LEVELS = [
"darken-3",
diff --git a/src/textual/cli/previews/easing.py b/src/textual/cli/previews/easing.py
index 70dc45758..7bca58efa 100644
--- a/src/textual/cli/previews/easing.py
+++ b/src/textual/cli/previews/easing.py
@@ -5,7 +5,7 @@ from rich.console import RenderableType
from textual._easing import EASING
from textual.app import App, ComposeResult
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.scrollbar import ScrollBarRender
from textual.widget import Widget
@@ -73,7 +73,7 @@ class EasingApp(App):
)
yield EasingButtons()
- with Vertical():
+ with VerticalScroll():
with Horizontal(id="inputs"):
yield Label("Animation Duration:", id="label")
yield duration_input
diff --git a/src/textual/containers.py b/src/textual/containers.py
index bbd4c13d7..a2ddbf422 100644
--- a/src/textual/containers.py
+++ b/src/textual/containers.py
@@ -14,11 +14,23 @@ class Container(Widget):
class Vertical(Widget):
- """A container widget which aligns children vertically."""
+ """A container which arranges children vertically."""
DEFAULT_CSS = """
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;
overflow-y: auto;
}
@@ -26,25 +38,61 @@ class Vertical(Widget):
class Horizontal(Widget):
- """A container widget which aligns children horizontally."""
+ """A container which arranges children horizontally."""
DEFAULT_CSS = """
Horizontal {
height: 1fr;
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):
- """A container widget with grid alignment."""
+ """A container with grid alignment."""
DEFAULT_CSS = """
Grid {
height: 1fr;
layout: grid;
- }
+ }
"""
@@ -52,7 +100,7 @@ class Content(Widget, can_focus=True, can_focus_children=False):
"""A container for content such as text."""
DEFAULT_CSS = """
- Vertical {
+ VerticalScroll {
height: 1fr;
layout: vertical;
overflow-y: auto;
diff --git a/src/textual/widgets/_list_view.py b/src/textual/widgets/_list_view.py
index 013c51ead..8f95d011d 100644
--- a/src/textual/widgets/_list_view.py
+++ b/src/textual/widgets/_list_view.py
@@ -4,7 +4,7 @@ from typing import ClassVar, Optional
from textual.await_remove import AwaitRemove
from textual.binding import Binding, BindingType
-from textual.containers import Vertical
+from textual.containers import VerticalScroll
from textual.geometry import clamp
from textual.message import Message
from textual.reactive import reactive
@@ -12,7 +12,7 @@ from textual.widget import AwaitMount, Widget
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.
Displays a vertical list of `ListItem`s which can be highlighted and
diff --git a/src/textual/widgets/_markdown.py b/src/textual/widgets/_markdown.py
index 8e36b3e5e..36931fd59 100644
--- a/src/textual/widgets/_markdown.py
+++ b/src/textual/widgets/_markdown.py
@@ -10,7 +10,7 @@ from rich.text import Text
from typing_extensions import TypeAlias
from ..app import ComposeResult
-from ..containers import Horizontal, Vertical
+from ..containers import Horizontal, VerticalScroll
from ..message import Message
from ..reactive import reactive, var
from ..widget import Widget
@@ -266,7 +266,7 @@ class MarkdownBulletList(MarkdownList):
width: 1fr;
}
- MarkdownBulletList Vertical {
+ MarkdownBulletList VerticalScroll {
height: auto;
width: 1fr;
}
@@ -277,7 +277,7 @@ class MarkdownBulletList(MarkdownList):
if isinstance(block, MarkdownListItem):
bullet = MarkdownBullet()
bullet.symbol = block.bullet
- yield Horizontal(bullet, Vertical(*block._blocks))
+ yield Horizontal(bullet, VerticalScroll(*block._blocks))
self._blocks.clear()
@@ -295,7 +295,7 @@ class MarkdownOrderedList(MarkdownList):
width: 1fr;
}
- MarkdownOrderedList Vertical {
+ MarkdownOrderedList VerticalScroll {
height: auto;
width: 1fr;
}
@@ -311,7 +311,7 @@ class MarkdownOrderedList(MarkdownList):
if isinstance(block, MarkdownListItem):
bullet = MarkdownBullet()
bullet.symbol = block.bullet.rjust(symbol_size + 1)
- yield Horizontal(bullet, Vertical(*block._blocks))
+ yield Horizontal(bullet, VerticalScroll(*block._blocks))
self._blocks.clear()
@@ -410,7 +410,7 @@ class MarkdownListItem(MarkdownBlock):
height: auto;
}
- MarkdownListItem > Vertical {
+ MarkdownListItem > VerticalScroll {
width: 1fr;
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."""
DEFAULT_CSS = """
diff --git a/tests/snapshot_tests/__snapshots__/test_snapshots.ambr b/tests/snapshot_tests/__snapshots__/test_snapshots.ambr
index c142d5d3d..f2855c724 100644
--- a/tests/snapshot_tests/__snapshots__/test_snapshots.ambr
+++ b/tests/snapshot_tests/__snapshots__/test_snapshots.ambr
@@ -1,3 +1,165 @@
+# name: test_alignment_containers
+ '''
+
+
+ '''
+# ---
# name: test_auto_table
'''