# icons.py -- QPainter-drawn icons for Radio Panel # # Each function takes (color: str) and returns a QIcon painted at 24x24. # All icons are simple geometric shapes -- clean, broadcast-console look. from PySide6.QtGui import QPainter, QColor, QPen, QBrush, QPixmap, QIcon, QPainterPath from PySide6.QtCore import Qt, QRectF, QPointF _S = 24 def _make_icon(paint_fn, color: str) -> QIcon: pix = QPixmap(_S, _S) pix.fill(Qt.transparent) p = QPainter(pix) p.setRenderHint(QPainter.Antialiasing) paint_fn(p, QRectF(2, 2, _S - 4, _S - 4), QColor(color)) p.end() return QIcon(pix) # ── Transport Icons ──────────────────────────────────────────────── def icon_play(color: str = "#00ff41") -> QIcon: def paint(p, r, c): p.setPen(Qt.NoPen) p.setBrush(c) path = QPainterPath() path.moveTo(r.left(), r.top()) path.lineTo(r.right(), r.center().y()) path.lineTo(r.left(), r.bottom()) path.closeSubpath() p.drawPath(path) return _make_icon(paint, color) def icon_pause(color: str = "#ffffff") -> QIcon: def paint(p, r, c): p.setPen(Qt.NoPen) p.setBrush(c) w = r.width() * 0.32 p.drawRect(QRectF(r.left(), r.top(), w, r.height())) p.drawRect(QRectF(r.right() - w, r.top(), w, r.height())) return _make_icon(paint, color) def icon_stop(color: str = "#ff4444") -> QIcon: def paint(p, r, c): p.setPen(Qt.NoPen) p.setBrush(c) margin = r.width() * 0.18 p.drawRect(r.adjusted(margin, margin, -margin, -margin)) return _make_icon(paint, color) def icon_skip_start(color: str = "#ffffff") -> QIcon: def paint(p, r, c): p.setPen(Qt.NoPen) p.setBrush(c) w = r.width() * 0.35 # Two triangles pointing left path = QPainterPath() path.moveTo(r.right(), r.top()) path.lineTo(r.left(), r.center().y()) path.lineTo(r.right(), r.bottom()) path.closeSubpath() p.drawPath(path) path2 = QPainterPath() path2.moveTo(r.center().x(), r.top()) path2.lineTo(r.left(), r.center().y()) path2.lineTo(r.center().x(), r.bottom()) path2.closeSubpath() p.drawPath(path2) return _make_icon(paint, color) def icon_skip_end(color: str = "#ffffff") -> QIcon: def paint(p, r, c): p.setPen(Qt.NoPen) p.setBrush(c) path = QPainterPath() path.moveTo(r.left(), r.top()) path.lineTo(r.right(), r.center().y()) path.lineTo(r.left(), r.bottom()) path.closeSubpath() p.drawPath(path) path2 = QPainterPath() path2.moveTo(r.center().x(), r.top()) path2.lineTo(r.right(), r.center().y()) path2.lineTo(r.center().x(), r.bottom()) path2.closeSubpath() p.drawPath(path2) return _make_icon(paint, color) def icon_skip_back(color: str = "#aaaaaa") -> QIcon: def paint(p, r, c): p.setPen(Qt.NoPen) p.setBrush(c) path = QPainterPath() path.moveTo(r.right() * 0.7, r.top()) path.lineTo(r.left(), r.center().y()) path.lineTo(r.right() * 0.7, r.bottom()) path.closeSubpath() p.drawPath(path) # Vertical bar bar_w = r.width() * 0.12 p.drawRect(QRectF(r.right() - bar_w, r.top(), bar_w, r.height())) return _make_icon(paint, color) def icon_skip_forward(color: str = "#aaaaaa") -> QIcon: def paint(p, r, c): p.setPen(Qt.NoPen) p.setBrush(c) path = QPainterPath() path.moveTo(r.left() + r.width() * 0.3, r.top()) path.lineTo(r.right(), r.center().y()) path.lineTo(r.left() + r.width() * 0.3, r.bottom()) path.closeSubpath() p.drawPath(path) bar_w = r.width() * 0.12 p.drawRect(QRectF(r.left(), r.top(), bar_w, r.height())) return _make_icon(paint, color) def icon_eject(color: str = "#ff4444") -> QIcon: def paint(p, r, c): pen = QPen(c, r.width() * 0.12) pen.setCapStyle(Qt.RoundCap) p.setPen(pen) p.setBrush(Qt.NoBrush) # Upward triangle mid_x = r.center().x() path = QPainterPath() path.moveTo(r.left() + r.width() * 0.2, r.center().y() + r.height() * 0.1) path.lineTo(mid_x, r.top() + r.height() * 0.2) path.lineTo(r.right() - r.width() * 0.2, r.center().y() + r.height() * 0.1) p.drawPath(path) # Horizontal line p.drawLine(QPointF(r.left() + r.width() * 0.2, r.bottom() - r.height() * 0.1), QPointF(r.right() - r.width() * 0.2, r.bottom() - r.height() * 0.1)) return _make_icon(paint, color) # ── Action Icons ─────────────────────────────────────────────────── def icon_plus(color: str = "#00ff41") -> QIcon: def paint(p, r, c): pen = QPen(c, r.width() * 0.12) pen.setCapStyle(Qt.RoundCap) p.setPen(pen) p.setBrush(Qt.NoBrush) mid = r.center() p.drawLine(QPointF(mid.x(), r.top()), QPointF(mid.x(), r.bottom())) p.drawLine(QPointF(r.left(), mid.y()), QPointF(r.right(), mid.y())) return _make_icon(paint, color) def icon_delete(color: str = "#ff4444") -> QIcon: def paint(p, r, c): pen = QPen(c, r.width() * 0.12) pen.setCapStyle(Qt.RoundCap) p.setPen(pen) p.setBrush(Qt.NoBrush) p.drawLine(QPointF(r.left(), r.top()), QPointF(r.right(), r.bottom())) p.drawLine(QPointF(r.right(), r.top()), QPointF(r.left(), r.bottom())) return _make_icon(paint, color) def icon_save(color: str = "#aaaaaa") -> QIcon: def paint(p, r, c): pen = QPen(c, r.width() * 0.12) pen.setCapStyle(Qt.RoundCap) p.setPen(pen) p.setBrush(Qt.NoBrush) # Arrow down into tray mid_x = r.center().x() p.drawLine(QPointF(mid_x, r.top()), QPointF(mid_x, r.bottom() - r.height() * 0.15)) # Arrow head p.drawLine(QPointF(mid_x - r.width() * 0.25, r.bottom() - r.height() * 0.4), QPointF(mid_x, r.bottom() - r.height() * 0.15)) p.drawLine(QPointF(mid_x + r.width() * 0.25, r.bottom() - r.height() * 0.4), QPointF(mid_x, r.bottom() - r.height() * 0.15)) # Tray bottom line p.drawLine(QPointF(r.left(), r.bottom()), QPointF(r.right(), r.bottom())) return _make_icon(paint, color) def icon_load(color: str = "#aaaaaa") -> QIcon: def paint(p, r, c): pen = QPen(c, r.width() * 0.12) pen.setCapStyle(Qt.RoundCap) p.setPen(pen) p.setBrush(Qt.NoBrush) # Arrow up from tray mid_x = r.center().x() p.drawLine(QPointF(mid_x, r.bottom()), QPointF(mid_x, r.top() + r.height() * 0.15)) p.drawLine(QPointF(mid_x - r.width() * 0.25, r.top() + r.height() * 0.35), QPointF(mid_x, r.top() + r.height() * 0.1)) p.drawLine(QPointF(mid_x + r.width() * 0.25, r.top() + r.height() * 0.35), QPointF(mid_x, r.top() + r.height() * 0.1)) p.drawLine(QPointF(r.left(), r.bottom()), QPointF(r.right(), r.bottom())) return _make_icon(paint, color) def icon_export(color: str = "#aaaaaa") -> QIcon: def paint(p, r, c): pen = QPen(c, r.width() * 0.12) pen.setCapStyle(Qt.RoundCap) p.setPen(pen) p.setBrush(Qt.NoBrush) # Box margin = r.width() * 0.15 p.drawRect(r.adjusted(margin, margin, -margin, -margin)) # Arrow out of box (top-right) p.drawLine(QPointF(r.right() - margin, r.top() + margin), QPointF(r.right(), r.top())) p.drawLine(QPointF(r.right() - r.width() * 0.3, r.top()), QPointF(r.right(), r.top())) p.drawLine(QPointF(r.right(), r.top() + r.height() * 0.3), QPointF(r.right(), r.top())) return _make_icon(paint, color) def icon_mute(color: str = "#ff4444") -> QIcon: def paint(p, r, c): pen = QPen(c, r.width() * 0.12) pen.setCapStyle(Qt.RoundCap) p.setPen(pen) p.setBrush(Qt.NoBrush) # Speaker body cx = r.left() + r.width() * 0.3 p.drawLine(QPointF(cx, r.top() + r.height() * 0.25), QPointF(cx, r.bottom() - r.height() * 0.25)) p.drawLine(QPointF(cx, r.top() + r.height() * 0.25), QPointF(r.left() + r.width() * 0.5, r.center().y() - r.height() * 0.15)) p.drawLine(QPointF(cx, r.bottom() - r.height() * 0.25), QPointF(r.left() + r.width() * 0.5, r.center().y() + r.height() * 0.15)) p.drawLine(QPointF(r.left() + r.width() * 0.2, r.center().y() - r.height() * 0.15), QPointF(r.left() + r.width() * 0.5, r.center().y() - r.height() * 0.15)) p.drawLine(QPointF(r.left() + r.width() * 0.2, r.center().y() + r.height() * 0.15), QPointF(r.left() + r.width() * 0.5, r.center().y() + r.height() * 0.15)) # X over speaker x = r.right() - r.width() * 0.2 y1 = r.top() + r.height() * 0.2 y2 = r.bottom() - r.height() * 0.2 p.drawLine(QPointF(x, y1), QPointF(r.right() - r.width() * 0.05, y2)) p.drawLine(QPointF(r.right() - r.width() * 0.05, y1), QPointF(x, y2)) return _make_icon(paint, color) def icon_solo(color: str = "#ffaa00") -> QIcon: def paint(p, r, c): p.setPen(Qt.NoPen) p.setBrush(c) # Circle with "S" center = r.center() radius = min(r.width(), r.height()) * 0.45 p.drawEllipse(center, radius, radius) # S letter (white) pen = QPen(QColor("#000000"), r.width() * 0.12) pen.setCapStyle(Qt.RoundCap) p.setPen(pen) p.setBrush(Qt.NoBrush) path = QPainterPath() path.moveTo(r.left() + r.width() * 0.45, r.top() + r.height() * 0.3) path.cubicTo( r.left() + r.width() * 0.7, r.top() + r.height() * 0.15, r.right() - r.width() * 0.25, r.top() + r.height() * 0.35, r.left() + r.width() * 0.55, r.top() + r.height() * 0.5 ) path.cubicTo( r.left() + r.width() * 0.3, r.top() + r.height() * 0.65, r.right() - r.width() * 0.15, r.top() + r.height() * 0.7, r.left() + r.width() * 0.45, r.bottom() - r.height() * 0.2 ) p.drawPath(path) return _make_icon(paint, color) def icon_mic(color: str = "#00ff41") -> QIcon: def paint(p, r, c): p.setPen(Qt.NoPen) p.setBrush(c) # Mic body - pill/rectangle shape bw = r.width() * 0.35 bh = r.height() * 0.45 bx = r.center().x() - bw / 2 by = r.top() + r.height() * 0.05 p.drawRoundedRect(QRectF(bx, by, bw, bh), bw * 0.3, bw * 0.3) # Mic arc at top path = QPainterPath() arc_w = bw * 0.6 arc_h = r.height() * 0.08 path.moveTo(r.center().x() - arc_w / 2, by) path.quadTo(r.center().x(), by - arc_h, r.center().x() + arc_w / 2, by) p.drawPath(path) # Stand lines down from body pen = QPen(c, r.width() * 0.06) pen.setCapStyle(Qt.RoundCap) p.setPen(pen) p.setBrush(Qt.NoBrush) stand_top = by + bh p.drawLine(QPointF(r.center().x(), stand_top), QPointF(r.center().x(), stand_top + r.height() * 0.15)) # Base base_w = r.width() * 0.45 base_h = r.height() * 0.1 p.drawRoundedRect(QRectF(r.center().x() - base_w / 2, stand_top + r.height() * 0.12, base_w, base_h), base_w * 0.15, base_w * 0.15) return _make_icon(paint, color) def icon_cog(color: str = "#aaaaaa") -> QIcon: def paint(p, r, c): pen = QPen(c, r.width() * 0.1) pen.setCapStyle(Qt.RoundCap) p.setPen(pen) p.setBrush(Qt.NoBrush) # Outer circle (gear ring) cx, cy = r.center().x(), r.center().y() outer_r = min(r.width(), r.height()) * 0.42 inner_r = outer_r * 0.6 # Draw gear teeth (6 bumps) import math p.setBrush(QColor(c)) for i in range(6): angle = i * 60 - 30 a_rad = math.radians(angle) bx = cx + outer_r * math.cos(a_rad) by = cy + outer_r * math.sin(a_rad) tooth = QPainterPath() tw = r.width() * 0.12 th = r.width() * 0.18 tooth.addRect(bx - tw/2, by - th/2, tw, th) p.drawRect(bx - tw/2, by - th/2, tw, th) # Center circle p.setPen(Qt.NoPen) p.setBrush(QColor(c)) p.drawEllipse(QPointF(cx, cy), inner_r, inner_r) # Inner hole p.setBrush(QColor("#000000")) p.drawEllipse(QPointF(cx, cy), inner_r * 0.55, inner_r * 0.55) return _make_icon(paint, color) def icon_theme(color: str = "#00ff41") -> QIcon: def paint(p, r, c): p.setPen(Qt.NoPen) # Draw 4 coloured quadrants mid = r.center() colors = [QColor("#00ff41"), QColor("#ffaa00"), QColor("#00aaff"), QColor("#ff4444")] for i, col in enumerate(colors): path = QPainterPath() if i == 0: path.moveTo(mid) path.arcTo(r, 0, 90) path.closeSubpath() elif i == 1: path.moveTo(mid) path.arcTo(r, 270, 90) path.closeSubpath() elif i == 2: path.moveTo(mid) path.arcTo(r, 180, 90) path.closeSubpath() else: path.moveTo(mid) path.arcTo(r, 90, 90) path.closeSubpath() p.setBrush(col) p.drawPath(path) # White circle in centre p.setBrush(QColor("#ffffff")) p.drawEllipse(mid, r.width() * 0.15, r.height() * 0.15) return _make_icon(paint, color)