diff --git a/src/textual/app.py b/src/textual/app.py index cf460fd4a7..0570dfdbe2 100644 --- a/src/textual/app.py +++ b/src/textual/app.py @@ -312,6 +312,8 @@ class App(Generic[ReturnType], DOMNode): App { background: $background; color: $text; + + /* When a widget is maximized */ Screen.-maximized-view { layout: vertical !important; hatch: right $panel; @@ -321,6 +323,10 @@ class App(Generic[ReturnType], DOMNode): dock: initial !important; } } + /* Fade the header title when app is blurred */ + &:blur HeaderTitle { + text-opacity: 50%; + } } *:disabled:can-focus { opacity: 0.7; @@ -820,6 +826,17 @@ def _context(self) -> Generator[None, None, None]: active_message_pump.reset(message_pump_reset_token) active_app.reset(app_reset_token) + def get_pseudo_classes(self) -> Iterable[str]: + """Pseudo classes for a widget. + + Returns: + Names of the pseudo classes. + """ + yield "focus" if self.app_focus else "blur" + yield "dark" if self.dark else "light" + if self.is_inline: + yield "inline" + def animate( self, attribute: str, @@ -3617,6 +3634,7 @@ def post_mount() -> None: def _watch_app_focus(self, focus: bool) -> None: """Respond to changes in app focus.""" + self.screen._update_styles() if focus: # If we've got a last-focused widget, if it still has a screen, # and if the screen is still the current screen and if nothing diff --git a/tests/snapshot_tests/__snapshots__/test_snapshots/test_app_focus_style.svg b/tests/snapshot_tests/__snapshots__/test_snapshots/test_app_focus_style.svg new file mode 100644 index 0000000000..79cdfbd91a --- /dev/null +++ b/tests/snapshot_tests/__snapshots__/test_snapshots/test_app_focus_style.svg @@ -0,0 +1,152 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + FocusApp + + + + + + + + + + +┌───────────┐ + +BLURRED + +└───────────┘ + + + + + + + + + + + + + + + + + + + + + diff --git a/tests/snapshot_tests/test_snapshots.py b/tests/snapshot_tests/test_snapshots.py index adba7113ca..b4caa1c5e9 100644 --- a/tests/snapshot_tests/test_snapshots.py +++ b/tests/snapshot_tests/test_snapshots.py @@ -6,6 +6,7 @@ from rich.text import Text from tests.snapshot_tests.language_snippets import SNIPPETS +from textual import events from textual.app import App, ComposeResult from textual.binding import Binding from textual.containers import Vertical @@ -124,7 +125,9 @@ async def run_before(pilot): pilot.app.query(Input).first().cursor_blink = False assert snap_compare( - SNAPSHOT_APPS_DIR / "masked_input.py", press=["A","B","C","0","1","-","D","E"], run_before=run_before + SNAPSHOT_APPS_DIR / "masked_input.py", + press=["A", "B", "C", "0", "1", "-", "D", "E"], + run_before=run_before, ) @@ -1820,3 +1823,39 @@ def on_mount(self) -> None: # ctrl+m to maximize, escape *should* minimize assert snap_compare(TextAreaExample(), press=["ctrl+m", "escape"]) + + +def test_app_focus_style(snap_compare): + """Test that app blur style can be selected.""" + + class FocusApp(App): + CSS = """ + Label { + padding: 1 2; + margin: 1 2; + background: $panel; + border: $primary; + } + App:focus { + .blurred { + visibility: hidden; + } + } + + App:blur { + .focussed { + visibility: hidden; + } + } + + """ + + def compose(self) -> ComposeResult: + yield Label("BLURRED", classes="blurred") + yield Label("FOCUSED", classes="focussed") + + async def run_before(pilot: Pilot) -> None: + pilot.app.post_message(events.AppBlur()) + await pilot.pause() + + assert snap_compare(FocusApp(), run_before=run_before) diff --git a/tests/test_test_runner.py b/tests/test_test_runner.py index b47bed98d2..8bb80d2071 100644 --- a/tests/test_test_runner.py +++ b/tests/test_test_runner.py @@ -12,8 +12,9 @@ def on_key(self, event: events.Key) -> None: app = TestApp() async with app.run_test() as pilot: - assert ( - str(pilot) == "" + assert str(pilot) in ( + "", + "", ) await pilot.press("tab", *"foo") await pilot.exit("bar")