diff --git a/beefy_player/providers/base.py b/beefy_player/providers/base.py index 7656fef..aecd863 100644 --- a/beefy_player/providers/base.py +++ b/beefy_player/providers/base.py @@ -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, ...] = () diff --git a/beefy_player/providers/bassdrive/__init__.py b/beefy_player/providers/bassdrive/__init__.py index 006a52d..a9307b8 100644 --- a/beefy_player/providers/bassdrive/__init__.py +++ b/beefy_player/providers/bassdrive/__init__.py @@ -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", diff --git a/beefy_player/providers/bassdrive/hero.py b/beefy_player/providers/bassdrive/hero.py index 715277c..54757d8 100644 --- a/beefy_player/providers/bassdrive/hero.py +++ b/beefy_player/providers/bassdrive/hero.py @@ -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") diff --git a/beefy_player/providers/somafm/__init__.py b/beefy_player/providers/somafm/__init__.py index 20c09f9..09843bd 100644 --- a/beefy_player/providers/somafm/__init__.py +++ b/beefy_player/providers/somafm/__init__.py @@ -30,7 +30,6 @@ class SomaFMProvider(RadioProvider): history_source = "songs/.json" hero = hero_art.LOGO - hero_gradient = True hero_lines = hero_art.TAGLINES @property diff --git a/beefy_player/providers/somafm/hero.py b/beefy_player/providers/somafm/hero.py index bd226c5..bcc4c2a 100644 --- a/beefy_player/providers/somafm/hero.py +++ b/beefy_player/providers/somafm/hero.py @@ -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 diff --git a/beefy_player/providers/tilderadio/__init__.py b/beefy_player/providers/tilderadio/__init__.py index 59f401b..366be44 100644 --- a/beefy_player/providers/tilderadio/__init__.py +++ b/beefy_player/providers/tilderadio/__init__.py @@ -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", diff --git a/beefy_player/providers/tilderadio/hero.py b/beefy_player/providers/tilderadio/hero.py index d852a49..2f3c78c 100644 --- a/beefy_player/providers/tilderadio/hero.py +++ b/beefy_player/providers/tilderadio/hero.py @@ -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 diff --git a/beefy_player/providers/wordmark.py b/beefy_player/providers/wordmark.py index 7e95a90..f6f7da2 100644 --- a/beefy_player/providers/wordmark.py +++ b/beefy_player/providers/wordmark.py @@ -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) diff --git a/beefy_player/widgets/header.py b/beefy_player/widgets/header.py index 08342b7..d70f3bc 100644 --- a/beefy_player/widgets/header.py +++ b/beefy_player/widgets/header.py @@ -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