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 @@
+
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")