"""Station list with favourites, live markers and a filter.""" 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 compact_count, truncate if TYPE_CHECKING: from ..app import BeefyPlayerApp class StationList(Widget): can_focus = False def __init__(self) -> None: super().__init__() self.border_title = "STATIONS" def set_filter(self, query: str) -> None: safe_query = query.replace("[", "(").replace("]", ")") if query else "" title = f"STATIONS · /{safe_query}" if safe_query else "STATIONS" if self.border_title != title: self.border_title = title def on_mouse_scroll_down(self, event) -> None: cast("BeefyPlayerApp", self.app).action_cursor_down() def on_mouse_scroll_up(self, event) -> None: cast("BeefyPlayerApp", self.app).action_cursor_up() def render(self) -> RenderableType: app = cast("BeefyPlayerApp", self.app) width = max(12, self.size.width) height = max(1, self.size.height) view = app.view if not view: message = "no stations match" if app.query else "loading stations…" return Group(Text(truncate(message, width), style="dim italic")) first = 0 if len(view) > height: first = max(0, min(app.index - height // 2, len(view) - height)) lines: list[Text] = [] for offset in range(min(height, len(view))): index = first + offset station = view[index] selected = index == app.index current = app.current is not None and app.current.id == station.id playing = current and app.playing_state not in ("stopped", "error") marker = "▌" if selected else " " star = "★" if station.favorite else " " play = ( ("‖" if app.playing_state == "paused" else "▶") if current else " " ) count_text = "" count_width = 0 if width >= 26 and station.listeners > 0: count_text = f"{compact_count(station.listeners)} ♪" count_width = cell_len(count_text) + 1 prefix = marker + star + play + " " available = width - cell_len(prefix) - count_width title = truncate(station.title, max(4, available)) gap = max(0, width - cell_len(prefix) - cell_len(title) - count_width) if selected: base = "" marker_style = "magenta" title_style = "bold underline" count_style = "bold" elif current: base = "" marker_style = "cyan" title_style = "cyan" count_style = "dim" else: base = "" marker_style = "" title_style = "" count_style = "dim" row = Text(no_wrap=True, style=base) row.append(marker, style=marker_style) row.append(star, style="bold yellow" if station.favorite else "") row.append(play, style="cyan" if playing else "") row.append(" ") row.append(title, style=title_style) row.append(" " * gap) if count_text: row.append(count_text, style=count_style) used = cell_len(prefix) + cell_len(title) + gap + cell_len(count_text) if used < width: row.append(" " * (width - used)) lines.append(row) return Group(*lines)