#include "ToastOverlay.hpp" #include #include #include #include #include #include "Theme.hpp" namespace geopro::app { namespace { // ── 自动消失时长(规范 §7.7:3–4s)── constexpr int kDismissMs = 3500; // 距窗口内容区底部的留白(引用间距令牌 xxl)。 constexpr int kBottomMargin = space::kXxl; // 卡片最大宽度,避免长文案铺满整窗。 constexpr int kMaxWidth = 480; // 左侧状态色竖条宽度(规范 §7.7:状态色左竖条,3px)。 constexpr int kBarWidth = 3; } // namespace ToastOverlay::ToastOverlay(QWidget* parent) : QWidget(parent) { setObjectName(QStringLiteral("toastCard")); setAttribute(Qt::WA_StyledBackground, true); // 令 QSS 背景在 QWidget 上生效 setCursor(Qt::PointingHandCursor); hide(); // 卡片样式:bg/panel 底 + 1px border/default 边框 + radius/md 圆角;左侧 3px status/info 状态竖条 // 以「左 border-left」近似;阴影无法用 QSS 绘制,故以边框 + 半透明底近似浮层高度。 applyTokenizedStyleSheet( this, QStringLiteral( "#toastCard { background:{{bg/panel}}; border:1px solid {{border/default}};" " border-left:%1px solid {{status/info}}; border-radius:%2px; }" "#toastLabel { color:{{text/primary}}; font-size:%3px; background:transparent;" " border:none; }") .arg(kBarWidth) .arg(radius::kMd) .arg(scaledPx(type::kBody))); auto* lay = new QHBoxLayout(this); // 左内边距补上状态竖条宽度,文案与竖条留出间隙。 lay->setContentsMargins(space::kLg + kBarWidth, space::kMd, space::kLg, space::kMd); lay->setSpacing(space::kMd); label_ = new QLabel(this); label_->setObjectName(QStringLiteral("toastLabel")); label_->setWordWrap(true); label_->setMaximumWidth(kMaxWidth); lay->addWidget(label_); // 单次计时器:到时隐藏(复用实例不销毁,规避悬垂指针)。 timer_ = new QTimer(this); timer_->setSingleShot(true); QObject::connect(timer_, &QTimer::timeout, this, [this] { hide(); }); // 监听父窗口尺寸/移动,跟随重定位。 if (parent) parent->installEventFilter(this); } void ToastOverlay::showMessage(const QString& msg) { if (!label_) return; label_->setText(msg); adjustSize(); reposition(); raise(); show(); timer_->start(kDismissMs); // 重置自动消失计时 } void ToastOverlay::reposition() { auto* p = parentWidget(); if (!p) return; const int x = (p->width() - width()) / 2; const int y = p->height() - height() - kBottomMargin; move(qMax(0, x), qMax(0, y)); } bool ToastOverlay::eventFilter(QObject* obj, QEvent* event) { if (obj == parentWidget() && (event->type() == QEvent::Resize || event->type() == QEvent::Move)) { if (isVisible()) reposition(); } return QWidget::eventFilter(obj, event); } void ToastOverlay::mousePressEvent(QMouseEvent* event) { hide(); // 点击立即关闭 timer_->stop(); QWidget::mousePressEvent(event); } void showToast(QWidget* anchorWindow, const QString& msg) { if (!anchorWindow) return; QWidget* win = anchorWindow->window(); // 取顶层窗口作为锚 if (!win) return; // 同一窗口复用一个 overlay:首次创建并以窗口为父,后续按 objectName 找回直接替换文案。 auto* overlay = win->findChild(QStringLiteral("toastCard")); if (!overlay) overlay = new ToastOverlay(win); overlay->showMessage(msg); } } // namespace geopro::app