Initial commit - Radio Panel v0.1
This commit is contained in:
+347
@@ -0,0 +1,347 @@
|
||||
# deck_widget.py
|
||||
|
||||
from PySide6.QtWidgets import (
|
||||
QWidget, QVBoxLayout, QHBoxLayout, QLabel,
|
||||
QPushButton, QFrame, QSizePolicy
|
||||
)
|
||||
from PySide6.QtCore import Qt, QTimer, Signal
|
||||
from PySide6.QtGui import QFont, QColor
|
||||
import os
|
||||
|
||||
|
||||
# ─────────────────────────────────────────
|
||||
# LCD DISPLAY WIDGET
|
||||
# ─────────────────────────────────────────
|
||||
|
||||
class LCDDisplay(QFrame):
|
||||
def __init__(self, parent=None):
|
||||
super().__init__(parent)
|
||||
self.setFrameStyle(QFrame.Box | QFrame.Sunken)
|
||||
self.setStyleSheet("""
|
||||
QFrame {
|
||||
background-color: #0a1a0a;
|
||||
border: 2px solid #1a2a1a;
|
||||
border-radius: 4px;
|
||||
}
|
||||
""")
|
||||
|
||||
layout = QVBoxLayout(self)
|
||||
layout.setContentsMargins(10, 8, 10, 8)
|
||||
layout.setSpacing(4)
|
||||
|
||||
self.track_label = QLabel("NO TRACK LOADED")
|
||||
self.track_label.setFont(QFont("Courier New", 13, QFont.Bold))
|
||||
self.track_label.setStyleSheet(
|
||||
"color: #00ff41; background: transparent;"
|
||||
)
|
||||
self.track_label.setAlignment(Qt.AlignLeft | Qt.AlignVCenter)
|
||||
self.track_label.setSizePolicy(QSizePolicy.Expanding, QSizePolicy.Fixed)
|
||||
|
||||
time_row = QHBoxLayout()
|
||||
time_row.setSpacing(4)
|
||||
|
||||
self.elapsed_label = QLabel("00:00")
|
||||
self.elapsed_label.setFont(QFont("Courier New", 32, QFont.Bold))
|
||||
self.elapsed_label.setStyleSheet(
|
||||
"color: #00ff41; background: transparent;"
|
||||
)
|
||||
self.elapsed_label.setAlignment(Qt.AlignLeft | Qt.AlignVCenter)
|
||||
|
||||
self.remaining_label = QLabel("-00:00")
|
||||
self.remaining_label.setFont(QFont("Courier New", 18, QFont.Bold))
|
||||
self.remaining_label.setStyleSheet(
|
||||
"color: #00cc33; background: transparent;"
|
||||
)
|
||||
self.remaining_label.setAlignment(Qt.AlignRight | Qt.AlignVCenter)
|
||||
|
||||
time_row.addWidget(self.elapsed_label)
|
||||
time_row.addStretch()
|
||||
time_row.addWidget(self.remaining_label)
|
||||
|
||||
status_row = QHBoxLayout()
|
||||
|
||||
self.status_label = QLabel("STOPPED")
|
||||
self.status_label.setFont(QFont("Courier New", 11, QFont.Bold))
|
||||
self.status_label.setStyleSheet(
|
||||
"color: #008822; background: transparent;"
|
||||
)
|
||||
|
||||
self.queue_label = QLabel("QUEUE: EMPTY")
|
||||
self.queue_label.setFont(QFont("Courier New", 10))
|
||||
self.queue_label.setStyleSheet(
|
||||
"color: #008822; background: transparent;"
|
||||
)
|
||||
self.queue_label.setAlignment(Qt.AlignRight)
|
||||
|
||||
status_row.addWidget(self.status_label)
|
||||
status_row.addStretch()
|
||||
status_row.addWidget(self.queue_label)
|
||||
|
||||
layout.addWidget(self.track_label)
|
||||
layout.addLayout(time_row)
|
||||
layout.addLayout(status_row)
|
||||
|
||||
def update_track(self, name: str):
|
||||
if len(name) > 40:
|
||||
name = name[:37] + "..."
|
||||
self.track_label.setText(name)
|
||||
|
||||
def update_time(self, elapsed: str, remaining: str):
|
||||
self.elapsed_label.setText(elapsed)
|
||||
self.remaining_label.setText(remaining)
|
||||
|
||||
def update_status(self, status: str):
|
||||
self.status_label.setText(status)
|
||||
|
||||
def update_queue(self, name: str):
|
||||
if name:
|
||||
short = name[:25] + "..." if len(name) > 25 else name
|
||||
self.queue_label.setText(f"QUEUE: {short}")
|
||||
else:
|
||||
self.queue_label.setText("QUEUE: EMPTY")
|
||||
|
||||
def set_playing(self, playing: bool):
|
||||
colour = "#00ff41" if playing else "#00aa2a"
|
||||
self.elapsed_label.setStyleSheet(
|
||||
f"color: {colour}; background: transparent;"
|
||||
)
|
||||
|
||||
|
||||
# ─────────────────────────────────────────
|
||||
# TRANSPORT BUTTON HELPER
|
||||
# ─────────────────────────────────────────
|
||||
|
||||
def make_transport_button(text: str,
|
||||
color: str = "#3a3a3a",
|
||||
text_color: str = "white",
|
||||
width: int = 52,
|
||||
height: int = 48) -> QPushButton:
|
||||
btn = QPushButton(text)
|
||||
btn.setFixedSize(width, height)
|
||||
btn.setFont(QFont("Arial", 11, QFont.Bold))
|
||||
btn.setStyleSheet(f"""
|
||||
QPushButton {{
|
||||
background-color: {color};
|
||||
color: {text_color};
|
||||
border: 1px solid #555;
|
||||
border-radius: 4px;
|
||||
}}
|
||||
QPushButton:hover {{
|
||||
background-color: #555;
|
||||
}}
|
||||
QPushButton:pressed {{
|
||||
background-color: #222;
|
||||
border: 1px solid #888;
|
||||
}}
|
||||
""")
|
||||
return btn
|
||||
|
||||
|
||||
# ─────────────────────────────────────────
|
||||
# DECK WIDGET
|
||||
# ─────────────────────────────────────────
|
||||
|
||||
class DeckWidget(QWidget):
|
||||
track_started = Signal(str) # emits filepath when playback begins
|
||||
|
||||
def __init__(self, deck_key: str, label: str, accent_color: str,
|
||||
engine, parent=None):
|
||||
super().__init__(parent)
|
||||
self.deck_key = deck_key
|
||||
self.engine = engine
|
||||
self.accent_color = accent_color
|
||||
|
||||
self._build_ui(label)
|
||||
self._connect_signals()
|
||||
|
||||
self.timer = QTimer(self)
|
||||
self.timer.setInterval(100)
|
||||
self.timer.timeout.connect(self._refresh_display)
|
||||
self.timer.start()
|
||||
|
||||
def _build_ui(self, label: str):
|
||||
self.setStyleSheet(f"""
|
||||
QWidget {{
|
||||
background-color: #1c1c1c;
|
||||
border: 2px solid {self.accent_color};
|
||||
border-radius: 6px;
|
||||
}}
|
||||
""")
|
||||
|
||||
root = QVBoxLayout(self)
|
||||
root.setContentsMargins(10, 10, 10, 10)
|
||||
root.setSpacing(8)
|
||||
|
||||
header = QLabel(label)
|
||||
header.setFont(QFont("Arial", 13, QFont.Bold))
|
||||
header.setStyleSheet(f"""
|
||||
color: {self.accent_color};
|
||||
background: transparent;
|
||||
border: none;
|
||||
padding: 2px;
|
||||
""")
|
||||
header.setAlignment(Qt.AlignCenter)
|
||||
root.addWidget(header)
|
||||
|
||||
self.lcd = LCDDisplay()
|
||||
root.addWidget(self.lcd)
|
||||
|
||||
transport = QHBoxLayout()
|
||||
transport.setSpacing(6)
|
||||
|
||||
self.btn_to_start = make_transport_button("⏮", "#3a3a3a", "white", 52, 48)
|
||||
self.btn_back_15 = make_transport_button("-15", "#3a3a3a", "#aaaaaa", 52, 48)
|
||||
self.btn_play = make_transport_button("▶", "#1a6b3a", "#00ff41", 68, 48)
|
||||
self.btn_fwd_15 = make_transport_button("+15", "#3a3a3a", "#aaaaaa", 52, 48)
|
||||
self.btn_to_end = make_transport_button("⏭", "#3a3a3a", "white", 52, 48)
|
||||
self.btn_stop = make_transport_button("■", "#8a1a1a", "#ff4444", 52, 48)
|
||||
|
||||
transport.addWidget(self.btn_to_start)
|
||||
transport.addWidget(self.btn_back_15)
|
||||
transport.addWidget(self.btn_play)
|
||||
transport.addWidget(self.btn_fwd_15)
|
||||
transport.addWidget(self.btn_to_end)
|
||||
transport.addStretch()
|
||||
transport.addWidget(self.btn_stop)
|
||||
|
||||
root.addLayout(transport)
|
||||
|
||||
def _connect_signals(self):
|
||||
self.btn_play.clicked.connect(self._toggle_play)
|
||||
self.btn_stop.clicked.connect(self._stop_eject)
|
||||
self.btn_to_start.clicked.connect(self._go_to_start)
|
||||
self.btn_to_end.clicked.connect(self._go_to_end)
|
||||
self.btn_back_15.clicked.connect(lambda: self._scrub(-15))
|
||||
self.btn_fwd_15.clicked.connect(lambda: self._scrub(15))
|
||||
|
||||
# ── PUBLIC — called by playlist ───────────────────────────────────────
|
||||
|
||||
def set_track_name(self, name: str):
|
||||
self.lcd.update_track(name)
|
||||
self.lcd.update_status("LOADED")
|
||||
self.lcd.update_time("00:00", "-00:00")
|
||||
|
||||
def set_queue_name(self, name: str):
|
||||
self.lcd.update_queue(name)
|
||||
|
||||
# ── PUBLIC MIDI METHODS ───────────────────────────────────────────────
|
||||
|
||||
def toggle_play(self):
|
||||
self._toggle_play()
|
||||
|
||||
def cue(self):
|
||||
deck = self.engine.decks[self.deck_key]
|
||||
if deck.track is None:
|
||||
return
|
||||
if deck.playing:
|
||||
deck.pause()
|
||||
deck.go_to_start()
|
||||
self.btn_play.setText("▶")
|
||||
self.btn_play.setStyleSheet("""
|
||||
QPushButton {
|
||||
background-color: #1a6b3a;
|
||||
color: #00ff41;
|
||||
border: 1px solid #555;
|
||||
border-radius: 4px;
|
||||
}
|
||||
QPushButton:hover { background-color: #2a7b4a; }
|
||||
""")
|
||||
self.lcd.update_status("CUE")
|
||||
self.lcd.set_playing(False)
|
||||
|
||||
def scrub(self, delta: float):
|
||||
self._scrub(delta)
|
||||
|
||||
# ── ACTIONS ───────────────────────────────────────────────────────────
|
||||
|
||||
def _toggle_play(self):
|
||||
deck = self.engine.decks[self.deck_key]
|
||||
if deck.track is None:
|
||||
return
|
||||
if deck.playing:
|
||||
deck.pause()
|
||||
self.btn_play.setText("▶")
|
||||
self.btn_play.setStyleSheet("""
|
||||
QPushButton {
|
||||
background-color: #1a6b3a;
|
||||
color: #00ff41;
|
||||
border: 1px solid #555;
|
||||
border-radius: 4px;
|
||||
}
|
||||
QPushButton:hover { background-color: #2a7b4a; }
|
||||
""")
|
||||
self.lcd.update_status("PAUSED")
|
||||
self.lcd.set_playing(False)
|
||||
else:
|
||||
deck.play()
|
||||
self.btn_play.setText("⏸")
|
||||
self.btn_play.setStyleSheet("""
|
||||
QPushButton {
|
||||
background-color: #00aa33;
|
||||
color: white;
|
||||
border: 1px solid #00ff41;
|
||||
border-radius: 4px;
|
||||
}
|
||||
QPushButton:hover { background-color: #00cc44; }
|
||||
""")
|
||||
self.lcd.update_status("PLAYING")
|
||||
self.lcd.set_playing(True)
|
||||
if deck.track:
|
||||
self.track_started.emit(deck.track.filepath)
|
||||
|
||||
def _stop_eject(self):
|
||||
deck = self.engine.decks[self.deck_key]
|
||||
deck.stop()
|
||||
self.btn_play.setText("▶")
|
||||
self.btn_play.setStyleSheet("""
|
||||
QPushButton {
|
||||
background-color: #1a6b3a;
|
||||
color: #00ff41;
|
||||
border: 1px solid #555;
|
||||
border-radius: 4px;
|
||||
}
|
||||
QPushButton:hover { background-color: #2a7b4a; }
|
||||
""")
|
||||
self.lcd.update_track("NO TRACK LOADED")
|
||||
self.lcd.update_time("00:00", "-00:00")
|
||||
self.lcd.update_status("STOPPED")
|
||||
self.lcd.set_playing(False)
|
||||
|
||||
def _go_to_start(self):
|
||||
self.engine.decks[self.deck_key].go_to_start()
|
||||
|
||||
def _go_to_end(self):
|
||||
self.engine.decks[self.deck_key].go_to_end()
|
||||
|
||||
def _scrub(self, seconds: float):
|
||||
self.engine.decks[self.deck_key].skip_seconds(seconds)
|
||||
|
||||
# ── DISPLAY REFRESH ───────────────────────────────────────────────────
|
||||
|
||||
def _refresh_display(self):
|
||||
deck = self.engine.decks[self.deck_key]
|
||||
|
||||
if not deck.playing and self.btn_play.text() == "⏸":
|
||||
self.btn_play.setText("▶")
|
||||
self.btn_play.setStyleSheet("""
|
||||
QPushButton {
|
||||
background-color: #1a6b3a;
|
||||
color: #00ff41;
|
||||
border: 1px solid #555;
|
||||
border-radius: 4px;
|
||||
}
|
||||
QPushButton:hover { background-color: #2a7b4a; }
|
||||
""")
|
||||
self.lcd.update_status("STOPPED")
|
||||
self.lcd.set_playing(False)
|
||||
|
||||
if deck.track:
|
||||
self.lcd.update_time(
|
||||
deck.get_elapsed(),
|
||||
deck.get_remaining()
|
||||
)
|
||||
|
||||
if deck.queued:
|
||||
self.lcd.update_queue(
|
||||
os.path.basename(deck.queued.filepath)
|
||||
)
|
||||
Reference in New Issue
Block a user