"""Full-screen keyboard help.""" from __future__ import annotations import sys import textwrap from typing import TYPE_CHECKING, cast from rich.console import Group from rich.table import Table from rich.text import Text from textual.app import ComposeResult from textual.screen import ModalScreen from textual.widgets import Static from ..providers import PROVIDERS, available_providers from .common import SPECTRUM_STOPS, gradient if TYPE_CHECKING: from ..app import BeefyPlayerApp HELP_ROWS: tuple[tuple[str, str], ...] = ( ("↑ / ↓ j / k", "move through the station list"), ("pgup / pgdn", "page through the station list"), ("enter", "tune in to the selected station"), ("space", "play / pause the current station"), ("s", "stop playback"), ("] / [", "next / previous station (tunes immediately)"), ("+ / -", "volume up / down"), ("m", "mute / unmute"), ("f", "favourite — favourites float to the top"), ("/", "filter stations by title, genre or description"), ("esc", "clear the filter / close overlays"), ( "v", "cycle visualiser: mirror → bars → wave → waterfall → radial → braille", ), ("r", "refresh stations, schedule and song history"), ("p", "switch station catalogue (SomaFM / tilderadio)"), ("g / G", "jump to the top / bottom of the list"), ("? ", "this help"), ("q", "quit"), ) ABOUT_LINUX = ( ( "The spectrum captures your actual PulseAudio/PipeWire output with " "parec (SYNC); otherwise it falls back to a muted ffmpeg stream tap " "(FFT, can drift) or a synthesized display (SIM)." ), ( "The desktop picks us up over MPRIS as a " "beefyplayer media player — try `playerctl -l` / `playerctl metadata`." ), ) ABOUT_MACOS = ( ( "The spectrum captures your Mac's real audio output (SYNC) with a " "native Core Audio process tap — nothing to install. macOS asks for " "System Audio Recording permission the first time; without it the " "analyser falls back to a muted ffmpeg stream tap (FFT, can drift) " "or a synthesized display (SIM)." ), ( "mpv publishes what is playing to the macOS Now Playing session, so " "Control Center and the keyboard media keys control this app." ), ) class HelpScreen(ModalScreen[None]): def compose(self) -> ComposeResult: app = cast("BeefyPlayerApp", self.app) provider = app.provider about = ( f"Stations and song history come from {provider.name} " f"({provider.station_source} + {provider.history_source}).", ) + (ABOUT_MACOS if sys.platform == "darwin" else ABOUT_LINUX) catalogues = Table.grid(padding=(0, 2), expand=False) catalogues.add_column(style="bold cyan", no_wrap=True, justify="right") catalogues.add_column(style="bold", no_wrap=True) catalogues.add_column(style="dim") for slug in available_providers(): catalogue = PROVIDERS[slug] marker = "●" if slug == provider.id else " " pane = "schedule pane" if catalogue.has_schedule else "station list" catalogues.add_row( marker, slug, f"{catalogue.name} — {pane}" ) pane_note = Text() if provider.has_schedule: pane_note.append( f"{provider.name} is a single stream, so the left pane is its " "broadcast schedule; the station filter and favourites only " "apply to multi-station catalogues.", style="dim italic", ) catalogue_title = Text() catalogue_title.append("CATALOGUES", style="bold cyan") catalogue_title.append( " switch with p or --provider at start", style="dim" ) table = Table.grid(padding=(0, 2), expand=False) table.add_column(style="bold cyan", no_wrap=True, justify="right") table.add_column(style="") for key, description in HELP_ROWS: table.add_row(key, description) header = Text() header.append("◤ ", style=f"bold {gradient(SPECTRUM_STOPS, 0.9)}") header.append("BEEFY PLAYER", style=f"bold {gradient(SPECTRUM_STOPS, 0.55)}") header.append(" keyboard & credits", style="dim") footer = Text() for paragraph in about: for line in textwrap.wrap(paragraph, width=70): footer.append(line + "\n", style="dim") body = Group( header, Text(""), table, Text(""), pane_note, catalogue_title, catalogues, Text(""), footer, ) yield Static(body, id="help") def on_key(self, event) -> None: event.stop() self.dismiss(None)