bubbletip.cpp 8.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297
  1. #include "bubbletip.h"
  2. #include <QApplication>
  3. #include <QGraphicsOpacityEffect>
  4. #include <QPainter>
  5. #include <QPainterPath>
  6. #include <QScreen>
  7. #include <QVBoxLayout>
  8. #include "qcolor.h"
  9. #include "qnamespace.h"
  10. #include <thememanager.h>
  11. QQueue<BubbleTip*> BubbleTip::m_queue;
  12. QMap<QWidget*, QMap<BubbleTip::Position, QQueue<BubbleTip*>>> BubbleTip::m_targetMap;
  13. BubbleTip::BubbleTip(QWidget* target,
  14. const QString& text,
  15. Position position,
  16. int duration,
  17. Type type,
  18. QWidget* parent)
  19. : QWidget(parent)
  20. , m_target(target)
  21. , m_position(position)
  22. , m_type(type)
  23. , m_label(new QLabel(text, this))
  24. , m_timer(new QTimer(this))
  25. , m_verticalOffset(0)
  26. {
  27. setWindowFlags(Qt::ToolTip | Qt::FramelessWindowHint);
  28. setAttribute(Qt::WA_TranslucentBackground);
  29. setAttribute(Qt::WA_ShowWithoutActivating);
  30. m_label->setAlignment(Qt::AlignCenter);
  31. QString textColor;
  32. switch (m_type) {
  33. case Success:
  34. textColor = "colorSuccess";
  35. break;
  36. case Warning:
  37. textColor = "colorWarning";
  38. break;
  39. case Error:
  40. textColor = "colorError";
  41. break;
  42. case Info:
  43. textColor = "colorInfo";
  44. break;
  45. default:
  46. textColor = "colorText";
  47. break;
  48. }
  49. textColor = "colorText";
  50. m_label->setStyleSheet(
  51. QString("color: %1;").arg(ThemeManager::instance().color(textColor).name()));
  52. QVBoxLayout* layout = new QVBoxLayout(this);
  53. layout->setContentsMargins(10, 8, 10, 8);
  54. layout->addWidget(m_label);
  55. setLayout(layout);
  56. if (m_target) {
  57. m_target->installEventFilter(this);
  58. m_targetMap[m_target][m_position].enqueue(this);
  59. }
  60. adjustSize();
  61. updatePosition();
  62. startShowAnimation();
  63. if (duration > 0) {
  64. m_timer->setSingleShot(true);
  65. connect(m_timer, &QTimer::timeout, this, &BubbleTip::startHideAnimation);
  66. m_timer->start(duration);
  67. }
  68. m_queue.enqueue(this);
  69. adjustTipsForTarget(m_target); // 关键:立即调整所有提示的位置
  70. }
  71. void BubbleTip::paintEvent(QPaintEvent* event)
  72. {
  73. Q_UNUSED(event);
  74. QPainter painter(this);
  75. painter.setRenderHint(QPainter::Antialiasing);
  76. // 获取背景和边框颜色
  77. QColor bgColor = ThemeManager::instance().color("colorBgElevated");
  78. QColor borderColor;
  79. // 根据类型选择边框颜色
  80. switch (m_type) {
  81. case Success:
  82. borderColor = ThemeManager::instance().color("colorSuccessBorder");
  83. break;
  84. case Warning:
  85. borderColor = ThemeManager::instance().color("colorWarningBorder");
  86. break;
  87. case Error:
  88. borderColor = ThemeManager::instance().color("colorErrorBorder");
  89. break;
  90. case Info:
  91. borderColor = ThemeManager::instance().color("colorInfoBorder");
  92. break;
  93. default:
  94. borderColor = ThemeManager::instance().color("colorBorder");
  95. break;
  96. }
  97. // 绘制背景
  98. QPainterPath path;
  99. path.addRoundedRect(rect(), 8, 8);
  100. // 添加轻微阴影效果
  101. // painter.setPen(Qt::NoPen);
  102. // painter.setBrush(QColor(0, 0, 0, 15));
  103. // painter.drawRoundedRect(rect().adjusted(2, 2, 2, 2), 8, 8);
  104. // 绘制主体
  105. painter.fillPath(path, bgColor);
  106. // 绘制边框
  107. QPen pen(borderColor);
  108. pen.setWidth(1);
  109. pen.setStyle(Qt::SolidLine);
  110. painter.setPen(pen);
  111. painter.drawPath(path);
  112. // 如果是有类型的提示,添加左侧彩色条
  113. if (m_type != Default) {
  114. QRect indicatorRect = QRect(rect().left() + 1, rect().top() + 1, 4, rect().height() - 2);
  115. QPainterPath indicatorPath;
  116. indicatorPath.addRoundedRect(indicatorRect, 2, 2);
  117. painter.fillPath(indicatorPath, borderColor);
  118. }
  119. }
  120. void BubbleTip::updatePosition()
  121. {
  122. if (!m_target)
  123. return;
  124. const int spacing = 5;
  125. QPoint basePos;
  126. // Step 1: 计算目标控件的全局坐标
  127. switch (m_position) {
  128. case Top:
  129. basePos = m_target->mapToGlobal(QPoint(0, 0));
  130. break;
  131. case Bottom:
  132. basePos = m_target->mapToGlobal(QPoint(0, m_target->height()));
  133. break;
  134. case Left:
  135. basePos = m_target->mapToGlobal(QPoint(-width(), 0));
  136. break;
  137. case Right:
  138. basePos = m_target->mapToGlobal(QPoint(m_target->width(), 0));
  139. break;
  140. case Center:
  141. basePos = m_target->mapToGlobal(QPoint(m_target->width() / 2, m_target->height() / 2));
  142. break;
  143. }
  144. // Step 2: 转换为父窗口坐标(如果存在父窗口)
  145. QWidget* parent = parentWidget();
  146. if (parent) {
  147. basePos = parent->mapFromGlobal(basePos);
  148. }
  149. // Step 3: 根据位置类型和偏移量计算最终坐标
  150. int x = 0, y = 0;
  151. switch (m_position) {
  152. case Top:
  153. x = basePos.x() + (m_target->width() - width()) / 2;
  154. y = basePos.y() + spacing + m_verticalOffset;
  155. break;
  156. case Bottom:
  157. x = basePos.x() + (m_target->width() - width()) / 2;
  158. y = basePos.y() - height() - spacing + m_verticalOffset;
  159. break;
  160. case Left:
  161. x = basePos.x() + width() + spacing;
  162. y = basePos.y() + (m_target->height() - height()) / 2 + m_verticalOffset;
  163. break;
  164. case Right:
  165. x = basePos.x() - width() - spacing;
  166. y = basePos.y() + (m_target->height() - height()) / 2 + m_verticalOffset;
  167. break;
  168. case Center:
  169. x = basePos.x() - width() / 2;
  170. y = basePos.y() - height() / 2 + m_verticalOffset;
  171. break;
  172. }
  173. // Step 4: 边界约束(防止超出屏幕)
  174. QRect screenGeometry = QApplication::primaryScreen()->availableGeometry();
  175. if (parent) {
  176. screenGeometry = parent->rect();
  177. }
  178. x = qMax(0, qMin(x, screenGeometry.width() - width()));
  179. y = qMax(0, qMin(y, screenGeometry.height() - height()));
  180. move(x, y);
  181. }
  182. void BubbleTip::startShowAnimation()
  183. {
  184. QGraphicsOpacityEffect* effect = new QGraphicsOpacityEffect(this);
  185. setGraphicsEffect(effect);
  186. QPropertyAnimation* anim = new QPropertyAnimation(effect, "opacity");
  187. anim->setDuration(300);
  188. anim->setStartValue(0);
  189. anim->setEndValue(1);
  190. anim->start(QPropertyAnimation::DeleteWhenStopped);
  191. show();
  192. }
  193. void BubbleTip::startHideAnimation()
  194. {
  195. QGraphicsOpacityEffect* effect = new QGraphicsOpacityEffect(this);
  196. setGraphicsEffect(effect);
  197. QPropertyAnimation* anim = new QPropertyAnimation(effect, "opacity");
  198. anim->setDuration(300);
  199. anim->setStartValue(1);
  200. anim->setEndValue(0);
  201. connect(anim, &QPropertyAnimation::finished, [this]() {
  202. m_queue.removeOne(this);
  203. m_targetMap[m_target][m_position].removeOne(this);
  204. adjustTipsForTarget(m_target); // 隐藏后调整剩余提示
  205. deleteLater();
  206. });
  207. anim->start(QPropertyAnimation::DeleteWhenStopped);
  208. }
  209. void BubbleTip::adjustTipsForTarget(QWidget* target)
  210. {
  211. if (!target || !m_targetMap.contains(target))
  212. return;
  213. const int spacing = 8; // 统一间距
  214. QMap<Position, QQueue<BubbleTip*>>& positionGroups = m_targetMap[target];
  215. // 独立处理每个位置组
  216. auto adjustGroup = [&](Position pos, auto offsetCalculator) {
  217. if (!positionGroups.contains(pos))
  218. return;
  219. int cumulativeOffset = 0;
  220. for (BubbleTip* tip : positionGroups[pos]) {
  221. tip->m_verticalOffset = offsetCalculator(cumulativeOffset);
  222. cumulativeOffset += tip->height() + spacing;
  223. tip->updatePosition();
  224. }
  225. };
  226. // 顶部提示:向上堆叠(负偏移)
  227. adjustGroup(Top, [](int offset) { return offset; });
  228. // 底部提示:向下堆叠(正偏移)
  229. adjustGroup(Bottom, [](int offset) { return -offset; });
  230. // 左右提示:垂直居中偏移
  231. adjustGroup(Left, [](int offset) { return offset; });
  232. adjustGroup(Right, [](int offset) { return offset; });
  233. }
  234. bool BubbleTip::eventFilter(QObject* watched, QEvent* event)
  235. {
  236. if (watched == m_target && (event->type() == QEvent::Move || event->type() == QEvent::Resize)) {
  237. adjustTipsForTarget(m_target); // 目标移动时立即调整位置
  238. }
  239. return QWidget::eventFilter(watched, event);
  240. }
  241. void BubbleTip::showTip(QWidget* target,
  242. const QString& text,
  243. Position position,
  244. int duration,
  245. Type type,
  246. QWidget* parent)
  247. {
  248. new BubbleTip(target, text, position, duration, type, parent);
  249. }
  250. void BubbleTip::hideAll()
  251. {
  252. for (BubbleTip* tip : m_queue) {
  253. tip->startHideAnimation();
  254. }
  255. m_queue.clear();
  256. m_targetMap.clear();
  257. }