#include "global.h" #include #include #include #include #include #include #include #include // 启动 500ms 闪烁:每 500ms 切换一次动态属性,QSS 据此在 40%↔100% 透明度间切换 QTimer* startBlink(QWidget* w, const char* prop) { // 复用已存在的定时器 if (auto* t = w->findChild(QString("blink_") + prop)) return t; QTimer* t = new QTimer(w); t->setObjectName(QString("blink_") + prop); t->setInterval(500); QObject::connect(t, &QTimer::timeout, w, [w, prop]() { bool cur = w->property(prop).toBool(); w->setProperty(prop, !cur); w->style()->unpolish(w); w->style()->polish(w); w->update(); }); w->setProperty(prop, true); t->start(); return t; } void stopBlink(QWidget* w, const char* prop) { if (auto* t = w->findChild(QString("blink_") + prop)) { t->stop(); delete t; } w->setProperty(prop, false); w->style()->unpolish(w); w->style()->polish(w); w->update(); } QString cancelButtonStyle(int fontPx) { return QString( "QPushButton{font-size:%1px;}" "QPushButton:hover,QPushButton:pressed,QPushButton:focus" "{background-color:%2;color:#FFFFFF;border:2px solid #FFA6A9;}") .arg(fontPx).arg(C::DANGER); } // ===================== 按钮焦点(选中)光圈 ===================== ButtonFocusGlow::ButtonFocusGlow(const QColor& glow, QObject* parent) : QObject(parent), m_glow(glow) { } // 触摸屏上手指抬起后指针并不会跟着移开,Qt 会把刚点过的按钮一直留在悬停(:hover)态。 // 本项目的悬停样式与选中样式是同一套(蓝底/亮边),残留的悬停看起来就是"还选中着", // 此时再按 Tab 选中另一个按钮,屏幕上就出现了两个"选中"。选中态一律以焦点为准, // 故每次产生新的选中(点击/触摸按下、焦点移入)时,把其它按钮残留的悬停态清掉。 static void clearStaleHover(QWidget* keep) { // 用 allWidgets 而非 qApp->findChildren:顶层窗口并非 QApplication 的子对象 const QWidgetList all = QApplication::allWidgets(); for (QWidget* w : all) { if (w == keep || !w->underMouse() || !qobject_cast(w)) continue; w->setAttribute(Qt::WA_UnderMouse, false); w->update(); } } bool ButtonFocusGlow::eventFilter(QObject* obj, QEvent* ev) { QWidget* w = qobject_cast(obj); if (!w) return QObject::eventFilter(obj, ev); switch (ev->type()) { case QEvent::FocusIn: clearStaleHover(w); // 新的选中出现 → 旧按钮的悬停残影一并清掉 if (qobject_cast(w) && !w->graphicsEffect()) { QGraphicsDropShadowEffect* g = new QGraphicsDropShadowEffect(w); // 挂一圈发光(偏移 0 → 四周光晕) g->setBlurRadius(40); // 光晕大小(越大越强) g->setOffset(0, 0); g->setColor(m_glow); w->setGraphicsEffect(g); } break; case QEvent::FocusOut: if (qobject_cast(w) && w->graphicsEffect()) { // 光晕带模糊、偏移为 0,会溢出按钮矩形画到父控件上。 // 只重绘按钮自身清不掉溢出的那圈,Tab 移走焦点后仍会留下蓝色残影, // 所以把父控件整块标脏,连同按钮一起重绘。 QWidget* parent = w->parentWidget(); w->setGraphicsEffect(nullptr); // Qt 会删除旧效果 w->update(); if (parent) parent->update(parent->rect()); } break; case QEvent::MouseButtonPress: // 触摸/点击与 Tab 归一到同一个焦点:谁最后操作,谁就是唯一的选中 if (static_cast(ev)->button() == Qt::LeftButton && (w->focusPolicy() & Qt::ClickFocus)) { w->setFocus(Qt::MouseFocusReason); clearStaleHover(w); } break; default: break; } return QObject::eventFilter(obj, ev); } void installButtonFocusGlow(QApplication* app) { if (app) app->installEventFilter(new ButtonFocusGlow(QColor("#7FBBFF"), app)); // 亮蓝,深色底更醒目 } // ===================== 方向键(旋钮)隔离 ===================== ArrowKeyGuard::ArrowKeyGuard(QObject* parent) : QObject(parent) { } bool ArrowKeyGuard::eventFilter(QObject* obj, QEvent* ev) { if (ev->type() == QEvent::KeyPress) { // 只在焦点落在按钮上时拦截:否则 QAbstractButton 会拿方向键去换焦 QWidget* fw = QApplication::focusWidget(); if (fw && qobject_cast(fw)) { const int k = static_cast(ev)->key(); if (Keys::up(k) || Keys::down(k)) return true; } } return QObject::eventFilter(obj, ev); } void installArrowKeyGuard(QApplication* app) { if (app) app->installEventFilter(new ArrowKeyGuard(app)); } QString appStyleSheet() { const QString s = QString(R"QSS( * { font-family: "Microsoft YaHei", "PingFang SC", "Source Han Sans CN"; outline: none; } QWidget { background-color: BG; color: TXT; } QWidget#pageRoot { background-color: BG; } QLabel { background: transparent; color: TXT; } QLabel[sub="true"] { color: SUB; } QLabel[title="true"] { color: TXT; font-weight: 700; } /* ===== 按钮 ===== */ QPushButton { background-color: PHI; color: TXT; border: 1px solid BD; border-radius: 12px; font-size: 40px; } QPushButton:hover { background-color: PRI; color: TXT; border-color: PRI; } QPushButton:pressed { background-color: PRI; color: #FFFFFF; border-color: PRI; } QPushButton:checked { background-color: PRI; color: #FFFFFF; border: none; } QPushButton:disabled { background-color: DIS; color: DIST; border-color: DIS; } QPushButton[variant="primary"] { background-color: PRI; color: #FFFFFF; border: none; } QPushButton[variant="primary"]:disabled { background-color: DIS; color: DIST; } QPushButton[variant="danger"] { background-color: DAN; color: #FFFFFF; border: 1px solid DAN; border-radius: 6px; } QPushButton[variant="danger"]:disabled { background-color: DIS; color: DIST; border-color: DIS; } QPushButton[variant="link"] { background: transparent; color: PRI; border: none; font-size: 22px; } QPushButton[variant="estop"] { background-color: DAN; color: #FFFFFF; border: 1px solid DAN; border-radius: 6px; font-size: 24px; font-weight: 700; } /* ===== 输入框 ===== */ QLineEdit { background-color: BG; border: 2px solid BD; border-radius: 8px; color: TXT; font-size: 50px; padding: 0 12px; } QLineEdit:focus { border: 3px solid PRI; } /* ===== 进度条 ===== */ QProgressBar { background-color: PHI; border: none; border-radius: 4px; text-align: center; color: #FFFFFF; font-size: 40px; } QProgressBar::chunk { background-color: PRI; border-radius: 4px; } /* ===== 表格 ===== */ QTableWidget { background-color: PAN; color: TXT; border: 1px solid BD; gridline-color: BD; font-size: 40px; alternate-background-color: #2A3442; } QHeaderView::section { background-color: PHI; color: TXT; border: none; border-right: 1px solid BD; border-bottom: 1px solid BD; padding: 8px; font-size: 40px; } QTableWidget::item { padding: 6px; } QTableWidget::item:selected { background-color: PRI; color: #FFFFFF; } /* ===== 数字键盘 ===== */ QPushButton[numpad="true"] { background-color: PHI; color: TXT; border: 1px solid BD; border-radius: 12px; font-size: 50px; } QPushButton[numpad="true"]:pressed { background-color: PRI; color: #FFFFFF; } /* ===== 焦点样式(Tab / Shift+Tab / 光标选中:高亮按钮显色 + 边框发亮) ===== */ QPushButton:focus { background-color: PRI; color: #FFFFFF; border: 4px solid #8ABFFF; } QPushButton:focus:disabled { background-color: DIS; color: DIST; border: 1px solid DIS; } QPushButton[variant="danger"]:focus, QPushButton[variant="estop"]:focus { background-color: DAN; border: 2px solid #FFA6A9; } QPushButton[variant="link"]:focus { background: transparent; color: PRI; border: none; } QPushButton[numpad="true"]:focus { background-color: PRI; color: #FFFFFF; border: 2px solid #8ABFFF; } /* ===== 告警文字(红色闪烁由 blink 属性驱动) ===== */ QLabel[alert="true"] { color: DAN; font-weight: 700; } QLabel[alert="true"][blink="false"] { color: rgba(229,72,77,102); } QLabel[amber="true"] { color: WAR; font-weight: 700; } /* ===== 数据卡片 ===== */ QWidget[card="true"] { background-color: PAN; border: 2px solid BD; border-radius: 12px; } QLabel[cardTitle="true"] { color: TXT; font-weight: 700; } )QSS"); // 用占位替换(注意:DIST 须在 DIS 之前替换,避免前缀误匹配) QString out = s; out.replace("DIST", C::DISABLED_T); out.replace("DIS", C::DISABLED); out.replace("BG", C::BG); out.replace("PAN", C::PANEL); out.replace("PHI", C::PANEL_HI); out.replace("BD", C::BORDER); out.replace("TXT", C::TEXT); out.replace("SUB", C::TEXT_SUB); out.replace("PRI", C::PRIMARY); out.replace("SUC", C::SUCCESS); out.replace("WAR", C::WARNING); out.replace("DAN", C::DANGER); return out; }