diff --git a/examples/collections/progressbarexample.cpp b/examples/collections/progressbarexample.cpp index 8c7010c9e..a79dfe507 100644 --- a/examples/collections/progressbarexample.cpp +++ b/examples/collections/progressbarexample.cpp @@ -9,6 +9,7 @@ #include #include #include +#include #include "progressbarexample.h" DWIDGET_USE_NAMESPACE @@ -28,6 +29,7 @@ ProgressBarExampleWindow::ProgressBarExampleWindow(QWidget *parent) addExampleWindow(new DProgressBarExample(this)); addExampleWindow(new DWaterProgressExample(this)); addExampleWindow(new DColoredProgressBarExample(this)); + addExampleWindow(new DIndeterminateProgressBarExample(this)); } DProgressBarExample::DProgressBarExample(QWidget *parent) @@ -181,3 +183,32 @@ int DColoredProgressBarExample::getFixedHeight() const { return 200; } + +DIndeterminateProgressBarExample::DIndeterminateProgressBarExample(QWidget *parent) + : ExampleWindowInterface(parent) +{ + auto mainLayout = new QVBoxLayout(this); + auto indeterBar = new DIndeterminateProgressbar(); + indeterBar->setFixedSize(500, 35); + mainLayout->addWidget(indeterBar, 0, Qt::AlignCenter); + setLayout(mainLayout); +} + +QString DIndeterminateProgressBarExample::getTitleName() const +{ + return "DIndeterminateProgressbar"; +} + +QString DIndeterminateProgressBarExample::getDescriptionInfo() const +{ + return QString("一个模糊进度条,不展示具体进度值,\n" + "用于等待时间不确定的情况。主要用\n" + "在小工具主窗口内部,作为一个中间状态\n" + "展示给用户,最终的结果往往会跟随成功\n" + "或者失败的图标。"); +} + +int DIndeterminateProgressBarExample::getFixedHeight() const +{ + return 200; +} diff --git a/examples/collections/progressbarexample.h b/examples/collections/progressbarexample.h index fb71257c5..c22ee1319 100644 --- a/examples/collections/progressbarexample.h +++ b/examples/collections/progressbarexample.h @@ -53,4 +53,16 @@ class DColoredProgressBarExample : public ExampleWindowInterface int getFixedHeight() const override; }; +class DIndeterminateProgressBarExample : public ExampleWindowInterface +{ + Q_OBJECT + +public: + explicit DIndeterminateProgressBarExample(QWidget *parent = nullptr); + + QString getTitleName() const override; + QString getDescriptionInfo() const override; + int getFixedHeight() const override; +}; + #endif // PROGRESSBAREXAMPLE_H diff --git a/include/DWidget/DIndeterminateProgressbar b/include/DWidget/DIndeterminateProgressbar new file mode 100644 index 000000000..1d45e0b4a --- /dev/null +++ b/include/DWidget/DIndeterminateProgressbar @@ -0,0 +1 @@ +#include "dindeterminateprogressbar.h" diff --git a/include/widgets/dbuttonbox.h b/include/widgets/dbuttonbox.h index c857e24e6..b5f31e114 100644 --- a/include/widgets/dbuttonbox.h +++ b/include/widgets/dbuttonbox.h @@ -1,4 +1,4 @@ -// SPDX-FileCopyrightText: 2019 - 2022 UnionTech Software Technology Co., Ltd. +// SPDX-FileCopyrightText: 2019 - 2024 UnionTech Software Technology Co., Ltd. // // SPDX-License-Identifier: LGPL-3.0-or-later @@ -47,6 +47,8 @@ class DButtonBoxButton : public QAbstractButton, public DCORE_NAMESPACE::DObject void paintEvent(QPaintEvent *e) override; void keyPressEvent(QKeyEvent *event) override; bool event(QEvent *e) override; + + friend class DButtonBox; }; class DButtonBoxPrivate; @@ -78,6 +80,9 @@ class DButtonBox : public QWidget, public DCORE_NAMESPACE::DObject void buttonReleased(QAbstractButton *); void buttonToggled(QAbstractButton *, bool); +protected: + bool eventFilter(QObject *o, QEvent *e) override; + private: void paintEvent(QPaintEvent *e) override; diff --git a/include/widgets/dindeterminateprogressbar.h b/include/widgets/dindeterminateprogressbar.h new file mode 100644 index 000000000..7883b1bea --- /dev/null +++ b/include/widgets/dindeterminateprogressbar.h @@ -0,0 +1,26 @@ +// SPDX-FileCopyrightText: 2024 UnionTech Software Technology Co., Ltd. +// +// SPDX-License-Identifier: LGPL-3.0-or-later + +#ifndef DINDETERMINATEPROGRESSBAR_H +#define DINDETERMINATEPROGRESSBAR_H + +#include +#include + +class DIndeterminateProgressbarPrivate; +class DIndeterminateProgressbar : public QWidget, public DTK_CORE_NAMESPACE::DObject +{ + Q_OBJECT +public: + explicit DIndeterminateProgressbar(QWidget *parent = nullptr); + +protected: + void paintEvent(QPaintEvent *e) override; + void resizeEvent(QResizeEvent *e) override; + +private: + D_DECLARE_PRIVATE(DIndeterminateProgressbar) +}; + +#endif // DINDETERMINATEPROGRESSBAR_H diff --git a/src/widgets/dbuttonbox.cpp b/src/widgets/dbuttonbox.cpp index 9bb89b540..d87e3f7fe 100644 --- a/src/widgets/dbuttonbox.cpp +++ b/src/widgets/dbuttonbox.cpp @@ -1,9 +1,10 @@ -// SPDX-FileCopyrightText: 2019 - 2022 UnionTech Software Technology Co., Ltd. +// SPDX-FileCopyrightText: 2019 - 2024 UnionTech Software Technology Co., Ltd. // // SPDX-License-Identifier: LGPL-3.0-or-later #include "dbuttonbox.h" #include "private/dbuttonbox_p.h" +#include #include "dstyleoption.h" #include "dstyle.h" @@ -12,10 +13,17 @@ #include #include #include -#include +#include DWIDGET_BEGIN_NAMESPACE +constexpr int HOVER_ANI_DURATION = 150; +constexpr int CHECK_ANI_DURATION = 200; +constexpr qreal HOVER_BACKGROUND_SCALE = 0.8; +constexpr qreal HOVER_BACKGROUND_ALPHAF = 0.6; +constexpr qreal SHADOW_ALPHAF = 0.2; +constexpr int SHADOW_HEIGHT = 2; + class DButtonBoxButtonPrivate : public DCORE_NAMESPACE::DObjectPrivate { public: @@ -291,6 +299,7 @@ void DButtonBoxButton::paintEvent(QPaintEvent *e) DStylePainter p(this); DStyleOptionButtonBoxButton option; initStyleOption(&option); + option.palette.setColor(QPalette::HighlightedText, this->palette().highlight().color()); p.drawControl(DStyle::CE_ButtonBoxButton, option); } @@ -351,6 +360,10 @@ bool DButtonBoxButton::event(QEvent *e) DButtonBoxPrivate::DButtonBoxPrivate(DButtonBox *qq) : DObjectPrivate(qq) + , m_hoverId(-1) + , m_checkedId(-1) + , m_hoverAnimation(new QVariantAnimation(qq)) + , m_checkMoveAnimation(new QVariantAnimation(qq)) { } @@ -366,9 +379,17 @@ void DButtonBoxPrivate::init() q->connect(group, SIGNAL(buttonPressed(QAbstractButton*)), q, SIGNAL(buttonPressed(QAbstractButton*))); q->connect(group, SIGNAL(buttonReleased(QAbstractButton*)), q, SIGNAL(buttonReleased(QAbstractButton*))); q->connect(group, SIGNAL(buttonToggled(QAbstractButton*, bool)), q, SIGNAL(buttonToggled(QAbstractButton*, bool))); + q->connect(m_hoverAnimation, &QVariantAnimation::valueChanged, q, [q]() { + q->update(); + }); + q->connect(m_checkMoveAnimation, &QVariantAnimation::valueChanged, q, [q]() { + q->update(); + }); + m_hoverAnimation->setDuration(HOVER_ANI_DURATION); + m_checkMoveAnimation->setDuration(CHECK_ANI_DURATION); layout = new QHBoxLayout(q); - layout->setContentsMargins(0,0,0,0); + layout->setContentsMargins(0, 0, 0, 0); layout->setSpacing(0); } @@ -484,6 +505,7 @@ void DButtonBox::setButtonList(const QList &list, bool check d->group->addButton(button); button->setCheckable(checkable); + button->installEventFilter(this); } } @@ -575,9 +597,11 @@ int DButtonBox::checkedId() const void DButtonBox::paintEvent(QPaintEvent *e) { Q_UNUSED(e) + D_D(DButtonBox); QStylePainter p(this); QStyleOptionButton opt; + p.setRenderHint(QPainter::Antialiasing); opt.state = QStyle::State_None; opt.rect = rect(); opt.direction = layoutDirection(); @@ -591,7 +615,97 @@ void DButtonBox::paintEvent(QPaintEvent *e) opt.state |= QStyle::State_Active; } - p.drawControl(QStyle::CE_PushButtonBevel, opt); + int radius = DStyle::pixelMetric(style(), DStyle::PM_FrameRadius); + if (d->m_hoverId >= 0 && d->m_hoverId < buttonList().size()) { + QColor background = this->palette().button().color(); + background.setAlphaF(HOVER_BACKGROUND_ALPHAF); + p.setBrush(background); + p.setPen(QPen(background, 1)); + p.drawRoundedRect(d->m_hoverAnimation->currentValue().toRect(), radius, radius); + } + + if (d->m_checkedId >= 0 && d->m_checkedId < buttonList().size()) { + const QColor &background = this->palette().dark().color(); + p.setBrush(background); + p.setPen(QPen(background, 1)); + + QRect rect; + if (d->m_checkMoveAnimation->currentValue().toRect().isValid()) + rect = d->m_checkMoveAnimation->currentValue().toRect(); + else + rect = buttonList().at(d->m_checkedId)->geometry(); + p.drawRoundedRect(rect, radius, radius); + + p.setPen(Qt::NoPen); + QColor shadowColor = Qt::black; + shadowColor.setAlphaF(SHADOW_ALPHAF); + p.setBrush(shadowColor); + + QPainterPath rectPath; + rectPath.addRoundedRect(rect, radius, radius); + + QRect excludeRect = rect; + excludeRect.setHeight(rect.height() - SHADOW_HEIGHT); + QPainterPath shadowPath; + shadowPath.addRoundedRect(excludeRect, radius, radius); + shadowPath = rectPath.subtracted(shadowPath); + + p.drawPath(shadowPath); + } +} + +bool DButtonBox::eventFilter(QObject *o, QEvent *e) +{ + D_D(DButtonBox); + for (int i = 0; i < buttonList().size(); ++i) { + if (o == buttonList().at(i)) { + DStyleOptionButtonBoxButton option; + dynamic_cast(o)->initStyleOption(&option); + if (option.state.testFlag(QStyle::State_On)) { + if (d->m_checkedId == i) + return false; + + if (d->m_checkedId >= 0 && d->m_checkedId < buttonList().size()) { + d->m_checkMoveAnimation->setStartValue(buttonList().at(d->m_checkedId)->geometry()); + d->m_checkedId = i; + d->m_checkMoveAnimation->setEndValue(buttonList().at(d->m_checkedId)->geometry()); + } else { + d->m_checkedId = i; + d->m_checkMoveAnimation->setStartValue(0); + d->m_checkMoveAnimation->setEndValue(0); + } + d->m_checkMoveAnimation->start(); + update(); + } + if (e->type() == QEvent::HoverEnter) { + if (d->m_hoverId == i) + return false; + + d->m_hoverId = i; + + if (d->m_hoverId < 0 || d->m_hoverId >= buttonList().size()) + return false; + + QRect smallRect = buttonList().at(d->m_hoverId)->geometry(); + smallRect.setSize(QSize(smallRect.width() * HOVER_BACKGROUND_SCALE, smallRect.height() * HOVER_BACKGROUND_SCALE)); + smallRect.moveCenter(buttonList().at(d->m_hoverId)->geometry().center()); + d->m_hoverAnimation->setStartValue(smallRect); + d->m_hoverAnimation->setEndValue(buttonList().at(d->m_hoverId)->geometry()); + d->m_hoverAnimation->start(); + update(); + } else if (e->type() == QEvent::HoverLeave) { + d->m_hoverId = -1; + update(); + } else if (e->type() == QEvent::Resize) { + d->m_hoverId = -1; + if (d->m_checkedId >= 0 && d->m_checkedId < buttonList().size()) { + d->m_checkMoveAnimation->setStartValue(buttonList().at(d->m_checkedId)->geometry()); + d->m_checkMoveAnimation->setEndValue(buttonList().at(d->m_checkedId)->geometry()); + } + } + } + } + return QWidget::eventFilter(o, e); } DWIDGET_END_NAMESPACE diff --git a/src/widgets/dindeterminateprogressbar.cpp b/src/widgets/dindeterminateprogressbar.cpp new file mode 100644 index 000000000..bb0713140 --- /dev/null +++ b/src/widgets/dindeterminateprogressbar.cpp @@ -0,0 +1,133 @@ +// SPDX-FileCopyrightText: 2024 UnionTech Software Technology Co., Ltd. +// +// SPDX-License-Identifier: LGPL-3.0-or-later + +#include "private/dindeterminateprogressbar_p.h" + +#include + +#include +#include +#include +#include +#include + +const int SPOT_WIDGET_WIDTH = 200; + +DIndeterminateProgressbarPrivate::DIndeterminateProgressbarPrivate(DIndeterminateProgressbar *qq) + : DObjectPrivate(qq) + , m_sliderWidget(new QWidget(qq)) + , m_timer(new QTimer(qq)) + , m_leftToRight(true) + , m_spotWidget(new QWidget(qq)) + , m_animation(new QPropertyAnimation(m_spotWidget, "pos", qq)) +{ +} + +DIndeterminateProgressbar::DIndeterminateProgressbar(QWidget *parent) + : QWidget(parent) + , DObject(*new DIndeterminateProgressbarPrivate(this)) +{ + D_D(DIndeterminateProgressbar); + d->m_spotWidget->setFixedSize(SPOT_WIDGET_WIDTH, height()); + d->m_spotWidget->move(-SPOT_WIDGET_WIDTH, 0); + + d->m_sliderWidget->setFixedWidth(150); + d->m_sliderWidget->move(0, 0); + + d->m_timer->setInterval(10); + static int step = 0; + connect(d->m_timer, &QTimer::timeout, this, [this, d]() { + if (d->m_sliderWidget->geometry().right() >= rect().right()) { + d->m_leftToRight = false; + } + + if (d->m_sliderWidget->geometry().left() <= rect().left()) { + d->m_leftToRight = true; + } + + d->m_leftToRight ? step += 2 : step -= 2; + d->m_sliderWidget->move(step, 0); + }); + d->m_timer->start(); +} + +void DIndeterminateProgressbar::resizeEvent(QResizeEvent *e) +{ + D_D(DIndeterminateProgressbar); + d->m_sliderWidget->setFixedHeight(height()); + d->m_spotWidget->setFixedSize(SPOT_WIDGET_WIDTH, height()); + + d->m_animation->setStartValue(QPoint(-SPOT_WIDGET_WIDTH, 0)); + d->m_animation->setEndValue(QPoint(rect().right(), 0)); + d->m_animation->setDuration(3000); + d->m_animation->setEasingCurve(QEasingCurve::InQuad); + d->m_animation->start(); + connect(d->m_animation, &QPropertyAnimation::finished, this, [d]() { + d->m_animation->start(); + }); + QWidget::resizeEvent(e); +} + +void DIndeterminateProgressbar::paintEvent(QPaintEvent *e) +{ + D_D(DIndeterminateProgressbar); + QWidget::paintEvent(e); + QPainter p(this); + + p.setRenderHint(QPainter::Antialiasing); + int radius; + this->height() <= DTK_WIDGET_NAMESPACE::DStyle::pixelMetric(style(), DTK_WIDGET_NAMESPACE::DStyle::PM_FrameRadius) * 2 + ? radius = height() / 2 + : radius = DTK_WIDGET_NAMESPACE::DStyle::pixelMetric(style(), DTK_WIDGET_NAMESPACE::DStyle::PM_FrameRadius); + + p.setBrush(QColor(0, 0, 0, int(0.1 * 255))); + p.setPen(Qt::NoPen); + + p.drawRoundedRect(rect(), radius, radius); + + QPen pen; + pen.setWidth(1); + pen.setColor(QColor(0, 0, 0, int(0.2 * 255))); + p.setBrush(Qt::NoBrush); + p.setPen(pen); + p.drawRoundedRect(rect().marginsRemoved(QMargins(1, 1, 1, 1)), radius, radius); + + p.setPen(Qt::NoPen); + p.setBrush(palette().highlight().color()); + p.drawRoundedRect(d->m_sliderWidget->geometry(), radius, radius); + + pen.setColor(QColor(0, 0, 0, int(0.3 * 255))); + p.setBrush(Qt::NoBrush); + p.setPen(pen); + p.drawRoundedRect(d->m_sliderWidget->geometry().marginsRemoved(QMargins(1, 1, 1, 1)), radius, radius); + + if (d->m_sliderWidget->width() < d->m_spotWidget->width() / 2) + return; + + QPointF pointStart(d->m_spotWidget->geometry().left(), d->m_spotWidget->geometry().center().y()); + QPointF pointEnd(d->m_spotWidget->geometry().right(), d->m_spotWidget->geometry().center().y()); + + QColor shadowColor(0, 0, 0, int(0.15 * 255)); + QColor spotColor(255, 255, 255, int(0.5 * 255)); + QColor highLightColor(palette().highlight().color()); + + QLinearGradient linear(pointStart, pointEnd); + linear.setColorAt(0, highLightColor); + linear.setColorAt(0.35, shadowColor); + linear.setColorAt(0.5, spotColor); + linear.setColorAt(0.65, shadowColor); + linear.setColorAt(1, highLightColor); + linear.setSpread(QGradient::PadSpread); + linear.setInterpolationMode(QLinearGradient::InterpolationMode::ColorInterpolation); + + p.setBrush(linear); + p.setPen(Qt::NoPen); + + QPainterPath clipPath; + clipPath.addRoundedRect(d->m_sliderWidget->geometry(), radius, radius); + p.setClipPath(clipPath); + p.setClipping(true); + p.drawRoundedRect(d->m_spotWidget->geometry().marginsRemoved(QMargins(2, 2, 2, 2)), radius, radius); + p.setClipping(false); +} diff --git a/src/widgets/dstyle.cpp b/src/widgets/dstyle.cpp index 234d0da24..0fa549352 100644 --- a/src/widgets/dstyle.cpp +++ b/src/widgets/dstyle.cpp @@ -1487,7 +1487,6 @@ void DStyle::drawControl(const QStyle *style, DStyle::ControlElement ce, const Q case CE_ButtonBoxButton: { if (const DStyleOptionButton *btn = qstyleoption_cast(opt)) { DStyleHelper dstyle(style); - dstyle.drawControl(CE_ButtonBoxButtonBevel, btn, p, w); DStyleOptionButton subopt = *btn; if (btn->features & DStyleOptionButton::HasDciIcon) subopt.dciIcon = btn->dciIcon; @@ -1498,6 +1497,7 @@ void DStyle::drawControl(const QStyle *style, DStyle::ControlElement ce, const Q DStyleOptionButtonBoxButton fropt; fropt = *boxbtn; fropt.rect = dstyle.subElementRect(SE_ButtonBoxButtonFocusRect, btn, w); + fropt.position = DStyleOptionButtonBoxButton::OnlyOne; style->drawPrimitive(PE_FrameFocusRect, &fropt, p, w); } } @@ -1572,6 +1572,13 @@ void DStyle::drawControl(const QStyle *style, DStyle::ControlElement ce, const Q break; } case CE_ButtonBoxButtonLabel: { + if (opt->state & StateFlag::State_MouseOver) { + constexpr qreal hoverScale = 1.2; // hover 时放大1.2倍 + p->scale(hoverScale, hoverScale); + p->setRenderHint(QPainter::SmoothPixmapTransform); + p->translate((1 - hoverScale) * opt->rect.width() / 2, (1 - hoverScale) * opt->rect.height() / 2); + } + style->drawControl(CE_PushButtonLabel, opt, p, w); break; } diff --git a/src/widgets/dtitlebar.cpp b/src/widgets/dtitlebar.cpp index 23d604ad0..6c32eeaf1 100644 --- a/src/widgets/dtitlebar.cpp +++ b/src/widgets/dtitlebar.cpp @@ -775,7 +775,7 @@ void DTitlebarPrivate::setIconVisible(bool visible) return; if (visible) { - if (auto spacerItem = dynamic_cast(leftLayout->takeAt(0))) + if (dynamic_cast(leftLayout->itemAt(0))) delete leftLayout->takeAt(0); leftLayout->insertSpacing(0, 10); diff --git a/src/widgets/private/dbuttonbox_p.h b/src/widgets/private/dbuttonbox_p.h index b383c9bf8..d67412893 100644 --- a/src/widgets/private/dbuttonbox_p.h +++ b/src/widgets/private/dbuttonbox_p.h @@ -1,4 +1,4 @@ -// SPDX-FileCopyrightText: 2019 - 2022 UnionTech Software Technology Co., Ltd. +// SPDX-FileCopyrightText: 2019 - 2024 UnionTech Software Technology Co., Ltd. // // SPDX-License-Identifier: LGPL-3.0-or-later @@ -12,6 +12,7 @@ QT_BEGIN_NAMESPACE class QBoxLayout; +class QVariantAnimation; QT_END_NAMESPACE DWIDGET_BEGIN_NAMESPACE @@ -26,6 +27,12 @@ class DButtonBoxPrivate : public DCORE_NAMESPACE::DObjectPrivate QButtonGroup *group; QBoxLayout *layout; + int m_hoverId; + int m_checkedId; + + QVariantAnimation *m_hoverAnimation; + QVariantAnimation *m_checkMoveAnimation; + D_DECLARE_PUBLIC(DButtonBox) }; diff --git a/src/widgets/private/dindeterminateprogressbar_p.h b/src/widgets/private/dindeterminateprogressbar_p.h new file mode 100644 index 000000000..330504358 --- /dev/null +++ b/src/widgets/private/dindeterminateprogressbar_p.h @@ -0,0 +1,29 @@ +// SPDX-FileCopyrightText: 2024 UnionTech Software Technology Co., Ltd. +// +// SPDX-License-Identifier: LGPL-3.0-or-later + +#ifndef DINDETERMINATEPROGRESSBAR_P_H +#define DINDETERMINATEPROGRESSBAR_P_H + +#include +#include + +#include + +class QPropertyAnimation; +class DIndeterminateProgressbarPrivate : public DTK_CORE_NAMESPACE::DObjectPrivate +{ +public: + DIndeterminateProgressbarPrivate(DIndeterminateProgressbar *qq); + + QWidget *m_sliderWidget; + QTimer *m_timer; + bool m_leftToRight; + QWidget *m_spotWidget; + QPropertyAnimation *m_animation; + +private: + D_DECLARE_PUBLIC(DIndeterminateProgressbar) +}; + +#endif // DINDETERMINATEPROGRESSBAR_P_H