#include "functionbutton.h" #include #include #include #include #include #include #include namespace Colors { // Element Plus 主题色 const QColor Primary = QColor(64, 158, 255); // #409EFF const QColor PrimaryLight = QColor(144, 202, 249); // #90CAF9 const QColor PrimaryDark = QColor(25, 118, 210); // #1976D2 // 功能色 const QColor Success = QColor(103, 194, 58); // #67C23A const QColor Warning = QColor(230, 162, 60); // #E6A23C const QColor Danger = QColor(245, 108, 108); // #F56C6C const QColor Info = QColor(144, 147, 153); // #909399 // 中性色 const QColor TextPrimary = QColor(48, 49, 51); // #303133 const QColor TextRegular = QColor(96, 98, 102); // #606266 const QColor TextSecondary = QColor(144, 147, 153); // #909399 const QColor TextPlaceholder = QColor(192, 196, 204); // #C0C4CC // 边框色 const QColor BorderBase = QColor(220, 223, 230); // #DCDFE6 const QColor BorderLight = QColor(228, 231, 237); // #E4E7ED const QColor BorderLighter = QColor(235, 238, 245); // #EBEEF5 const QColor BorderExtraLight = QColor(244, 245, 247); // #F4F5F7 // 背景色 const QColor Background = Qt::white; // #FFFFFF const QColor BackgroundPage = QColor(245, 245, 245); // #F5F5F5 const QColor BackgroundBase = QColor(248, 249, 250); // #F8F9FA // 兼容旧版本 const QColor PrimaryHover = PrimaryLight; const QColor PrimaryPressed = PrimaryDark; const QColor Border = BorderBase; const QColor Text = TextPrimary; const QColor SecondaryText = TextSecondary; const QColor Shadow = QColor(0, 0, 0, 60); const QColor Indicator = Info; } // namespace Colors namespace Fonts { static QFont buttonFont() { QFont font; font.setPointSize(10); font.setWeight(QFont::Medium); return font; } } // namespace Fonts // PopoverTriggerButton 实现 PopoverTriggerButton::PopoverTriggerButton(QWidget* parent) : QPushButton(parent) { setCursor(Qt::PointingHandCursor); setSizePolicy(QSizePolicy::Fixed, QSizePolicy::Fixed); setFixedSize(20, 70); setMinimumSize(20, 70); setMaximumSize(20, 70); } QSize PopoverTriggerButton::sizeHint() const { return QSize(20, 70); // 强制返回固定大小 } QSize PopoverTriggerButton::minimumSizeHint() const { return QSize(20, 70); // 强制返回固定最小大小 } void PopoverTriggerButton::resizeEvent(QResizeEvent* event) { // 强制保持固定大小,忽略任何外部尺寸变化 if (size() != QSize(20, 70)) { setFixedSize(20, 70); } QPushButton::resizeEvent(event); } Popover::Popover(QWidget* parent) : QWidget(parent) { // 使用更安全的窗口标志组合,避免分层窗口问题 setWindowFlags(Qt::Tool | Qt::FramelessWindowHint | Qt::WindowStaysOnTopHint); // 移除透明背景属性,避免UpdateLayeredWindowIndirect错误 // setAttribute(Qt::WA_TranslucentBackground); // 设置窗口属性 setAttribute(Qt::WA_DeleteOnClose, false); setAttribute(Qt::WA_ShowWithoutActivating, true); setAttribute(Qt::WA_X11NetWmWindowTypePopupMenu, true); // 暂时移除阴影效果,避免分层窗口问题 // 我们将在paintEvent中手动绘制阴影 /* auto shadow = new QGraphicsDropShadowEffect(this); shadow->setBlurRadius(qMax(0.0, 20.0)); shadow->setColor(Colors::Shadow); shadow->setXOffset(0); shadow->setYOffset(qMax(0.0, 5.0)); setGraphicsEffect(shadow); */ mainLayout = new QVBoxLayout(this); mainLayout->setContentsMargins(25, 25, 25, 25); mainLayout->setSpacing(10); // 移除样式表,使用paintEvent手动绘制背景 // setStyleSheet("QWidget { background-color: white; border: 1px solid #e0e0e0; border-radius: 8px; }"); // 连接应用程序状态变化信号 connect(qApp, &QApplication::applicationStateChanged, this, [this](Qt::ApplicationState state) { if (state != Qt::ApplicationActive && isVisible()) { hideAnimated(); } }); // 安装事件过滤器以检测点击外部区域 qApp->installEventFilter(this); } Popover::~Popover() { // 移除事件过滤器 qApp->removeEventFilter(this); } void Popover::setContentWidget(QWidget* widget) { // 清除现有内容 QLayoutItem* child; while ((child = mainLayout->takeAt(0)) != nullptr) { delete child->widget(); delete child; } // 添加新内容 mainLayout->addWidget(widget); // 动态计算最小尺寸,考虑阴影边距 if (widget->minimumSize().isValid()) { QMargins margins = mainLayout->contentsMargins(); int extraWidth = margins.left() + margins.right(); int extraHeight = margins.top() + margins.bottom(); setMinimumSize(widget->minimumSize() + QSize(extraWidth, extraHeight)); } } void Popover::showAnimated(const QPoint& position) { setOpacity(0.0); // 确保窗口尺寸有效 if (width() <= 0 || height() <= 0) { adjustSize(); } // 确保位置在屏幕范围内 QScreen* screen = QGuiApplication::screenAt(position); if (!screen) { screen = QGuiApplication::primaryScreen(); } if (screen) { QRect screenGeometry = screen->availableGeometry(); QPoint adjustedPos = position; // 计算阴影边距,确保为正值 QMargins shadowMargins = this->shadowMargins(); int leftMargin = qMax(0, shadowMargins.left()); int topMargin = qMax(0, shadowMargins.top()); int rightMargin = qMax(0, shadowMargins.right()); int bottomMargin = qMax(0, shadowMargins.bottom()); // 确保窗口完全在屏幕内 int minX = screenGeometry.left() + leftMargin; int minY = screenGeometry.top() + topMargin; int maxX = screenGeometry.right() - width() - rightMargin; int maxY = screenGeometry.bottom() - height() - bottomMargin; // 调整X坐标 adjustedPos.setX(qBound(minX, adjustedPos.x(), maxX)); // 调整Y坐标 adjustedPos.setY(qBound(minY, adjustedPos.y(), maxY)); // 最终验证位置的有效性 if (adjustedPos.x() >= 0 && adjustedPos.y() >= 0) { move(adjustedPos); } else { // 如果计算出的位置仍然无效,使用屏幕中心 QPoint centerPos = screenGeometry.center() - QPoint(width() / 2, height() / 2); move(centerPos); } } else { // 如果无法获取屏幕信息,确保位置为正值 QPoint safePos = QPoint(qMax(0, position.x()), qMax(0, position.y())); move(safePos); } show(); QPropertyAnimation* animation = new QPropertyAnimation(this, "opacity"); animation->setDuration(200); animation->setStartValue(0.0); animation->setEndValue(1.0); animation->setEasingCurve(QEasingCurve::OutCubic); animation->start(QAbstractAnimation::DeleteWhenStopped); } void Popover::hideAnimated() { QPropertyAnimation* animation = new QPropertyAnimation(this, "opacity"); animation->setDuration(150); animation->setStartValue(1.0); animation->setEndValue(0.0); animation->setEasingCurve(QEasingCurve::InCubic); connect(animation, &QPropertyAnimation::finished, this, &Popover::hide); animation->start(QAbstractAnimation::DeleteWhenStopped); } void Popover::setOpacity(qreal opacity) { m_opacity = opacity; setWindowOpacity(opacity); } QMargins Popover::shadowMargins() const { // 返回固定的小边距,用于手动绘制的阴影 return QMargins(5, 5, 5, 5); } void Popover::closeEvent(QCloseEvent* event) { emit popoverClosed(); QWidget::closeEvent(event); } void Popover::paintEvent(QPaintEvent* event) { Q_UNUSED(event); QPainter painter(this); painter.setRenderHint(QPainter::Antialiasing); QRect widgetRect = rect(); // 手动绘制简单的阴影效果 QMargins shadow = shadowMargins(); // 绘制阴影 - 简单的偏移矩形 QRect shadowRect = widgetRect.adjusted(shadow.left(), shadow.top(), 0, 0); if (shadowRect.width() > 0 && shadowRect.height() > 0) { painter.setPen(Qt::NoPen); painter.setBrush(QColor(0, 0, 0, 30)); // 半透明黑色阴影 painter.drawRoundedRect(shadowRect, 8, 8); } // 绘制主背景 QRect bgRect = widgetRect.adjusted(0, 0, -shadow.right(), -shadow.bottom()); if (bgRect.width() > 0 && bgRect.height() > 0) { painter.setPen(Colors::Border); painter.setBrush(Colors::Background); painter.drawRoundedRect(bgRect, 8, 8); } } QSize Popover::sizeHint() const { if (mainLayout->count() > 0) { QWidget* content = mainLayout->itemAt(0)->widget(); if (content) { QMargins margins = mainLayout->contentsMargins(); return content->sizeHint() + QSize(margins.left() + margins.right(), margins.top() + margins.bottom()); } } return QWidget::sizeHint(); } bool Popover::eventFilter(QObject* obj, QEvent* event) { if (event->type() == QEvent::MouseButtonPress || event->type() == QEvent::NonClientAreaMouseButtonPress) { QMouseEvent* mouseEvent = static_cast(event); // 检查点击是否在弹窗外部,如果是则关闭弹窗 if (isVisible() && !geometry().contains(mouseEvent->globalPos())) { // 如果点击的是父窗口或其他任何地方,都关闭弹窗 hideAnimated(); return false; // 不阻止事件传播,让父窗口能正常处理点击 } } // 检测窗口激活状态变化 else if (event->type() == QEvent::WindowActivate) { // 如果其他窗口被激活,关闭弹窗 if (isVisible() && obj != this) { hideAnimated(); } } return QWidget::eventFilter(obj, event); } /////////////////// // FunctionButton 实现 FunctionButton::FunctionButton(const QIcon& icon, const QString& text, QWidget* parent) : QPushButton(parent) , m_icon(icon) , m_text(text) { setFixedSize(100, 70); // 固定大小 setCursor(Qt::PointingHandCursor); } void FunctionButton::setText(const QString& text) { m_text = text; update(); // 触发重绘 } QString FunctionButton::text() const { return m_text; } void FunctionButton::paintEvent(QPaintEvent* event) { Q_UNUSED(event); QPainter painter(this); painter.setRenderHint(QPainter::Antialiasing); // 绘制按钮背景 QRect rect = this->rect(); QColor bgColor = Colors::Primary; if (isDown()) { bgColor = Colors::PrimaryPressed; } else if (underMouse()) { bgColor = Colors::PrimaryHover; } painter.setPen(Qt::NoPen); painter.setBrush(bgColor); painter.drawRoundedRect(rect, 5, 5); // 绘制图标 if (!m_icon.isNull()) { int iconSize = 24; int iconLeft = (width() - iconSize) / 2; int iconTop = 10; QRect iconRect(iconLeft, iconTop, iconSize, iconSize); m_icon.paint(&painter, iconRect); } // 绘制文本 painter.setPen(Qt::white); QFont font = Fonts::buttonFont(); painter.setFont(font); QFontMetrics metrics(font); int textWidth = metrics.horizontalAdvance(m_text); int textLeft = (width() - textWidth) / 2; int textTop = 45; // 文本在图标下方 painter.drawText(QRect(textLeft, textTop, textWidth, 20), Qt::AlignCenter, m_text); } void FunctionButton::enterEvent(QEvent* event) { QPushButton::enterEvent(event); update(); } void FunctionButton::leaveEvent(QEvent* event) { QPushButton::leaveEvent(event); update(); } // PopoverButtonGroup 实现 PopoverButtonGroup::PopoverButtonGroup(Qt::Orientation orientation, QWidget* parent) : QWidget(parent) { if (orientation == Qt::Horizontal) { QHBoxLayout* hLayout = new QHBoxLayout(this); hLayout->setSpacing(15); hLayout->setContentsMargins(0, 0, 0, 0); hLayout->setAlignment(Qt::AlignLeft); // 左对齐,避免拉伸 layout = hLayout; } else { QVBoxLayout* vLayout = new QVBoxLayout(this); vLayout->setSpacing(15); vLayout->setContentsMargins(0, 0, 0, 0); vLayout->setAlignment(Qt::AlignTop); // 顶部对齐,避免拉伸 layout = vLayout; } } void PopoverButtonGroup::addButton(FunctionButton* button, Popover* popover) { // 创建容器布局来组合功能按钮和Popover按钮 QWidget* container = new QWidget; QHBoxLayout* containerLayout = new QHBoxLayout(container); containerLayout->setContentsMargins(0, 0, 0, 0); containerLayout->setSpacing(0); // 添加功能按钮 containerLayout->addWidget(button); buttons.append(button); if (popover) { // 创建Popover触发按钮 QPushButton* popoverBtn = createPopoverTriggerButton(); containerLayout->addWidget(popoverBtn); // 存储关联关系 popoverMap[popoverBtn] = popover; buttonPopoverMap[button] = popoverBtn; // 连接Popover按钮点击事件 connect(popoverBtn, &QPushButton::clicked, this, [this, popoverBtn]() { togglePopover(popoverBtn); }); } // 添加容器到主布局 layout->addWidget(container); } void PopoverButtonGroup::addPopoverToButton(FunctionButton* button, Popover* popover) { if (buttons.contains(button) && !buttonPopoverMap.contains(button)) { // 找到按钮所在的容器 QWidget* container = button->parentWidget(); if (!container) return; // 创建Popover触发按钮 QPushButton* popoverBtn = createPopoverTriggerButton(); QHBoxLayout* containerLayout = qobject_cast(container->layout()); if (containerLayout) { containerLayout->addWidget(popoverBtn); // 存储关联关系 popoverMap[popoverBtn] = popover; buttonPopoverMap[button] = popoverBtn; // 连接Popover按钮点击事件 connect(popoverBtn, &QPushButton::clicked, this, [this, popoverBtn]() { togglePopover(popoverBtn); }); } } } void PopoverButtonGroup::removeButton(FunctionButton* button) { if (!buttons.contains(button)) return; // 移除按钮从列表中 buttons.removeAll(button); // 如果有关联的Popover按钮,也要移除 if (buttonPopoverMap.contains(button)) { QPushButton* popoverBtn = buttonPopoverMap[button]; popoverMap.remove(popoverBtn); buttonPopoverMap.remove(button); popoverBtn->deleteLater(); } // 找到按钮所在的容器并从布局中移除 QWidget* container = button->parentWidget(); if (container && container->parentWidget() == this) { layout->removeWidget(container); container->deleteLater(); } } QPushButton* PopoverButtonGroup::createPopoverTriggerButton() { auto btn = new PopoverTriggerButton(this); // 绘制下拉箭头(垂直居中) QPixmap pixmap(16, 16); pixmap.fill(Qt::transparent); QPainter painter(&pixmap); painter.setRenderHint(QPainter::Antialiasing); painter.setPen(QPen(Colors::Primary, 2)); QPolygon arrow; arrow << QPoint(4, 6) << QPoint(8, 10) << QPoint(12, 6); painter.drawPolyline(arrow); btn->setIcon(QIcon(pixmap)); btn->setIconSize(QSize(16, 16)); return btn; } void PopoverButtonGroup::togglePopover(QPushButton* popoverBtn) { if (!popoverMap.contains(popoverBtn)) return; Popover* popover = popoverMap[popoverBtn]; if (popover->isVisible()) { popover->hideAnimated(); } else { // 关闭其他Popover for (auto btn : popoverMap.keys()) { if (btn != popoverBtn && popoverMap[btn]->isVisible()) { popoverMap[btn]->hideAnimated(); } } // 显示当前Popover QPoint pos = popoverBtn->mapToGlobal(QPoint(0, popoverBtn->height())); popover->showAnimated(pos); } } QSize PopoverButtonGroup::sizeHint() const { if (buttons.isEmpty()) { return QSize(0, 0); } // 每个FunctionButton的固定大小 const int buttonWidth = 100; const int buttonHeight = 70; const int spacing = 15; // 与构造函数中设置的spacing保持一致 // 计算Popover触发按钮的宽度(如果有的话) const int popoverBtnWidth = 20; // createPopoverTriggerButton中设置的宽度 int totalWidth = 0; int maxHeight = buttonHeight; if (qobject_cast(layout)) { // 水平布局:累加宽度 for (int i = 0; i < buttons.size(); ++i) { totalWidth += buttonWidth; // 检查是否有对应的Popover按钮 FunctionButton* btn = buttons[i]; if (buttonPopoverMap.contains(btn)) { totalWidth += popoverBtnWidth; // 添加Popover按钮宽度 } if (i > 0) totalWidth += spacing; } } else { // 垂直布局:累加高度,取最大宽度 totalWidth = buttonWidth + popoverBtnWidth; // 最大可能宽度 maxHeight = 0; for (int i = 0; i < buttons.size(); ++i) { maxHeight += buttonHeight; if (i > 0) maxHeight += spacing; } } return QSize(totalWidth, maxHeight); }