| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575 |
- #include "functionbutton.h"
- #include <QAbstractAnimation>
- #include <QApplication>
- #include <QGuiApplication>
- #include <QPropertyAnimation>
- #include <QScreen>
- #include <QDebug>
- #include <QResizeEvent>
- 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<QMouseEvent*>(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<QHBoxLayout*>(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<QHBoxLayout*>(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);
- }
|