feat(ui): scrollable help menu, Esc-to-close, + blush/matrix/wraith themes

The help overlay now scrolls (↑/↓, PgUp/PgDn, Home/End) with a position
indicator and only Esc dismisses it, so stray keystrokes can't close a menu
that overflows the screen. Adds three bundled vestments (blush, matrix,
wraith); they're auto-discovered via Theme::available(), so they appear in
the menu and /theme list with no hardcoded entries.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
leetcrypt
2026-06-01 22:09:08 -07:00
parent 5e8a409ec2
commit b4c5f9a9fa
5 changed files with 122 additions and 15 deletions
+25 -1
View File
@@ -128,6 +128,8 @@ pub struct App {
pub sbx_scroll: usize,
/// Whether the help overlay is showing.
pub show_help: bool,
/// Vertical scroll offset (rows) into the help overlay when it doesn't fit.
pub help_scroll: u16,
/// A reconnect handshake is in flight (Ctrl-R after a disconnect).
pub reconnecting: bool,
/// Transient error shown as a popup over the clergy (cleared on next keypress).
@@ -159,6 +161,7 @@ impl App {
chat_scroll: 0,
sbx_scroll: 0,
show_help: false,
help_scroll: 0,
reconnecting: false,
error: None,
password: String::new(),
@@ -610,9 +613,29 @@ pub async fn run(params: net::ConnParams, mut session: Session, mut theme: Theme
});
}
} else if app.show_help {
app.show_help = false; // any key dismisses the overlay
// While the help overlay is up, arrows / paging scroll it
// (when it's taller than the screen); only Esc closes, so
// stray keystrokes can't dismiss a menu you're still reading.
let max = term
.size()
.map(|s| ui::help_max_scroll(s.width, s.height, &theme))
.unwrap_or(0);
match k.code {
KeyCode::Up => app.help_scroll = app.help_scroll.saturating_sub(1),
KeyCode::Down => app.help_scroll = (app.help_scroll + 1).min(max),
KeyCode::PageUp => app.help_scroll = app.help_scroll.saturating_sub(10),
KeyCode::PageDown => app.help_scroll = (app.help_scroll + 10).min(max),
KeyCode::Home => app.help_scroll = 0,
KeyCode::End => app.help_scroll = max,
KeyCode::Esc => {
app.show_help = false; // Esc dismisses the overlay
app.help_scroll = 0;
}
_ => {} // ignore other keys so the menu stays put
}
} else if k.code == KeyCode::F(1) {
app.show_help = true; // F1 from any mode
app.help_scroll = 0;
} else if k.code == KeyCode::F(2) {
if app.sandbox.is_none() {
} else if app.can_drive() {
@@ -876,6 +899,7 @@ fn handle_command(
let room = &session.room;
if line == "/help" || line == "/?" {
app.show_help = true;
app.help_scroll = 0;
} else if line == "/pw" || line == "/password" {
// Show the room password locally (never broadcast). Handy when the
// server's password was autogenerated and you need to read it off / share