feat(tui): implement SomaFM terminal radio player
This commit is contained in:
1 parent
941e45a20f
commit
237de2efe9
21 files changed
+4937
No files matched your search
@@ -0,0 +1,97 @@
|
||||
"""Header: logo, clock and a live ticker."""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
from typing import TYPE_CHECKING, cast
|
||||
|
||||
from rich.cells import cell_len
|
||||
from rich.console import Group, RenderableType
|
||||
from rich.text import Text
|
||||
from textual.widget import Widget
|
||||
|
||||
from .common import (
|
||||
SPECTRUM_STOPS,
|
||||
clock_time,
|
||||
date_label,
|
||||
gradient,
|
||||
marquee,
|
||||
state_glyph,
|
||||
)
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from ..app import SomaFMApp
|
||||
|
||||
LOGO = (
|
||||
"▄▀▀▀ ▄▀▀▄ █▄ ▄█ ▄▀▀▄",
|
||||
"▀▀▀▄ █ █ █ █ █ █▀▀█",
|
||||
"▄▄▄▀ ▀▄▄▀ █ █ █ █",
|
||||
)
|
||||
|
||||
MIDDLE = ("SOMA FM RADIO", "LISTENER SUPPORTED", "SAN FRANCISCO")
|
||||
|
||||
|
||||
class HeaderBar(Widget):
|
||||
can_focus = False
|
||||
|
||||
def render(self) -> RenderableType:
|
||||
app = cast("SomaFMApp", self.app)
|
||||
width = max(24, self.size.width)
|
||||
height = max(1, self.size.height)
|
||||
state = app.playing_state
|
||||
glyph, colour = state_glyph(state)
|
||||
|
||||
logo_width = max(len(row) for row in LOGO)
|
||||
middle_width = max(len(row) for row in MIDDLE)
|
||||
right = (clock_time(), date_label(), f"vol {app.volume}%")
|
||||
right_width = max(len(item) for item in right)
|
||||
|
||||
show_middle = width >= logo_width + middle_width + 8
|
||||
show_right = width >= logo_width + right_width + 8
|
||||
if show_middle and show_right:
|
||||
show_middle = width >= logo_width + 4 + middle_width + 2 + right_width
|
||||
|
||||
lines: list[Text] = []
|
||||
for index, row in enumerate(LOGO):
|
||||
line = Text(no_wrap=True)
|
||||
for column, char in enumerate(row):
|
||||
position = (
|
||||
column / max(1, logo_width - 1) + index * 0.055
|
||||
) % 1.0
|
||||
line.append(
|
||||
char, style=f"bold {gradient(SPECTRUM_STOPS, position)}"
|
||||
)
|
||||
if show_middle:
|
||||
line.append(" " * 4)
|
||||
line.append(MIDDLE[index], style="dim")
|
||||
if show_right:
|
||||
used = logo_width
|
||||
if show_middle:
|
||||
used += 4 + middle_width
|
||||
pad = max(1, width - used - right_width)
|
||||
line.append(" " * pad)
|
||||
style = "bold" if index == 0 else "dim"
|
||||
line.append(right[index], style=style)
|
||||
lines.append(line)
|
||||
|
||||
if height >= 4:
|
||||
ticker = Text(no_wrap=True)
|
||||
prefix = f"{glyph} {state.upper():<8} "
|
||||
ticker.append(prefix, style=f"bold {colour}")
|
||||
station = app.current
|
||||
if app.notice:
|
||||
text = app.notice
|
||||
text_style = "italic yellow"
|
||||
elif station is not None:
|
||||
song = station.now_playing
|
||||
if song is not None:
|
||||
text = f"{station.title} · {song.display_artist} — {song.display_title}"
|
||||
else:
|
||||
text = f"{station.title} · {station.description}"
|
||||
text_style = ""
|
||||
else:
|
||||
text = "Pick a station — press enter to tune in"
|
||||
text_style = "italic dim"
|
||||
ticker.append(marquee(text, max(4, width - cell_len(prefix))), style=text_style)
|
||||
lines.append(ticker)
|
||||
|
||||
return Group(*lines[:height])
|
||||
Reference in new issue
Block a user