From a11ff16519c06d3d31b6e920a0c3e08362714b38 Mon Sep 17 00:00:00 2001 From: Will McGugan Date: Tue, 21 Nov 2023 09:48:57 +0000 Subject: [PATCH] fix min width (#3712) * fix min width * changelog * snapshot tests * whitespace --- CHANGELOG.md | 1 + src/textual/widget.py | 10 +- src/textual/widgets/_input.py | 3 +- src/textual/widgets/_select.py | 2 +- .../__snapshots__/test_snapshots.ambr | 474 ++++++++++++++++++ .../snapshot_apps/missing_vertical_scroll.py | 34 ++ .../snapshot_apps/vertical_max_height.py | 26 + .../snapshot_apps/vertical_min_height.py | 26 + tests/snapshot_tests/test_snapshots.py | 37 +- 9 files changed, 601 insertions(+), 12 deletions(-) create mode 100644 tests/snapshot_tests/snapshot_apps/missing_vertical_scroll.py create mode 100644 tests/snapshot_tests/snapshot_apps/vertical_max_height.py create mode 100644 tests/snapshot_tests/snapshot_apps/vertical_min_height.py diff --git a/CHANGELOG.md b/CHANGELOG.md index d559902246..980abbc52e 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -44,6 +44,7 @@ and this project adheres to [Semantic Versioning](http://semver.org/). - Breaking change: `Button` no longer inherits from `Static`, now it inherits directly from `Widget` https://github.com/Textualize/textual/issues/3603 - Rich markup in markdown headings is now escaped when building the TOC https://github.com/Textualize/textual/issues/3689 - Mechanics behind mouse clicks. See [this](https://github.com/Textualize/textual/pull/3495#issue-1934915047) for more details. https://github.com/Textualize/textual/pull/3495 +- Breaking change: max/min-width/height now includes padding and border. https://github.com/Textualize/textual/pull/3712 ## [0.41.0] - 2023-10-31 diff --git a/src/textual/widget.py b/src/textual/widget.py index ac5da1e04d..9031717e01 100644 --- a/src/textual/widget.py +++ b/src/textual/widget.py @@ -1024,7 +1024,9 @@ def _get_box_model( min_width = styles.min_width.resolve( content_container, viewport, width_fraction ) - content_width = max(content_width, min_width) + if is_border_box: + min_width -= gutter.width + content_width = max(content_width, min_width, Fraction(0)) if styles.max_width is not None: # Restrict to maximum width, if set @@ -1066,13 +1068,17 @@ def _get_box_model( min_height = styles.min_height.resolve( content_container, viewport, height_fraction ) - content_height = max(content_height, min_height) + if is_border_box: + min_height -= gutter.height + content_height = max(content_height, min_height, Fraction(0)) if styles.max_height is not None: # Restrict maximum height, if set max_height = styles.max_height.resolve( content_container, viewport, height_fraction ) + if is_border_box: + max_height -= gutter.height content_height = min(content_height, max_height) content_height = max(Fraction(0), content_height) diff --git a/src/textual/widgets/_input.py b/src/textual/widgets/_input.py index fd2a24bf46..0b5dceef08 100644 --- a/src/textual/widgets/_input.py +++ b/src/textual/widgets/_input.py @@ -145,8 +145,7 @@ class Input(Widget, can_focus=True): padding: 0 2; border: tall $background; width: 100%; - height: 1; - min-height: 1; + height: 3; } Input:focus { border: tall $accent; diff --git a/src/textual/widgets/_select.py b/src/textual/widgets/_select.py index 92b1cb75f2..33fb70d505 100644 --- a/src/textual/widgets/_select.py +++ b/src/textual/widgets/_select.py @@ -206,7 +206,7 @@ class Select(Generic[SelectType], Vertical, can_focus=True): width: 1fr; display: none; height: auto; - max-height: 10; + max-height: 12; overlay: screen; constrain: y; } diff --git a/tests/snapshot_tests/__snapshots__/test_snapshots.ambr b/tests/snapshot_tests/__snapshots__/test_snapshots.ambr index f9abc1ed64..25ea7c838d 100644 --- a/tests/snapshot_tests/__snapshots__/test_snapshots.ambr +++ b/tests/snapshot_tests/__snapshots__/test_snapshots.ambr @@ -21214,6 +21214,166 @@ ''' # --- +# name: test_missing_vertical_scroll + ''' + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + MissingScrollbarApp + + + + + + + + + + ▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔ + 000 + 111 + 2▄▄2▄▄2▄▄ + 333 + 444 + 555 + 666 + 777 + 888 + 999 + 101010 + 111111 + 121212 + 131313 + 141414 + 151515 + 161616 + 171717 + 181818 + 191919 + 202020 + 212121 + ▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁ + + + + + ''' +# --- # name: test_modal_dialog_bindings ''' @@ -36128,6 +36288,320 @@ ''' # --- +# name: test_vertical_max_height + ''' + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + VerticalApp + + + + + + + + + + ────────────────────────────────────────────────────────────────────────────── + + + + + + #top + + + + + + + ────────────────────────────────────────────────────────────────────────────── + ────────────────────────────────────────────────────────────────────────────── + + + + #bottom + + + + + ────────────────────────────────────────────────────────────────────────────── + + + + + ''' +# --- +# name: test_vertical_min_height + ''' + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + VerticalApp + + + + + + + + + + ────────────────────────────────────────────────────────────────────────────── + + + + #top + + + + + ────────────────────────────────────────────────────────────────────────────── + ────────────────────────────────────────────────────────────────────────────── + + + + + + #bottom + + + + + + + ────────────────────────────────────────────────────────────────────────────── + + + + + ''' +# --- # name: test_viewport_height_and_width_properties ''' diff --git a/tests/snapshot_tests/snapshot_apps/missing_vertical_scroll.py b/tests/snapshot_tests/snapshot_apps/missing_vertical_scroll.py new file mode 100644 index 0000000000..cb91bc06d9 --- /dev/null +++ b/tests/snapshot_tests/snapshot_apps/missing_vertical_scroll.py @@ -0,0 +1,34 @@ +from textual.app import App, ComposeResult +from textual.containers import Horizontal +from textual.widgets import OptionList + + +class MissingScrollbarApp(App[None]): + CSS = """ + OptionList { + height: 1fr; + } + + #left { + min-width: 25; + } + + #middle { + width: 5fr; + } + + #right { + min-width: 30; + } + """ + + def compose(self) -> ComposeResult: + options = [str(n) for n in range(200)] + with Horizontal(): + yield OptionList(*options, id="left") + yield OptionList(*options, id="middle") + yield OptionList(*options, id="right") + + +if __name__ == "__main__": + MissingScrollbarApp().run() diff --git a/tests/snapshot_tests/snapshot_apps/vertical_max_height.py b/tests/snapshot_tests/snapshot_apps/vertical_max_height.py new file mode 100644 index 0000000000..03196c52dd --- /dev/null +++ b/tests/snapshot_tests/snapshot_apps/vertical_max_height.py @@ -0,0 +1,26 @@ +from textual.app import App, ComposeResult +from textual.containers import Vertical +from textual.widgets import Placeholder + + +class VerticalApp(App): + CSS = """ + #top { + height: 1fr; + border: white; + } + #bottom { + height:3fr; + border: white; + max-height: 10; + } + """ + + def compose(self) -> ComposeResult: + with Vertical(): + yield Placeholder(id="top") + yield Placeholder(id="bottom") + + +if __name__ == "__main__": + VerticalApp().run() diff --git a/tests/snapshot_tests/snapshot_apps/vertical_min_height.py b/tests/snapshot_tests/snapshot_apps/vertical_min_height.py new file mode 100644 index 0000000000..b0f167f82e --- /dev/null +++ b/tests/snapshot_tests/snapshot_apps/vertical_min_height.py @@ -0,0 +1,26 @@ +from textual.app import App, ComposeResult +from textual.containers import Vertical +from textual.widgets import Placeholder + + +class VerticalApp(App): + CSS = """ + #top { + min-height: 10; + height: 1fr; + border: white; + } + #bottom { + height:3fr; + border: white; + } + """ + + def compose(self) -> ComposeResult: + with Vertical(): + yield Placeholder(id="top") + yield Placeholder(id="bottom") + + +if __name__ == "__main__": + VerticalApp().run() diff --git a/tests/snapshot_tests/test_snapshots.py b/tests/snapshot_tests/test_snapshots.py index 03b0b60337..9754c48e7b 100644 --- a/tests/snapshot_tests/test_snapshots.py +++ b/tests/snapshot_tests/test_snapshots.py @@ -548,7 +548,9 @@ def test_scrollbar_thumb_height(snap_compare): def test_css_hot_reloading(snap_compare, monkeypatch): """Regression test for https://github.com/Textualize/textual/issues/2063.""" - monkeypatch.setenv("TEXTUAL", "debug") # This will make sure we create a file monitor. + monkeypatch.setenv( + "TEXTUAL", "debug" + ) # This will make sure we create a file monitor. async def run_before(pilot): css_file = pilot.app.CSS_PATH @@ -564,7 +566,9 @@ async def run_before(pilot): def test_css_hot_reloading_on_screen(snap_compare, monkeypatch): """Regression test for https://github.com/Textualize/textual/issues/3454.""" - monkeypatch.setenv("TEXTUAL", "debug") # This will make sure we create a file monitor. + monkeypatch.setenv( + "TEXTUAL", "debug" + ) # This will make sure we create a file monitor. async def run_before(pilot): css_file = pilot.app.screen.CSS_PATH @@ -573,14 +577,17 @@ async def run_before(pilot): await pilot.app._on_css_change() assert snap_compare( - SNAPSHOT_APPS_DIR / "hot_reloading_app_with_screen_css.py", run_before=run_before + SNAPSHOT_APPS_DIR / "hot_reloading_app_with_screen_css.py", + run_before=run_before, ) def test_datatable_hot_reloading(snap_compare, monkeypatch): """Regression test for https://github.com/Textualize/textual/issues/3312.""" - monkeypatch.setenv("TEXTUAL", "debug") # This will make sure we create a file monitor. + monkeypatch.setenv( + "TEXTUAL", "debug" + ) # This will make sure we create a file monitor. async def run_before(pilot): css_file = pilot.app.CSS_PATH @@ -703,9 +710,8 @@ async def run_before(pilot) -> None: def test_command_palette(snap_compare) -> None: - async def run_before(pilot) -> None: - #await pilot.press("ctrl+backslash") + # await pilot.press("ctrl+backslash") pilot.app.screen.query_one(Input).cursor_blink = False await pilot.press("A") await pilot.app.screen.workers.wait_for_complete() @@ -907,4 +913,21 @@ def test_notifications_loading_overlap_order(snap_compare): This tests that notifications stay on top of loading indicators and it also tests that loading a widget will remove its scrollbars. """ - assert snap_compare(SNAPSHOT_APPS_DIR / "notifications_above_loading.py", terminal_size=(80, 20)) + assert snap_compare( + SNAPSHOT_APPS_DIR / "notifications_above_loading.py", terminal_size=(80, 20) + ) + + +def test_missing_vertical_scroll(snap_compare): + """Regression test for https://github.com/Textualize/textual/issues/3687""" + assert snap_compare(SNAPSHOT_APPS_DIR / "missing_vertical_scroll.py") + + +def test_vertical_min_height(snap_compare): + """Test vertical min height takes border in to account.""" + assert snap_compare(SNAPSHOT_APPS_DIR / "vertical_min_height.py") + + +def test_vertical_max_height(snap_compare): + """Test vertical max height takes border in to account.""" + assert snap_compare(SNAPSHOT_APPS_DIR / "vertical_max_height.py")