터치 패널 반응성 개선과 커서 숨김

This commit is contained in:
seo committed 2026-07-30 04:50:34 +09:00
1 parent 209b587eac
commit ed76621201
1 file changed
+84 -38
+84 -38
View File
@@ -4,8 +4,8 @@ import subprocess
import sys import sys
from pathlib import Path from pathlib import Path
from PyQt5.QtCore import QPointF, Qt, QTimer from PyQt5.QtCore import QObject, QPointF, Qt, QThread, QTimer, pyqtSignal
from PyQt5.QtGui import QColor, QFont, QPainter, QPainterPath, QPen from PyQt5.QtGui import QColor, QPainter, QPainterPath, QPen
from PyQt5.QtWidgets import ( from PyQt5.QtWidgets import (
QApplication, QApplication,
QFrame, QFrame,
@@ -51,15 +51,20 @@ class StatusDot(QWidget):
self.pulse = 0 self.pulse = 0
self.setFixedSize(22, 22) self.setFixedSize(22, 22)
def set_state(self, active, warning=False): def set_warning(self, warning=False):
self.active = active
self.warning = warning self.warning = warning
self.pulse = 4 if active else 0 self.update()
def start_pulse(self):
self.active = True
self.pulse = 4
self.update() self.update()
def tick(self): def tick(self):
if self.pulse > 0: if self.pulse > 0:
self.pulse -= 1 self.pulse -= 1
if self.pulse <= 0:
self.active = False
self.update() self.update()
def paintEvent(self, event): def paintEvent(self, event):
@@ -79,8 +84,9 @@ class StatusDot(QWidget):
class StatusTile(QFrame): class StatusTile(QFrame):
def __init__(self, title): def __init__(self, title, pulse_delay_ms=0):
super().__init__() super().__init__()
self.pulse_delay_ms = pulse_delay_ms
self.dot = StatusDot() self.dot = StatusDot()
self.title = QLabel(title) self.title = QLabel(title)
self.title.setObjectName("statusTitle") self.title.setObjectName("statusTitle")
@@ -105,7 +111,9 @@ class StatusTile(QFrame):
self.setObjectName("statusTile") self.setObjectName("statusTile")
def update_state(self, active, detail, warning=False): def update_state(self, active, detail, warning=False):
self.dot.set_state(active, warning) self.dot.set_warning(warning)
if active:
QTimer.singleShot(self.pulse_delay_ms, self.dot.start_pulse)
self.detail.setText(detail) self.detail.setText(detail)
def tick(self): def tick(self):
@@ -119,6 +127,22 @@ class FanButton(QPushButton):
self.setMinimumHeight(44) self.setMinimumHeight(44)
class BridgeWorker(QObject):
finished = pyqtSignal(str, object)
failed = pyqtSignal(str, str)
def __init__(self, action, args):
super().__init__()
self.action = action
self.args = args
def run(self):
try:
self.finished.emit(self.action, run_bridge(*self.args))
except Exception as exc:
self.failed.emit(self.action, str(exc))
class MetricCard(QFrame): class MetricCard(QFrame):
def __init__(self, title): def __init__(self, title):
super().__init__() super().__init__()
@@ -218,17 +242,20 @@ class TouchPanel(QMainWindow):
self.metrics = {} self.metrics = {}
self.charts = {} self.charts = {}
self.last_counters = {} self.last_counters = {}
self.bridge_busy = False
self.worker_thread = None
self.worker = None
self.current_mode = "auto" self.current_mode = "auto"
self.current_pwm = 120 self.current_pwm = 120
self.in_flight = False
self.setWindowTitle("Control Touch Panel") self.setWindowTitle("Control Touch Panel")
self.setCursor(Qt.BlankCursor)
self.setMinimumSize(800, 480) self.setMinimumSize(800, 480)
self._build_ui() self._build_ui()
self.refresh_timer = QTimer(self) self.refresh_timer = QTimer(self)
self.refresh_timer.timeout.connect(self.refresh) self.refresh_timer.timeout.connect(self.refresh)
self.refresh_timer.start(1200) self.refresh_timer.start(1500)
self.pulse_timer = QTimer(self) self.pulse_timer = QTimer(self)
self.pulse_timer.timeout.connect(self.tick_pulses) self.pulse_timer.timeout.connect(self.tick_pulses)
@@ -252,14 +279,14 @@ class TouchPanel(QMainWindow):
title.setObjectName("sectionTitle") title.setObjectName("sectionTitle")
left_layout.addWidget(title) left_layout.addWidget(title)
for key, label in [ for index, (key, label) in enumerate([
("io", "IO"), ("io", "IO"),
("wan", "WAN"), ("wan", "WAN"),
("lan", "LAN"), ("lan", "LAN"),
("wifi24", "2.4G"), ("wifi24", "2.4G"),
("wifi5", "5G"), ("wifi5", "5G"),
]: ]):
row = StatusTile(label) row = StatusTile(label, index * 70)
self.rows[key] = row self.rows[key] = row
left_layout.addWidget(row) left_layout.addWidget(row)
left_layout.addStretch(1) left_layout.addStretch(1)
@@ -274,8 +301,11 @@ class TouchPanel(QMainWindow):
fan_title.setObjectName("sectionTitle") fan_title.setObjectName("sectionTitle")
self.clock = QLabel("--:--:--") self.clock = QLabel("--:--:--")
self.clock.setObjectName("clock") self.clock.setObjectName("clock")
self.status_text = QLabel("")
self.status_text.setObjectName("statusText")
header.addWidget(fan_title) header.addWidget(fan_title)
header.addStretch(1) header.addStretch(1)
header.addWidget(self.status_text)
header.addWidget(self.clock) header.addWidget(self.clock)
right_layout.addLayout(header) right_layout.addLayout(header)
@@ -331,10 +361,6 @@ class TouchPanel(QMainWindow):
chart_grid.addWidget(chart, index // 2, index % 2) chart_grid.addWidget(chart, index // 2, index % 2)
right_layout.addLayout(chart_grid, 1) right_layout.addLayout(chart_grid, 1)
self.notice = QLabel("프로토타입")
self.notice.setObjectName("notice")
self.notice.setWordWrap(True)
right_layout.addWidget(self.notice)
right_layout.addStretch(1) right_layout.addStretch(1)
layout.addWidget(left) layout.addWidget(left)
@@ -347,21 +373,12 @@ class TouchPanel(QMainWindow):
def slider_changed(self, value): def slider_changed(self, value):
self.current_pwm = value self.current_pwm = value
self.fan_pwm.setText(f"{value} PWM / {value / 255 * 100:.1f}%") if "pwm" in self.metrics:
self.metrics["pwm"].set_value(f"{value} / {value / 255 * 100:.1f}%")
def set_fan(self, mode, pwm): def set_fan(self, mode, pwm):
if self.in_flight: if self.start_bridge("fan", "fan", mode, pwm):
return self.status_text.setText("팬 변경 중")
self.in_flight = True
self.notice.setText("팬 변경 중")
try:
self.snapshot = run_bridge("fan", mode, pwm)
self.notice.setText("팬 상태를 바꿨어")
self.render()
except Exception as exc:
self.notice.setText(str(exc))
finally:
self.in_flight = False
def step_pwm(self, delta): def step_pwm(self, delta):
value = max(0, min(255, self.slider.value() + delta)) value = max(0, min(255, self.slider.value() + delta))
@@ -369,14 +386,42 @@ class TouchPanel(QMainWindow):
self.set_fan("manual", value) self.set_fan("manual", value)
def refresh(self): def refresh(self):
if self.in_flight: self.start_bridge("status", "status")
return
try: def start_bridge(self, action, *args):
self.snapshot = run_bridge("status") if self.bridge_busy:
self.render() return False
except Exception as exc: self.bridge_busy = True
self.notice.setText(str(exc)) self.worker_thread = QThread(self)
self.rows["io"].update_state(False, "control snapshot 실패", True) self.worker = BridgeWorker(action, args)
self.worker.moveToThread(self.worker_thread)
self.worker_thread.started.connect(self.worker.run)
self.worker.finished.connect(self.bridge_done)
self.worker.failed.connect(self.bridge_failed)
self.worker.finished.connect(self.worker_thread.quit)
self.worker.failed.connect(self.worker_thread.quit)
self.worker.finished.connect(self.worker.deleteLater)
self.worker.failed.connect(self.worker.deleteLater)
self.worker_thread.finished.connect(self.worker_thread.deleteLater)
self.worker_thread.finished.connect(self.bridge_finished)
self.worker_thread.start()
return True
def bridge_done(self, action, data):
self.snapshot = data
self.status_text.setText("팬 변경 완료" if action == "fan" else "")
self.render()
def bridge_failed(self, action, message):
del action
self.status_text.setText("통신 오류")
self.rows["io"].update_state(False, "snapshot 실패", True)
print(message, file=sys.stderr)
def bridge_finished(self):
self.bridge_busy = False
self.worker_thread = None
self.worker = None
def render(self): def render(self):
data = self.snapshot data = self.snapshot
@@ -500,6 +545,7 @@ class TouchPanel(QMainWindow):
def main(): def main():
app = QApplication(sys.argv) app = QApplication(sys.argv)
app.setOverrideCursor(Qt.BlankCursor)
app.setStyleSheet( app.setStyleSheet(
""" """
QWidget { background: #0b1220; color: #e5edf7; font-family: "Noto Sans CJK KR", "Noto Sans CJK KR Regular", "Noto Sans KR", sans-serif; } QWidget { background: #0b1220; color: #e5edf7; font-family: "Noto Sans CJK KR", "Noto Sans CJK KR Regular", "Noto Sans KR", sans-serif; }
@@ -515,7 +561,7 @@ def main():
#chartTitle { color: #dce8f8; font-size: 14px; font-weight: 700; background: transparent; } #chartTitle { color: #dce8f8; font-size: 14px; font-weight: 700; background: transparent; }
#chartCaption { color: #93a4bd; font-size: 11px; background: transparent; } #chartCaption { color: #93a4bd; font-size: 11px; background: transparent; }
#clock { font-size: 18px; color: #8fb3ff; font-weight: 700; } #clock { font-size: 18px; color: #8fb3ff; font-weight: 700; }
#notice { padding: 10px; background: #17233a; border: 1px solid #2b3c59; border-radius: 12px; color: #a8b8d0; } #statusText { color: #93a4bd; font-size: 13px; background: transparent; }
QPushButton { min-height: 42px; border: 1px solid #33445f; border-radius: 14px; background: #1a2942; color: #dce8f8; font-size: 18px; font-weight: 700; } QPushButton { min-height: 42px; border: 1px solid #33445f; border-radius: 14px; background: #1a2942; color: #dce8f8; font-size: 18px; font-weight: 700; }
QPushButton:checked { background: #16a34a; color: white; border-color: #22c55e; } QPushButton:checked { background: #16a34a; color: white; border-color: #22c55e; }
QPushButton#stepButton { min-width: 56px; font-size: 28px; } QPushButton#stepButton { min-width: 56px; font-size: 28px; }