#include "Message.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include namespace Psc::Message { static Config global_config; class Toast; class ResizeWatcher; static QHash>> containers; static QHash> watchers; static QHash>> 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 ThemeColors { QColor bg; QColor text; QColor progress_bg; QColor progress_fg; }; static ThemeColors 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; } ThemeColors 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; ybounded(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 ThemeColors& 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{}; ThemeColors 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].isEmpty()) return; auto item = queues[top].dequeue(); 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.removeFirst(); 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].enqueue({type,text}); QTimer::singleShot(global_config.queue_interval, top, [top,corner](){ process_queue(top,corner); }); } void set_config(const Config& cfg) { global_config=cfg; } } // namespace Message