TUI: add double-escape quit and minor layout tweaks

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
adlee-was-taken
2026-04-07 19:38:38 -04:00
parent 420928f11e
commit 0c0588f920
4 changed files with 28 additions and 29 deletions

View File

@@ -1,4 +1,3 @@
// SPDX-License-Identifier: GPL-3.0-or-later
// Centralized timing configuration for all animations and pauses // Centralized timing configuration for all animations and pauses
// Edit these values to tune the feel of card animations and CPU gameplay // Edit these values to tune the feel of card animations and CPU gameplay

View File

@@ -2,6 +2,8 @@
from __future__ import annotations from __future__ import annotations
import time
from textual.app import ComposeResult from textual.app import ComposeResult
from textual.containers import Container, Horizontal, Vertical from textual.containers import Container, Horizontal, Vertical
from textual.screen import Screen from textual.screen import Screen
@@ -23,6 +25,7 @@ class ConnectScreen(Screen):
def __init__(self): def __init__(self):
super().__init__() super().__init__()
self._mode: str = "login" # "login" or "signup" self._mode: str = "login" # "login" or "signup"
self._last_esc: float = 0.0
def compose(self) -> ComposeResult: def compose(self) -> ComposeResult:
with Container(id="connect-container"): with Container(id="connect-container"):
@@ -30,7 +33,7 @@ class ConnectScreen(Screen):
# Login form # Login form
with Vertical(id="login-form"): with Vertical(id="login-form"):
yield Static("Log in to play") yield Static("Log in to play\n")
yield Input(placeholder="Username", id="input-username") yield Input(placeholder="Username", id="input-username")
yield Input(placeholder="Password", password=True, id="input-password") yield Input(placeholder="Password", password=True, id="input-password")
with Horizontal(id="connect-buttons"): with Horizontal(id="connect-buttons"):
@@ -64,7 +67,7 @@ class ConnectScreen(Screen):
with Horizontal(classes="screen-footer"): with Horizontal(classes="screen-footer"):
yield Static("", id="connect-footer-left", classes="screen-footer-left") yield Static("", id="connect-footer-left", classes="screen-footer-left")
yield Static("\\[q] quit", id="connect-footer-right", classes="screen-footer-right") yield Static("\\[q]uit or \\[esc]x2", id="connect-footer-right", classes="screen-footer-right")
def on_mount(self) -> None: def on_mount(self) -> None:
self._update_form_visibility() self._update_form_visibility()
@@ -103,11 +106,17 @@ class ConnectScreen(Screen):
self._update_form_visibility() self._update_form_visibility()
def handle_escape(self) -> None: def handle_escape(self) -> None:
"""Escape goes back to login if on signup form.""" """Escape goes back to login if on signup form. Double-escape quits."""
if self._mode == "signup": if self._mode == "signup":
self._mode = "login" self._mode = "login"
self._set_status("") self._set_status("")
self._update_form_visibility() self._update_form_visibility()
self._last_esc = 0.0
else:
now = time.monotonic()
if now - self._last_esc < 0.5:
self.app.exit()
self._last_esc = now
def on_input_submitted(self, event: Input.Submitted) -> None: def on_input_submitted(self, event: Input.Submitted) -> None:
if event.input.id == "input-password": if event.input.id == "input-password":

View File

@@ -72,7 +72,7 @@ class LobbyScreen(Screen):
# In-room: player list + controls + settings # In-room: player list + controls + settings
with Vertical(id="in-room"): with Vertical(id="in-room"):
yield Static("", id="room-info") yield Static("", id="room-info")
yield Static("[bold]Players[/bold]", id="player-list-label") yield Static("[bold]Players[/bold]\n", id="player-list-label")
yield Static("", id="player-list") yield Static("", id="player-list")
# CPU controls: compact [+] [-] # CPU controls: compact [+] [-]
@@ -185,18 +185,18 @@ class LobbyScreen(Screen):
yield Label("Wolfpack") yield Label("Wolfpack")
yield Switch(id="sw-wolfpack") yield Switch(id="sw-wolfpack")
with Collapsible(title="Deck Style", collapsed=True, id="coll-deck"): with Horizontal(classes="setting-row"):
with Horizontal(classes="setting-row"): yield Label("Deck Style")
yield Select( yield Select(
[(name.replace("-", " ").title(), name) for name in DECK_PRESETS], [(name.replace("-", " ").title(), name) for name in DECK_PRESETS],
value="classic", value="classic",
id="sel-deck-style", id="sel-deck-style",
allow_blank=False, allow_blank=False,
) )
yield Static( yield Static(
self._render_deck_preview("classic"), self._render_deck_preview("classic"),
id="deck-preview", id="deck-preview",
) )
yield Button("Start Game", id="btn-start", variant="success") yield Button("Start Game", id="btn-start", variant="success")
@@ -398,15 +398,7 @@ class LobbyScreen(Screen):
) )
line3 = "".join(parts3) line3 = "".join(parts3)
parts4: list[str] = [] return f"{line1}\n{line2}\n{line3}"
for color_name in seen:
hc = BACK_COLORS.get(color_name, BACK_COLORS["red"])
parts4.append(
f"[{bc}]└───┘[/{bc}] "
)
line4 = "".join(parts4)
return f"{line1}\n{line2}\n{line3}\n{line4}"
def _add_random_cpu(self) -> None: def _add_random_cpu(self) -> None:
"""Add a random CPU (server picks the profile).""" """Add a random CPU (server picks the profile)."""

View File

@@ -205,9 +205,8 @@ LobbyScreen {
#deck-preview { #deck-preview {
width: auto; width: auto;
height: auto; height: 3;
padding: 1 1 0 1; padding: 0 1;
text-align: center;
} }
.rule-row { .rule-row {