From 8d6acf3a8ea4b37f86b321dbf430be5be01b1267 Mon Sep 17 00:00:00 2001 From: Christian Kolset Date: Mon, 13 Apr 2026 14:36:55 -0600 Subject: init --- daq_system/ui/device_panel.py | 127 ++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 127 insertions(+) create mode 100644 daq_system/ui/device_panel.py (limited to 'daq_system/ui/device_panel.py') diff --git a/daq_system/ui/device_panel.py b/daq_system/ui/device_panel.py new file mode 100644 index 0000000..9b6c7e4 --- /dev/null +++ b/daq_system/ui/device_panel.py @@ -0,0 +1,127 @@ +""" +ui/device_panel.py + +Left sidebar showing all registered devices with status indicators. +""" + +from PyQt6.QtWidgets import ( + QWidget, QVBoxLayout, QLabel, QPushButton, + QHBoxLayout, QScrollArea, QFrame +) +from PyQt6.QtCore import Qt, pyqtSignal + +from devices.base_device import DeviceStatus +from devices.device_registry import DeviceRegistry +from core.acquisition import AcquisitionEngine + + +class DeviceCard(QFrame): + config_clicked = pyqtSignal(str) + toggle_clicked = pyqtSignal(str, bool) + + STATUS_COLORS = { + DeviceStatus.CONNECTED: "#3fb950", + DeviceStatus.SIMULATED: "#58a6ff", + DeviceStatus.DISCONNECTED: "#8b949e", + DeviceStatus.CONNECTING: "#d29922", + DeviceStatus.ERROR: "#f85149", + } + + def __init__(self, device, parent=None): + super().__init__(parent) + self.device = device + self.setObjectName("deviceCard") + self._build() + + def _build(self): + layout = QVBoxLayout(self) + layout.setContentsMargins(8, 8, 8, 8) + layout.setSpacing(4) + + # Header row + hdr = QHBoxLayout() + icon = QLabel(self.device.info.icon) + icon.setObjectName("deviceIcon") + hdr.addWidget(icon) + + name = QLabel(self.device.info.name) + name.setObjectName("deviceName") + hdr.addWidget(name) + hdr.addStretch() + + color = self.STATUS_COLORS.get(self.device.status, "#8b949e") + self.status_dot = QLabel("●") + self.status_dot.setStyleSheet(f"color: {color}; font-size: 10px;") + hdr.addWidget(self.status_dot) + + layout.addLayout(hdr) + + # ID + type + sub = QLabel(f"{self.device.info.device_id} · {self.device.info.device_type}") + sub.setObjectName("deviceSub") + layout.addWidget(sub) + + # Channel count + ch_count = sum(1 for ch in self.device.info.channels if ch.enabled) + ch_lbl = QLabel(f"{ch_count} channel{'s' if ch_count != 1 else ''}") + ch_lbl.setObjectName("deviceChannelCount") + layout.addWidget(ch_lbl) + + # Config button + cfg_btn = QPushButton("Configure") + cfg_btn.setObjectName("configButton") + cfg_btn.clicked.connect(lambda: self.config_clicked.emit(self.device.info.device_id)) + layout.addWidget(cfg_btn) + + def refresh_status(self): + color = self.STATUS_COLORS.get(self.device.status, "#8b949e") + self.status_dot.setStyleSheet(f"color: {color}; font-size: 10px;") + + +class DevicePanel(QWidget): + config_requested = pyqtSignal(str) + + def __init__(self, registry: DeviceRegistry, engine: AcquisitionEngine): + super().__init__() + self.registry = registry + self.engine = engine + self._cards = {} + self._build_ui() + self.refresh() + + def _build_ui(self): + layout = QVBoxLayout(self) + layout.setContentsMargins(0, 0, 0, 0) + layout.setSpacing(0) + + header = QLabel(" DEVICES") + header.setObjectName("panelHeader") + layout.addWidget(header) + + scroll = QScrollArea() + scroll.setWidgetResizable(True) + scroll.setHorizontalScrollBarPolicy(Qt.ScrollBarPolicy.ScrollBarAlwaysOff) + scroll.setObjectName("deviceScroll") + + self._container = QWidget() + self._inner = QVBoxLayout(self._container) + self._inner.setContentsMargins(8, 8, 8, 8) + self._inner.setSpacing(8) + self._inner.addStretch() + + scroll.setWidget(self._container) + layout.addWidget(scroll) + + def refresh(self): + # Remove old cards + for card in self._cards.values(): + self._inner.removeWidget(card) + card.deleteLater() + self._cards.clear() + + for dev in self.registry.all_instances(): + card = DeviceCard(dev) + card.config_clicked.connect(self.config_requested.emit) + # Insert before the stretch + self._inner.insertWidget(self._inner.count() - 1, card) + self._cards[dev.info.device_id] = card -- cgit v1.2.3