"""无业务状态的可复用 Qt 控件。""" from PyQt5.QtCore import Qt, QRectF, pyqtSignal from PyQt5.QtGui import QColor, QPainter from PyQt5.QtWidgets import QCheckBox, QLabel, QFrame, QHBoxLayout try: from PyQt5.QtWidgets import QGraphicsDropShadowEffect except ImportError: # 轻量测试桩不提供图形效果类,真实 PyQt5 仍使用阴影。 class QGraphicsDropShadowEffect: # type: ignore[no-redef] def __init__(self, *_args, **_kwargs): pass def setBlurRadius(self, *_args): pass def setOffset(self, *_args): pass def setColor(self, *_args): pass from .theme import C_BORDER, C_GREEN, C_RED, C_TITLE _NL_BASE = f"color: {C_TITLE}; font-size: 12px;" class NodeLabel(QLabel): """可点击标签:hover 显示下划线和手型光标,点击发出 clicked 信号。""" clicked = pyqtSignal() def __init__(self, text="", tooltip="点击切换节点", base=None, hover=None): super().__init__(text) self._base = base or _NL_BASE self._hover = hover or self._base + " text-decoration: underline;" self.setStyleSheet(self._base) if tooltip: self.setToolTip(tooltip) def enterEvent(self, event): self.setStyleSheet(self._hover) self.setCursor(Qt.PointingHandCursor) super().enterEvent(event) def leaveEvent(self, event): self.setStyleSheet(self._base) self.unsetCursor() super().leaveEvent(event) def mouseReleaseEvent(self, event): if event.button() == Qt.LeftButton: self.clicked.emit() super().mouseReleaseEvent(event) class ToggleSwitch(QCheckBox): """苹果风格开关:绿/灰胶囊加滑动圆钮,自绘且不依赖图片资源。""" def __init__(self, checked=False, parent=None): super().__init__(parent) self.setChecked(checked) self.setFixedSize(42, 22) self.setCursor(Qt.PointingHandCursor) def paintEvent(self, event): painter = QPainter(self) painter.setRenderHint(QPainter.Antialiasing) painter.setPen(Qt.NoPen) painter.setBrush(QColor(C_GREEN) if self.isChecked() else QColor("#c8ccd4")) painter.drawRoundedRect(QRectF(0, 0, 42, 22), 11, 11) knob_x = 20.0 if self.isChecked() else 2.0 painter.setBrush(QColor("#ffffff")) painter.drawEllipse(QRectF(knob_x, 2.0, 18.0, 18.0)) painter.end() def mousePressEvent(self, event): if event.button() == Qt.LeftButton: self.setChecked(not self.isChecked()) self.repaint() event.accept() return super().mousePressEvent(event) class StatusDot(QLabel): """代理状态小圆点。""" def __init__(self): super().__init__() self.setFixedSize(14, 14) self.set_off() def set_on(self): self.setStyleSheet(f""" QLabel {{ background-color: {C_GREEN}; border-radius: 7px; border: 1px solid rgba(81, 207, 102, 50); }} """) def set_off(self): self.setStyleSheet(f""" QLabel {{ background-color: {C_RED}; border-radius: 7px; border: 1px solid rgba(255, 107, 107, 50); }} """) def set_disabled(self): self.setStyleSheet(f""" QLabel {{ background-color: {C_BORDER}; border-radius: 7px; border: 1px solid rgba(136, 153, 170, 50); }} """) class ProxyModeCard(QFrame): """整个卡片可点击;描边表示选择,圆点表示所选模式的运行状态。""" clicked = pyqtSignal() def __init__(self, title: str, parent=None): super().__init__(parent) self.setCursor(Qt.PointingHandCursor) self._glow = QGraphicsDropShadowEffect(self) self._glow.setBlurRadius(12) self._glow.setOffset(0, 1) self.setGraphicsEffect(self._glow) row = QHBoxLayout(self) row.setContentsMargins(11, 10, 11, 10) row.setSpacing(5) self.title = QLabel(title) self.title.setStyleSheet("font-weight: bold; background: transparent;") self.dot = StatusDot() for child in (self.title, self.dot): child.setAttribute(Qt.WA_TransparentForMouseEvents) row.addWidget(self.title) row.addStretch() row.addWidget(self.dot) self.set_state(False, False) def set_state(self, selected: bool, running: bool) -> None: border = C_TITLE if selected else "#cbd6e2" background = "#f5f7f9" if selected else "#fafcfe" self.setStyleSheet( f"QFrame {{ background-color: {background}; border: {'2px' if selected else '1px'} " f"solid {border}; border-radius: 8px; }}" "QFrame:hover { border-color: #e2e8ef; background-color: #ffffff; }" "QLabel { border: none; }" ) self._glow.setColor(QColor(136, 153, 170, 105 if selected else 52)) if not selected: self.dot.set_disabled() elif running: self.dot.set_on() else: self.dot.set_off() def mouseReleaseEvent(self, event): if event.button() == Qt.LeftButton and self.rect().contains(event.pos()): self.clicked.emit() super().mouseReleaseEvent(event)