functionbutton.cpp 17 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575
  1. #include "functionbutton.h"
  2. #include <QAbstractAnimation>
  3. #include <QApplication>
  4. #include <QGuiApplication>
  5. #include <QPropertyAnimation>
  6. #include <QScreen>
  7. #include <QDebug>
  8. #include <QResizeEvent>
  9. namespace Colors {
  10. // Element Plus 主题色
  11. const QColor Primary = QColor(64, 158, 255); // #409EFF
  12. const QColor PrimaryLight = QColor(144, 202, 249); // #90CAF9
  13. const QColor PrimaryDark = QColor(25, 118, 210); // #1976D2
  14. // 功能色
  15. const QColor Success = QColor(103, 194, 58); // #67C23A
  16. const QColor Warning = QColor(230, 162, 60); // #E6A23C
  17. const QColor Danger = QColor(245, 108, 108); // #F56C6C
  18. const QColor Info = QColor(144, 147, 153); // #909399
  19. // 中性色
  20. const QColor TextPrimary = QColor(48, 49, 51); // #303133
  21. const QColor TextRegular = QColor(96, 98, 102); // #606266
  22. const QColor TextSecondary = QColor(144, 147, 153); // #909399
  23. const QColor TextPlaceholder = QColor(192, 196, 204); // #C0C4CC
  24. // 边框色
  25. const QColor BorderBase = QColor(220, 223, 230); // #DCDFE6
  26. const QColor BorderLight = QColor(228, 231, 237); // #E4E7ED
  27. const QColor BorderLighter = QColor(235, 238, 245); // #EBEEF5
  28. const QColor BorderExtraLight = QColor(244, 245, 247); // #F4F5F7
  29. // 背景色
  30. const QColor Background = Qt::white; // #FFFFFF
  31. const QColor BackgroundPage = QColor(245, 245, 245); // #F5F5F5
  32. const QColor BackgroundBase = QColor(248, 249, 250); // #F8F9FA
  33. // 兼容旧版本
  34. const QColor PrimaryHover = PrimaryLight;
  35. const QColor PrimaryPressed = PrimaryDark;
  36. const QColor Border = BorderBase;
  37. const QColor Text = TextPrimary;
  38. const QColor SecondaryText = TextSecondary;
  39. const QColor Shadow = QColor(0, 0, 0, 60);
  40. const QColor Indicator = Info;
  41. } // namespace Colors
  42. namespace Fonts {
  43. static QFont buttonFont()
  44. {
  45. QFont font;
  46. font.setPointSize(10);
  47. font.setWeight(QFont::Medium);
  48. return font;
  49. }
  50. } // namespace Fonts
  51. // PopoverTriggerButton 实现
  52. PopoverTriggerButton::PopoverTriggerButton(QWidget* parent)
  53. : QPushButton(parent)
  54. {
  55. setCursor(Qt::PointingHandCursor);
  56. setSizePolicy(QSizePolicy::Fixed, QSizePolicy::Fixed);
  57. setFixedSize(20, 70);
  58. setMinimumSize(20, 70);
  59. setMaximumSize(20, 70);
  60. }
  61. QSize PopoverTriggerButton::sizeHint() const
  62. {
  63. return QSize(20, 70); // 强制返回固定大小
  64. }
  65. QSize PopoverTriggerButton::minimumSizeHint() const
  66. {
  67. return QSize(20, 70); // 强制返回固定最小大小
  68. }
  69. void PopoverTriggerButton::resizeEvent(QResizeEvent* event)
  70. {
  71. // 强制保持固定大小,忽略任何外部尺寸变化
  72. if (size() != QSize(20, 70)) {
  73. setFixedSize(20, 70);
  74. }
  75. QPushButton::resizeEvent(event);
  76. }
  77. Popover::Popover(QWidget* parent)
  78. : QWidget(parent)
  79. {
  80. // 使用更安全的窗口标志组合,避免分层窗口问题
  81. setWindowFlags(Qt::Tool | Qt::FramelessWindowHint | Qt::WindowStaysOnTopHint);
  82. // 移除透明背景属性,避免UpdateLayeredWindowIndirect错误
  83. // setAttribute(Qt::WA_TranslucentBackground);
  84. // 设置窗口属性
  85. setAttribute(Qt::WA_DeleteOnClose, false);
  86. setAttribute(Qt::WA_ShowWithoutActivating, true);
  87. setAttribute(Qt::WA_X11NetWmWindowTypePopupMenu, true);
  88. // 暂时移除阴影效果,避免分层窗口问题
  89. // 我们将在paintEvent中手动绘制阴影
  90. /*
  91. auto shadow = new QGraphicsDropShadowEffect(this);
  92. shadow->setBlurRadius(qMax(0.0, 20.0));
  93. shadow->setColor(Colors::Shadow);
  94. shadow->setXOffset(0);
  95. shadow->setYOffset(qMax(0.0, 5.0));
  96. setGraphicsEffect(shadow);
  97. */
  98. mainLayout = new QVBoxLayout(this);
  99. mainLayout->setContentsMargins(25, 25, 25, 25);
  100. mainLayout->setSpacing(10);
  101. // 移除样式表,使用paintEvent手动绘制背景
  102. // setStyleSheet("QWidget { background-color: white; border: 1px solid #e0e0e0; border-radius: 8px; }");
  103. // 连接应用程序状态变化信号
  104. connect(qApp, &QApplication::applicationStateChanged, this, [this](Qt::ApplicationState state) {
  105. if (state != Qt::ApplicationActive && isVisible()) {
  106. hideAnimated();
  107. }
  108. });
  109. // 安装事件过滤器以检测点击外部区域
  110. qApp->installEventFilter(this);
  111. }
  112. Popover::~Popover()
  113. {
  114. // 移除事件过滤器
  115. qApp->removeEventFilter(this);
  116. }
  117. void Popover::setContentWidget(QWidget* widget)
  118. {
  119. // 清除现有内容
  120. QLayoutItem* child;
  121. while ((child = mainLayout->takeAt(0)) != nullptr) {
  122. delete child->widget();
  123. delete child;
  124. }
  125. // 添加新内容
  126. mainLayout->addWidget(widget);
  127. // 动态计算最小尺寸,考虑阴影边距
  128. if (widget->minimumSize().isValid()) {
  129. QMargins margins = mainLayout->contentsMargins();
  130. int extraWidth = margins.left() + margins.right();
  131. int extraHeight = margins.top() + margins.bottom();
  132. setMinimumSize(widget->minimumSize() + QSize(extraWidth, extraHeight));
  133. }
  134. }
  135. void Popover::showAnimated(const QPoint& position)
  136. {
  137. setOpacity(0.0);
  138. // 确保窗口尺寸有效
  139. if (width() <= 0 || height() <= 0) {
  140. adjustSize();
  141. }
  142. // 确保位置在屏幕范围内
  143. QScreen* screen = QGuiApplication::screenAt(position);
  144. if (!screen) {
  145. screen = QGuiApplication::primaryScreen();
  146. }
  147. if (screen) {
  148. QRect screenGeometry = screen->availableGeometry();
  149. QPoint adjustedPos = position;
  150. // 计算阴影边距,确保为正值
  151. QMargins shadowMargins = this->shadowMargins();
  152. int leftMargin = qMax(0, shadowMargins.left());
  153. int topMargin = qMax(0, shadowMargins.top());
  154. int rightMargin = qMax(0, shadowMargins.right());
  155. int bottomMargin = qMax(0, shadowMargins.bottom());
  156. // 确保窗口完全在屏幕内
  157. int minX = screenGeometry.left() + leftMargin;
  158. int minY = screenGeometry.top() + topMargin;
  159. int maxX = screenGeometry.right() - width() - rightMargin;
  160. int maxY = screenGeometry.bottom() - height() - bottomMargin;
  161. // 调整X坐标
  162. adjustedPos.setX(qBound(minX, adjustedPos.x(), maxX));
  163. // 调整Y坐标
  164. adjustedPos.setY(qBound(minY, adjustedPos.y(), maxY));
  165. // 最终验证位置的有效性
  166. if (adjustedPos.x() >= 0 && adjustedPos.y() >= 0) {
  167. move(adjustedPos);
  168. } else {
  169. // 如果计算出的位置仍然无效,使用屏幕中心
  170. QPoint centerPos = screenGeometry.center() - QPoint(width() / 2, height() / 2);
  171. move(centerPos);
  172. }
  173. } else {
  174. // 如果无法获取屏幕信息,确保位置为正值
  175. QPoint safePos = QPoint(qMax(0, position.x()), qMax(0, position.y()));
  176. move(safePos);
  177. }
  178. show();
  179. QPropertyAnimation* animation = new QPropertyAnimation(this, "opacity");
  180. animation->setDuration(200);
  181. animation->setStartValue(0.0);
  182. animation->setEndValue(1.0);
  183. animation->setEasingCurve(QEasingCurve::OutCubic);
  184. animation->start(QAbstractAnimation::DeleteWhenStopped);
  185. }
  186. void Popover::hideAnimated()
  187. {
  188. QPropertyAnimation* animation = new QPropertyAnimation(this, "opacity");
  189. animation->setDuration(150);
  190. animation->setStartValue(1.0);
  191. animation->setEndValue(0.0);
  192. animation->setEasingCurve(QEasingCurve::InCubic);
  193. connect(animation, &QPropertyAnimation::finished, this, &Popover::hide);
  194. animation->start(QAbstractAnimation::DeleteWhenStopped);
  195. }
  196. void Popover::setOpacity(qreal opacity)
  197. {
  198. m_opacity = opacity;
  199. setWindowOpacity(opacity);
  200. }
  201. QMargins Popover::shadowMargins() const
  202. {
  203. // 返回固定的小边距,用于手动绘制的阴影
  204. return QMargins(5, 5, 5, 5);
  205. }
  206. void Popover::closeEvent(QCloseEvent* event)
  207. {
  208. emit popoverClosed();
  209. QWidget::closeEvent(event);
  210. }
  211. void Popover::paintEvent(QPaintEvent* event)
  212. {
  213. Q_UNUSED(event);
  214. QPainter painter(this);
  215. painter.setRenderHint(QPainter::Antialiasing);
  216. QRect widgetRect = rect();
  217. // 手动绘制简单的阴影效果
  218. QMargins shadow = shadowMargins();
  219. // 绘制阴影 - 简单的偏移矩形
  220. QRect shadowRect = widgetRect.adjusted(shadow.left(), shadow.top(), 0, 0);
  221. if (shadowRect.width() > 0 && shadowRect.height() > 0) {
  222. painter.setPen(Qt::NoPen);
  223. painter.setBrush(QColor(0, 0, 0, 30)); // 半透明黑色阴影
  224. painter.drawRoundedRect(shadowRect, 8, 8);
  225. }
  226. // 绘制主背景
  227. QRect bgRect = widgetRect.adjusted(0, 0, -shadow.right(), -shadow.bottom());
  228. if (bgRect.width() > 0 && bgRect.height() > 0) {
  229. painter.setPen(Colors::Border);
  230. painter.setBrush(Colors::Background);
  231. painter.drawRoundedRect(bgRect, 8, 8);
  232. }
  233. }
  234. QSize Popover::sizeHint() const
  235. {
  236. if (mainLayout->count() > 0) {
  237. QWidget* content = mainLayout->itemAt(0)->widget();
  238. if (content) {
  239. QMargins margins = mainLayout->contentsMargins();
  240. return content->sizeHint()
  241. + QSize(margins.left() + margins.right(), margins.top() + margins.bottom());
  242. }
  243. }
  244. return QWidget::sizeHint();
  245. }
  246. bool Popover::eventFilter(QObject* obj, QEvent* event)
  247. {
  248. if (event->type() == QEvent::MouseButtonPress
  249. || event->type() == QEvent::NonClientAreaMouseButtonPress) {
  250. QMouseEvent* mouseEvent = static_cast<QMouseEvent*>(event);
  251. // 检查点击是否在弹窗外部,如果是则关闭弹窗
  252. if (isVisible() && !geometry().contains(mouseEvent->globalPos())) {
  253. // 如果点击的是父窗口或其他任何地方,都关闭弹窗
  254. hideAnimated();
  255. return false; // 不阻止事件传播,让父窗口能正常处理点击
  256. }
  257. }
  258. // 检测窗口激活状态变化
  259. else if (event->type() == QEvent::WindowActivate) {
  260. // 如果其他窗口被激活,关闭弹窗
  261. if (isVisible() && obj != this) {
  262. hideAnimated();
  263. }
  264. }
  265. return QWidget::eventFilter(obj, event);
  266. }
  267. ///////////////////
  268. // FunctionButton 实现
  269. FunctionButton::FunctionButton(const QIcon& icon, const QString& text, QWidget* parent)
  270. : QPushButton(parent)
  271. , m_icon(icon)
  272. , m_text(text)
  273. {
  274. setFixedSize(100, 70); // 固定大小
  275. setCursor(Qt::PointingHandCursor);
  276. }
  277. void FunctionButton::setText(const QString& text)
  278. {
  279. m_text = text;
  280. update(); // 触发重绘
  281. }
  282. QString FunctionButton::text() const
  283. {
  284. return m_text;
  285. }
  286. void FunctionButton::paintEvent(QPaintEvent* event)
  287. {
  288. Q_UNUSED(event);
  289. QPainter painter(this);
  290. painter.setRenderHint(QPainter::Antialiasing);
  291. // 绘制按钮背景
  292. QRect rect = this->rect();
  293. QColor bgColor = Colors::Primary;
  294. if (isDown()) {
  295. bgColor = Colors::PrimaryPressed;
  296. } else if (underMouse()) {
  297. bgColor = Colors::PrimaryHover;
  298. }
  299. painter.setPen(Qt::NoPen);
  300. painter.setBrush(bgColor);
  301. painter.drawRoundedRect(rect, 5, 5);
  302. // 绘制图标
  303. if (!m_icon.isNull()) {
  304. int iconSize = 24;
  305. int iconLeft = (width() - iconSize) / 2;
  306. int iconTop = 10;
  307. QRect iconRect(iconLeft, iconTop, iconSize, iconSize);
  308. m_icon.paint(&painter, iconRect);
  309. }
  310. // 绘制文本
  311. painter.setPen(Qt::white);
  312. QFont font = Fonts::buttonFont();
  313. painter.setFont(font);
  314. QFontMetrics metrics(font);
  315. int textWidth = metrics.horizontalAdvance(m_text);
  316. int textLeft = (width() - textWidth) / 2;
  317. int textTop = 45; // 文本在图标下方
  318. painter.drawText(QRect(textLeft, textTop, textWidth, 20), Qt::AlignCenter, m_text);
  319. }
  320. void FunctionButton::enterEvent(QEvent* event)
  321. {
  322. QPushButton::enterEvent(event);
  323. update();
  324. }
  325. void FunctionButton::leaveEvent(QEvent* event)
  326. {
  327. QPushButton::leaveEvent(event);
  328. update();
  329. }
  330. // PopoverButtonGroup 实现
  331. PopoverButtonGroup::PopoverButtonGroup(Qt::Orientation orientation, QWidget* parent)
  332. : QWidget(parent)
  333. {
  334. if (orientation == Qt::Horizontal) {
  335. QHBoxLayout* hLayout = new QHBoxLayout(this);
  336. hLayout->setSpacing(15);
  337. hLayout->setContentsMargins(0, 0, 0, 0);
  338. hLayout->setAlignment(Qt::AlignLeft); // 左对齐,避免拉伸
  339. layout = hLayout;
  340. } else {
  341. QVBoxLayout* vLayout = new QVBoxLayout(this);
  342. vLayout->setSpacing(15);
  343. vLayout->setContentsMargins(0, 0, 0, 0);
  344. vLayout->setAlignment(Qt::AlignTop); // 顶部对齐,避免拉伸
  345. layout = vLayout;
  346. }
  347. }
  348. void PopoverButtonGroup::addButton(FunctionButton* button, Popover* popover)
  349. {
  350. // 创建容器布局来组合功能按钮和Popover按钮
  351. QWidget* container = new QWidget;
  352. QHBoxLayout* containerLayout = new QHBoxLayout(container);
  353. containerLayout->setContentsMargins(0, 0, 0, 0);
  354. containerLayout->setSpacing(0);
  355. // 添加功能按钮
  356. containerLayout->addWidget(button);
  357. buttons.append(button);
  358. if (popover) {
  359. // 创建Popover触发按钮
  360. QPushButton* popoverBtn = createPopoverTriggerButton();
  361. containerLayout->addWidget(popoverBtn);
  362. // 存储关联关系
  363. popoverMap[popoverBtn] = popover;
  364. buttonPopoverMap[button] = popoverBtn;
  365. // 连接Popover按钮点击事件
  366. connect(popoverBtn, &QPushButton::clicked, this, [this, popoverBtn]() {
  367. togglePopover(popoverBtn);
  368. });
  369. }
  370. // 添加容器到主布局
  371. layout->addWidget(container);
  372. }
  373. void PopoverButtonGroup::addPopoverToButton(FunctionButton* button, Popover* popover)
  374. {
  375. if (buttons.contains(button) && !buttonPopoverMap.contains(button)) {
  376. // 找到按钮所在的容器
  377. QWidget* container = button->parentWidget();
  378. if (!container)
  379. return;
  380. // 创建Popover触发按钮
  381. QPushButton* popoverBtn = createPopoverTriggerButton();
  382. QHBoxLayout* containerLayout = qobject_cast<QHBoxLayout*>(container->layout());
  383. if (containerLayout) {
  384. containerLayout->addWidget(popoverBtn);
  385. // 存储关联关系
  386. popoverMap[popoverBtn] = popover;
  387. buttonPopoverMap[button] = popoverBtn;
  388. // 连接Popover按钮点击事件
  389. connect(popoverBtn, &QPushButton::clicked, this, [this, popoverBtn]() {
  390. togglePopover(popoverBtn);
  391. });
  392. }
  393. }
  394. }
  395. void PopoverButtonGroup::removeButton(FunctionButton* button)
  396. {
  397. if (!buttons.contains(button))
  398. return;
  399. // 移除按钮从列表中
  400. buttons.removeAll(button);
  401. // 如果有关联的Popover按钮,也要移除
  402. if (buttonPopoverMap.contains(button)) {
  403. QPushButton* popoverBtn = buttonPopoverMap[button];
  404. popoverMap.remove(popoverBtn);
  405. buttonPopoverMap.remove(button);
  406. popoverBtn->deleteLater();
  407. }
  408. // 找到按钮所在的容器并从布局中移除
  409. QWidget* container = button->parentWidget();
  410. if (container && container->parentWidget() == this) {
  411. layout->removeWidget(container);
  412. container->deleteLater();
  413. }
  414. }
  415. QPushButton* PopoverButtonGroup::createPopoverTriggerButton()
  416. {
  417. auto btn = new PopoverTriggerButton(this);
  418. // 绘制下拉箭头(垂直居中)
  419. QPixmap pixmap(16, 16);
  420. pixmap.fill(Qt::transparent);
  421. QPainter painter(&pixmap);
  422. painter.setRenderHint(QPainter::Antialiasing);
  423. painter.setPen(QPen(Colors::Primary, 2));
  424. QPolygon arrow;
  425. arrow << QPoint(4, 6) << QPoint(8, 10) << QPoint(12, 6);
  426. painter.drawPolyline(arrow);
  427. btn->setIcon(QIcon(pixmap));
  428. btn->setIconSize(QSize(16, 16));
  429. return btn;
  430. }
  431. void PopoverButtonGroup::togglePopover(QPushButton* popoverBtn)
  432. {
  433. if (!popoverMap.contains(popoverBtn))
  434. return;
  435. Popover* popover = popoverMap[popoverBtn];
  436. if (popover->isVisible()) {
  437. popover->hideAnimated();
  438. } else {
  439. // 关闭其他Popover
  440. for (auto btn : popoverMap.keys()) {
  441. if (btn != popoverBtn && popoverMap[btn]->isVisible()) {
  442. popoverMap[btn]->hideAnimated();
  443. }
  444. }
  445. // 显示当前Popover
  446. QPoint pos = popoverBtn->mapToGlobal(QPoint(0, popoverBtn->height()));
  447. popover->showAnimated(pos);
  448. }
  449. }
  450. QSize PopoverButtonGroup::sizeHint() const
  451. {
  452. if (buttons.isEmpty()) {
  453. return QSize(0, 0);
  454. }
  455. // 每个FunctionButton的固定大小
  456. const int buttonWidth = 100;
  457. const int buttonHeight = 70;
  458. const int spacing = 15; // 与构造函数中设置的spacing保持一致
  459. // 计算Popover触发按钮的宽度(如果有的话)
  460. const int popoverBtnWidth = 20; // createPopoverTriggerButton中设置的宽度
  461. int totalWidth = 0;
  462. int maxHeight = buttonHeight;
  463. if (qobject_cast<QHBoxLayout*>(layout)) {
  464. // 水平布局:累加宽度
  465. for (int i = 0; i < buttons.size(); ++i) {
  466. totalWidth += buttonWidth;
  467. // 检查是否有对应的Popover按钮
  468. FunctionButton* btn = buttons[i];
  469. if (buttonPopoverMap.contains(btn)) {
  470. totalWidth += popoverBtnWidth; // 添加Popover按钮宽度
  471. }
  472. if (i > 0) totalWidth += spacing;
  473. }
  474. } else {
  475. // 垂直布局:累加高度,取最大宽度
  476. totalWidth = buttonWidth + popoverBtnWidth; // 最大可能宽度
  477. maxHeight = 0;
  478. for (int i = 0; i < buttons.size(); ++i) {
  479. maxHeight += buttonHeight;
  480. if (i > 0) maxHeight += spacing;
  481. }
  482. }
  483. return QSize(totalWidth, maxHeight);
  484. }