| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297 |
- #include "bubbletip.h"
- #include <QApplication>
- #include <QGraphicsOpacityEffect>
- #include <QPainter>
- #include <QPainterPath>
- #include <QScreen>
- #include <QVBoxLayout>
- #include "qcolor.h"
- #include "qnamespace.h"
- #include <thememanager.h>
- QQueue<BubbleTip*> BubbleTip::m_queue;
- QMap<QWidget*, QMap<BubbleTip::Position, QQueue<BubbleTip*>>> 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<Position, QQueue<BubbleTip*>>& 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();
- }
|