139 lines
4.7 KiB
Python
139 lines
4.7 KiB
Python
"""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"),
|
|
("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)
|