From 54028ac4d5f77123b14bc5bf4c0c7f320ad9a047 Mon Sep 17 00:00:00 2001 From: laserpants Date: Mon, 13 Jun 2016 14:49:43 +0300 Subject: [PATCH] implement checkbox --- components/checkbox.cpp | 364 +++++++++++++++++++++++++++---- components/checkbox.h | 38 +++- components/checkbox_internal.cpp | 82 +++++++ components/checkbox_internal.h | 45 ++++ components/checkbox_p.h | 38 ++++ 5 files changed, 522 insertions(+), 45 deletions(-) create mode 100644 components/checkbox_internal.cpp create mode 100644 components/checkbox_internal.h create mode 100644 components/checkbox_p.h diff --git a/components/checkbox.cpp b/components/checkbox.cpp index 7fcfab5..a96cd28 100644 --- a/components/checkbox.cpp +++ b/components/checkbox.cpp @@ -1,67 +1,347 @@ -#include -#include -#include -#include #include "checkbox.h" +#include +#include +#include +#include +#include +#include +#include "checkbox_p.h" +#include "lib/rippleoverlay.h" +#include "lib/style.h" +#include "lib/ripple.h" + +CheckboxPrivate::CheckboxPrivate(Checkbox *q) + : q_ptr(q), + checkedIcon(new CheckboxIcon(QIcon("../qt-material-widgets/ic_check_box_black_24px.svg"))), + uncheckedIcon(new CheckboxIcon(QIcon("../qt-material-widgets/ic_check_box_outline_blank_black_24px.svg"))), + iconSize(24), + useThemeColors(true) +{ +} + +void CheckboxPrivate::init() +{ + Q_Q(Checkbox); + + q->setCheckable(true); + + uncheckedIcon->setParent(q); + checkedIcon->setParent(q); + + ripple = new RippleOverlay(q->parentWidget()); + machine = new QStateMachine(q); + + QFont f(q->font()); + f.setPointSizeF(11); + q->setFont(f); + + uncheckedState = new QState; + transitionState = new QState; + checkedState = new QState; + + machine->addState(uncheckedState); + machine->addState(transitionState); + machine->addState(checkedState); + + machine->setInitialState(uncheckedState); + + QSignalTransition *transition; + QPropertyAnimation *animation; + + transition = new QSignalTransition(q, SIGNAL(toggled(bool))); + transition->setTargetState(transitionState); + uncheckedState->addTransition(transition); + + transition = new QSignalTransition(transitionState, SIGNAL(propertiesAssigned())); + transition->setTargetState(checkedState); + transitionState->addTransition(transition); + + animation = new QPropertyAnimation(checkedIcon, "iconSize"); + animation->setDuration(250); + transition->addAnimation(animation); + + transition = new QSignalTransition(q, SIGNAL(toggled(bool))); + transition->setTargetState(uncheckedState); + checkedState->addTransition(transition); + + animation = new QPropertyAnimation(checkedIcon, "opacity"); + animation->setDuration(350); + transition->addAnimation(animation); + + animation = new QPropertyAnimation(checkedIcon, "iconSize"); + animation->setDuration(1250); + transition->addAnimation(animation); + + q->assignAnimationProperties(); + updatePalette(); + + machine->start(); + + QCoreApplication::processEvents(); +} + +void CheckboxPrivate::updatePalette() +{ + Q_Q(Checkbox); + + if (q->isEnabled()) { + checkedIcon->setColor(q->checkedColor()); + uncheckedIcon->setColor(q->uncheckedColor()); + } else { + checkedIcon->setColor(q->disabledColor()); + uncheckedIcon->setColor(q->disabledColor()); + } +} Checkbox::Checkbox(QWidget *parent) - : QAbstractButton(parent) + : QAbstractButton(parent), + d_ptr(new CheckboxPrivate(this)) { - setFixedSize(200, 200); + d_func()->init(); } Checkbox::~Checkbox() { } -void Checkbox::mousePressEvent(QMouseEvent *event) +void Checkbox::setUseThemeColors(bool state) { - QAbstractButton::mousePressEvent(event); + Q_D(Checkbox); + + d->useThemeColors = state; + d->updatePalette(); + update(); } -void Checkbox::mouseReleaseEvent(QMouseEvent *event) +bool Checkbox::useThemeColors() const { - QAbstractButton::mouseReleaseEvent(event); + Q_D(const Checkbox); + + return d->useThemeColors; +} + +void Checkbox::setCheckedColor(const QColor &color) +{ + Q_D(Checkbox); + + d->checkedColor = color; + setUseThemeColors(false); +} + +QColor Checkbox::checkedColor() const +{ + Q_D(const Checkbox); + + if (d->useThemeColors || !d->checkedColor.isValid()) { + return Style::instance().themeColor("primary1"); + } else { + return d->checkedColor; + } +} + +void Checkbox::setUncheckedColor(const QColor &color) +{ + Q_D(Checkbox); + + d->uncheckedColor = color; + setUseThemeColors(false); +} + +QColor Checkbox::uncheckedColor() const +{ + Q_D(const Checkbox); + + if (d->useThemeColors || !d->uncheckedColor.isValid()) { + return Style::instance().themeColor("text"); + } else { + return d->uncheckedColor; + } +} + +void Checkbox::setTextColor(const QColor &color) +{ + Q_D(Checkbox); + + d->textColor = color; + setUseThemeColors(false); +} + +QColor Checkbox::textColor() const +{ + Q_D(const Checkbox); + + if (d->useThemeColors || !d->textColor.isValid()) { + return Style::instance().themeColor("text"); + } else { + return d->textColor; + } +} + +void Checkbox::setDisabledColor(const QColor &color) +{ + Q_D(Checkbox); + + d->disabledColor = color; + setUseThemeColors(false); +} + +QColor Checkbox::disabledColor() const +{ + Q_D(const Checkbox); + + if (d->useThemeColors || !d->disabledColor.isValid()) { + // Transparency will not work here, since we use this color to + // tint the icon using a QGraphicsColorizeEffect + return Style::instance().themeColor("accent3"); + } else { + return d->disabledColor; + } +} + +void Checkbox::setCheckedIcon(const QIcon &icon) +{ + Q_D(Checkbox); + + d->checkedIcon->setIcon(icon); +} + +QIcon Checkbox::checkedIcon() const +{ + Q_D(const Checkbox); + + return d->checkedIcon->icon(); +} + +void Checkbox::setUncheckedIcon(const QIcon &icon) +{ + Q_D(Checkbox); + + d->uncheckedIcon->setIcon(icon); +} + +QIcon Checkbox::uncheckedIcon() const +{ + Q_D(const Checkbox); + + return d->uncheckedIcon->icon(); +} + +void Checkbox::setIconSize(int size) +{ + Q_D(Checkbox); + + if (size > 38) { + size = 38; + qWarning() << "Checkbox::setIconSize: maximum allowed icon size is 38"; + } + + d->iconSize = size; + assignAnimationProperties(); + + if (isChecked()) { + d->checkedIcon->setIconSize(size); + d->uncheckedIcon->setIconSize(0); + } else { + d->uncheckedIcon->setIconSize(size); + d->checkedIcon->setIconSize(0); + } + update(); +} + +int Checkbox::iconSize() const +{ + Q_D(const Checkbox); + + return d->iconSize; +} + +QSize Checkbox::sizeHint() const +{ + QString s(text()); + if (s.isEmpty()) + return QSize(32, 32); + + QFontMetrics fm = fontMetrics(); + QSize sz = fm.size(Qt::TextShowMnemonic, s); + + return QSize(sz.width() + 44, 32); +} + +bool Checkbox::event(QEvent *event) +{ + Q_D(Checkbox); + + switch (event->type()) + { + case QEvent::Resize: + case QEvent::Move: + d->ripple->setGeometry(geometry().adjusted(-8, -8, 8, 8)); + d->checkedIcon->setGeometry(rect()); + d->uncheckedIcon->setGeometry(rect()); + break; + case QEvent::ParentChange: + QWidget *widget; + if ((widget = parentWidget())) { + d->ripple->setParent(widget); + } + break; + case QEvent::EnabledChange: + d->updatePalette(); + if (isChecked()) { + d->checkedIcon->setIconSize(d->iconSize); + d->uncheckedIcon->setIconSize(0); + } else { + d->checkedIcon->setIconSize(0); + d->uncheckedIcon->setIconSize(d->iconSize); + } + break; + default: + break; + } + return QAbstractButton::event(event); +} + +void Checkbox::mousePressEvent(QMouseEvent *event) +{ + Q_UNUSED(event) + + if (!isEnabled()) + return; + + Q_D(Checkbox); + + Ripple *ripple = new Ripple(QPoint(24, 24)); + ripple->setRadiusEndValue(24); + ripple->setColor(isChecked() ? d->checkedIcon->color() : Qt::black); + d->ripple->addRipple(ripple); + + setChecked(!isChecked()); } void Checkbox::paintEvent(QPaintEvent *event) { Q_UNUSED(event) - QStylePainter p(this); - QStyleOptionButton opt; - initStyleOption(&opt); - p.drawControl(QStyle::CE_CheckBox, opt); + QPainter painter(this); - p.drawRect(rect().adjusted(0, 0, -1, -1)); // tmp + QPen pen; + pen.setColor(isEnabled() ? textColor() : disabledColor()); + painter.setPen(pen); + + painter.drawText(44, 21, text()); } -void Checkbox::initStyleOption(QStyleOptionButton *option) const +void Checkbox::assignAnimationProperties() { - if (!option) - return; - //Q_D(const QCheckBox); - option->initFrom(this); - /* - if (d->down) - option->state |= QStyle::State_Sunken; - if (d->tristate && d->noChange) - option->state |= QStyle::State_NoChange; - else - option->state |= d->checked ? QStyle::State_On : QStyle::State_Off; - */ - if (testAttribute(Qt::WA_Hover) && underMouse()) { - /* - if (d->hovering) - option->state |= QStyle::State_MouseOver; - else - option->state &= ~QStyle::State_MouseOver; - */ - } - option->text = "Label label"; - /* - option->text = d->text; - option->icon = d->icon; - option->iconSize = iconSize(); - */ + Q_D(Checkbox); + + d->uncheckedState->assignProperty(d->checkedIcon, "opacity", 0); + d->uncheckedState->assignProperty(d->checkedIcon, "iconSize", d->iconSize); + d->uncheckedState->assignProperty(d->uncheckedIcon, "color", uncheckedColor()); + + d->transitionState->assignProperty(d->checkedIcon, "iconSize", 0); + + d->checkedState->assignProperty(d->checkedIcon, "opacity", 1); + d->checkedState->assignProperty(d->checkedIcon, "iconSize", d->iconSize); + d->checkedState->assignProperty(d->uncheckedIcon, "color", checkedColor()); } diff --git a/components/checkbox.h b/components/checkbox.h index 75b6707..3802318 100644 --- a/components/checkbox.h +++ b/components/checkbox.h @@ -3,7 +3,7 @@ #include -class QStyleOptionButton; +class CheckboxPrivate; class Checkbox : public QAbstractButton { @@ -13,12 +13,44 @@ public: explicit Checkbox(QWidget *parent = 0); ~Checkbox(); + void setUseThemeColors(bool state); + bool useThemeColors() const; + + void setCheckedColor(const QColor &color); + QColor checkedColor() const; + + void setUncheckedColor(const QColor &color); + QColor uncheckedColor() const; + + void setTextColor(const QColor &color); + QColor textColor() const; + + void setDisabledColor(const QColor &color); + QColor disabledColor() const; + + void setCheckedIcon(const QIcon &icon); + QIcon checkedIcon() const; + + void setUncheckedIcon(const QIcon &icon); + QIcon uncheckedIcon() const; + + void setIconSize(int size); + int iconSize() const; + + QSize sizeHint() const; + protected: + bool event(QEvent *event) Q_DECL_OVERRIDE; void mousePressEvent(QMouseEvent *event) Q_DECL_OVERRIDE; - void mouseReleaseEvent(QMouseEvent *event) Q_DECL_OVERRIDE; void paintEvent(QPaintEvent *event) Q_DECL_OVERRIDE; - void initStyleOption(QStyleOptionButton *option) const; + virtual void assignAnimationProperties(); + + const QScopedPointer d_ptr; + +private: + Q_DISABLE_COPY(Checkbox) + Q_DECLARE_PRIVATE(Checkbox) }; #endif // CHECKBOX_H diff --git a/components/checkbox_internal.cpp b/components/checkbox_internal.cpp new file mode 100644 index 0000000..728ec49 --- /dev/null +++ b/components/checkbox_internal.cpp @@ -0,0 +1,82 @@ +#include "checkbox_internal.h" +#include +#include + +CheckboxIcon::CheckboxIcon(const QIcon &icon, QWidget *parent) + : QWidget(parent), + _icon(icon), + _iconSize(24), + _opacity(1.0), + _effect(new QGraphicsColorizeEffect) +{ + setAttribute(Qt::WA_TransparentForMouseEvents); + + setGraphicsEffect(_effect); +} + +CheckboxIcon::~CheckboxIcon() +{ +} + +void CheckboxIcon::setColor(const QColor &color) +{ + if (_effect->color() == color) + return; + + _effect->setColor(color); + update(); +} + +QColor CheckboxIcon::color() const +{ + return _effect->color(); +} + +void CheckboxIcon::setIconSize(qreal size) +{ + _iconSize = size; + update(); +} + +qreal CheckboxIcon::iconSize() const +{ + return _iconSize; +} + +void CheckboxIcon::setOpacity(qreal opacity) +{ + _opacity = opacity; + update(); +} + +qreal CheckboxIcon::opacity() const +{ + return _opacity; +} + +void CheckboxIcon::paintEvent(QPaintEvent *event) +{ + Q_UNUSED(event) + +#ifdef DEBUG_LAYOUT + QPainter debug(this); + debug.drawRect(rect().adjusted(0, 0, -1, -1)); +#endif + + if (0 == _iconSize) + return; + + QPainter painter(this); + painter.setRenderHint(QPainter::Antialiasing); + painter.setOpacity(_opacity); + + const qreal p = static_cast((height())-_iconSize)/2; + const qreal z = static_cast(_iconSize)/24; + + QTransform t; + t.translate(p, p); + t.scale(z, z); + painter.setTransform(t); + + _icon.paint(&painter, QRect(0, 0, 24, 24)); +} diff --git a/components/checkbox_internal.h b/components/checkbox_internal.h new file mode 100644 index 0000000..fb6aced --- /dev/null +++ b/components/checkbox_internal.h @@ -0,0 +1,45 @@ +#ifndef CHECKBOX_INTERNAL_H +#define CHECKBOX_INTERNAL_H + +#include +#include + +class QGraphicsColorizeEffect; + +class CheckboxIcon : public QWidget +{ + Q_OBJECT + + Q_PROPERTY(QColor color READ color WRITE setColor) + Q_PROPERTY(qreal iconSize READ iconSize WRITE setIconSize) + Q_PROPERTY(qreal opacity READ opacity WRITE setOpacity) + +public: + CheckboxIcon(const QIcon &icon, QWidget *parent = 0); + ~CheckboxIcon(); + + inline void setIcon(const QIcon &icon) { _icon = icon; update(); } + inline QIcon icon() const { return _icon; } + + void setColor(const QColor &color); + QColor color() const; + + void setIconSize(qreal size); + qreal iconSize() const; + + void setOpacity(qreal opacity); + qreal opacity() const; + +protected: + void paintEvent(QPaintEvent *event) Q_DECL_OVERRIDE; + +private: + Q_DISABLE_COPY(CheckboxIcon) + + QIcon _icon; + qreal _iconSize; + qreal _opacity; + QGraphicsColorizeEffect *const _effect; +}; + +#endif // CHECKBOX_INTERNAL_H diff --git a/components/checkbox_p.h b/components/checkbox_p.h new file mode 100644 index 0000000..4cca759 --- /dev/null +++ b/components/checkbox_p.h @@ -0,0 +1,38 @@ +#ifndef CHECKBOX_P_H +#define CHECKBOX_P_H + +#include +#include +#include "checkbox_internal.h" + +class Checkbox; +class RippleOverlay; + +class CheckboxPrivate +{ + Q_DISABLE_COPY(CheckboxPrivate) + Q_DECLARE_PUBLIC(Checkbox) + +public: + CheckboxPrivate(Checkbox *q); + + void init(); + void updatePalette(); + + Checkbox *const q_ptr; + RippleOverlay *ripple; + CheckboxIcon *checkedIcon; + CheckboxIcon *uncheckedIcon; + QStateMachine *machine; + QState *uncheckedState; + QState *transitionState; + QState *checkedState; + int iconSize; + QColor checkedColor; + QColor uncheckedColor; + QColor textColor; + QColor disabledColor; + bool useThemeColors; +}; + +#endif // CHECKBOX_P_H