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/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/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/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/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/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/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;