From 20357d05d0b42241ebf12f329104dd1d40453ee5 Mon Sep 17 00:00:00 2001 From: johanneshilden Date: Fri, 29 Sep 2017 12:14:01 +0300 Subject: [PATCH] Add Circular Progress component --- components/components.pro | 9 +- components/qtmaterialcircularprogress.cpp | 254 ++++++++++++++++++ components/qtmaterialcircularprogress.h | 48 ++++ .../qtmaterialcircularprogress_internal.cpp | 20 ++ .../qtmaterialcircularprogress_internal.h | 70 +++++ components/qtmaterialcircularprogress_p.h | 30 +++ examples/circularprogresssettingseditor.cpp | 102 +++++++ examples/circularprogresssettingseditor.h | 27 ++ examples/circularprogresssettingsform.ui | 127 +++++++++ examples/examples.pro | 9 +- examples/mainwindow.cpp | 4 + 11 files changed, 695 insertions(+), 5 deletions(-) create mode 100644 components/qtmaterialcircularprogress.cpp create mode 100644 components/qtmaterialcircularprogress.h create mode 100644 components/qtmaterialcircularprogress_internal.cpp create mode 100644 components/qtmaterialcircularprogress_internal.h create mode 100644 components/qtmaterialcircularprogress_p.h create mode 100644 examples/circularprogresssettingseditor.cpp create mode 100644 examples/circularprogresssettingseditor.h create mode 100644 examples/circularprogresssettingsform.ui diff --git a/components/components.pro b/components/components.pro index 74bf6f4..4e45412 100644 --- a/components/components.pro +++ b/components/components.pro @@ -10,7 +10,9 @@ SOURCES = \ lib/qtmaterialcheckable_internal.cpp \ lib/qtmaterialcheckable.cpp \ lib/qtmaterialripple.cpp \ - lib/qtmaterialrippleoverlay.cpp + lib/qtmaterialrippleoverlay.cpp \ + qtmaterialcircularprogress_internal.cpp \ + qtmaterialcircularprogress.cpp HEADERS = \ qtmaterialavatar_p.h \ qtmaterialavatar.h \ @@ -27,6 +29,9 @@ HEADERS = \ lib/qtmaterialcheckable_p.h \ lib/qtmaterialripple.h \ lib/qtmaterialrippleoverlay.h \ - lib/qtmaterialcheckable.h + lib/qtmaterialcheckable.h \ + qtmaterialcircularprogress_internal.h \ + qtmaterialcircularprogress_p.h \ + qtmaterialcircularprogress.h RESOURCES += \ resources.qrc diff --git a/components/qtmaterialcircularprogress.cpp b/components/qtmaterialcircularprogress.cpp new file mode 100644 index 0000000..c783f75 --- /dev/null +++ b/components/qtmaterialcircularprogress.cpp @@ -0,0 +1,254 @@ +#include "qtmaterialcircularprogress.h" +#include "qtmaterialcircularprogress_p.h" +#include +#include +#include +#include "qtmaterialcircularprogress_internal.h" +#include "lib/qtmaterialstyle.h" + +/*! + * \class QtMaterialCircularProgressPrivate + * \internal + */ + +QtMaterialCircularProgressPrivate::QtMaterialCircularProgressPrivate(QtMaterialCircularProgress *q) + : q_ptr(q) +{ +} + +QtMaterialCircularProgressPrivate::~QtMaterialCircularProgressPrivate() +{ +} + +void QtMaterialCircularProgressPrivate::init() +{ + Q_Q(QtMaterialCircularProgress); + + delegate = new QtMaterialCircularProgressDelegate(q); + progressType = Material::IndeterminateProgress; + penWidth = 6.25; + size = 64; + useThemeColors = true; + + q->setSizePolicy(QSizePolicy(QSizePolicy::MinimumExpanding, + QSizePolicy::MinimumExpanding)); + + QParallelAnimationGroup *group = new QParallelAnimationGroup(q); + group->setLoopCount(-1); + + QPropertyAnimation *animation; + + animation = new QPropertyAnimation(q); + animation->setPropertyName("dashLength"); + animation->setTargetObject(delegate); + animation->setEasingCurve(QEasingCurve::InOutQuad); + animation->setStartValue(0.1); + animation->setKeyValueAt(0.15, 0.2); + animation->setKeyValueAt(0.6, 20); + animation->setKeyValueAt(0.7, 20); + animation->setEndValue(20); + animation->setDuration(2050); + + group->addAnimation(animation); + + animation = new QPropertyAnimation(q); + animation->setPropertyName("dashOffset"); + animation->setTargetObject(delegate); + animation->setEasingCurve(QEasingCurve::InOutSine); + animation->setStartValue(0); + animation->setKeyValueAt(0.15, 0); + animation->setKeyValueAt(0.6, -7); + animation->setKeyValueAt(0.7, -7); + animation->setEndValue(-25); + animation->setDuration(2050); + + group->addAnimation(animation); + + animation = new QPropertyAnimation(q); + animation->setPropertyName("angle"); + animation->setTargetObject(delegate); + animation->setStartValue(0); + animation->setEndValue(719); + animation->setDuration(2050); + + group->addAnimation(animation); + + group->start(); +} + +/*! + * \class QtMaterialCircularProgress + */ + +QtMaterialCircularProgress::QtMaterialCircularProgress(QWidget *parent) + : QProgressBar(parent), + d_ptr(new QtMaterialCircularProgressPrivate(this)) +{ + d_func()->init(); +} + +QtMaterialCircularProgress::~QtMaterialCircularProgress() +{ +} + +void QtMaterialCircularProgress::setProgressType(Material::ProgressType type) +{ + Q_D(QtMaterialCircularProgress); + + d->progressType = type; + update(); +} + +Material::ProgressType QtMaterialCircularProgress::progressType() const +{ + Q_D(const QtMaterialCircularProgress); + + return d->progressType; +} + +void QtMaterialCircularProgress::setUseThemeColors(bool value) +{ + Q_D(QtMaterialCircularProgress); + + if (d->useThemeColors == value) { + return; + } + + d->useThemeColors = value; + update(); +} + +bool QtMaterialCircularProgress::useThemeColors() const +{ + Q_D(const QtMaterialCircularProgress); + + return d->useThemeColors; +} + +void QtMaterialCircularProgress::setLineWidth(qreal width) +{ + Q_D(QtMaterialCircularProgress); + + d->penWidth = width; + update(); + updateGeometry(); +} + +qreal QtMaterialCircularProgress::lineWidth() const +{ + Q_D(const QtMaterialCircularProgress); + + return d->penWidth; +} + +void QtMaterialCircularProgress::setSize(int size) +{ + Q_D(QtMaterialCircularProgress); + + d->size = size; + update(); + updateGeometry(); +} + +int QtMaterialCircularProgress::size() const +{ + Q_D(const QtMaterialCircularProgress); + + return d->size; +} + +void QtMaterialCircularProgress::setColor(const QColor &color) +{ + Q_D(QtMaterialCircularProgress); + + d->color = color; + + MATERIAL_DISABLE_THEME_COLORS + update(); +} + +QColor QtMaterialCircularProgress::color() const +{ + Q_D(const QtMaterialCircularProgress); + + if (d->useThemeColors || !d->color.isValid()) { + return QtMaterialStyle::instance().themeColor("primary1"); + } else { + return d->color; + } +} + +/*! + * \reimp + */ +QSize QtMaterialCircularProgress::sizeHint() const +{ + Q_D(const QtMaterialCircularProgress); + + const qreal s = d->size+d->penWidth+8; + return QSize(s, s); +} + +/*! + * \reimp + */ +void QtMaterialCircularProgress::paintEvent(QPaintEvent *event) +{ + Q_UNUSED(event) + + Q_D(QtMaterialCircularProgress); + + QPainter painter(this); + painter.setRenderHint(QPainter::Antialiasing); + + if (!isEnabled()) + { + QPen pen; + pen.setCapStyle(Qt::RoundCap); + pen.setWidthF(d->penWidth); + pen.setColor(QtMaterialStyle::instance().themeColor("border")); + painter.setPen(pen); + painter.drawLine(rect().center()-QPointF(20, 20), rect().center()+QPointF(20, 20)); + painter.drawLine(rect().center()+QPointF(20, -20), rect().center()-QPointF(20, -20)); + return; + } + + if (Material::IndeterminateProgress == d->progressType) + { + painter.translate(width()/2, height()/2); + painter.rotate(d->delegate->angle()); + } + + QPen pen; + pen.setCapStyle(Qt::RoundCap); + pen.setWidthF(d->penWidth); + pen.setColor(color()); + + if (Material::IndeterminateProgress == d->progressType) + { + QVector pattern; + pattern << d->delegate->dashLength()*d->size/50 << 30*d->size/50; + + pen.setDashOffset(d->delegate->dashOffset()*d->size/50); + pen.setDashPattern(pattern); + + painter.setPen(pen); + + painter.drawEllipse(QPoint(0, 0), d->size/2, d->size/2); + } + else + { + painter.setPen(pen); + + const qreal x = (width()-d->size)/2; + const qreal y = (height()-d->size)/2; + + const qreal a = 360*(value()-minimum())/(maximum()-minimum()); + + QPainterPath path; + path.arcMoveTo(x, y, d->size, d->size, 0); + path.arcTo(x, y, d->size, d->size, 0, a); + + painter.drawPath(path); + } +} diff --git a/components/qtmaterialcircularprogress.h b/components/qtmaterialcircularprogress.h new file mode 100644 index 0000000..dfc306e --- /dev/null +++ b/components/qtmaterialcircularprogress.h @@ -0,0 +1,48 @@ +#ifndef QTMATERIALCIRCULARPROGRESS_H +#define QTMATERIALCIRCULARPROGRESS_H + +#include +#include "lib/qtmaterialtheme.h" + +class QtMaterialCircularProgressPrivate; + +class QtMaterialCircularProgress : public QProgressBar +{ + Q_OBJECT + + Q_PROPERTY(qreal lineWidth WRITE setLineWidth READ lineWidth) + Q_PROPERTY(qreal size WRITE setSize READ size) + Q_PROPERTY(QColor color WRITE setColor READ color) + +public: + explicit QtMaterialCircularProgress(QWidget *parent = 0); + ~QtMaterialCircularProgress(); + + void setProgressType(Material::ProgressType type); + Material::ProgressType progressType() const; + + void setUseThemeColors(bool value); + bool useThemeColors() const; + + void setLineWidth(qreal width); + qreal lineWidth() const; + + void setSize(int size); + int size() const; + + void setColor(const QColor &color); + QColor color() const; + + QSize sizeHint() const Q_DECL_OVERRIDE; + +protected: + void paintEvent(QPaintEvent *event) Q_DECL_OVERRIDE; + + const QScopedPointer d_ptr; + +private: + Q_DISABLE_COPY(QtMaterialCircularProgress) + Q_DECLARE_PRIVATE(QtMaterialCircularProgress) +}; + +#endif // QTMATERIALCIRCULARPROGRESS_H diff --git a/components/qtmaterialcircularprogress_internal.cpp b/components/qtmaterialcircularprogress_internal.cpp new file mode 100644 index 0000000..b11a25f --- /dev/null +++ b/components/qtmaterialcircularprogress_internal.cpp @@ -0,0 +1,20 @@ +#include "qtmaterialcircularprogress_internal.h" + +/*! + * \class QtMaterialCircularProgressDelegate + * \internal + */ + +QtMaterialCircularProgressDelegate::QtMaterialCircularProgressDelegate(QtMaterialCircularProgress *parent) + : QObject(parent), + m_progress(parent), + m_dashOffset(0), + m_dashLength(89), + m_angle(0) +{ + Q_ASSERT(parent); +} + +QtMaterialCircularProgressDelegate::~QtMaterialCircularProgressDelegate() +{ +} diff --git a/components/qtmaterialcircularprogress_internal.h b/components/qtmaterialcircularprogress_internal.h new file mode 100644 index 0000000..a05bd63 --- /dev/null +++ b/components/qtmaterialcircularprogress_internal.h @@ -0,0 +1,70 @@ +#ifndef QTMATERIALCIRCULARPROGRESS_INTERNAL_H +#define QTMATERIALCIRCULARPROGRESS_INTERNAL_H + +#include +#include "qtmaterialcircularprogress.h" + +class QtMaterialCircularProgressDelegate : public QObject +{ + Q_OBJECT + + Q_PROPERTY(qreal dashOffset WRITE setDashOffset READ dashOffset) + Q_PROPERTY(qreal dashLength WRITE setDashLength READ dashLength) + Q_PROPERTY(int angle WRITE setAngle READ angle) + +public: + QtMaterialCircularProgressDelegate(QtMaterialCircularProgress *parent); + ~QtMaterialCircularProgressDelegate(); + + inline void setDashOffset(qreal offset); + inline qreal dashOffset() const; + + inline void setDashLength(qreal length); + inline qreal dashLength() const; + + inline void setAngle(int angle); + inline int angle() const; + +private: + Q_DISABLE_COPY(QtMaterialCircularProgressDelegate) + + QtMaterialCircularProgress *const m_progress; + qreal m_dashOffset; + qreal m_dashLength; + int m_angle; +}; + +inline void QtMaterialCircularProgressDelegate::setDashOffset(qreal offset) +{ + m_dashOffset = offset; + m_progress->update(); +} + +inline qreal QtMaterialCircularProgressDelegate::dashOffset() const +{ + return m_dashOffset; +} + +inline void QtMaterialCircularProgressDelegate::setDashLength(qreal length) +{ + m_dashLength = length; + m_progress->update(); +} + +inline qreal QtMaterialCircularProgressDelegate::dashLength() const +{ + return m_dashLength; +} + +inline void QtMaterialCircularProgressDelegate::setAngle(int angle) +{ + m_angle = angle; + m_progress->update(); +} + +inline int QtMaterialCircularProgressDelegate::angle() const +{ + return m_angle; +} + +#endif // QTMATERIALCIRCULARPROGRESS_INTERNAL_H diff --git a/components/qtmaterialcircularprogress_p.h b/components/qtmaterialcircularprogress_p.h new file mode 100644 index 0000000..451bc9c --- /dev/null +++ b/components/qtmaterialcircularprogress_p.h @@ -0,0 +1,30 @@ +#ifndef QTMATERIALCIRCULARPROGRESS_P_H +#define QTMATERIALCIRCULARPROGRESS_P_H + +#include +#include "lib/qtmaterialtheme.h" + +class QtMaterialCircularProgress; +class QtMaterialCircularProgressDelegate; + +class QtMaterialCircularProgressPrivate +{ + Q_DISABLE_COPY(QtMaterialCircularProgressPrivate) + Q_DECLARE_PUBLIC(QtMaterialCircularProgress) + +public: + QtMaterialCircularProgressPrivate(QtMaterialCircularProgress *q); + ~QtMaterialCircularProgressPrivate(); + + void init(); + + QtMaterialCircularProgress *const q_ptr; + QtMaterialCircularProgressDelegate *delegate; + Material::ProgressType progressType; + QColor color; + qreal penWidth; + int size; + bool useThemeColors; +}; + +#endif // QTMATERIALCIRCULARPROGRESS_P_H diff --git a/examples/circularprogresssettingseditor.cpp b/examples/circularprogresssettingseditor.cpp new file mode 100644 index 0000000..bdc1bae --- /dev/null +++ b/examples/circularprogresssettingseditor.cpp @@ -0,0 +1,102 @@ +#include "circularprogresssettingseditor.h" +#include +#include +#include "qtmaterialcircularprogress.h" + +CircularProgressSettingsEditor::CircularProgressSettingsEditor(QWidget *parent) + : QWidget(parent), + ui(new Ui::CircularProgressSettingsForm), + m_progress(new QtMaterialCircularProgress) +{ + QVBoxLayout *layout = new QVBoxLayout; + setLayout(layout); + + QWidget *widget = new QWidget; + layout->addWidget(widget); + + QWidget *canvas = new QWidget; + canvas->setStyleSheet("QWidget { background: white; }"); + layout->addWidget(canvas); + + ui->setupUi(widget); + layout->setContentsMargins(20, 20, 20, 20); + + layout = new QVBoxLayout; + canvas->setLayout(layout); + layout->addWidget(m_progress); + layout->setAlignment(m_progress, Qt::AlignCenter); + + setupForm(); + + connect(ui->disabledCheckBox, SIGNAL(toggled(bool)), this, SLOT(updateWidget())); + connect(ui->progressTypeComboBox, SIGNAL(currentIndexChanged(int)), this, SLOT(updateWidget())); + connect(ui->progressSlider, SIGNAL(valueChanged(int)), this, SLOT(updateWidget())); + connect(ui->lineWidthDoubleSpinBox, SIGNAL(valueChanged(double)), this, SLOT(updateWidget())); + connect(ui->sizeSpinBox, SIGNAL(valueChanged(int)), this, SLOT(updateWidget())); + connect(ui->useThemeColorsCheckBox, SIGNAL(toggled(bool)), this, SLOT(updateWidget())); + connect(ui->colorToolButton, SIGNAL(pressed()), this, SLOT(selectColor())); + + ui->sizeSpinBox->setRange(10, 200); + ui->progressSlider->setRange(0, 100); +} + +CircularProgressSettingsEditor::~CircularProgressSettingsEditor() +{ + delete ui; +} + +void CircularProgressSettingsEditor::setupForm() +{ + switch (m_progress->progressType()) + { + case Material::DeterminateProgress: + ui->progressTypeComboBox->setCurrentIndex(0); + break; + case Material::IndeterminateProgress: + ui->progressTypeComboBox->setCurrentIndex(1); + break; + default: + break; + } + + ui->disabledCheckBox->setChecked(!m_progress->isEnabled()); + ui->progressSlider->setValue(m_progress->value()); + ui->lineWidthDoubleSpinBox->setValue(m_progress->lineWidth()); + ui->sizeSpinBox->setValue(m_progress->size()); + ui->useThemeColorsCheckBox->setChecked(m_progress->useThemeColors()); +} + +void CircularProgressSettingsEditor::updateWidget() +{ + switch (ui->progressTypeComboBox->currentIndex()) + { + case 0: + m_progress->setProgressType(Material::DeterminateProgress); + break; + case 1: + m_progress->setProgressType(Material::IndeterminateProgress); + break; + default: + break; + } + + m_progress->setDisabled(ui->disabledCheckBox->isChecked()); + m_progress->setValue(ui->progressSlider->value()); + m_progress->setLineWidth(ui->lineWidthDoubleSpinBox->value()); + m_progress->setSize(ui->sizeSpinBox->value()); + m_progress->setUseThemeColors(ui->useThemeColorsCheckBox->isChecked()); +} + +void CircularProgressSettingsEditor::selectColor() +{ + QColorDialog dialog; + if (dialog.exec()) { + QColor color = dialog.selectedColor(); + QString senderName = sender()->objectName(); + if ("colorToolButton" == senderName) { + m_progress->setColor(color); + ui->colorLineEdit->setText(color.name(QColor::HexRgb)); + } + } + setupForm(); +} diff --git a/examples/circularprogresssettingseditor.h b/examples/circularprogresssettingseditor.h new file mode 100644 index 0000000..5077989 --- /dev/null +++ b/examples/circularprogresssettingseditor.h @@ -0,0 +1,27 @@ +#ifndef CIRCULARPROGRESSSETTINGSEDITOR_H +#define CIRCULARPROGRESSSETTINGSEDITOR_H + +#include +#include "ui_circularprogresssettingsform.h" + +class QtMaterialCircularProgress; + +class CircularProgressSettingsEditor : public QWidget +{ + Q_OBJECT + +public: + explicit CircularProgressSettingsEditor(QWidget *parent = 0); + ~CircularProgressSettingsEditor(); + +protected slots: + void setupForm(); + void updateWidget(); + void selectColor(); + +private: + Ui::CircularProgressSettingsForm *const ui; + QtMaterialCircularProgress *const m_progress; +}; + +#endif // CIRCULARPROGRESSSETTINGSEDITOR_H diff --git a/examples/circularprogresssettingsform.ui b/examples/circularprogresssettingsform.ui new file mode 100644 index 0000000..e4fe7c5 --- /dev/null +++ b/examples/circularprogresssettingsform.ui @@ -0,0 +1,127 @@ + + + CircularProgressSettingsForm + + + + 0 + 0 + 400 + 300 + + + + Form + + + + + 0 + 0 + 221 + 197 + + + + + + + Disabled + + + + + + + + + + Progress type + + + + + + + + Determinate + + + + + Indeterminate + + + + + + + + Line width + + + + + + + + + + Size + + + + + + + + + + Use theme colors + + + + + + + + + + Color + + + + + + + + + + + + ... + + + + + + + + + Progress + + + + + + + Qt::Horizontal + + + + + + + + + diff --git a/examples/examples.pro b/examples/examples.pro index 8b9e262..d73e065 100644 --- a/examples/examples.pro +++ b/examples/examples.pro @@ -4,11 +4,13 @@ SOURCES = mainwindow.cpp \ main.cpp \ avatarsettingseditor.cpp \ badgesettingseditor.cpp \ - checkboxsettingseditor.cpp + checkboxsettingseditor.cpp \ + circularprogresssettingseditor.cpp HEADERS = mainwindow.h \ avatarsettingseditor.h \ badgesettingseditor.h \ - checkboxsettingseditor.h + checkboxsettingseditor.h \ + circularprogresssettingseditor.h LIBS += ../components/libcomponents.a INCLUDEPATH += ../components/ TARGET = ../examples-exe @@ -19,4 +21,5 @@ RESOURCES += \ FORMS += \ avatarsettingsform.ui \ badgesettingsform.ui \ - checkboxsettingsform.ui + checkboxsettingsform.ui \ + circularprogresssettingsform.ui diff --git a/examples/mainwindow.cpp b/examples/mainwindow.cpp index 1bb7a35..81af844 100644 --- a/examples/mainwindow.cpp +++ b/examples/mainwindow.cpp @@ -5,6 +5,7 @@ #include "avatarsettingseditor.h" #include "badgesettingseditor.h" #include "checkboxsettingseditor.h" +#include "circularprogresssettingseditor.h" MainWindow::MainWindow(QWidget *parent) : QMainWindow(parent) @@ -27,14 +28,17 @@ MainWindow::MainWindow(QWidget *parent) AvatarSettingsEditor *avatar = new AvatarSettingsEditor; BadgeSettingsEditor *badge = new BadgeSettingsEditor; CheckBoxSettingsEditor *checkbox = new CheckBoxSettingsEditor; + CircularProgressSettingsEditor *circularProgress = new CircularProgressSettingsEditor; stack->addWidget(avatar); stack->addWidget(badge); stack->addWidget(checkbox); + stack->addWidget(circularProgress); list->addItem("Avatar"); list->addItem("Badge"); list->addItem("Checkbox"); + list->addItem("Circular Progress"); list->setCurrentRow(0);