From 38e636921c1e39bd83a11659888fca1af385da9b Mon Sep 17 00:00:00 2001 From: Dave Pearson Date: Mon, 24 Apr 2023 11:06:31 +0100 Subject: [PATCH 1/6] Remove default scrollbars from Container As per #2361 this removes default scrollbars on Container (and so adds them on ScrollableContainer). --- src/textual/containers.py | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/src/textual/containers.py b/src/textual/containers.py index 54aa36d507..4ef1873c58 100644 --- a/src/textual/containers.py +++ b/src/textual/containers.py @@ -18,7 +18,7 @@ class Container(Widget): Container { height: 1fr; layout: vertical; - overflow: auto; + overflow: hidden hidden; } """ @@ -26,6 +26,13 @@ class Container(Widget): class ScrollableContainer(Widget, inherit_bindings=False): """Base container widget that binds navigation keys for scrolling.""" + DEFAULT_CSS = """ + ScrollableContainer { + layout: vertical; + overflow: auto auto; + } + """ + BINDINGS: ClassVar[list[BindingType]] = [ Binding("up", "scroll_up", "Scroll Up", show=False), Binding("down", "scroll_down", "Scroll Down", show=False), From d06a4e579d4dbfa3ed196bb234f5b027ccc71627 Mon Sep 17 00:00:00 2001 From: Dave Pearson Date: Mon, 24 Apr 2023 11:09:09 +0100 Subject: [PATCH 2/6] By default hide the vertical scrollbar on HorizontalScroll In connection with #2361. --- src/textual/containers.py | 1 + 1 file changed, 1 insertion(+) diff --git a/src/textual/containers.py b/src/textual/containers.py index 4ef1873c58..1150687170 100644 --- a/src/textual/containers.py +++ b/src/textual/containers.py @@ -101,6 +101,7 @@ class HorizontalScroll(ScrollableContainer, can_focus=True): HorizontalScroll { height: 1fr; layout: horizontal; + overflow-y: hidden; overflow-x: auto; } """ From a91b2c6b3564545c5e188b4c082c1038664cb79c Mon Sep 17 00:00:00 2001 From: Dave Pearson Date: Mon, 24 Apr 2023 11:09:52 +0100 Subject: [PATCH 3/6] By default hide the horizontal scrollbar on VerticalScroll In connection with #2361. --- src/textual/containers.py | 1 + 1 file changed, 1 insertion(+) diff --git a/src/textual/containers.py b/src/textual/containers.py index 1150687170..92640068be 100644 --- a/src/textual/containers.py +++ b/src/textual/containers.py @@ -77,6 +77,7 @@ class VerticalScroll(ScrollableContainer, can_focus=True): VerticalScroll { width: 1fr; layout: vertical; + overflow-x: hidden; overflow-y: auto; } """ From b896e9d7f94a93fc6bc81d3880b59866d207f76f Mon Sep 17 00:00:00 2001 From: Dave Pearson Date: Mon, 24 Apr 2023 11:10:37 +0100 Subject: [PATCH 4/6] Update tests to handle scroll changes to Container See #2361. --- docs/examples/styles/scrollbar_size.py | 4 ++-- docs/examples/styles/scrollbar_size2.css | 2 +- docs/examples/styles/scrollbar_size2.py | 10 ++++++---- docs/examples/styles/scrollbars.css | 2 +- docs/examples/styles/scrollbars.py | 8 +++++--- docs/examples/widgets/content_switcher.py | 7 +++++-- src/textual/cli/previews/easing.py | 2 +- tests/snapshot_tests/snapshot_apps/auto-table.py | 4 ++-- 8 files changed, 23 insertions(+), 16 deletions(-) diff --git a/docs/examples/styles/scrollbar_size.py b/docs/examples/styles/scrollbar_size.py index bf4390c317..971a65a2ac 100644 --- a/docs/examples/styles/scrollbar_size.py +++ b/docs/examples/styles/scrollbar_size.py @@ -1,5 +1,5 @@ from textual.app import App -from textual.containers import Container +from textual.containers import ScrollableContainer from textual.widgets import Label TEXT = """I must not fear. @@ -14,7 +14,7 @@ class ScrollbarApp(App): def compose(self): - yield Container(Label(TEXT * 5), classes="panel") + yield ScrollableContainer(Label(TEXT * 5), classes="panel") app = ScrollbarApp(css_path="scrollbar_size.css") diff --git a/docs/examples/styles/scrollbar_size2.css b/docs/examples/styles/scrollbar_size2.css index 6054ceed1c..f361d940b6 100644 --- a/docs/examples/styles/scrollbar_size2.css +++ b/docs/examples/styles/scrollbar_size2.css @@ -1,4 +1,4 @@ -Container { +ScrollableContainer { width: 1fr; } diff --git a/docs/examples/styles/scrollbar_size2.py b/docs/examples/styles/scrollbar_size2.py index 66bd7e397a..88dbba2e43 100644 --- a/docs/examples/styles/scrollbar_size2.py +++ b/docs/examples/styles/scrollbar_size2.py @@ -1,5 +1,5 @@ from textual.app import App -from textual.containers import Horizontal, Container +from textual.containers import Horizontal, ScrollableContainer from textual.widgets import Label TEXT = """I must not fear. @@ -15,10 +15,12 @@ class ScrollbarApp(App): def compose(self): yield Horizontal( - Container(Label(TEXT * 5), id="v1"), - Container(Label(TEXT * 5), id="v2"), - Container(Label(TEXT * 5), id="v3"), + ScrollableContainer(Label(TEXT * 5), id="v1"), + ScrollableContainer(Label(TEXT * 5), id="v2"), + ScrollableContainer(Label(TEXT * 5), id="v3"), ) app = ScrollbarApp(css_path="scrollbar_size2.css") +if __name__ == "__main__": + app.run() diff --git a/docs/examples/styles/scrollbars.css b/docs/examples/styles/scrollbars.css index b949522744..925b721f0b 100644 --- a/docs/examples/styles/scrollbars.css +++ b/docs/examples/styles/scrollbars.css @@ -9,6 +9,6 @@ Label { scrollbar-corner-color: blue; } -Horizontal > Container { +Horizontal > ScrollableContainer { width: 50%; } diff --git a/docs/examples/styles/scrollbars.py b/docs/examples/styles/scrollbars.py index 371027eccc..3a6a45570b 100644 --- a/docs/examples/styles/scrollbars.py +++ b/docs/examples/styles/scrollbars.py @@ -1,5 +1,5 @@ from textual.app import App -from textual.containers import Horizontal, Container +from textual.containers import Horizontal, ScrollableContainer from textual.widgets import Label TEXT = """I must not fear. @@ -15,9 +15,11 @@ class ScrollbarApp(App): def compose(self): yield Horizontal( - Container(Label(TEXT * 10)), - Container(Label(TEXT * 10), classes="right"), + ScrollableContainer(Label(TEXT * 10)), + ScrollableContainer(Label(TEXT * 10), classes="right"), ) app = ScrollbarApp(css_path="scrollbars.css") +if __name__ == "__main__": + app.run() diff --git a/docs/examples/widgets/content_switcher.py b/docs/examples/widgets/content_switcher.py index 1a774a8055..f9197a2996 100644 --- a/docs/examples/widgets/content_switcher.py +++ b/docs/examples/widgets/content_switcher.py @@ -1,5 +1,7 @@ +from rich.align import VerticalCenter + from textual.app import App, ComposeResult -from textual.containers import Horizontal +from textual.containers import Horizontal, VerticalScroll from textual.widgets import Button, ContentSwitcher, DataTable, Markdown MARKDOWN_EXAMPLE = """# Three Flavours Cornetto @@ -37,7 +39,8 @@ def compose(self) -> ComposeResult: with ContentSwitcher(initial="data-table"): # (4)! yield DataTable(id="data-table") - yield Markdown(MARKDOWN_EXAMPLE, id="markdown") + with VerticalScroll(id="markdown"): + yield Markdown(MARKDOWN_EXAMPLE) def on_button_pressed(self, event: Button.Pressed) -> None: self.query_one(ContentSwitcher).current = event.button.id # (5)! diff --git a/src/textual/cli/previews/easing.py b/src/textual/cli/previews/easing.py index e593bbf93a..f533fcf862 100644 --- a/src/textual/cli/previews/easing.py +++ b/src/textual/cli/previews/easing.py @@ -79,7 +79,7 @@ def compose(self) -> ComposeResult: yield duration_input with Horizontal(): yield self.animated_bar - yield Container(self.opacity_widget, id="other") + yield VerticalScroll(self.opacity_widget, id="other") yield Footer() def on_button_pressed(self, event: Button.Pressed) -> None: diff --git a/tests/snapshot_tests/snapshot_apps/auto-table.py b/tests/snapshot_tests/snapshot_apps/auto-table.py index 9e7f70b006..54c215fbcd 100644 --- a/tests/snapshot_tests/snapshot_apps/auto-table.py +++ b/tests/snapshot_tests/snapshot_apps/auto-table.py @@ -2,7 +2,7 @@ from random import randrange from textual.app import App -from textual.containers import Container, Horizontal, Vertical +from textual.containers import Container, Horizontal, ScrollableContainer, Vertical from textual.screen import Screen from textual.widgets import DataTable, Header, Label @@ -109,7 +109,7 @@ def __init__(self): super().__init__( "", - Container( + ScrollableContainer( Horizontal(self.__info, id="issue-info"), Horizontal(*[Status(str(i)) for i in range(4)], id="statuses-box"), id="issues-box", From a005b81a39ea3bffbb5f1e8f1a8a6d216ee78bae Mon Sep 17 00:00:00 2001 From: Dave Pearson Date: Mon, 24 Apr 2023 11:18:41 +0100 Subject: [PATCH 5/6] Update the CHANGELOG --- CHANGELOG.md | 3 +++ 1 file changed, 3 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index c8ce08816b..2f5ef9d800 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -10,6 +10,9 @@ and this project adheres to [Semantic Versioning](http://semver.org/). ### Changed - `textual run` execs apps in a new context. +- Breaking change: `Container` no longer shows required scrollbars by default https://github.com/Textualize/textual/issues/2361 +- Breaking change: `VerticalScroll` no longer shows a required horizontal scrollbar by default +- Breaking change: `HorizontalScroll` no longer shows a required vertical scrollbar by default ### Added From 2055a8996f94f3270a225f773e9f8b15305db4c7 Mon Sep 17 00:00:00 2001 From: Dave Pearson Date: Mon, 24 Apr 2023 13:13:11 +0100 Subject: [PATCH 6/6] Move the tutorial over to using ScrollableContainer --- docs/examples/tutorial/stopwatch.py | 6 +++--- docs/examples/tutorial/stopwatch02.py | 6 +++--- docs/examples/tutorial/stopwatch03.py | 6 +++--- docs/examples/tutorial/stopwatch04.py | 6 +++--- docs/examples/tutorial/stopwatch05.py | 6 +++--- docs/examples/tutorial/stopwatch06.py | 6 +++--- docs/tutorial.md | 6 +++--- 7 files changed, 21 insertions(+), 21 deletions(-) diff --git a/docs/examples/tutorial/stopwatch.py b/docs/examples/tutorial/stopwatch.py index ffb87ea4c2..af3c3503cb 100644 --- a/docs/examples/tutorial/stopwatch.py +++ b/docs/examples/tutorial/stopwatch.py @@ -1,9 +1,9 @@ from time import monotonic from textual.app import App, ComposeResult -from textual.containers import Container +from textual.containers import ScrollableContainer from textual.reactive import reactive -from textual.widgets import Button, Header, Footer, Static +from textual.widgets import Button, Footer, Header, Static class TimeDisplay(Static): @@ -83,7 +83,7 @@ def compose(self) -> ComposeResult: """Called to add widgets to the app.""" yield Header() yield Footer() - yield Container(Stopwatch(), Stopwatch(), Stopwatch(), id="timers") + yield ScrollableContainer(Stopwatch(), Stopwatch(), Stopwatch(), id="timers") def action_add_stopwatch(self) -> None: """An action to add a timer.""" diff --git a/docs/examples/tutorial/stopwatch02.py b/docs/examples/tutorial/stopwatch02.py index 8baa3831e9..5937cd8058 100644 --- a/docs/examples/tutorial/stopwatch02.py +++ b/docs/examples/tutorial/stopwatch02.py @@ -1,6 +1,6 @@ from textual.app import App, ComposeResult -from textual.containers import Container -from textual.widgets import Button, Header, Footer, Static +from textual.containers import ScrollableContainer +from textual.widgets import Button, Footer, Header, Static class TimeDisplay(Static): @@ -27,7 +27,7 @@ def compose(self) -> ComposeResult: """Create child widgets for the app.""" yield Header() yield Footer() - yield Container(Stopwatch(), Stopwatch(), Stopwatch()) + yield ScrollableContainer(Stopwatch(), Stopwatch(), Stopwatch()) def action_toggle_dark(self) -> None: """An action to toggle dark mode.""" diff --git a/docs/examples/tutorial/stopwatch03.py b/docs/examples/tutorial/stopwatch03.py index 0c455fecb1..8e1fcfb140 100644 --- a/docs/examples/tutorial/stopwatch03.py +++ b/docs/examples/tutorial/stopwatch03.py @@ -1,6 +1,6 @@ from textual.app import App, ComposeResult -from textual.containers import Container -from textual.widgets import Button, Header, Footer, Static +from textual.containers import ScrollableContainer +from textual.widgets import Button, Footer, Header, Static class TimeDisplay(Static): @@ -28,7 +28,7 @@ def compose(self) -> ComposeResult: """Create child widgets for the app.""" yield Header() yield Footer() - yield Container(Stopwatch(), Stopwatch(), Stopwatch()) + yield ScrollableContainer(Stopwatch(), Stopwatch(), Stopwatch()) def action_toggle_dark(self) -> None: """An action to toggle dark mode.""" diff --git a/docs/examples/tutorial/stopwatch04.py b/docs/examples/tutorial/stopwatch04.py index 2394fd20ca..36acc023c9 100644 --- a/docs/examples/tutorial/stopwatch04.py +++ b/docs/examples/tutorial/stopwatch04.py @@ -1,6 +1,6 @@ from textual.app import App, ComposeResult -from textual.containers import Container -from textual.widgets import Button, Header, Footer, Static +from textual.containers import ScrollableContainer +from textual.widgets import Button, Footer, Header, Static class TimeDisplay(Static): @@ -35,7 +35,7 @@ def compose(self) -> ComposeResult: """Create child widgets for the app.""" yield Header() yield Footer() - yield Container(Stopwatch(), Stopwatch(), Stopwatch()) + yield ScrollableContainer(Stopwatch(), Stopwatch(), Stopwatch()) def action_toggle_dark(self) -> None: """An action to toggle dark mode.""" diff --git a/docs/examples/tutorial/stopwatch05.py b/docs/examples/tutorial/stopwatch05.py index 6543ac5ebc..fee7691d4e 100644 --- a/docs/examples/tutorial/stopwatch05.py +++ b/docs/examples/tutorial/stopwatch05.py @@ -1,9 +1,9 @@ from time import monotonic from textual.app import App, ComposeResult -from textual.containers import Container +from textual.containers import ScrollableContainer from textual.reactive import reactive -from textual.widgets import Button, Header, Footer, Static +from textual.widgets import Button, Footer, Header, Static class TimeDisplay(Static): @@ -55,7 +55,7 @@ def compose(self) -> ComposeResult: """Create child widgets for the app.""" yield Header() yield Footer() - yield Container(Stopwatch(), Stopwatch(), Stopwatch()) + yield ScrollableContainer(Stopwatch(), Stopwatch(), Stopwatch()) def action_toggle_dark(self) -> None: """An action to toggle dark mode.""" diff --git a/docs/examples/tutorial/stopwatch06.py b/docs/examples/tutorial/stopwatch06.py index 84e862f98f..b78864c874 100644 --- a/docs/examples/tutorial/stopwatch06.py +++ b/docs/examples/tutorial/stopwatch06.py @@ -1,9 +1,9 @@ from time import monotonic from textual.app import App, ComposeResult -from textual.containers import Container +from textual.containers import ScrollableContainer from textual.reactive import reactive -from textual.widgets import Button, Header, Footer, Static +from textual.widgets import Button, Footer, Header, Static class TimeDisplay(Static): @@ -78,7 +78,7 @@ def compose(self) -> ComposeResult: """Called to add widgets to the app.""" yield Header() yield Footer() - yield Container(Stopwatch(), Stopwatch(), Stopwatch()) + yield ScrollableContainer(Stopwatch(), Stopwatch(), Stopwatch()) def action_toggle_dark(self) -> None: """An action to toggle dark mode.""" diff --git a/docs/tutorial.md b/docs/tutorial.md index 604398b1d2..b69defb5ce 100644 --- a/docs/tutorial.md +++ b/docs/tutorial.md @@ -157,7 +157,7 @@ Let's add those to the app. Just a skeleton for now, we will add the rest of the --8<-- "docs/examples/tutorial/stopwatch02.py" ``` -We've imported two new widgets in this code: `Button`, which creates a clickable button, and `Static` which is a base class for a simple control. We've also imported `Container` from `textual.containers` which (as the name suggests) is a `Widget` which contains other widgets. +We've imported two new widgets in this code: `Button`, which creates a clickable button, and `Static` which is a base class for a simple control. We've also imported `ScrollableContainer` from `textual.containers` which (as the name suggests) is a `Widget` which contains other widgets. We've defined an empty `TimeDisplay` widget by extending `Static`. We will flesh this out later. @@ -174,7 +174,7 @@ The Button constructor takes a label to be displayed in the button (`"Start"`, ` To add widgets to our application we first need to yield them from the app's `compose()` method: -The new line in `StopwatchApp.compose()` yields a single `Container` object which will create a scrolling list of stopwatches. When classes contain other widgets (like `Container`) they will typically accept their child widgets as positional arguments. We want to start the app with three stopwatches, so we construct three `Stopwatch` instances and pass them to the container's constructor. +The new line in `StopwatchApp.compose()` yields a single `ScrollableContainer` object which will create a scrolling list of stopwatches. When classes contain other widgets (like `ScrollableContainer`) they will typically accept their child widgets as positional arguments. We want to start the app with three stopwatches, so we construct three `Stopwatch` instances and pass them to the container's constructor. ### The unstyled app @@ -438,7 +438,7 @@ Let's use these methods to implement adding and removing stopwatches to our app. Here's a summary of the changes: -- The `Container` object in `StopWatchApp` grew a `"timers"` ID. +- The `ScrollableContainer` object in `StopWatchApp` grew a `"timers"` ID. - Added `action_add_stopwatch` to add a new stopwatch. - Added `action_remove_stopwatch` to remove a stopwatch. - Added keybindings for the actions.