refactor(providers): paint every hero with the palette gradient
This commit is contained in:
1 parent
e6684ddbbd
commit
6aac4f4085
9 files changed
+28
-55
No files matched your search
@@ -107,9 +107,10 @@ class RadioProvider(ABC):
|
||||
|
||||
#: Rows of header art drawn at the top left (rich markup is allowed).
|
||||
hero: tuple[str, ...] = ()
|
||||
#: Paint :attr:`hero` with the app's spectrum gradient (a wordmark) instead
|
||||
#: of the colours the art carries itself (a picture).
|
||||
hero_gradient: bool = False
|
||||
#: Paint :attr:`hero` with the app's active palette gradient instead of the
|
||||
#: colours the art carries itself. On by default so every catalogue's
|
||||
#: wordmark follows the terminal's palette; a picture hero can turn it off.
|
||||
hero_gradient: bool = True
|
||||
#: Short lines printed beside the hero, top to bottom.
|
||||
hero_lines: tuple[str, ...] = ()
|
||||
|
||||
|
||||
@@ -29,8 +29,8 @@ class BassdriveProvider(RadioProvider):
|
||||
station_source = "bassdrive.com + icecast status"
|
||||
history_source = "icecast status (rolling)"
|
||||
|
||||
# The wordmark in the station's own two blues, in the shared half-block
|
||||
# font; the caption is the station's own description.
|
||||
# The wordmark in the shared half-block font, painted with the app's
|
||||
# palette gradient; the caption is the station's own description.
|
||||
hero = hero_art.HERO
|
||||
hero_lines = (
|
||||
"BASSDRIVE",
|
||||
|
||||
@@ -1,14 +1,11 @@
|
||||
"""Bassdrive's header hero: its wordmark in the station's two blues.
|
||||
"""Bassdrive's header hero: its wordmark in the shared half-block font.
|
||||
|
||||
The logo paints "BASS" in the pale blue and "DRIVE" in the deeper one; those
|
||||
map onto the stock palette's bright cyan and cyan, so the wordmark still reads
|
||||
as Bassdrive without hard-coded RGB.
|
||||
The header paints it with the app's active palette gradient, like every other
|
||||
catalogue's wordmark, so the logo follows the terminal's colours.
|
||||
"""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
from ..wordmark import render
|
||||
|
||||
HERO: tuple[str, ...] = render(
|
||||
"BASSDRIVE", ["bright_cyan"] * 4 + ["cyan"] * 5
|
||||
)
|
||||
HERO: tuple[str, ...] = render("BASSDRIVE")
|
||||
@@ -30,7 +30,6 @@ class SomaFMProvider(RadioProvider):
|
||||
history_source = "songs/<station>.json"
|
||||
|
||||
hero = hero_art.LOGO
|
||||
hero_gradient = True
|
||||
hero_lines = hero_art.TAGLINES
|
||||
|
||||
@property
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
"""SomaFM's header hero: the block-letter wordmark and the three lines beside it.
|
||||
|
||||
The glyphs come from the shared half-block font
|
||||
(:mod:`beefy_player.providers.wordmark`); the header paints them with the app's
|
||||
spectrum gradient (``hero_gradient``), so the logo takes on the terminal's own
|
||||
colours instead of carrying RGB values around.
|
||||
(:mod:`beefy_player.providers.wordmark`); the header paints every catalogue's
|
||||
wordmark with the app's active palette gradient, so the logo takes on the
|
||||
terminal's own colours instead of carrying RGB values around.
|
||||
"""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
@@ -29,8 +29,8 @@ class TilderadioProvider(RadioProvider):
|
||||
station_source = "tilderadio.org/api/now/"
|
||||
history_source = "now_playing + history[]"
|
||||
|
||||
# The station's own radio icon, converted to half blocks by
|
||||
# ``tools/ansi_art.py``; the caption is the tildeverse's motto.
|
||||
# The station's wordmark in the shared half-block font, painted with the
|
||||
# app's palette gradient; the caption is the tildeverse's motto.
|
||||
hero = hero_art.HERO
|
||||
hero_lines = (
|
||||
"TILDERADIO",
|
||||
|
||||
@@ -1,7 +1,8 @@
|
||||
"""tilderadio's header hero: its name in the shared half-block font.
|
||||
|
||||
No colours of its own — the wordmark inherits the terminal's default
|
||||
foreground, so it looks like part of the chrome rather than a logo pasted in.
|
||||
The shared half-block font; the header paints it with the app's active
|
||||
palette gradient, so the wordmark follows the terminal's colours like the rest
|
||||
of the chrome.
|
||||
"""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
@@ -2,9 +2,9 @@
|
||||
|
||||
Every shipped catalogue draws its brand the same way: four-column capitals,
|
||||
six pixels tall, emitted as three rows of half blocks (``▀``/``▄``/``█``) — the
|
||||
same shape as the original SomaFM logo. Catalogues differ only in the word
|
||||
they spell and the ANSI colours they paint it with, so the font lives here and
|
||||
each provider's ``hero.py`` is one line of data.
|
||||
same shape as the original SomaFM logo. The header paints every wordmark with
|
||||
the app's active palette gradient, so catalogues differ only in the word they
|
||||
spell; the font lives here and each provider's ``hero.py`` is one line of data.
|
||||
|
||||
Glyphs are 6x4 bitmaps (``M`` is 6x5). Two pixel rows become one text row, so
|
||||
a wordmark is always three rows tall and the header keeps a fixed height.
|
||||
@@ -12,8 +12,6 @@ a wordmark is always three rows tall and the header keeps a fixed height.
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
from collections.abc import Sequence
|
||||
|
||||
#: 6-row bitmap per capital; all rows of one glyph are the same width.
|
||||
GLYPHS: dict[str, tuple[str, ...]] = {
|
||||
"A": (".##.", "#..#", "####", "#..#", "#..#", "#..#"),
|
||||
@@ -65,36 +63,10 @@ def _glyph_rows(char: str) -> tuple[str, ...]:
|
||||
)
|
||||
|
||||
|
||||
def render(text: str, styles: Sequence[str | None] | None = None) -> tuple[str, ...]:
|
||||
"""Spell ``text`` as three rows of half blocks.
|
||||
|
||||
``styles`` gives one rich-markup style per character (``None`` leaves it in
|
||||
the terminal's default colour). Contiguous runs of the same style share a
|
||||
markup span, so ``render("BASSDRIVE", ...)`` paints the first four letters
|
||||
and the last five separately.
|
||||
"""
|
||||
if styles is not None and len(styles) != len(text):
|
||||
raise ValueError("styles must provide one entry per character")
|
||||
|
||||
def render(text: str) -> tuple[str, ...]:
|
||||
"""Spell ``text`` as three rows of half blocks."""
|
||||
glyphs = [_glyph_rows(char.upper()) for char in text]
|
||||
rows: list[str] = []
|
||||
for row in range(ROWS):
|
||||
cells: list[tuple[str | None, str]] = []
|
||||
for index, glyph in enumerate(glyphs):
|
||||
style = styles[index] if styles is not None else None
|
||||
if index:
|
||||
cells.append((style, _GAP))
|
||||
cells.extend((style, char) for char in glyph[row])
|
||||
while cells and cells[-1][1] == " ":
|
||||
cells.pop()
|
||||
|
||||
segments: list[tuple[str | None, str]] = []
|
||||
for style, char in cells:
|
||||
if segments and segments[-1][0] == style:
|
||||
segments[-1] = (style, segments[-1][1] + char)
|
||||
else:
|
||||
segments.append((style, char))
|
||||
rows.append(
|
||||
"".join(f"[{style}]{body}[/]" if style else body for style, body in segments)
|
||||
)
|
||||
rows.append(_GAP.join(glyph[row] for glyph in glyphs).rstrip())
|
||||
return tuple(rows)
|
||||
@@ -35,8 +35,11 @@ def _hero_line(
|
||||
line.no_wrap = True
|
||||
return line
|
||||
|
||||
# Iterate the *text* of the row, not its markup: a hero is allowed to
|
||||
# carry styles of its own, and those brackets are not cells to paint.
|
||||
plain = Text.from_markup(markup).plain
|
||||
line = Text(no_wrap=True)
|
||||
for column, char in enumerate(markup):
|
||||
for column, char in enumerate(plain):
|
||||
position = (column / max(1, width - 1) + index * 0.055) % 1.0
|
||||
line.append(char, style=f"bold {gradient(stops, position)}")
|
||||
return line
|
||||
|
||||
Reference in new issue
Block a user