#include "Message.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include namespace Flex_Qt::Message { static Config global_config; class Toast; class Resize_Watcher; static std::unordered_map>> containers; static std::unordered_map> watchers; static std::unordered_map>> queues; /* -------------------- 工具函数 -------------------- */ static bool is_dark_palette() { QColor c = QApplication::palette().window().color(); return c.lightness() < 128; } static QColor interpolate(const QColor& a, const QColor& b, double t) { return QColor( a.red() + (b.red() - a.red()) * t, a.green() + (b.green() - a.green()) * t, a.blue() + (b.blue() - a.blue()) * t, a.alpha() + (b.alpha() - a.alpha()) * t); } struct Theme_Colors { QColor bg; QColor text; QColor progress_bg; QColor progress_fg; }; static Theme_Colors theme_for(Type t) { bool dark; if (global_config.theme == Theme_Mode::Auto) dark = is_dark_palette(); else dark = (global_config.theme == Theme_Mode::Dark); QColor base; switch (t) { case success: base = QColor("#52C41A"); break; case error: base = QColor("#F5222D"); break; case warning: base = QColor("#FAAD14"); break; default: base = QColor("#1890FF"); break; } Theme_Colors c; if (dark) { c.bg = base.darker(250); c.bg.setAlpha(210); c.text = Qt::white; c.progress_fg = base; c.progress_bg = QColor(255, 255, 255, 30); } else { c.bg = base.lighter(180); c.bg.setAlpha(220); c.text = QColor(30, 30, 30); c.progress_fg = base.darker(110); c.progress_bg = QColor(0, 0, 0, 20); } return c; } /* -------------------- 毛玻璃 -------------------- */ static QPixmap blur_pixmap(const QPixmap& src, int radius) { QGraphicsScene scene; QGraphicsPixmapItem item; item.setPixmap(src); QGraphicsBlurEffect blur; blur.setBlurRadius(radius); item.setGraphicsEffect(&blur); scene.addItem(&item); QImage result(src.size(), QImage::Format_ARGB32); result.fill(Qt::transparent); QPainter painter(&result); scene.render(&painter); return QPixmap::fromImage(result); } static QPixmap generate_noise(int w, int h) { QImage img(w, h, QImage::Format_ARGB32); for (int y = 0; y < h; ++y) for (int x = 0; x < w; ++x) { int gray = QRandomGenerator::global()->bounded(20); img.setPixelColor(x, y, QColor(255, 255, 255, gray)); } return QPixmap::fromImage(img); } /* -------------------- Toast -------------------- */ class Toast : public QWidget { public: Toast(Type t, const QString& text, QWidget* parent, int duration) : QWidget(parent), type(t), duration_ms(duration) { setAttribute(Qt::WA_DeleteOnClose); setWindowFlags(Qt::FramelessWindowHint); auto shadow = new QGraphicsDropShadowEffect(this); shadow->setBlurRadius(30); shadow->setOffset(0, 8); shadow->setColor(QColor(0, 0, 0, 120)); setGraphicsEffect(shadow); opacity = new QGraphicsOpacityEffect(this); setGraphicsEffect(opacity); opacity->setOpacity(0); fade = new QPropertyAnimation(opacity, "opacity", this); fade->setDuration(200 * global_config.animation_speed); move_anim = new QPropertyAnimation(this, "pos", this); move_anim->setDuration(400 * global_config.animation_speed); move_anim->setEasingCurve(QEasingCurve::OutBack); label = new QLabel(text, this); label->setWordWrap(true); label->setMaximumWidth(global_config.max_width); progress = new QProgressBar(this); progress->setRange(0, duration_ms); progress->setValue(duration_ms); progress->setTextVisible(false); progress->setFixedHeight(3); auto layout = new QVBoxLayout(this); layout->setContentsMargins(16, 12, 16, 8); layout->addWidget(label); layout->addWidget(progress); adjustSize(); apply_theme(theme_for(type)); start_timer(); } void appear_from(const QPoint& start, const QPoint& end) { prepare_blur(); move(start); show(); fade->setStartValue(0); fade->setEndValue(1); fade->start(); move_anim->setStartValue(start); move_anim->setEndValue(end); move_anim->start(); } void move_to(const QPoint& target) { if (pos() == target) return; move_anim->stop(); move_anim->setStartValue(pos()); move_anim->setEndValue(target); move_anim->start(); } void disappear() { fade->setStartValue(1); fade->setEndValue(0); connect(fade, &QPropertyAnimation::finished, this, &QWidget::deleteLater); fade->start(); } protected: void mousePressEvent(QMouseEvent*) override { disappear(); } void enterEvent(QEvent*) override { if (timer) timer->stop(); } void leaveEvent(QEvent*) override { if (timer) timer->start(); } void paintEvent(QPaintEvent* e) override { QPainter painter(this); painter.setRenderHint(QPainter::Antialiasing); QRectF r = rect(); QPainterPath path; path.addRoundedRect(r, 12, 12); // 1️⃣ 裁剪圆角区域 painter.setClipPath(path); // 2️⃣ 画毛玻璃 if (global_config.enable_blur && !blurred_background.isNull()) { painter.drawPixmap(r.toRect(), blurred_background); } // 3️⃣ 半透明背景 painter.fillPath(path, current_theme.bg); // 4️⃣ Acrylic 噪点 if (global_config.acrylic_mode) { painter.drawPixmap(r.toRect(), generate_noise(width(), height())); } // 5️⃣ 解除裁剪 painter.setClipping(false); // 6️⃣ 调用基类,让子控件正常绘制 QWidget::paintEvent(e); } private: void prepare_blur() { if (!global_config.enable_blur) return; QWidget* p = parentWidget(); if (!p) return; QPixmap grab = p->grab(geometry()); blurred_background = blur_pixmap(grab, 20); } void apply_theme(const Theme_Colors& c) { current_theme = c; label->setStyleSheet(QString( "background:transparent;color:%1;") .arg(c.text.name())); progress->setStyleSheet(QString( "QProgressBar{background:%1;border:none;}" "QProgressBar::chunk{background:%2;border-radius:2px;}") .arg(c.progress_bg.name(QColor::HexArgb)) .arg(c.progress_fg.name())); } void start_timer() { if (!timer) { timer = new QTimer(this); timer->setInterval(16); connect(timer, &QTimer::timeout, this, [this]() { elapsed_ms += 16; int remain = duration_ms - elapsed_ms; progress->setValue(std::max(0, remain)); if (remain <= 0) { timer->stop(); disappear(); } }); } timer->start(); } Type type; int duration_ms; int elapsed_ms = 0; QLabel* label{}; QProgressBar* progress{}; QTimer* timer{}; QGraphicsOpacityEffect* opacity{}; QPropertyAnimation* fade{}; QPropertyAnimation* move_anim{}; Theme_Colors current_theme; QPixmap blurred_background; }; /* -------------------- 布局 -------------------- */ static QWidget* top_parent(QWidget* w) { while (w && w->parentWidget()) w = w->parentWidget(); return w; } static void relayout(QWidget* top, Corner corner) { auto& list = containers[top]; constexpr int margin = 24; constexpr int spacing = 16; int y = (corner == Top_Left || corner == Top_Right) ? margin : top->height() - margin; for (auto& toast : list) { if (!toast) continue; QPoint target; if (corner == Top_Left) { target = {margin, y}; y += toast->height() + spacing; } else if (corner == Top_Right) { target = {top->width() - toast->width() - margin, y}; y += toast->height() + spacing; } else if (corner == Bottom_Left) { y -= toast->height(); target = {margin, y}; y -= spacing; } else { y -= toast->height(); target = {top->width() - toast->width() - margin, y}; y -= spacing; } toast->move_to(target); } } static void process_queue(QWidget* top, Corner corner) { if (queues[top].empty()) return; auto item = std::move(queues[top].front()); queues[top].pop(); auto* toast = new Toast( item.first, item.second, top, global_config.default_duration); auto& list = containers[top]; constexpr int margin = 24; constexpr int spacing = 16; // --------- 1️⃣ 计算最终位置 ---------- int y = (corner == Top_Left || corner == Top_Right) ? margin : top->height() - margin; for (auto& t : list) { if (!t) continue; if (corner == Top_Left || corner == Top_Right) y += t->height() + spacing; else y -= t->height() + spacing; } QPoint final_pos; if (corner == Top_Left) final_pos = QPoint(margin, y); else if (corner == Top_Right) final_pos = QPoint(top->width() - toast->width() - margin, y); else if (corner == Bottom_Left) final_pos = QPoint(margin, y - toast->height()); else final_pos = QPoint(top->width() - toast->width() - margin, y - toast->height()); // --------- 2️⃣ 立即定位到 final ---------- toast->move(final_pos); // --------- 3️⃣ 加入容器 ---------- list.push_back(toast); // --------- 4️⃣ 计算飞入起点 ---------- QPoint start = final_pos; if (corner == Top_Right || corner == Bottom_Right) start.setX(top->width()); else start.setX(-toast->width()); // --------- 5️⃣ 飞入 ---------- toast->appear_from(start, final_pos); // --------- 6️⃣ 超限处理 ---------- if (list.size() > global_config.max_count) { auto old = list.front(); list.erase(list.begin()); if (old) old->disappear(); } // --------- 7️⃣ 旧 toast 重排 ---------- relayout(top, corner); } /* -------------------- 公共接口 -------------------- */ void show(Type type, const QString& text, QWidget* parent, int, Corner corner) { if (!parent) return; QWidget* top = top_parent(parent); if (!top) return; queues[top].push({type, text}); QTimer::singleShot(global_config.queue_interval, top, [top, corner]() { process_queue(top, corner); }); } void set_config(const Config& cfg) { global_config = cfg; } }