diff --git a/CHANGELOG.md b/CHANGELOG.md index f8839e0f25..6726edc577 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -29,6 +29,10 @@ and this project adheres to [Semantic Versioning](http://semver.org/). - "Discover" hits in the command palette are no longer sorted alphabetically https://github.com/Textualize/textual/pull/4720 - `Markdown.LinkClicked.href` is now automatically unquoted https://github.com/Textualize/textual/pull/4749 +### Added + +- Added `Footer` component style handling of padding for the key/description https://github.com/Textualize/textual/pull/4651 + ## [0.72.0] - 2024-07-09 ### Changed @@ -55,7 +59,6 @@ and this project adheres to [Semantic Versioning](http://semver.org/). - Fixed link inside markdown table not posting a `Markdown.LinkClicked` message https://github.com/Textualize/textual/issues/4683 - Fixed issue with mouse movements on non-active screen https://github.com/Textualize/textual/pull/4688 - ## [0.70.0] - 2024-06-19 ### Fixed diff --git a/src/textual/widgets/_footer.py b/src/textual/widgets/_footer.py index aa39e3719e..09a743ac48 100644 --- a/src/textual/widgets/_footer.py +++ b/src/textual/widgets/_footer.py @@ -33,6 +33,11 @@ class FooterKey(Widget): color: $secondary; background: $panel; text-style: bold; + padding: 0 1; + } + + .footer-key--description { + padding: 0 1 0 0; } &:light .footer-key--key { @@ -57,6 +62,14 @@ class FooterKey(Widget): } } + &.-compact { + .footer-key--key { + padding: 0; + } + .footer-key--description { + padding: 0 0 0 1; + } + } } """ @@ -83,19 +96,27 @@ def render(self) -> Text: key_style = self.get_component_rich_style("footer-key--key") description_style = self.get_component_rich_style("footer-key--description") key_display = self.key_display + key_padding = self.get_component_styles("footer-key--key").padding + description_padding = self.get_component_styles( + "footer-key--description" + ).padding if self.upper_case_keys: key_display = key_display.upper() if self.ctrl_to_caret and key_display.lower().startswith("ctrl+"): key_display = "^" + key_display.split("+", 1)[1] description = self.description - if self.compact: - label_text = Text.assemble( - (key_display, key_style), " ", (description, description_style) - ) - else: - label_text = Text.assemble( - (f" {key_display} ", key_style), (description, description_style), " " - ) + label_text = Text.assemble( + ( + " " * key_padding.left + key_display + " " * key_padding.right, + key_style, + ), + ( + " " * description_padding.left + + description + + " " * description_padding.right, + description_style, + ), + ) label_text.stylize_before(self.rich_style) return label_text diff --git a/tests/snapshot_tests/__snapshots__/test_snapshots.ambr b/tests/snapshot_tests/__snapshots__/test_snapshots.ambr index fc13eb3a6d..5efd95785d 100644 --- a/tests/snapshot_tests/__snapshots__/test_snapshots.ambr +++ b/tests/snapshot_tests/__snapshots__/test_snapshots.ambr @@ -21310,6 +21310,480 @@ ''' # --- +# name: test_footer_classic_styling + ''' + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + ClassicFooterStylingApp + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +  CTRL+T  Toggle Dark mode  CTRL+Q  Quit                                          + + + + + ''' +# --- +# name: test_footer_compact + ''' + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + ToggleCompactFooterApp + + + + + + + + + + + + + + + + + + + + +                                  Compact Footer                                  + + + + + + + + + + + + ^t Toggle Compact Footer^q Quit + + + + + ''' +# --- +# name: test_footer_compact_with_hover + ''' + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + ToggleCompactFooterApp + + + + + + + + + + + + + + + + + + + + +                                  Compact Footer                                  + + + + + + + + + + + + ^t Toggle Compact Footer^q Quit + + + + + ''' +# --- # name: test_footer_render ''' @@ -21468,6 +21942,323 @@ ''' # --- +# name: test_footer_standard_after_reactive_change + ''' + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + ToggleCompactFooterApp + + + + + + + + + + + + + + + + + + + + +                                 Standard Footer                                  + + + + + + + + + + + +  ^t Toggle Compact Footer  ^q Quit  + + + + + ''' +# --- +# name: test_footer_standard_with_hover + ''' + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + ToggleCompactFooterApp + + + + + + + + + + + + + + + + + + + + +                                 Standard Footer                                  + + + + + + + + + + + +  ^t Toggle Compact Footer  ^q Quit  + + + + + ''' +# --- # name: test_fr_margins ''' diff --git a/tests/snapshot_tests/snapshot_apps/footer_classic_styling.py b/tests/snapshot_tests/snapshot_apps/footer_classic_styling.py new file mode 100644 index 0000000000..7f43027d49 --- /dev/null +++ b/tests/snapshot_tests/snapshot_apps/footer_classic_styling.py @@ -0,0 +1,50 @@ +from textual.app import App, ComposeResult +from textual.widgets import Footer + + +class ClassicFooterStylingApp(App): + """ + This app attempts to replicate the styling of the classic footer in + Textual before v0.63.0, in particular the distinct background color + for the binding keys and spacing between the key and binding description. + + Regression test for https://github.com/Textualize/textual/issues/4557 + """ + + CSS = """ + Footer { + background: $accent; + + FooterKey { + background: $accent; + color: $text; + + .footer-key--key { + background: $accent-darken-2; + color: $text; + } + + .footer-key--description { + padding: 0 1; + } + } + } + """ + + BINDINGS = [ + ("ctrl+t", "app.toggle_dark", "Toggle Dark mode"), + ("ctrl+q", "quit", "Quit"), + ] + + def compose(self) -> ComposeResult: + yield Footer() + + def on_mount(self) -> None: + footer = self.query_one(Footer) + footer.upper_case_keys = True + footer.ctrl_to_caret = False + + +if __name__ == "__main__": + app = ClassicFooterStylingApp() + app.run() diff --git a/tests/snapshot_tests/snapshot_apps/footer_toggle_compact.py b/tests/snapshot_tests/snapshot_apps/footer_toggle_compact.py new file mode 100644 index 0000000000..de8a01d9f3 --- /dev/null +++ b/tests/snapshot_tests/snapshot_apps/footer_toggle_compact.py @@ -0,0 +1,34 @@ +from textual.app import App, ComposeResult +from textual.widgets import Footer, Label + + +class ToggleCompactFooterApp(App): + CSS = """ + Screen { + align: center middle; + } + """ + + BINDINGS = [ + ("ctrl+t", "toggle_compact_footer", "Toggle Compact Footer"), + ("ctrl+q", "quit", "Quit"), + ] + + def compose(self) -> ComposeResult: + yield Label("Compact Footer") + yield Footer() + + def on_mount(self) -> None: + footer = self.query_one(Footer) + footer.compact = True + + def action_toggle_compact_footer(self) -> None: + footer = self.query_one(Footer) + footer.compact = not footer.compact + footer_type = "Compact" if footer.compact else "Standard" + self.query_one(Label).update(f"{footer_type} Footer") + + +if __name__ == "__main__": + app = ToggleCompactFooterApp() + app.run() diff --git a/tests/snapshot_tests/test_snapshots.py b/tests/snapshot_tests/test_snapshots.py index b74e44c316..35e8e12317 100644 --- a/tests/snapshot_tests/test_snapshots.py +++ b/tests/snapshot_tests/test_snapshots.py @@ -1303,6 +1303,46 @@ def test_grid_auto(snap_compare): assert snap_compare(SNAPSHOT_APPS_DIR / "grid_auto.py") +def test_footer_compact(snap_compare): + """Test Footer in the compact style""" + assert snap_compare(SNAPSHOT_APPS_DIR / "footer_toggle_compact.py") + + +def test_footer_compact_with_hover(snap_compare): + """Test Footer in the compact style when the mouse is hovering over a keybinding""" + + async def run_before(pilot) -> None: + await pilot.hover("Footer", offset=(0, 0)) + + assert snap_compare( + SNAPSHOT_APPS_DIR / "footer_toggle_compact.py", run_before=run_before + ) + + +def test_footer_standard_after_reactive_change(snap_compare): + """Test Footer in the standard style after `compact` reactive change""" + assert snap_compare( + SNAPSHOT_APPS_DIR / "footer_toggle_compact.py", press=["ctrl+t"] + ) + + +def test_footer_standard_with_hover(snap_compare): + """Test Footer in the standard style when the mouse is hovering over a keybinding""" + + async def run_before(pilot) -> None: + await pilot.press("ctrl+t") + await pilot.hover("Footer", offset=(0, 0)) + + assert snap_compare( + SNAPSHOT_APPS_DIR / "footer_toggle_compact.py", run_before=run_before + ) + + +def test_footer_classic_styling(snap_compare): + """Regression test for https://github.com/Textualize/textual/issues/4557""" + assert snap_compare(SNAPSHOT_APPS_DIR / "footer_classic_styling.py") + + def test_option_list_scrolling_with_multiline_options(snap_compare): # https://github.com/Textualize/textual/issues/4705 assert snap_compare(WIDGET_EXAMPLES_DIR / "option_list_tables.py", press=["up"])