functionbutton.cpp 16 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540
  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::paintEvent(QPaintEvent* event)
  278. {
  279. Q_UNUSED(event);
  280. QPainter painter(this);
  281. painter.setRenderHint(QPainter::Antialiasing);
  282. // 绘制按钮背景
  283. QRect rect = this->rect();
  284. QColor bgColor = Colors::Primary;
  285. if (isDown()) {
  286. bgColor = Colors::PrimaryPressed;
  287. } else if (underMouse()) {
  288. bgColor = Colors::PrimaryHover;
  289. }
  290. painter.setPen(Qt::NoPen);
  291. painter.setBrush(bgColor);
  292. painter.drawRoundedRect(rect, 5, 5);
  293. // 绘制图标
  294. if (!m_icon.isNull()) {
  295. int iconSize = 24;
  296. int iconLeft = (width() - iconSize) / 2;
  297. int iconTop = 10;
  298. QRect iconRect(iconLeft, iconTop, iconSize, iconSize);
  299. m_icon.paint(&painter, iconRect);
  300. }
  301. // 绘制文本
  302. painter.setPen(Qt::white);
  303. QFont font = Fonts::buttonFont();
  304. painter.setFont(font);
  305. QFontMetrics metrics(font);
  306. int textWidth = metrics.horizontalAdvance(m_text);
  307. int textLeft = (width() - textWidth) / 2;
  308. int textTop = 45; // 文本在图标下方
  309. painter.drawText(QRect(textLeft, textTop, textWidth, 20), Qt::AlignCenter, m_text);
  310. }
  311. void FunctionButton::enterEvent(QEvent* event)
  312. {
  313. QPushButton::enterEvent(event);
  314. update();
  315. }
  316. void FunctionButton::leaveEvent(QEvent* event)
  317. {
  318. QPushButton::leaveEvent(event);
  319. update();
  320. }
  321. // PopoverButtonGroup 实现
  322. PopoverButtonGroup::PopoverButtonGroup(Qt::Orientation orientation, QWidget* parent)
  323. : QWidget(parent)
  324. {
  325. if (orientation == Qt::Horizontal) {
  326. QHBoxLayout* hLayout = new QHBoxLayout(this);
  327. hLayout->setSpacing(15);
  328. hLayout->setContentsMargins(0, 0, 0, 0);
  329. hLayout->setAlignment(Qt::AlignLeft); // 左对齐,避免拉伸
  330. layout = hLayout;
  331. } else {
  332. QVBoxLayout* vLayout = new QVBoxLayout(this);
  333. vLayout->setSpacing(15);
  334. vLayout->setContentsMargins(0, 0, 0, 0);
  335. vLayout->setAlignment(Qt::AlignTop); // 顶部对齐,避免拉伸
  336. layout = vLayout;
  337. }
  338. }
  339. void PopoverButtonGroup::addButton(FunctionButton* button, Popover* popover)
  340. {
  341. // 创建容器布局来组合功能按钮和Popover按钮
  342. QWidget* container = new QWidget;
  343. QHBoxLayout* containerLayout = new QHBoxLayout(container);
  344. containerLayout->setContentsMargins(0, 0, 0, 0);
  345. containerLayout->setSpacing(0);
  346. // 添加功能按钮
  347. containerLayout->addWidget(button);
  348. buttons.append(button);
  349. if (popover) {
  350. // 创建Popover触发按钮
  351. QPushButton* popoverBtn = createPopoverTriggerButton();
  352. containerLayout->addWidget(popoverBtn);
  353. // 存储关联关系
  354. popoverMap[popoverBtn] = popover;
  355. buttonPopoverMap[button] = popoverBtn;
  356. // 连接Popover按钮点击事件
  357. connect(popoverBtn, &QPushButton::clicked, this, [this, popoverBtn]() {
  358. togglePopover(popoverBtn);
  359. });
  360. }
  361. // 添加容器到主布局
  362. layout->addWidget(container);
  363. }
  364. void PopoverButtonGroup::addPopoverToButton(FunctionButton* button, Popover* popover)
  365. {
  366. if (buttons.contains(button) && !buttonPopoverMap.contains(button)) {
  367. // 找到按钮所在的容器
  368. QWidget* container = button->parentWidget();
  369. if (!container)
  370. return;
  371. // 创建Popover触发按钮
  372. QPushButton* popoverBtn = createPopoverTriggerButton();
  373. QHBoxLayout* containerLayout = qobject_cast<QHBoxLayout*>(container->layout());
  374. if (containerLayout) {
  375. containerLayout->addWidget(popoverBtn);
  376. // 存储关联关系
  377. popoverMap[popoverBtn] = popover;
  378. buttonPopoverMap[button] = popoverBtn;
  379. // 连接Popover按钮点击事件
  380. connect(popoverBtn, &QPushButton::clicked, this, [this, popoverBtn]() {
  381. togglePopover(popoverBtn);
  382. });
  383. }
  384. }
  385. }
  386. QPushButton* PopoverButtonGroup::createPopoverTriggerButton()
  387. {
  388. auto btn = new PopoverTriggerButton(this);
  389. // 绘制下拉箭头(垂直居中)
  390. QPixmap pixmap(16, 16);
  391. pixmap.fill(Qt::transparent);
  392. QPainter painter(&pixmap);
  393. painter.setRenderHint(QPainter::Antialiasing);
  394. painter.setPen(QPen(Colors::Primary, 2));
  395. QPolygon arrow;
  396. arrow << QPoint(4, 6) << QPoint(8, 10) << QPoint(12, 6);
  397. painter.drawPolyline(arrow);
  398. btn->setIcon(QIcon(pixmap));
  399. btn->setIconSize(QSize(16, 16));
  400. return btn;
  401. }
  402. void PopoverButtonGroup::togglePopover(QPushButton* popoverBtn)
  403. {
  404. if (!popoverMap.contains(popoverBtn))
  405. return;
  406. Popover* popover = popoverMap[popoverBtn];
  407. if (popover->isVisible()) {
  408. popover->hideAnimated();
  409. } else {
  410. // 关闭其他Popover
  411. for (auto btn : popoverMap.keys()) {
  412. if (btn != popoverBtn && popoverMap[btn]->isVisible()) {
  413. popoverMap[btn]->hideAnimated();
  414. }
  415. }
  416. // 显示当前Popover
  417. QPoint pos = popoverBtn->mapToGlobal(QPoint(0, popoverBtn->height()));
  418. popover->showAnimated(pos);
  419. }
  420. }
  421. QSize PopoverButtonGroup::sizeHint() const
  422. {
  423. if (buttons.isEmpty()) {
  424. return QSize(0, 0);
  425. }
  426. // 每个FunctionButton的固定大小
  427. const int buttonWidth = 100;
  428. const int buttonHeight = 70;
  429. const int spacing = 15; // 与构造函数中设置的spacing保持一致
  430. // 计算Popover触发按钮的宽度(如果有的话)
  431. const int popoverBtnWidth = 20; // createPopoverTriggerButton中设置的宽度
  432. int totalWidth = 0;
  433. int maxHeight = buttonHeight;
  434. if (qobject_cast<QHBoxLayout*>(layout)) {
  435. // 水平布局:累加宽度
  436. for (int i = 0; i < buttons.size(); ++i) {
  437. totalWidth += buttonWidth;
  438. // 检查是否有对应的Popover按钮
  439. FunctionButton* btn = buttons[i];
  440. if (buttonPopoverMap.contains(btn)) {
  441. totalWidth += popoverBtnWidth; // 添加Popover按钮宽度
  442. }
  443. if (i > 0) totalWidth += spacing;
  444. }
  445. } else {
  446. // 垂直布局:累加高度,取最大宽度
  447. totalWidth = buttonWidth + popoverBtnWidth; // 最大可能宽度
  448. maxHeight = 0;
  449. for (int i = 0; i < buttons.size(); ++i) {
  450. maxHeight += buttonHeight;
  451. if (i > 0) maxHeight += spacing;
  452. }
  453. }
  454. return QSize(totalWidth, maxHeight);
  455. }