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/DBounceAnimation b/include/DWidget/DBounceAnimation new file mode 100644 index 000000000..b088a7338 --- /dev/null +++ b/include/DWidget/DBounceAnimation @@ -0,0 +1 @@ +#include "dbounceanimation.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/dbounceanimation.h b/include/widgets/dbounceanimation.h new file mode 100644 index 000000000..b5ff98fa5 --- /dev/null +++ b/include/widgets/dbounceanimation.h @@ -0,0 +1,31 @@ +// SPDX-FileCopyrightText: 2024 UnionTech Software Technology Co., Ltd. +// +// SPDX-License-Identifier: LGPL-3.0-or-later +#ifndef DBOUNCEANIMATION_H +#define DBOUNCEANIMATION_H + +#include +#include + +class QPropertyAnimation; +class QAbstractScrollArea; +class DBounceAnimationPrivate; +class DBounceAnimation : public QObject, public DTK_CORE_NAMESPACE::DObject +{ + Q_OBJECT +public: + explicit DBounceAnimation(QObject *parent = nullptr); + + void setAnimationTarget(QAbstractScrollArea *w); + void setAniMationEnable(bool enable); + +protected: + bool eventFilter(QObject *o, QEvent *e) override; + void bounceBack(Qt::Orientation orientation); + +private: + D_DECLARE_PRIVATE(DBounceAnimation) + +}; + +#endif // DBOUNCEANIMATION_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/dfloatingmessage.h b/include/widgets/dfloatingmessage.h index 382ee6b55..dea681929 100644 --- a/include/widgets/dfloatingmessage.h +++ b/include/widgets/dfloatingmessage.h @@ -1,4 +1,4 @@ -// SPDX-FileCopyrightText: 2019 - 2023 UnionTech Software Technology Co., Ltd. +// SPDX-FileCopyrightText: 2019 - 2024 UnionTech Software Technology Co., Ltd. // // SPDX-License-Identifier: LGPL-3.0-or-later @@ -38,6 +38,7 @@ class DFloatingMessage : public DFloatingWidget Q_SIGNALS: void closeButtonClicked(); + void messageClosed(); protected: using DFloatingWidget::setWidget; 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/include/widgets/dmessagemanager.h b/include/widgets/dmessagemanager.h index 198815368..d3cb5e06a 100644 --- a/include/widgets/dmessagemanager.h +++ b/include/widgets/dmessagemanager.h @@ -1,11 +1,11 @@ -// SPDX-FileCopyrightText: 2022 UnionTech Software Technology Co., Ltd. +// SPDX-FileCopyrightText: 2022 - 2024 UnionTech Software Technology Co., Ltd. // // SPDX-License-Identifier: LGPL-3.0-or-later #ifndef DMESSAGEMANAGER_H #define DMESSAGEMANAGER_H -#include +#include #include #include #include @@ -16,9 +16,11 @@ DGUI_END_NAMESPACE DWIDGET_BEGIN_NAMESPACE class DFloatingMessage; -class DMessageManager: public QObject +class DMessageManagerPrivate; +class DMessageManager: public QObject, public DCORE_NAMESPACE::DObject { Q_OBJECT + D_DECLARE_PRIVATE(DMessageManager) private: DMessageManager(); //构造函数是私有的 diff --git a/include/widgets/dtoolbutton.h b/include/widgets/dtoolbutton.h index 95d4feb50..61982ea02 100644 --- a/include/widgets/dtoolbutton.h +++ b/include/widgets/dtoolbutton.h @@ -5,23 +5,34 @@ #ifndef DTOOLBUTTON_H #define DTOOLBUTTON_H -#include #include +#include + +#include +#include + DWIDGET_BEGIN_NAMESPACE +DGUI_USE_NAMESPACE -class LIBDTKWIDGETSHARED_EXPORT DToolButton : public QToolButton +class DToolButtonPrivate; +class LIBDTKWIDGETSHARED_EXPORT DToolButton : public QToolButton, public DCORE_NAMESPACE::DObject { Q_OBJECT public: DToolButton(QWidget *parent = nullptr); void setAlignment(Qt::Alignment flag); Qt::Alignment alignment() const; + void setDciIcon(const DDciIcon &dciIcon); protected: void paintEvent(QPaintEvent *event) override; void initStyleOption(QStyleOptionToolButton *option) const; QSize sizeHint() const override; + bool event(QEvent *e) override; + +private: + D_DECLARE_PRIVATE(DToolButton) }; DWIDGET_END_NAMESPACE diff --git a/src/widgets/assets/icons/bloom/checkbox_checked.dci b/src/widgets/assets/icons/bloom/checkbox_checked.dci new file mode 100644 index 000000000..de3949458 Binary files /dev/null and b/src/widgets/assets/icons/bloom/checkbox_checked.dci differ diff --git a/src/widgets/assets/icons/bloom/checkbox_unchecked.dci b/src/widgets/assets/icons/bloom/checkbox_unchecked.dci new file mode 100644 index 000000000..413e4145a Binary files /dev/null and b/src/widgets/assets/icons/bloom/checkbox_unchecked.dci differ diff --git a/src/widgets/assets/icons/bloom/radio_checked.dci b/src/widgets/assets/icons/bloom/radio_checked.dci new file mode 100644 index 000000000..69df89f3b Binary files /dev/null and b/src/widgets/assets/icons/bloom/radio_checked.dci differ diff --git a/src/widgets/assets/icons/bloom/radio_unchecked.dci b/src/widgets/assets/icons/bloom/radio_unchecked.dci new file mode 100644 index 000000000..792ce8ec2 Binary files /dev/null and b/src/widgets/assets/icons/bloom/radio_unchecked.dci differ diff --git a/src/widgets/assets/icons/bloom/switch_off.dci b/src/widgets/assets/icons/bloom/switch_off.dci new file mode 100644 index 000000000..a02a0ed1e Binary files /dev/null and b/src/widgets/assets/icons/bloom/switch_off.dci differ diff --git a/src/widgets/assets/icons/bloom/switch_on.dci b/src/widgets/assets/icons/bloom/switch_on.dci new file mode 100644 index 000000000..a0cc26539 Binary files /dev/null and b/src/widgets/assets/icons/bloom/switch_on.dci differ diff --git a/src/widgets/assets/icons/dtk-icon-theme.qrc b/src/widgets/assets/icons/dtk-icon-theme.qrc index c4761d464..9a47c6167 100644 --- a/src/widgets/assets/icons/dtk-icon-theme.qrc +++ b/src/widgets/assets/icons/dtk-icon-theme.qrc @@ -72,5 +72,11 @@ bloom/window_maximize.dci bloom/window_minimize.dci bloom/window_normal.dci + bloom/switch_on.dci + bloom/switch_off.dci + bloom/radio_checked.dci + bloom/radio_unchecked.dci + bloom/checkbox_checked.dci + bloom/checkbox_unchecked.dci diff --git a/src/widgets/dbounceanimation.cpp b/src/widgets/dbounceanimation.cpp new file mode 100644 index 000000000..d574375a7 --- /dev/null +++ b/src/widgets/dbounceanimation.cpp @@ -0,0 +1,107 @@ +// SPDX-FileCopyrightText: 2024 UnionTech Software Technology Co., Ltd. +// +// SPDX-License-Identifier: LGPL-3.0-or-later +#include "private/dbounceanimation_p.h" +#include +#include +#include +#include +#include +#include +#include + +DBounceAnimationPrivate::DBounceAnimationPrivate(DBounceAnimation *qq) + : DObjectPrivate (qq) + , m_animation(nullptr) + , m_animationTarget(nullptr) + , m_deltaSum(0) +{ +} + +DBounceAnimation::DBounceAnimation(QObject *parent) + : QObject(parent) + , DObject(*new DBounceAnimationPrivate(this)) +{ +} + +void DBounceAnimation::setAnimationTarget(QAbstractScrollArea *w) +{ + D_D(DBounceAnimation); + if (!w) + return; + + if (d->m_animationTarget == w) + return; + + d->m_animationTarget = w; +} + +void DBounceAnimation::setAniMationEnable(bool enable) +{ + D_D(DBounceAnimation); + enable ? d->m_animationTarget->installEventFilter(this) + : d->m_animationTarget->removeEventFilter(this); +} + +bool DBounceAnimation::eventFilter(QObject *o, QEvent *e) +{ + D_D(DBounceAnimation); + if (e->type() == QEvent::Wheel) { + if (auto absscroll = dynamic_cast(o)) { + if (auto wheelEvent = dynamic_cast(e)) { + if (absscroll->verticalScrollBar()->value() <= 0 || absscroll->verticalScrollBar()->value() >= absscroll->verticalScrollBar()->maximum()) { + d->m_deltaSum += wheelEvent->delta(); + bounceBack(wheelEvent->angleDelta().x() == 0 ? Qt::Vertical : Qt::Horizontal); + } + } + } + } + + return false; +} + +void DBounceAnimation::bounceBack(Qt::Orientation orientation) +{ + D_D(DBounceAnimation); + if (d->m_animation) + return; + + if (orientation & Qt::Vertical && d->m_animationTarget->verticalScrollBar()->maximum() == d->m_animationTarget->verticalScrollBar()->minimum()) + return; + + if (orientation & Qt::Horizontal && d->m_animationTarget->horizontalScrollBar()->maximum() == d->m_animationTarget->horizontalScrollBar()->minimum()) + return; + + d->m_animation = new QPropertyAnimation(this); + d->m_animation->setTargetObject(d->m_animationTarget->viewport()); + d->m_animation->setPropertyName("pos"); + d->m_animation->setDuration(100); + d->m_animation->setEasingCurve(QEasingCurve::InQuart); + d->m_animation->setStartValue(QPoint(d->m_animationTarget->viewport()->x(), d->m_animationTarget->viewport()->y())); + + QTimer::singleShot(100, this, [this, d, orientation]() { + + if (orientation & Qt::Vertical) { + d->m_animation->setEndValue( + QPoint(d->m_animationTarget->viewport()->x(), d->m_animationTarget->viewport()->y() + d->m_deltaSum / 16)); + } else { + d->m_animation->setEndValue( + QPoint(d->m_animationTarget->viewport()->x() + d->m_deltaSum / 16, d->m_animationTarget->viewport()->y())); + } + + d->m_animation->start(); + + connect(d->m_animation, &QPropertyAnimation::finished, this, [d]() { + if (d->m_animation->direction() == QPropertyAnimation::Backward) { + delete d->m_animation; + d->m_animation = nullptr; + return; + } + + d->m_animation->setDirection(QPropertyAnimation::Direction::Backward); + d->m_animation->setDuration(1000); + d->m_animation->start(QPropertyAnimation::DeleteWhenStopped); + d->m_deltaSum = 0; + }); + }); +} diff --git a/src/widgets/dbuttonbox.cpp b/src/widgets/dbuttonbox.cpp index 9bb89b540..d90cf735d 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 + +#include DWIDGET_BEGIN_NAMESPACE +constexpr int HOVER_ANI_DURATION = 150; +constexpr int CHECK_ANI_DURATION = 200; +constexpr qreal HOVER_BACKGROUND_SCALE = 0.8; +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,11 @@ bool DButtonBoxButton::event(QEvent *e) DButtonBoxPrivate::DButtonBoxPrivate(DButtonBox *qq) : DObjectPrivate(qq) + , m_hoverId(-1) + , m_checkedId(-1) + , m_pressId(-1) + , m_hoverAnimation(new QVariantAnimation(qq)) + , m_checkMoveAnimation(new QVariantAnimation(qq)) { } @@ -366,9 +380,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 +506,7 @@ void DButtonBox::setButtonList(const QList &list, bool check d->group->addButton(button); button->setCheckable(checkable); + button->installEventFilter(this); } } @@ -575,9 +598,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 +616,156 @@ void DButtonBox::paintEvent(QPaintEvent *e) opt.state |= QStyle::State_Active; } - p.drawControl(QStyle::CE_PushButtonBevel, opt); + bool isDarkType = DGuiApplicationHelper::instance()->themeType() == DGuiApplicationHelper::DarkType; + int radius = DStyle::pixelMetric(style(), DStyle::PM_FrameRadius); + QColor background; + if (d->m_hoverId >= 0 && d->m_hoverId < buttonList().size()) { + if (isDarkType) { + background = "#141414"; + background.setAlphaF(0.2); + } else { + background = Qt::black; + background.setAlphaF(0.1); + } + + p.setPen(Qt::NoPen); + p.setBrush(background); + + auto rect = d->m_hoverAnimation->currentValue().toRect(); + p.drawRoundedRect(rect, radius, radius); + + if (isDarkType) { // 深色模式需要绘制上下阴影 + QPainterPath rectPath; + rectPath.addRoundedRect(rect, radius, radius); + + QRect excludeRect = rect; + excludeRect.setHeight(rect.height() - SHADOW_HEIGHT); + QPainterPath bottomShadowPath; + bottomShadowPath.addRoundedRect(excludeRect, radius, radius); + bottomShadowPath = rectPath.subtracted(bottomShadowPath); + + background.setAlphaF(0.5); + p.setBrush(background); + p.drawPath(bottomShadowPath); // 下阴影 + + excludeRect.moveBottom(rect.bottom()); + QPainterPath topShadowPath; + topShadowPath.addRoundedRect(excludeRect, radius, radius); + topShadowPath = rectPath.subtracted(topShadowPath); + + background = Qt::white; + background.setAlphaF(0.1); + p.setBrush(background); + p.drawPath(topShadowPath); // 上阴影 + } + } + + if (d->m_pressId >= 0 && d->m_pressId < buttonList().size()) { + background = Qt::black; + background.setAlphaF(isDarkType ? 0.15 : 0.2); + + p.setBrush(background); + p.setPen(Qt::NoPen); + p.drawRoundedRect(d->m_hoverAnimation->currentValue().toRect(), radius, radius); + } + + if (d->m_checkedId >= 0 && d->m_checkedId < buttonList().size()) { + background = Qt::black; + background.setAlphaF(isDarkType ? 0.3 : 0.1); + p.setBrush(background); + p.setPen(Qt::NoPen); + + 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(0.2); + 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); + + if (isDarkType) { + background.setAlphaF(0.5); + p.setBrush(background); + } + 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::MouseButtonPress) { + if (d->m_pressId == i) + return false; + + d->m_pressId = i; + d->m_hoverId = -1; + update(); + } else if (e->type() == QEvent::MouseButtonRelease) { + d->m_pressId = -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/dfloatingmessage.cpp b/src/widgets/dfloatingmessage.cpp index 2c5270909..c581f289e 100644 --- a/src/widgets/dfloatingmessage.cpp +++ b/src/widgets/dfloatingmessage.cpp @@ -1,4 +1,4 @@ -// SPDX-FileCopyrightText: 2019 - 2023 UnionTech Software Technology Co., Ltd. +// SPDX-FileCopyrightText: 2019 - 2024 UnionTech Software Technology Co., Ltd. // // SPDX-License-Identifier: LGPL-3.0-or-later @@ -11,6 +11,7 @@ #include #include #include +#include class MessageLabel : public QLabel { @@ -54,13 +55,17 @@ void DFloatingMessagePrivate::init() iconButton->setIconSize(DSizeModeHelper::element(QSize(20, 20), QSize(30, 30))); hBoxLayout->addWidget(iconButton); + hBoxLayout->addSpacing(10); hBoxLayout->addWidget(labMessage); if (notifyType == DFloatingMessage::MessageType::TransientType) { //临时消息 timer = new QTimer(q); timer->setInterval(4000); timer->setSingleShot(true); - q->connect(timer, &QTimer::timeout, q, &DFloatingMessage::close); + q->connect(timer, &QTimer::timeout, q, [q]() { + q->close(); + Q_EMIT q->messageClosed(); + }); } else { //常驻消息 content = nullptr; closeButton = new DDialogCloseButton(q); @@ -69,8 +74,18 @@ void DFloatingMessagePrivate::init() hBoxLayout->addWidget(closeButton); q->connect(closeButton, &DIconButton::clicked, q, &DFloatingMessage::closeButtonClicked); - q->connect(closeButton, &DIconButton::clicked, q, &DFloatingMessage::close); + q->connect(closeButton, &DIconButton::clicked, q, [q]() { + q->close(); + Q_EMIT q->messageClosed(); + }); } + + auto effect = new QGraphicsDropShadowEffect(q); + effect->setColor(QColor(0, 0, 0, 0.1 * 255)); + effect->setBlurRadius(20); + effect->setXOffset(0); + effect->setYOffset(2); + q->setGraphicsEffect(effect); } /*! diff --git a/src/widgets/dindeterminateprogressbar.cpp b/src/widgets/dindeterminateprogressbar.cpp new file mode 100644 index 000000000..e0d04d4dc --- /dev/null +++ b/src/widgets/dindeterminateprogressbar.cpp @@ -0,0 +1,138 @@ +// 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 +#include + +DGUI_USE_NAMESPACE + +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); + update(); + }); + 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); + + bool isDarkType = DGuiApplicationHelper::instance()->themeType() == DGuiApplicationHelper::DarkType; + QColor color = isDarkType ? QColor(255, 255, 255, int(0.1 * 255)) : QColor(0, 0, 0, int(0.1 * 255)); + p.setBrush(color); + p.setPen(Qt::NoPen); + + p.drawRoundedRect(rect(), radius, radius); + + QPen pen; + pen.setWidth(1); + pen.setColor(color); + p.setBrush(Qt::NoBrush); + p.setPen(pen); + p.drawRoundedRect(rect(), radius, radius); + + p.setPen(Qt::NoPen); + p.setBrush(palette().highlight().color()); + p.drawRoundedRect(d->m_sliderWidget->geometry(), radius, radius); + + QColor highLightColor(palette().highlight().color()); + auto borderColor = isDarkType ? DGuiApplicationHelper::adjustColor(highLightColor, 0, 0, +10, 0, 0, 0, 0) + : DGuiApplicationHelper::adjustColor(highLightColor, 0, 0, -20, 0, 0, 0, -20); + pen.setColor(borderColor); + p.setBrush(Qt::NoBrush); + p.setPen(pen); + p.drawRoundedRect(d->m_sliderWidget->geometry(), 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 spotColor = DGuiApplicationHelper::adjustColor(highLightColor, 0, +30, +30, 0, 0, 0, 0); + + QLinearGradient linear(pointStart, pointEnd); + linear.setColorAt(0, highLightColor); + linear.setColorAt(0.5, spotColor); + 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().marginsRemoved(QMargins(1, 1, 1, 1)), radius - 1, radius - 1); + p.setClipPath(clipPath); + p.setClipping(true); + p.drawRoundedRect(d->m_spotWidget->geometry(), radius, radius); + p.setClipping(false); +} diff --git a/src/widgets/dlistview.cpp b/src/widgets/dlistview.cpp index 16672ba05..8b7bff3d6 100644 --- a/src/widgets/dlistview.cpp +++ b/src/widgets/dlistview.cpp @@ -13,6 +13,8 @@ #include "dstyleditemdelegate.h" #include "dstyle.h" +#include + DWIDGET_BEGIN_NAMESPACE DVariantListModel::DVariantListModel(QObject *parent) : @@ -196,6 +198,11 @@ DListView::DListView(QWidget *parent) : DObject(*new DListViewPrivate(this)) { d_func()->init(); + if (!qEnvironmentVariableIsSet("DTK_DISABLE_LISTVIEW_ANIMATION")) { + auto ani = new DBounceAnimation(this); + ani->setAnimationTarget(this); + ani->setAniMationEnable(true); + } } /*! diff --git a/src/widgets/dmainwindow.cpp b/src/widgets/dmainwindow.cpp index d0232e6a9..a1daa8ccb 100644 --- a/src/widgets/dmainwindow.cpp +++ b/src/widgets/dmainwindow.cpp @@ -43,6 +43,9 @@ DMainWindowPrivate::DMainWindowPrivate(DMainWindow *qq) titlebar = new DTitlebar(qq); titlebar->setAccessibleName("DMainWindowTitlebar"); auto noTitlebarEnabled = []{ + if (qEnvironmentVariable("DDE_CURRENT_COMPOSITOR") == "TreeLand") { + return true; + } QFunctionPointer enableNoTitlebar = qApp->platformFunction("_d_isEnableNoTitlebar"); bool enabled = qApp->platformName() == "dwayland" || qApp->property("_d_isDwayland").toBool(); return enabled && enableNoTitlebar != nullptr; diff --git a/src/widgets/dmessagemanager.cpp b/src/widgets/dmessagemanager.cpp index 6e7a362b5..043731e81 100644 --- a/src/widgets/dmessagemanager.cpp +++ b/src/widgets/dmessagemanager.cpp @@ -1,19 +1,48 @@ -// SPDX-FileCopyrightText: 2022 UnionTech Software Technology Co., Ltd. +// SPDX-FileCopyrightText: 2022 - 2024 UnionTech Software Technology Co., Ltd. // // SPDX-License-Identifier: LGPL-3.0-or-later -#include "dmessagemanager.h" +#include "private/dmessagemanager_p.h" #include #include #include #include +#include #define D_MESSAGE_MANAGER_CONTENT "_d_message_manager_content" +const int MARGIN = 20; +const int MESSGAE_HEIGHT = 50; +const int ANIMATION_DURATION = 400; Q_DECLARE_METATYPE(QMargins) +class ImageLabel : public QLabel { + Q_OBJECT + Q_PROPERTY(qreal opacity READ opacity WRITE setOpacity) +public: + explicit ImageLabel(QWidget *parent=nullptr) + : QLabel (parent) + , m_opacity(0) + { + }; + void setOpacity(qreal opac) { m_opacity = opac; } + qreal opacity() { return m_opacity ;} + +protected: + void paintEvent(QPaintEvent *e) override + { + Q_UNUSED(e) + QPainter p(this); + p.setRenderHint(QPainter::Antialiasing); + p.setOpacity(m_opacity); + p.drawPixmap(rect().marginsRemoved(contentsMargins()), *pixmap()); + }; +private: + qreal m_opacity; +}; + // 仅仅为了接口兼容, 符号不会减少, 如果使用了这个接口,实际调用走的有namespace的 class Q_DECL_EXPORT DMessageManager: public QObject { @@ -91,7 +120,30 @@ static void sendMessage_helper(DMessageManager *manager, QWidget *par, IconType manager->sendMessage(par, floMsg); } +DMessageManagerPrivate::DMessageManagerPrivate(DMessageManager *qq) + : DObjectPrivate(qq) + , m_aniGeometry(new QPropertyAnimation(qq)) + , m_aniOpacity(new QPropertyAnimation(qq)) + , m_aniGroup(new QParallelAnimationGroup(qq)) + , m_label(new ImageLabel) +{ + m_aniGeometry->setPropertyName("geometry"); + m_aniGeometry->setDuration(ANIMATION_DURATION); + m_aniGeometry->setEasingCurve(QEasingCurve::OutCubic); + + m_aniOpacity->setPropertyName("opacity"); + m_aniOpacity->setDuration(ANIMATION_DURATION); + m_aniOpacity->setEasingCurve(QEasingCurve::OutCubic); + m_aniOpacity->setTargetObject(m_label); + m_aniOpacity->setStartValue(0); + m_aniOpacity->setEndValue(1); + + m_aniGroup->addAnimation(m_aniGeometry); + m_aniGroup->addAnimation(m_aniOpacity); +} + DMessageManager::DMessageManager() //私有静态构造函数 + : DObject(*new DMessageManagerPrivate(this)) { } @@ -114,6 +166,7 @@ DMessageManager *DMessageManager::instance() //公有静态函数 */ void DMessageManager::sendMessage(QWidget *par, DFloatingMessage *floMsg) { + D_D(DMessageManager); QWidget *content = par->findChild(D_MESSAGE_MANAGER_CONTENT, Qt::FindDirectChildrenOnly); if (!content) { @@ -125,7 +178,7 @@ void DMessageManager::sendMessage(QWidget *par, DFloatingMessage *floMsg) if (par->property("_d_margins").isValid()) content->setContentsMargins(magins); else - content->setContentsMargins(QMargins(20, 0, 20, 0)); + content->setContentsMargins(QMargins(MARGIN, 0, MARGIN, 0)); content->installEventFilter(this); par->installEventFilter(this); @@ -133,10 +186,60 @@ void DMessageManager::sendMessage(QWidget *par, DFloatingMessage *floMsg) layout->setSpacing(0); layout->setContentsMargins(0, 0, 0, 0); layout->setDirection(QBoxLayout::BottomToTop); - content->show(); + } + + if (content->layout()->count() >= 1) { + content->layout()->itemAt(content->layout()->count() - 1)->widget()->hide(); + delete content->layout()->takeAt(content->layout()->count() - 1); } static_cast(content->layout())->addWidget(floMsg, 0, Qt::AlignHCenter); + + // 限制通知消息的最大宽度 + for (DFloatingMessage *message : content->findChildren(QString(), Qt::FindDirectChildrenOnly)) { + message->setMaximumWidth(par->rect().marginsRemoved(content->contentsMargins()).width()); + message->setMinimumHeight(message->sizeHint().height()); + } + + QRect geometry(QPoint(0, 0), floMsg->sizeHint() + QSize(MARGIN * 2, 0)); + geometry.moveCenter(par->rect().center()); + geometry.moveBottom(par->rect().bottom() - MESSGAE_HEIGHT); + + content->setGeometry(geometry); + content->hide(); + + if (d->m_aniGeometry->state() == QPropertyAnimation::State::Running) + return; + + d->m_label->setParent(par); + d->m_label->setAlignment(Qt::AlignCenter); + d->m_label->setContentsMargins(MARGIN, 0, MARGIN, 0); + d->m_label->setPixmap(floMsg->grab()); + d->m_label->setScaledContents(true); + d->m_label->show(); + d->m_aniGeometry->setTargetObject(d->m_label); + d->m_aniOpacity->setTargetObject(d->m_label); + d->m_aniGeometry->setStartValue(QRect(par->rect().center().x(), par->rect().bottom(), 0, 0)); + d->m_aniGeometry->setEndValue(content->geometry()); + d->m_aniGroup->start(); + connect(d->m_aniGroup, &QPropertyAnimation::finished, this, [d, content]() { + if (d->m_aniGroup->direction() == QAbstractAnimation::Backward) { + d->m_aniGroup->setDirection(QAbstractAnimation::Forward); + } else { + content->show(); + } + d->m_label->hide(); + }); + + connect(floMsg, &DFloatingMessage::messageClosed, [=, this]() { + d->m_aniGeometry->setStartValue(QRect(par->rect().center().x(), par->rect().bottom(), 0, 0)); + d->m_aniGeometry->setEndValue(content->geometry()); + d->m_label->setPixmap(floMsg->grab()); + + d->m_aniGroup->setDirection(QAbstractAnimation::Backward); + d->m_label->show(); + d->m_aniGroup->start(); + }); } /*! @@ -184,19 +287,11 @@ bool DMessageManager::setContentMargens(QWidget *par, const QMargins &margins) */ bool DMessageManager::eventFilter(QObject *watched, QEvent *event) { - if (event->type() == QEvent::LayoutRequest || event->type() == QEvent::Resize) { - if (QWidget *widget = qobject_cast(watched)) { - QWidget *content; - - if (widget->objectName() == D_MESSAGE_MANAGER_CONTENT) { - content = widget; - } else { - content = widget->findChild(D_MESSAGE_MANAGER_CONTENT, Qt::FindDirectChildrenOnly); - } + if (event->type() == QEvent::Resize) { + if (auto content = watched->findChild(D_MESSAGE_MANAGER_CONTENT, Qt::FindDirectChildrenOnly)) { - QWidget *par = content->parentWidget(); + auto par = qobject_cast(watched); - // 限制通知消息的最大宽度 for (DFloatingMessage *message : content->findChildren(QString(), Qt::FindDirectChildrenOnly)) { message->setMaximumWidth(par->rect().marginsRemoved(content->contentsMargins()).width()); message->setMinimumHeight(message->sizeHint().height()); @@ -204,7 +299,7 @@ bool DMessageManager::eventFilter(QObject *watched, QEvent *event) QRect geometry(QPoint(0, 0), content->sizeHint()); geometry.moveCenter(par->rect().center()); - geometry.moveBottom(par->rect().bottom()); + geometry.moveBottom(par->rect().bottom() - MESSGAE_HEIGHT); content->setGeometry(geometry); } } else if (event->type() == QEvent::ChildRemoved) { diff --git a/src/widgets/dsearchedit.cpp b/src/widgets/dsearchedit.cpp index afafab3a0..35acfdcd6 100644 --- a/src/widgets/dsearchedit.cpp +++ b/src/widgets/dsearchedit.cpp @@ -39,6 +39,9 @@ DWIDGET_BEGIN_NAMESPACE DCORE_USE_NAMESPACE DGUI_USE_NAMESPACE +constexpr int ANI_DURATION = 200; +constexpr int HIDE_CURSOR_MARGIN = -4; + #ifdef ENABLE_AI class VoiceDevice : public QIODevice { @@ -270,7 +273,11 @@ DSearchEditPrivate::DSearchEditPrivate(DSearchEdit *q) , action(nullptr) , iconWidget(nullptr) , label(nullptr) + , animation(new QPropertyAnimation) { + animation->setPropertyName("pos"); + animation->setEasingCurve(QEasingCurve::OutCubic); + animation->setDuration(ANI_DURATION); } DSearchEditPrivate::~DSearchEditPrivate() @@ -377,15 +384,31 @@ void DSearchEditPrivate::_q_toEditMode(bool focus) { D_Q(DSearchEdit); - if (focus || !q->lineEdit()->text().isEmpty()) { - action->setVisible(true); - iconWidget->setVisible(false); - lineEdit->setPlaceholderText(placeholderText); - } else { - action->setVisible(false); - iconWidget->setVisible(true); - lineEdit->setPlaceholderText(QString()); - } + if (animation->state() == QPropertyAnimation::Running) + return; + + auto textMargins = q->lineEdit()->textMargins(); + QMargins marginsInAnimation(HIDE_CURSOR_MARGIN, 0, 0, 0); + + if (!animation->parent()) + animation->setParent(iconWidget); + + animation->setTargetObject(iconWidget); + animation->setStartValue(QPoint(q->lineEdit()->geometry().center().x() - iconWidget->width() / 2, iconWidget->pos().y())); + animation->setEndValue(QPoint(0, iconWidget->pos().y())); + + q->connect(animation, &QPropertyAnimation::finished, q, [q, this, textMargins]() { + q->lineEdit()->setTextMargins(textMargins); + if (animation->direction() == QPropertyAnimation::Direction::Forward) { + action->setVisible(true); + iconWidget->setVisible(false); + lineEdit->setPlaceholderText(placeholderText); + } else { + iconWidget->setVisible(true); + lineEdit->setPlaceholderText(QString()); + iconWidget->move(QPoint(q->lineEdit()->geometry().center().x() - iconWidget->width() / 2, iconWidget->pos().y())); + } + }); #ifdef ENABLE_AI //Focus disappears, clear voice check @@ -394,6 +417,20 @@ void DSearchEditPrivate::_q_toEditMode(bool focus) _q_onVoiceActionTrigger(false); } #endif + + if (!q->lineEdit()->text().isEmpty()) + return; + + if (focus) { + animation->setDirection(QPropertyAnimation::Direction::Forward); + } else { + action->setVisible(false); + animation->setDirection(QPropertyAnimation::Direction::Backward); + } + + iconWidget->setVisible(true); + q->lineEdit()->setTextMargins(marginsInAnimation); + animation->start(); } void DSearchEditPrivate::_q_onVoiceActionTrigger(bool checked) diff --git a/src/widgets/dstyle.cpp b/src/widgets/dstyle.cpp index 234d0da24..d1fd1160a 100644 --- a/src/widgets/dstyle.cpp +++ b/src/widgets/dstyle.cpp @@ -1,4 +1,4 @@ -// SPDX-FileCopyrightText: 2019 - 2023 UnionTech Software Technology Co., Ltd. +// SPDX-FileCopyrightText: 2019 - 2024 UnionTech Software Technology Co., Ltd. // // SPDX-License-Identifier: LGPL-3.0-or-later @@ -1339,9 +1339,18 @@ void DStyle::drawPrimitive(const QStyle *style, DStyle::PrimitiveElement pe, con //先绘画阴影 DDrawUtils::drawShadow(p, btn->rect + shadow_margin, frameRadius, frameRadius, QColor(0, 0, 0, 0.25 * 255), shadowRadius, QPoint(offsetX, offsetY)); //再绘画上面的待显示区域 - p->setPen(QPen(btn->dpalette.frameShadowBorder(), 1)); + p->setPen(Qt::NoPen); p->setBrush(btn->noBackground ? Qt::NoBrush : p->background()); p->drawRoundedRect(opt->rect, frameRadius, frameRadius); + + p->setBrush(Qt::NoBrush); + QPen pen; + pen.setWidth(1); + pen.setColor(DGuiApplicationHelper::instance()->themeType() == DGuiApplicationHelper::DarkType + ? QColor(255, 255, 255, int(0.1 * 255)) + : QColor(0, 0, 0, int(0.12 * 255))); + p->setPen(pen); + p->drawRoundedRect(opt->rect, frameRadius, frameRadius); } break; } @@ -1443,9 +1452,6 @@ void DStyle::drawControl(const QStyle *style, DStyle::ControlElement ce, const Q DStyleOptionButton option = *btn; option.dpalette = btn->dpalette; option.rect = dstyle.subElementRect(SE_SwitchButtonGroove, opt, w); - dstyle.drawPrimitive(PE_SwitchButtonGroove, &option, p, w); - option.rect = dstyle.subElementRect(SE_SwitchButtonHandle, opt, w); - dstyle.drawPrimitive(PE_SwitchButtonHandle, &option, p, w); if (btn->state & State_HasFocus) { QStyleOptionFocusRect fropt; @@ -1487,7 +1493,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 +1503,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 +1578,14 @@ 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.x() + opt->rect.width() / 2) / hoverScale + , (1 - hoverScale) * (opt->rect.y() + opt->rect.height() / 2) / hoverScale); + } + style->drawControl(CE_PushButtonLabel, opt, p, w); break; } diff --git a/src/widgets/dswitchbutton.cpp b/src/widgets/dswitchbutton.cpp index 273269737..48e5d1bc0 100644 --- a/src/widgets/dswitchbutton.cpp +++ b/src/widgets/dswitchbutton.cpp @@ -3,15 +3,19 @@ // SPDX-License-Identifier: LGPL-3.0-or-later #include "dswitchbutton.h" -#include -#include #include "private/dswitchbutton_p.h" -#include +#include +#include +#include +#include +#include DWIDGET_BEGIN_NAMESPACE +constexpr int DCI_ICON_SIZE = 120; + /*! @~english @brief DSwitchButton::DSwitchButton implements a switch button @@ -48,12 +52,16 @@ QSize DSwitchButton::sizeHint() const */ void DSwitchButton::paintEvent(QPaintEvent *e) { + D_D(DSwitchButton); Q_UNUSED(e); DStylePainter painter(this); DStyleOptionButton opt; initStyleOption(&opt); painter.drawControl(DStyle::CE_SwitchButton, opt); + + painter.setRenderHint(QPainter::SmoothPixmapTransform); + painter.drawImage(rect().adjusted(4, -8, -4, 8), d->player.currentImage()); // 为了显示按钮的阴影所留的空白 } /*! @@ -108,7 +116,38 @@ void DSwitchButtonPrivate::init() q->setObjectName("DSwitchButton"); q->setSizePolicy(QSizePolicy::Fixed, QSizePolicy::Fixed); q->setCheckable(true); - q->connect(q, &DSwitchButton::toggled, q, &DSwitchButton::checkedChanged); + + auto initPlayer= [this, q]() { + DDciIcon icon = !checked ? DDciIcon::fromTheme("switch_on") : DDciIcon::fromTheme("switch_off"); + player.setIcon(icon); + player.setMode(DDciIcon::Mode::Normal); + auto palette = DDciIconPalette::fromQPalette(q->palette()); + player.setPalette(palette); + player.setDevicePixelRatio(qApp->devicePixelRatio()); + player.setIconSize(DCI_ICON_SIZE); + player.setTheme(DGuiApplicationHelper::instance()->themeType() == DGuiApplicationHelper::DarkType + ? DDciIcon::Dark : DDciIcon::Light); + }; + + initPlayer(); + + q->connect(q, &DSwitchButton::toggled, q, [q, this](bool ckd) { + if (checked == ckd) + return; + + checked = ckd; + DDciIcon icon = checked ? DDciIcon::fromTheme("switch_on") : DDciIcon::fromTheme("switch_off"); + player.setIcon(icon); + player.play(DDciIcon::Mode::Normal); + + Q_EMIT q->checkedChanged(checked); + }); + + q->connect(&player, &DDciIconPlayer::updated, q, [q]() { + q->update(); + }); + + q->connect(DGuiApplicationHelper::instance(), &DGuiApplicationHelper::themeTypeChanged, q, initPlayer); } DWIDGET_END_NAMESPACE diff --git a/src/widgets/dtitlebar.cpp b/src/widgets/dtitlebar.cpp index 6c32eeaf1..20873d65a 100644 --- a/src/widgets/dtitlebar.cpp +++ b/src/widgets/dtitlebar.cpp @@ -354,6 +354,10 @@ void DTitlebarPrivate::init() q->setFocusPolicy(Qt::StrongFocus); auto noTitlebarEnabled = []{ + if (qEnvironmentVariable("DDE_CURRENT_COMPOSITOR") == "TreeLand") { + return true; + } + QFunctionPointer enableNoTitlebar = qApp->platformFunction("_d_isEnableNoTitlebar"); bool enabled = qApp->platformName() == "dwayland" || qApp->property("_d_isDwayland").toBool(); return enabled && enableNoTitlebar != nullptr; @@ -427,7 +431,7 @@ void DTitlebarPrivate::updateFullscreen() void DTitlebarPrivate::updateButtonsState(Qt::WindowFlags type) { D_Q(DTitlebar); - bool useDXcb = DPlatformWindowHandle::isEnabledDXcb(targetWindow()); + bool useDXcb = DPlatformWindowHandle::isEnabledDXcb(targetWindow()) || qEnvironmentVariable("DDE_CURRENT_COMPOSITOR") == "TreeLand"; bool isFullscreen = targetWindow()->windowState().testFlag(Qt::WindowFullScreen); // bool forceShow = !useDXcb; @@ -1104,7 +1108,7 @@ bool DTitlebar::eventFilter(QObject *obj, QEvent *event) switch (event->type()) { case QEvent::ShowToParent: d->handleParentWindowIdChange(); - d->updateButtonsState(d->targetWindow()->windowFlags()); + d->handleParentWindowStateChange(); break; case QEvent::Resize: if (d->autoHideOnFullscreen) { diff --git a/src/widgets/dtoolbutton.cpp b/src/widgets/dtoolbutton.cpp index a2ac4d42f..7ce71c191 100644 --- a/src/widgets/dtoolbutton.cpp +++ b/src/widgets/dtoolbutton.cpp @@ -2,13 +2,25 @@ // // SPDX-License-Identifier: LGPL-3.0-or-later -#include "dtoolbutton.h" +#include "private/dtoolbutton_p.h" + +#include +#include #include #include +#include +#include +#include DWIDGET_BEGIN_NAMESPACE +Dtk::Widget::DToolButtonPrivate::DToolButtonPrivate(DToolButton *qq) + : DObjectPrivate(qq) +{ + +} + /*! @~english @class Dtk::Widget::DToolButton @@ -17,8 +29,15 @@ DWIDGET_BEGIN_NAMESPACE DToolButton::DToolButton(QWidget *parent) : QToolButton(parent) + , DObject(*new DToolButtonPrivate(this)) { - + D_D(DToolButton); + connect(this, &DToolButton::pressed, this, [d]() { + d->m_dciPlayer.play(DDciIcon::Pressed); + }); + connect(this, &DToolButton::released, this, [d]() { + d->m_dciPlayer.play(DDciIcon::Normal); + }); } /*! @@ -30,10 +49,26 @@ DToolButton::DToolButton(QWidget *parent) void DToolButton::paintEvent(QPaintEvent *event) { + D_D(DToolButton); Q_UNUSED(event) QStylePainter p(this); QStyleOptionToolButton opt; initStyleOption(&opt); + + if (!d->m_dciIcon.isNull()) { + p.setRenderHint(QPainter::SmoothPixmapTransform); + p.drawImage(rect(), d->m_dciPlayer.currentImage()); + + if (opt.state & QStyle::State_HasFocus) { + p.setPen(QPen(palette().highlight().color(), 2)); + p.setBrush(Qt::NoBrush); + p.setRenderHint(QPainter::Antialiasing); + int radius = DStyle::pixelMetric(style(), DStyle::PM_FrameRadius); + p.drawRoundedRect(opt.rect.marginsRemoved(QMargins(1, 1, 1, 1)), radius, radius); + } + return; + } + p.drawComplexControl(QStyle::CC_ToolButton, opt); } @@ -60,6 +95,26 @@ QSize DToolButton::sizeHint() const return QToolButton::sizeHint(); } +bool DToolButton::event(QEvent *e) +{ + D_D(DToolButton); + if (d->m_dciIcon.isNull()) + return QToolButton::event(e); + + if (e->type() == QEvent::WindowActivate) { + auto palette = DDciIconPalette::fromQPalette(this->palette()); + d->m_dciPlayer.setPalette(palette); + d->m_dciPlayer.setTheme(DGuiApplicationHelper::instance()->themeType() == DGuiApplicationHelper::DarkType + ? DDciIcon::Dark : DDciIcon::Light); + d->m_dciPlayer.setMode(DDciIcon::Normal); + } else if (e->type() == QEvent::HoverEnter) { + d->m_dciPlayer.play(DDciIcon::Hover); + } else if (e->type() == QEvent::HoverLeave) { + d->m_dciPlayer.play(DDciIcon::Normal); + } + return QToolButton::event(e); +} + /*! @~english @fn void DToolButton::setAlignment(Qt::Alignment flag) @@ -85,4 +140,22 @@ Qt::Alignment DToolButton::alignment() const return Qt::AlignLeft; } +void DToolButton::setDciIcon(const DDciIcon &dciIcon) +{ + D_D(DToolButton); + d->m_dciIcon = dciIcon; + d->m_dciPlayer.setIcon(dciIcon); + d->m_dciPlayer.setIconSize(120); + + connect(&d->m_dciPlayer, &DDciIconPlayer::updated, this, [this]() { + update(); + }); + + connect(DGuiApplicationHelper::instance(), &DGuiApplicationHelper::themeTypeChanged, this, [this, d](DGuiApplicationHelper::ColorType colorType) { + auto palette = DDciIconPalette::fromQPalette(this->palette()); + d->m_dciPlayer.setPalette(palette); + d->m_dciPlayer.setTheme(colorType ? DDciIcon::Dark : DDciIcon::Light); + }); +} + DWIDGET_END_NAMESPACE diff --git a/src/widgets/private/dbounceanimation_p.h b/src/widgets/private/dbounceanimation_p.h new file mode 100644 index 000000000..09e2d5b3b --- /dev/null +++ b/src/widgets/private/dbounceanimation_p.h @@ -0,0 +1,23 @@ +// SPDX-FileCopyrightText: 2024 UnionTech Software Technology Co., Ltd. +// +// SPDX-License-Identifier: LGPL-3.0-or-later +#ifndef DBOUNCEANIMATION_P_H +#define DBOUNCEANIMATION_P_H + +#include "dbounceanimation.h" +#include + +class DBounceAnimationPrivate : public DTK_CORE_NAMESPACE::DObjectPrivate +{ +public: + DBounceAnimationPrivate(DBounceAnimation *qq); + + QPropertyAnimation *m_animation; + QAbstractScrollArea *m_animationTarget; + int m_deltaSum; + +private: + D_DECLARE_PUBLIC(DBounceAnimation) +}; + +#endif // DBOUNCEANIMATION_P_H diff --git a/src/widgets/private/dbuttonbox_p.h b/src/widgets/private/dbuttonbox_p.h index b383c9bf8..9a540728d 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,13 @@ class DButtonBoxPrivate : public DCORE_NAMESPACE::DObjectPrivate QButtonGroup *group; QBoxLayout *layout; + int m_hoverId; + int m_checkedId; + int m_pressId; + + 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 diff --git a/src/widgets/private/dmessagemanager_p.h b/src/widgets/private/dmessagemanager_p.h new file mode 100644 index 000000000..0b77cf637 --- /dev/null +++ b/src/widgets/private/dmessagemanager_p.h @@ -0,0 +1,33 @@ +// SPDX-FileCopyrightText: 2024 UnionTech Software Technology Co., Ltd. +// +// SPDX-License-Identifier: LGPL-3.0-or-later + +#ifndef DABOUTDIALOG_P_H +#define DABOUTDIALOG_P_H + +#include "dmessagemanager.h" + +#include + +#include +#include +#include + +DWIDGET_BEGIN_NAMESPACE + +class DMessageManagerPrivate : public DCORE_NAMESPACE::DObjectPrivate +{ +public: + DMessageManagerPrivate(DMessageManager *qq); + + QPropertyAnimation *m_aniGeometry; + QPropertyAnimation *m_aniOpacity;; + QParallelAnimationGroup *m_aniGroup; + QLabel *m_label; + + D_DECLARE_PUBLIC(DMessageManager); +}; + +DWIDGET_END_NAMESPACE + +#endif // DABOUTDIALOG_P_H diff --git a/src/widgets/private/dsearchedit_p.h b/src/widgets/private/dsearchedit_p.h index d5c3eaac3..58d614b33 100644 --- a/src/widgets/private/dsearchedit_p.h +++ b/src/widgets/private/dsearchedit_p.h @@ -9,6 +9,7 @@ #include #include +#include QT_BEGIN_NAMESPACE class QAudioInput; @@ -37,6 +38,7 @@ class DSearchEditPrivate : DLineEditPrivate QWidget *iconWidget; QLabel *label; + QPropertyAnimation *animation; #ifdef ENABLE_AI QAction *voiceAction = nullptr; diff --git a/src/widgets/private/dswitchbutton_p.h b/src/widgets/private/dswitchbutton_p.h index 6fc76e72e..81c8513ad 100644 --- a/src/widgets/private/dswitchbutton_p.h +++ b/src/widgets/private/dswitchbutton_p.h @@ -6,9 +6,11 @@ #define DSWITCHBUTTON_P_H #include +#include #include +DGUI_USE_NAMESPACE DWIDGET_BEGIN_NAMESPACE class DSwitchButtonPrivate : public DTK_CORE_NAMESPACE::DObjectPrivate @@ -28,6 +30,8 @@ class DSwitchButtonPrivate : public DTK_CORE_NAMESPACE::DObjectPrivate double animationStartValue = 0.0; double animationEndValue = 0.0; + DDciIconPlayer player; + public: D_DECLARE_PUBLIC(DSwitchButton) }; diff --git a/src/widgets/private/dtoolbutton_p.h b/src/widgets/private/dtoolbutton_p.h new file mode 100644 index 000000000..ff90bf41e --- /dev/null +++ b/src/widgets/private/dtoolbutton_p.h @@ -0,0 +1,26 @@ +// SPDX-FileCopyrightText: 2024 UnionTech Software Technology Co., Ltd. +// +// SPDX-License-Identifier: LGPL-3.0-or-later + +#ifndef DABOUTDIALOG_P_H +#define DABOUTDIALOG_P_H + +#include +#include + +DWIDGET_BEGIN_NAMESPACE + +class DToolButtonPrivate : public DCORE_NAMESPACE::DObjectPrivate +{ +public: + DToolButtonPrivate(DToolButton *qq); + + DDciIcon m_dciIcon; + DDciIconPlayer m_dciPlayer; + + Q_DECLARE_PUBLIC(DToolButton) +}; + +DWIDGET_END_NAMESPACE + +#endif // DABOUTDIALOG_P_H