#include "bubbletip.h" #include #include #include #include #include #include #include "qcolor.h" #include "qnamespace.h" #include QQueue BubbleTip::m_queue; QMap>> BubbleTip::m_targetMap; BubbleTip::BubbleTip(QWidget* target, const QString& text, Position position, int duration, Type type, QWidget* parent) : QWidget(parent) , m_target(target) , m_position(position) , m_type(type) , m_label(new QLabel(text, this)) , m_timer(new QTimer(this)) , m_verticalOffset(0) { setWindowFlags(Qt::ToolTip | Qt::FramelessWindowHint); setAttribute(Qt::WA_TranslucentBackground); setAttribute(Qt::WA_ShowWithoutActivating); m_label->setAlignment(Qt::AlignCenter); QString textColor; switch (m_type) { case Success: textColor = "colorSuccess"; break; case Warning: textColor = "colorWarning"; break; case Error: textColor = "colorError"; break; case Info: textColor = "colorInfo"; break; default: textColor = "colorText"; break; } textColor = "colorText"; m_label->setStyleSheet( QString("color: %1;").arg(ThemeManager::instance().color(textColor).name())); QVBoxLayout* layout = new QVBoxLayout(this); layout->setContentsMargins(10, 8, 10, 8); layout->addWidget(m_label); setLayout(layout); if (m_target) { m_target->installEventFilter(this); m_targetMap[m_target][m_position].enqueue(this); } adjustSize(); updatePosition(); startShowAnimation(); if (duration > 0) { m_timer->setSingleShot(true); connect(m_timer, &QTimer::timeout, this, &BubbleTip::startHideAnimation); m_timer->start(duration); } m_queue.enqueue(this); adjustTipsForTarget(m_target); // 关键:立即调整所有提示的位置 } void BubbleTip::paintEvent(QPaintEvent* event) { Q_UNUSED(event); QPainter painter(this); painter.setRenderHint(QPainter::Antialiasing); // 获取背景和边框颜色 QColor bgColor = ThemeManager::instance().color("colorBgElevated"); QColor borderColor; // 根据类型选择边框颜色 switch (m_type) { case Success: borderColor = ThemeManager::instance().color("colorSuccessBorder"); break; case Warning: borderColor = ThemeManager::instance().color("colorWarningBorder"); break; case Error: borderColor = ThemeManager::instance().color("colorErrorBorder"); break; case Info: borderColor = ThemeManager::instance().color("colorInfoBorder"); break; default: borderColor = ThemeManager::instance().color("colorBorder"); break; } // 绘制背景 QPainterPath path; path.addRoundedRect(rect(), 8, 8); // 添加轻微阴影效果 // painter.setPen(Qt::NoPen); // painter.setBrush(QColor(0, 0, 0, 15)); // painter.drawRoundedRect(rect().adjusted(2, 2, 2, 2), 8, 8); // 绘制主体 painter.fillPath(path, bgColor); // 绘制边框 QPen pen(borderColor); pen.setWidth(1); pen.setStyle(Qt::SolidLine); painter.setPen(pen); painter.drawPath(path); // 如果是有类型的提示,添加左侧彩色条 if (m_type != Default) { QRect indicatorRect = QRect(rect().left() + 1, rect().top() + 1, 4, rect().height() - 2); QPainterPath indicatorPath; indicatorPath.addRoundedRect(indicatorRect, 2, 2); painter.fillPath(indicatorPath, borderColor); } } void BubbleTip::updatePosition() { if (!m_target) return; const int spacing = 5; QPoint basePos; // Step 1: 计算目标控件的全局坐标 switch (m_position) { case Top: basePos = m_target->mapToGlobal(QPoint(0, 0)); break; case Bottom: basePos = m_target->mapToGlobal(QPoint(0, m_target->height())); break; case Left: basePos = m_target->mapToGlobal(QPoint(-width(), 0)); break; case Right: basePos = m_target->mapToGlobal(QPoint(m_target->width(), 0)); break; case Center: basePos = m_target->mapToGlobal(QPoint(m_target->width() / 2, m_target->height() / 2)); break; } // Step 2: 转换为父窗口坐标(如果存在父窗口) QWidget* parent = parentWidget(); if (parent) { basePos = parent->mapFromGlobal(basePos); } // Step 3: 根据位置类型和偏移量计算最终坐标 int x = 0, y = 0; switch (m_position) { case Top: x = basePos.x() + (m_target->width() - width()) / 2; y = basePos.y() + spacing + m_verticalOffset; break; case Bottom: x = basePos.x() + (m_target->width() - width()) / 2; y = basePos.y() - height() - spacing + m_verticalOffset; break; case Left: x = basePos.x() + width() + spacing; y = basePos.y() + (m_target->height() - height()) / 2 + m_verticalOffset; break; case Right: x = basePos.x() - width() - spacing; y = basePos.y() + (m_target->height() - height()) / 2 + m_verticalOffset; break; case Center: x = basePos.x() - width() / 2; y = basePos.y() - height() / 2 + m_verticalOffset; break; } // Step 4: 边界约束(防止超出屏幕) QRect screenGeometry = QApplication::primaryScreen()->availableGeometry(); if (parent) { screenGeometry = parent->rect(); } x = qMax(0, qMin(x, screenGeometry.width() - width())); y = qMax(0, qMin(y, screenGeometry.height() - height())); move(x, y); } void BubbleTip::startShowAnimation() { QGraphicsOpacityEffect* effect = new QGraphicsOpacityEffect(this); setGraphicsEffect(effect); QPropertyAnimation* anim = new QPropertyAnimation(effect, "opacity"); anim->setDuration(300); anim->setStartValue(0); anim->setEndValue(1); anim->start(QPropertyAnimation::DeleteWhenStopped); show(); } void BubbleTip::startHideAnimation() { QGraphicsOpacityEffect* effect = new QGraphicsOpacityEffect(this); setGraphicsEffect(effect); QPropertyAnimation* anim = new QPropertyAnimation(effect, "opacity"); anim->setDuration(300); anim->setStartValue(1); anim->setEndValue(0); connect(anim, &QPropertyAnimation::finished, [this]() { m_queue.removeOne(this); m_targetMap[m_target][m_position].removeOne(this); adjustTipsForTarget(m_target); // 隐藏后调整剩余提示 deleteLater(); }); anim->start(QPropertyAnimation::DeleteWhenStopped); } void BubbleTip::adjustTipsForTarget(QWidget* target) { if (!target || !m_targetMap.contains(target)) return; const int spacing = 8; // 统一间距 QMap>& positionGroups = m_targetMap[target]; // 独立处理每个位置组 auto adjustGroup = [&](Position pos, auto offsetCalculator) { if (!positionGroups.contains(pos)) return; int cumulativeOffset = 0; for (BubbleTip* tip : positionGroups[pos]) { tip->m_verticalOffset = offsetCalculator(cumulativeOffset); cumulativeOffset += tip->height() + spacing; tip->updatePosition(); } }; // 顶部提示:向上堆叠(负偏移) adjustGroup(Top, [](int offset) { return offset; }); // 底部提示:向下堆叠(正偏移) adjustGroup(Bottom, [](int offset) { return -offset; }); // 左右提示:垂直居中偏移 adjustGroup(Left, [](int offset) { return offset; }); adjustGroup(Right, [](int offset) { return offset; }); } bool BubbleTip::eventFilter(QObject* watched, QEvent* event) { if (watched == m_target && (event->type() == QEvent::Move || event->type() == QEvent::Resize)) { adjustTipsForTarget(m_target); // 目标移动时立即调整位置 } return QWidget::eventFilter(watched, event); } void BubbleTip::showTip(QWidget* target, const QString& text, Position position, int duration, Type type, QWidget* parent) { new BubbleTip(target, text, position, duration, type, parent); } void BubbleTip::hideAll() { for (BubbleTip* tip : m_queue) { tip->startHideAnimation(); } m_queue.clear(); m_targetMap.clear(); }