108 lines
3.6 KiB
C++
108 lines
3.6 KiB
C++
#include "ToastOverlay.hpp"
|
||
|
||
#include <QEvent>
|
||
#include <QHBoxLayout>
|
||
#include <QLabel>
|
||
#include <QMouseEvent>
|
||
#include <QTimer>
|
||
|
||
#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<ToastOverlay*>(QStringLiteral("toastCard"));
|
||
if (!overlay) overlay = new ToastOverlay(win);
|
||
overlay->showMessage(msg);
|
||
}
|
||
|
||
} // namespace geopro::app
|