2026-04-29 16:21:44 +10:00
|
|
|
# top_bar_widget.py
|
|
|
|
|
|
|
|
|
|
from PySide6.QtWidgets import QFrame, QHBoxLayout, QLabel, QPushButton
|
|
|
|
|
from PySide6.QtCore import Qt, QTimer, Signal
|
|
|
|
|
from PySide6.QtGui import QFont
|
|
|
|
|
from datetime import datetime
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
THEMES = [
|
2026-05-08 17:19:11 +10:00
|
|
|
# Dark themes
|
2026-04-29 16:21:44 +10:00
|
|
|
{
|
|
|
|
|
'name': 'GREEN',
|
|
|
|
|
'accent': '#00ff41',
|
|
|
|
|
'accent_dim': '#00aa2a',
|
|
|
|
|
'bg': '#0d0d0d',
|
|
|
|
|
'bg2': '#111111',
|
|
|
|
|
'border': '#1a3a1a',
|
|
|
|
|
'text': '#cccccc',
|
2026-05-08 17:19:11 +10:00
|
|
|
'is_light': False,
|
2026-04-29 16:21:44 +10:00
|
|
|
},
|
|
|
|
|
{
|
|
|
|
|
'name': 'AMBER',
|
|
|
|
|
'accent': '#ffaa00',
|
|
|
|
|
'accent_dim': '#cc8800',
|
|
|
|
|
'bg': '#0d0d00',
|
|
|
|
|
'bg2': '#111100',
|
|
|
|
|
'border': '#3a3000',
|
|
|
|
|
'text': '#ddddcc',
|
2026-05-08 17:19:11 +10:00
|
|
|
'is_light': False,
|
2026-04-29 16:21:44 +10:00
|
|
|
},
|
|
|
|
|
{
|
|
|
|
|
'name': 'BLUE',
|
|
|
|
|
'accent': '#00aaff',
|
|
|
|
|
'accent_dim': '#0077cc',
|
|
|
|
|
'bg': '#00080d',
|
|
|
|
|
'bg2': '#001122',
|
|
|
|
|
'border': '#001a3a',
|
|
|
|
|
'text': '#ccd8dd',
|
2026-05-08 17:19:11 +10:00
|
|
|
'is_light': False,
|
2026-04-29 16:21:44 +10:00
|
|
|
},
|
|
|
|
|
{
|
|
|
|
|
'name': 'RED',
|
|
|
|
|
'accent': '#ff4444',
|
|
|
|
|
'accent_dim': '#cc2222',
|
|
|
|
|
'bg': '#0d0000',
|
|
|
|
|
'bg2': '#110000',
|
|
|
|
|
'border': '#3a0000',
|
|
|
|
|
'text': '#ddcccc',
|
2026-05-08 17:19:11 +10:00
|
|
|
'is_light': False,
|
2026-04-29 16:21:44 +10:00
|
|
|
},
|
2026-04-30 00:30:23 +10:00
|
|
|
{
|
|
|
|
|
'name': 'PURPLE',
|
|
|
|
|
'accent': '#cc00ff',
|
|
|
|
|
'accent_dim': '#9900cc',
|
|
|
|
|
'bg': '#0d000d',
|
|
|
|
|
'bg2': '#1a001a',
|
|
|
|
|
'border': '#3a003a',
|
|
|
|
|
'text': '#ddccdd',
|
2026-05-08 17:19:11 +10:00
|
|
|
'is_light': False,
|
2026-04-30 00:30:23 +10:00
|
|
|
},
|
|
|
|
|
{
|
|
|
|
|
'name': 'CYAN',
|
|
|
|
|
'accent': '#00ffff',
|
|
|
|
|
'accent_dim': '#00cccc',
|
|
|
|
|
'bg': '#000d0d',
|
|
|
|
|
'bg2': '#001a1a',
|
|
|
|
|
'border': '#003a3a',
|
|
|
|
|
'text': '#ccdddd',
|
2026-05-08 17:19:11 +10:00
|
|
|
'is_light': False,
|
2026-04-30 00:30:23 +10:00
|
|
|
},
|
|
|
|
|
{
|
|
|
|
|
'name': 'ORANGE',
|
|
|
|
|
'accent': '#ff8800',
|
|
|
|
|
'accent_dim': '#cc6600',
|
|
|
|
|
'bg': '#0d0700',
|
|
|
|
|
'bg2': '#1a1100',
|
|
|
|
|
'border': '#3a2200',
|
|
|
|
|
'text': '#ddccbb',
|
2026-05-08 17:19:11 +10:00
|
|
|
'is_light': False,
|
2026-04-30 00:30:23 +10:00
|
|
|
},
|
|
|
|
|
{
|
|
|
|
|
'name': 'PINK',
|
|
|
|
|
'accent': '#ff1493',
|
|
|
|
|
'accent_dim': '#cc0066',
|
|
|
|
|
'bg': '#0d0005',
|
|
|
|
|
'bg2': '#1a000a',
|
|
|
|
|
'border': '#3a0015',
|
|
|
|
|
'text': '#ddccdd',
|
2026-05-08 17:19:11 +10:00
|
|
|
'is_light': False,
|
2026-04-30 00:30:23 +10:00
|
|
|
},
|
|
|
|
|
{
|
|
|
|
|
'name': 'LIME',
|
|
|
|
|
'accent': '#ccff00',
|
|
|
|
|
'accent_dim': '#99cc00',
|
|
|
|
|
'bg': '#0a0d00',
|
|
|
|
|
'bg2': '#111a00',
|
|
|
|
|
'border': '#223a00',
|
|
|
|
|
'text': '#ddddcc',
|
2026-05-08 17:19:11 +10:00
|
|
|
'is_light': False,
|
2026-04-30 00:30:23 +10:00
|
|
|
},
|
|
|
|
|
{
|
|
|
|
|
'name': 'GOLD',
|
|
|
|
|
'accent': '#ffd700',
|
|
|
|
|
'accent_dim': '#ccaa00',
|
|
|
|
|
'bg': '#0d0d05',
|
|
|
|
|
'bg2': '#1a1a0a',
|
|
|
|
|
'border': '#3a3a15',
|
|
|
|
|
'text': '#ddddbb',
|
2026-05-08 17:19:11 +10:00
|
|
|
'is_light': False,
|
2026-04-30 00:30:23 +10:00
|
|
|
},
|
|
|
|
|
{
|
|
|
|
|
'name': 'VIOLET',
|
|
|
|
|
'accent': '#8a2be2',
|
|
|
|
|
'accent_dim': '#6600cc',
|
|
|
|
|
'bg': '#05000d',
|
|
|
|
|
'bg2': '#0a0015',
|
|
|
|
|
'border': '#15002a',
|
|
|
|
|
'text': '#ccbbdd',
|
2026-05-08 17:19:11 +10:00
|
|
|
'is_light': False,
|
2026-04-30 00:30:23 +10:00
|
|
|
},
|
|
|
|
|
{
|
|
|
|
|
'name': 'TURQUOISE',
|
|
|
|
|
'accent': '#40e0d0',
|
|
|
|
|
'accent_dim': '#20b0a0',
|
|
|
|
|
'bg': '#000d0a',
|
|
|
|
|
'bg2': '#001a15',
|
|
|
|
|
'border': '#00382d',
|
|
|
|
|
'text': '#ccddda',
|
2026-05-08 17:19:11 +10:00
|
|
|
'is_light': False,
|
2026-04-30 00:30:23 +10:00
|
|
|
},
|
|
|
|
|
{
|
|
|
|
|
'name': 'CRIMSON',
|
|
|
|
|
'accent': '#dc143c',
|
|
|
|
|
'accent_dim': '#aa0022',
|
|
|
|
|
'bg': '#0d0002',
|
|
|
|
|
'bg2': '#1a0005',
|
|
|
|
|
'border': '#3a000a',
|
|
|
|
|
'text': '#ddcccc',
|
2026-05-08 17:19:11 +10:00
|
|
|
'is_light': False,
|
2026-04-30 00:30:23 +10:00
|
|
|
},
|
|
|
|
|
{
|
|
|
|
|
'name': 'CHARTREUSE',
|
|
|
|
|
'accent': '#7fff00',
|
|
|
|
|
'accent_dim': '#5fcc00',
|
|
|
|
|
'bg': '#050d00',
|
|
|
|
|
'bg2': '#0a1a00',
|
|
|
|
|
'border': '#153a00',
|
|
|
|
|
'text': '#ddddcc',
|
2026-05-08 17:19:11 +10:00
|
|
|
'is_light': False,
|
|
|
|
|
},
|
|
|
|
|
# Light themes
|
|
|
|
|
{
|
|
|
|
|
'name': 'LIGHT BLUE',
|
|
|
|
|
'accent': '#0066cc',
|
|
|
|
|
'accent_dim': '#0044aa',
|
|
|
|
|
'bg': '#f5f5f5',
|
|
|
|
|
'bg2': '#e8e8e8',
|
|
|
|
|
'border': '#bbbbbb',
|
|
|
|
|
'text': '#222222',
|
|
|
|
|
'is_light': True,
|
|
|
|
|
},
|
|
|
|
|
{
|
|
|
|
|
'name': 'LIGHT GREEN',
|
|
|
|
|
'accent': '#00aa44',
|
|
|
|
|
'accent_dim': '#008833',
|
|
|
|
|
'bg': '#f0f5f0',
|
|
|
|
|
'bg2': '#e0e8e0',
|
|
|
|
|
'border': '#aaccaa',
|
|
|
|
|
'text': '#112211',
|
|
|
|
|
'is_light': True,
|
|
|
|
|
},
|
|
|
|
|
{
|
|
|
|
|
'name': 'LIGHT AMBER',
|
|
|
|
|
'accent': '#cc8800',
|
|
|
|
|
'accent_dim': '#aa6600',
|
|
|
|
|
'bg': '#f5f3f0',
|
|
|
|
|
'bg2': '#e8e5e0',
|
|
|
|
|
'border': '#ccbb99',
|
|
|
|
|
'text': '#221100',
|
|
|
|
|
'is_light': True,
|
|
|
|
|
},
|
|
|
|
|
{
|
|
|
|
|
'name': 'LIGHT PURPLE',
|
|
|
|
|
'accent': '#8844cc',
|
|
|
|
|
'accent_dim': '#6622aa',
|
|
|
|
|
'bg': '#f3f0f5',
|
|
|
|
|
'bg2': '#e5e0e8',
|
|
|
|
|
'border': '#ccbbdd',
|
|
|
|
|
'text': '#221122',
|
|
|
|
|
'is_light': True,
|
2026-04-30 00:30:23 +10:00
|
|
|
},
|
2026-04-29 16:21:44 +10:00
|
|
|
]
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
class TopBarWidget(QFrame):
|
|
|
|
|
theme_changed = Signal(dict)
|
|
|
|
|
|
|
|
|
|
def __init__(self, parent=None):
|
|
|
|
|
super().__init__(parent)
|
|
|
|
|
|
|
|
|
|
self._theme_index = 0
|
|
|
|
|
|
|
|
|
|
self.setFrameStyle(QFrame.Box | QFrame.Raised)
|
|
|
|
|
self.setFixedHeight(80)
|
|
|
|
|
self._apply_bar_style()
|
|
|
|
|
|
|
|
|
|
self._build_ui()
|
|
|
|
|
|
|
|
|
|
self.timer = QTimer(self)
|
|
|
|
|
self.timer.setInterval(1000)
|
|
|
|
|
self.timer.timeout.connect(self._update)
|
|
|
|
|
self.timer.start()
|
|
|
|
|
|
|
|
|
|
self._update()
|
|
|
|
|
|
|
|
|
|
def _apply_bar_style(self):
|
|
|
|
|
theme = THEMES[self._theme_index]
|
|
|
|
|
self.setStyleSheet(f"""
|
|
|
|
|
QFrame {{
|
|
|
|
|
background-color: {theme['bg2']};
|
|
|
|
|
border: 2px solid {theme['border']};
|
|
|
|
|
border-radius: 6px;
|
|
|
|
|
}}
|
|
|
|
|
""")
|
|
|
|
|
|
|
|
|
|
def _build_ui(self):
|
|
|
|
|
layout = QHBoxLayout(self)
|
|
|
|
|
layout.setContentsMargins(20, 8, 20, 8)
|
|
|
|
|
layout.setSpacing(12)
|
|
|
|
|
|
|
|
|
|
# Theme cycle button
|
|
|
|
|
self.theme_btn = QPushButton("◈")
|
|
|
|
|
self.theme_btn.setFixedSize(40, 40)
|
|
|
|
|
self.theme_btn.setFont(QFont("Arial", 16, QFont.Bold))
|
|
|
|
|
self.theme_btn.setToolTip("Cycle colour theme")
|
|
|
|
|
self.theme_btn.clicked.connect(self._cycle_theme)
|
|
|
|
|
self._style_theme_btn()
|
|
|
|
|
layout.addWidget(self.theme_btn)
|
|
|
|
|
|
|
|
|
|
# Station name
|
|
|
|
|
self.station_label = QLabel("RADIO PANEL")
|
|
|
|
|
self.station_label.setFont(QFont("Arial", 16, QFont.Bold))
|
|
|
|
|
self.station_label.setStyleSheet(
|
|
|
|
|
"color: #444444; background: transparent; border: none;"
|
|
|
|
|
)
|
|
|
|
|
self.station_label.setAlignment(Qt.AlignLeft | Qt.AlignVCenter)
|
|
|
|
|
layout.addWidget(self.station_label)
|
|
|
|
|
|
|
|
|
|
layout.addStretch()
|
|
|
|
|
|
|
|
|
|
# Date
|
|
|
|
|
self.date_label = QLabel("")
|
|
|
|
|
self.date_label.setFont(QFont("Arial", 20, QFont.Bold))
|
|
|
|
|
self.date_label.setAlignment(Qt.AlignCenter | Qt.AlignVCenter)
|
|
|
|
|
layout.addWidget(self.date_label)
|
|
|
|
|
|
|
|
|
|
layout.addStretch()
|
|
|
|
|
|
|
|
|
|
# Clock
|
|
|
|
|
self.clock_label = QLabel("")
|
|
|
|
|
self.clock_label.setFont(QFont("Courier New", 42, QFont.Bold))
|
|
|
|
|
self.clock_label.setAlignment(Qt.AlignRight | Qt.AlignVCenter)
|
|
|
|
|
self.clock_label.setMinimumWidth(220)
|
2026-05-08 17:19:11 +10:00
|
|
|
self._style_clock_and_date()
|
2026-04-29 16:21:44 +10:00
|
|
|
layout.addWidget(self.clock_label)
|
|
|
|
|
|
|
|
|
|
def _style_theme_btn(self):
|
|
|
|
|
theme = THEMES[self._theme_index]
|
|
|
|
|
self.theme_btn.setStyleSheet(f"""
|
|
|
|
|
QPushButton {{
|
|
|
|
|
background-color: transparent;
|
|
|
|
|
color: {theme['accent']};
|
|
|
|
|
border: 2px solid {theme['accent']};
|
|
|
|
|
border-radius: 20px;
|
|
|
|
|
}}
|
|
|
|
|
QPushButton:hover {{
|
|
|
|
|
background-color: {theme['accent']};
|
2026-05-08 17:19:11 +10:00
|
|
|
color: {'#ffffff' if not theme['is_light'] else '#000000'};
|
2026-04-29 16:21:44 +10:00
|
|
|
}}
|
|
|
|
|
QPushButton:pressed {{
|
|
|
|
|
background-color: {theme['accent_dim']};
|
|
|
|
|
}}
|
|
|
|
|
""")
|
|
|
|
|
|
2026-05-08 17:19:11 +10:00
|
|
|
def _style_clock_and_date(self):
|
2026-04-29 16:21:44 +10:00
|
|
|
theme = THEMES[self._theme_index]
|
|
|
|
|
self.clock_label.setStyleSheet(
|
|
|
|
|
f"color: {theme['accent']}; background: transparent; border: none;"
|
|
|
|
|
)
|
2026-05-08 17:19:11 +10:00
|
|
|
# Date color adapts to light/dark
|
|
|
|
|
date_color = '#555555' if theme['is_light'] else '#aaaaaa'
|
|
|
|
|
self.date_label.setStyleSheet(
|
|
|
|
|
f"color: {date_color}; background: transparent; border: none;"
|
|
|
|
|
)
|
2026-04-29 16:21:44 +10:00
|
|
|
|
|
|
|
|
def _cycle_theme(self):
|
|
|
|
|
self._theme_index = (self._theme_index + 1) % len(THEMES)
|
|
|
|
|
self._apply_bar_style()
|
|
|
|
|
self._style_theme_btn()
|
2026-05-08 17:19:11 +10:00
|
|
|
self._style_clock_and_date()
|
2026-04-29 16:21:44 +10:00
|
|
|
self.theme_changed.emit(THEMES[self._theme_index])
|
|
|
|
|
|
|
|
|
|
def _update(self):
|
|
|
|
|
now = datetime.now()
|
|
|
|
|
self.clock_label.setText(now.strftime("%H:%M:%S"))
|
|
|
|
|
self.date_label.setText(now.strftime("%A %d %B %Y"))
|
|
|
|
|
|
|
|
|
|
@property
|
|
|
|
|
def current_theme(self) -> dict:
|
|
|
|
|
return THEMES[self._theme_index]
|