# cart_widget.py from PySide6.QtWidgets import ( QWidget, QVBoxLayout, QHBoxLayout, QLabel, QPushButton, QFrame, QSizePolicy, QGridLayout ) from PySide6.QtCore import Qt, QTimer from PySide6.QtGui import QFont import os class CartSlot(QFrame): def __init__(self, cart_key: str, slot_number: int, engine, parent=None): super().__init__(parent) self.cart_key = cart_key self.slot_number = slot_number self.engine = engine self.setFrameStyle(QFrame.Box | QFrame.Raised) self.setLineWidth(1) self.setStyleSheet(""" QFrame { background-color: #1e1e1e; border: 1px solid #444; border-radius: 5px; } """) self.setSizePolicy(QSizePolicy.Expanding, QSizePolicy.Fixed) self.setFixedHeight(110) self._build_ui() 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): root = QHBoxLayout(self) root.setContentsMargins(8, 6, 8, 6) root.setSpacing(8) badge = QLabel(str(self.slot_number)) badge.setFixedSize(28, 28) badge.setAlignment(Qt.AlignCenter) badge.setFont(QFont("Arial", 12, QFont.Bold)) badge.setStyleSheet(""" background-color: #cc3300; color: white; border-radius: 14px; border: none; """) root.addWidget(badge, alignment=Qt.AlignVCenter) self.lcd_frame = QFrame() self.lcd_frame.setStyleSheet(""" QFrame { background-color: #0a1a0a; border: 1px solid #1a2a1a; border-radius: 3px; } """) self.lcd_frame.setSizePolicy(QSizePolicy.Expanding, QSizePolicy.Expanding) lcd_layout = QVBoxLayout(self.lcd_frame) lcd_layout.setContentsMargins(6, 4, 6, 4) lcd_layout.setSpacing(2) self.track_label = QLabel("EMPTY") self.track_label.setFont(QFont("Courier New", 9, QFont.Bold)) self.track_label.setStyleSheet( "color: #00ff41; background: transparent; border: none;" ) self.track_label.setAlignment(Qt.AlignLeft | Qt.AlignVCenter) time_row = QHBoxLayout() self.elapsed_label = QLabel("00:00") self.elapsed_label.setFont(QFont("Courier New", 14, QFont.Bold)) self.elapsed_label.setStyleSheet( "color: #00ff41; background: transparent; border: none;" ) self.remaining_label = QLabel("-00:00") self.remaining_label.setFont(QFont("Courier New", 10)) self.remaining_label.setStyleSheet( "color: #00cc33; background: transparent; border: none;" ) self.remaining_label.setAlignment(Qt.AlignRight | Qt.AlignVCenter) time_row.addWidget(self.elapsed_label) time_row.addStretch() time_row.addWidget(self.remaining_label) self.status_label = QLabel("STOPPED") self.status_label.setFont(QFont("Courier New", 8)) self.status_label.setStyleSheet( "color: #008822; background: transparent; border: none;" ) lcd_layout.addWidget(self.track_label) lcd_layout.addLayout(time_row) lcd_layout.addWidget(self.status_label) root.addWidget(self.lcd_frame) btn_col = QVBoxLayout() btn_col.setSpacing(4) self.btn_play = QPushButton("▶") self.btn_play.setFixedSize(60, 44) self.btn_play.setFont(QFont("Arial", 13, QFont.Bold)) self.btn_play.setStyleSheet(""" QPushButton { background-color: #1a6b3a; color: #00ff41; border: 1px solid #555; border-radius: 3px; } QPushButton:hover { background-color: #2a7b4a; } QPushButton:pressed { background-color: #0a5a2a; } """) self.btn_eject = QPushButton("⏏") self.btn_eject.setFixedSize(60, 30) self.btn_eject.setFont(QFont("Arial", 11, QFont.Bold)) self.btn_eject.setStyleSheet(""" QPushButton { background-color: #8a1a1a; color: #ff4444; border: 1px solid #555; border-radius: 3px; } QPushButton:hover { background-color: #9a2a2a; } QPushButton:pressed { background-color: #6a0a0a; } """) btn_col.addWidget(self.btn_play) btn_col.addWidget(self.btn_eject) root.addLayout(btn_col) def apply_theme(self, theme: dict): """Apply theme to cart slot LCD.""" self.lcd_frame.setStyleSheet(f""" QFrame {{ background-color: {theme['bg']}; border: 1px solid {theme['border']}; border-radius: 3px; }} """) def _connect_signals(self): self.btn_play.clicked.connect(self._toggle_play) self.btn_eject.clicked.connect(self._eject) def load_track(self, filepath: str): self.engine.load_track(self.cart_key, filepath) name = os.path.basename(filepath) short = name[:28] + "..." if len(name) > 28 else name self.track_label.setText(short) self.status_label.setText("LOADED") self.elapsed_label.setText("00:00") self.remaining_label.setText("-00:00") def toggle(self): deck = self.engine.decks[self.cart_key] if deck.track is None: return if deck.playing: deck.stop() deck.go_to_start() self._set_ui_stopped() self.status_label.setText("READY") else: deck.play() self._set_ui_playing() def _set_ui_playing(self): self.btn_play.setText("⏸") self.btn_play.setStyleSheet(""" QPushButton { background-color: #00aa33; color: white; border: 1px solid #00ff41; border-radius: 3px; } QPushButton:hover { background-color: #00cc44; } """) self.status_label.setText("PLAYING") self.elapsed_label.setStyleSheet( "color: #00ff41; background: transparent; border: none;" ) def _set_ui_stopped(self): self.btn_play.setText("▶") self.btn_play.setStyleSheet(""" QPushButton { background-color: #1a6b3a; color: #00ff41; border: 1px solid #555; border-radius: 3px; } QPushButton:hover { background-color: #2a7b4a; } """) self.elapsed_label.setText("00:00") self.remaining_label.setText("-00:00") self.elapsed_label.setStyleSheet( "color: #00aa2a; background: transparent; border: none;" ) def _toggle_play(self): deck = self.engine.decks[self.cart_key] if deck.track is None: return if deck.playing: deck.pause() self._set_ui_stopped() self.status_label.setText("PAUSED") else: deck.play() self._set_ui_playing() def _eject(self): deck = self.engine.decks[self.cart_key] deck.stop() self._set_ui_stopped() self.track_label.setText("EMPTY") self.status_label.setText("STOPPED") def _refresh_display(self): deck = self.engine.decks[self.cart_key] if not deck.playing and self.btn_play.text() == "⏸": self._set_ui_stopped() self.status_label.setText("STOPPED") if deck.track: self.elapsed_label.setText(deck.get_elapsed()) self.remaining_label.setText(deck.get_remaining()) class CartBankWidget(QWidget): def __init__(self, engine, parent=None): super().__init__(parent) self.engine = engine self._build_ui() def _build_ui(self): self.setStyleSheet(""" QWidget { background-color: #111; border: 2px solid #cc3300; border-radius: 6px; } """) root = QVBoxLayout(self) root.setContentsMargins(8, 6, 8, 6) root.setSpacing(6) header = QLabel("CARTS") header.setFont(QFont("Arial", 11, QFont.Bold)) header.setStyleSheet(""" color: #cc3300; background: transparent; border: none; padding: 2px; """) header.setAlignment(Qt.AlignCenter) root.addWidget(header) grid = QGridLayout() grid.setSpacing(6) self.slots = [] for i in range(4): cart_key = f"cart{i + 1}" slot = CartSlot(cart_key, i + 1, self.engine) row = i // 2 col = i % 2 grid.addWidget(slot, row, col) self.slots.append(slot) root.addLayout(grid) def apply_theme(self, theme: dict): """Propagate theme to all cart slots.""" for slot in self.slots: slot.apply_theme(theme)