diff --git a/README.md b/README.md index c42feca..9e5af98 100644 --- a/README.md +++ b/README.md @@ -20,7 +20,7 @@ - [x] Raised Button - [ ] Scroll Bar - [ ] Select Field -- [ ] Slider +- [x] Slider - [ ] Snackbar - [ ] Tabs - [ ] Text Field diff --git a/components/components.pro b/components/components.pro index 8bf94d5..2459a10 100644 --- a/components/components.pro +++ b/components/components.pro @@ -20,7 +20,9 @@ SOURCES = \ qtmaterialprogress_internal.cpp \ qtmaterialprogress.cpp \ qtmaterialcircularprogress_internal.cpp \ - qtmaterialcircularprogress.cpp + qtmaterialcircularprogress.cpp \ + qtmaterialslider_internal.cpp \ + qtmaterialslider.cpp HEADERS = \ qtmaterialavatar_p.h \ qtmaterialavatar.h \ @@ -54,6 +56,9 @@ HEADERS = \ qtmaterialprogress.h \ qtmaterialcircularprogress_internal.h \ qtmaterialcircularprogress_p.h \ - qtmaterialcircularprogress.h + qtmaterialcircularprogress.h \ + qtmaterialslider_internal.h \ + qtmaterialslider_p.h \ + qtmaterialslider.h RESOURCES += \ resources.qrc diff --git a/components/qtmaterialslider.cpp b/components/qtmaterialslider.cpp new file mode 100644 index 0000000..d04a1f5 --- /dev/null +++ b/components/qtmaterialslider.cpp @@ -0,0 +1,394 @@ +#include "qtmaterialslider.h" +#include "qtmaterialslider_p.h" +#include +#include +#include "qtmaterialslider_internal.h" +#include "lib/qtmaterialstyle.h" +#include "lib/qtmaterialstatetransitionevent.h" + +/*! + * \class QtMaterialSliderPrivate + * \internal + */ + +QtMaterialSliderPrivate::QtMaterialSliderPrivate(QtMaterialSlider *q) + : q_ptr(q) +{ +} + +QtMaterialSliderPrivate::~QtMaterialSliderPrivate() +{ +} + +void QtMaterialSliderPrivate::init() +{ + Q_Q(QtMaterialSlider); + + thumb = new QtMaterialSliderThumb(q); + track = new QtMaterialSliderTrack(thumb, q); + stateMachine = new QtMaterialSliderStateMachine(q, thumb, track); + stepTo = 0; + oldValue = q->value(); + trackWidth = 2; + hoverTrack = false; + hoverThumb = false; + hover = false; + step = false; + pageStepMode = true; + useThemeColors = true; + + q->setMouseTracking(true); + q->setFocusPolicy(Qt::StrongFocus); + q->setPageStep(1); + + QSizePolicy sp(QSizePolicy::Expanding, + QSizePolicy::Fixed); + + if (q->orientation() == Qt::Vertical) { + sp.transpose(); + } + + q->setSizePolicy(sp); + q->setAttribute(Qt::WA_WState_OwnSizePolicy, false); + + stateMachine->start(); + QCoreApplication::processEvents(); +} + +QRectF QtMaterialSliderPrivate::trackBoundingRect() const +{ + Q_Q(const QtMaterialSlider); + + qreal hw = static_cast(trackWidth)/2; + + return Qt::Horizontal == q->orientation() + ? QRectF(QT_MATERIAL_SLIDER_MARGIN, q->height()/2 - hw, + q->width() - QT_MATERIAL_SLIDER_MARGIN*2, hw*2) + : QRectF(q->width()/2 - hw, QT_MATERIAL_SLIDER_MARGIN, hw*2, + q->height() - QT_MATERIAL_SLIDER_MARGIN*2); +} + +QRectF QtMaterialSliderPrivate::thumbBoundingRect() const +{ + Q_Q(const QtMaterialSlider); + + return Qt::Horizontal == q->orientation() + ? QRectF(thumb->offset(), q->height()/2 - QT_MATERIAL_SLIDER_MARGIN, + QT_MATERIAL_SLIDER_MARGIN*2, QT_MATERIAL_SLIDER_MARGIN*2) + : QRectF(q->width()/2 - QT_MATERIAL_SLIDER_MARGIN, thumb->offset(), + QT_MATERIAL_SLIDER_MARGIN*2, QT_MATERIAL_SLIDER_MARGIN*2); +} + +int QtMaterialSliderPrivate::valueFromPosition(const QPoint &pos) const +{ + Q_Q(const QtMaterialSlider); + + const int position = Qt::Horizontal == q->orientation() ? pos.x() : pos.y(); + + const int span = Qt::Horizontal == q->orientation() + ? q->width() - QT_MATERIAL_SLIDER_MARGIN*2 + : q->height() - QT_MATERIAL_SLIDER_MARGIN*2; + + return QtMaterialStyle::sliderValueFromPosition( + q->minimum(), + q->maximum(), + position - QT_MATERIAL_SLIDER_MARGIN, + span, + q->invertedAppearance()); +} + +void QtMaterialSliderPrivate::setHovered(bool status) +{ + Q_Q(QtMaterialSlider); + + if (hover == status) { + return; + } + + hover = status; + + if (!q->hasFocus()) { + if (status) { + stateMachine->postEvent(new QtMaterialStateTransitionEvent(SliderNoFocusMouseEnter)); + } else { + stateMachine->postEvent(new QtMaterialStateTransitionEvent(SliderNoFocusMouseLeave)); + } + } + + q->update(); +} + +/*! + * \class QtMaterialSlider + */ + +QtMaterialSlider::QtMaterialSlider(QWidget *parent) + : QAbstractSlider(parent), + d_ptr(new QtMaterialSliderPrivate(this)) +{ + d_func()->init(); +} + +QtMaterialSlider::~QtMaterialSlider() +{ +} + +void QtMaterialSlider::setUseThemeColors(bool value) +{ + Q_D(QtMaterialSlider); + + d->useThemeColors = value; + d->stateMachine->setupProperties(); +} + +bool QtMaterialSlider::useThemeColors() const +{ + Q_D(const QtMaterialSlider); + + return d->useThemeColors; +} + +void QtMaterialSlider::setThumbColor(const QColor &color) +{ + Q_D(QtMaterialSlider); + + d->thumbColor = color; + setUseThemeColors(false); +} + +QColor QtMaterialSlider::thumbColor() const +{ + Q_D(const QtMaterialSlider); + + if (d->useThemeColors || !d->thumbColor.isValid()) { + return QtMaterialStyle::instance().themeColor("primary1"); + } else { + return d->thumbColor; + } +} + +void QtMaterialSlider::setTrackColor(const QColor &color) +{ + Q_D(QtMaterialSlider); + + d->trackColor = color; + setUseThemeColors(false); +} + +QColor QtMaterialSlider::trackColor() const +{ + Q_D(const QtMaterialSlider); + + if (d->useThemeColors || !d->trackColor.isValid()) { + return QtMaterialStyle::instance().themeColor("accent3"); + } else { + return d->trackColor; + } +} + +void QtMaterialSlider::setDisabledColor(const QColor &color) +{ + Q_D(QtMaterialSlider); + + d->disabledColor = color; + setUseThemeColors(false); +} + +QColor QtMaterialSlider::disabledColor() const +{ + Q_D(const QtMaterialSlider); + + if (d->useThemeColors || !d->disabledColor.isValid()) { + return QtMaterialStyle::instance().themeColor("disabled"); + } else { + return d->disabledColor; + } +} + +void QtMaterialSlider::setPageStepMode(bool pageStep) +{ + Q_D(QtMaterialSlider); + + d->pageStepMode = pageStep; +} + +bool QtMaterialSlider::pageStepMode() const +{ + Q_D(const QtMaterialSlider); + + return d->pageStepMode; +} + +/*! + * \remip + */ +QSize QtMaterialSlider::minimumSizeHint() const +{ + return Qt::Horizontal == orientation() + ? QSize(130, 34) + : QSize(34, 130); +} + +void QtMaterialSlider::setInvertedAppearance(bool value) +{ + QAbstractSlider::setInvertedAppearance(value); + + updateThumbOffset(); +} + +/*! + * \remip + */ +void QtMaterialSlider::sliderChange(SliderChange change) +{ + Q_D(QtMaterialSlider); + + if (SliderOrientationChange == change) + { + QSizePolicy sp(QSizePolicy::Expanding, QSizePolicy::Fixed); + if (orientation() == Qt::Vertical) { + sp.transpose(); + } + setSizePolicy(sp); + } + else if (SliderValueChange == change) + { + if (minimum() == value()) { + triggerAction(SliderToMinimum); + d->stateMachine->postEvent(new QtMaterialStateTransitionEvent(SliderChangedToMinimum)); + } else if (maximum() == value()) { + triggerAction(SliderToMaximum); + } + if (minimum() == d->oldValue) { + d->stateMachine->postEvent(new QtMaterialStateTransitionEvent(SliderChangedFromMinimum)); + } + d->oldValue = value(); + } + + updateThumbOffset(); + + QAbstractSlider::sliderChange(change); +} + +/*! + * \remip + */ +void QtMaterialSlider::mouseMoveEvent(QMouseEvent *event) +{ + Q_D(QtMaterialSlider); + + if (isSliderDown()) + { + setSliderPosition(d->valueFromPosition(event->pos())); + } + else + { + QRectF track(d->trackBoundingRect().adjusted(-2, -2, 2, 2)); + + if (track.contains(event->pos()) != d->hoverTrack) { + d->hoverTrack = !d->hoverTrack; + update(); + } + + QRectF thumb(0, 0, 16, 16); + thumb.moveCenter(d->thumbBoundingRect().center()); + + if (thumb.contains(event->pos()) != d->hoverThumb) { + d->hoverThumb = !d->hoverThumb; + update(); + } + + d->setHovered(d->hoverTrack || d->hoverThumb); + } + + QAbstractSlider::mouseMoveEvent(event); +} + +/*! + * \remip + */ +void QtMaterialSlider::mousePressEvent(QMouseEvent *event) +{ + Q_D(QtMaterialSlider); + + const QPoint pos = event->pos(); + + QRectF thumb(0, 0, 16, 16); + thumb.moveCenter(d->thumbBoundingRect().center()); + + if (thumb.contains(pos)) { + setSliderDown(true); + return; + } + + if (!d->pageStepMode) { + setSliderPosition(d->valueFromPosition(event->pos())); + d->thumb->setHaloSize(0); + setSliderDown(true); + return; + } + + d->step = true; + d->stepTo = d->valueFromPosition(pos); + + SliderAction action = d->stepTo > sliderPosition() + ? SliderPageStepAdd + : SliderPageStepSub; + + triggerAction(action); + setRepeatAction(action, 400, 8); +} + +/*! + * \remip + */ +void QtMaterialSlider::mouseReleaseEvent(QMouseEvent *event) +{ + Q_D(QtMaterialSlider); + + if (isSliderDown()) { + setSliderDown(false); + } else if (d->step) { + d->step = false; + setRepeatAction(SliderNoAction, 0); + } + + QAbstractSlider::mouseReleaseEvent(event); +} + +/*! + * \remip + */ +void QtMaterialSlider::leaveEvent(QEvent *event) +{ + Q_D(QtMaterialSlider); + + if (d->hoverTrack) { + d->hoverTrack = false; + update(); + } + if (d->hoverThumb) { + d->hoverThumb = false; + update(); + } + + d->setHovered(false); + + QAbstractSlider::leaveEvent(event); +} + +void QtMaterialSlider::updateThumbOffset() +{ + Q_D(QtMaterialSlider); + + const int offset = QtMaterialStyle::sliderPositionFromValue( + minimum(), + maximum(), + sliderPosition(), + Qt::Horizontal == orientation() + ? width() - QT_MATERIAL_SLIDER_MARGIN*2 + : height() - QT_MATERIAL_SLIDER_MARGIN*2, + invertedAppearance()); + + d->thumb->setOffset(offset); +} diff --git a/components/qtmaterialslider.h b/components/qtmaterialslider.h new file mode 100644 index 0000000..f8e642d --- /dev/null +++ b/components/qtmaterialslider.h @@ -0,0 +1,58 @@ +#ifndef QTMATERIALSLIDER_H +#define QTMATERIALSLIDER_H + +#include +#include + +#define QT_MATERIAL_SLIDER_MARGIN 30 + +class QtMaterialSliderPrivate; + +class QtMaterialSlider : public QAbstractSlider +{ + Q_OBJECT + + Q_PROPERTY(QColor thumbColor WRITE setThumbColor READ thumbColor) + Q_PROPERTY(QColor trackColor WRITE setTrackColor READ trackColor) + Q_PROPERTY(QColor disabledColor WRITE setDisabledColor READ disabledColor) + +public: + explicit QtMaterialSlider(QWidget *parent = 0); + ~QtMaterialSlider(); + + void setUseThemeColors(bool value); + bool useThemeColors() const; + + void setThumbColor(const QColor &color); + QColor thumbColor() const; + + void setTrackColor(const QColor &color); + QColor trackColor() const; + + void setDisabledColor(const QColor &color); + QColor disabledColor() const; + + void setPageStepMode(bool pageStep); + bool pageStepMode() const; + + QSize minimumSizeHint() const Q_DECL_OVERRIDE; + + void setInvertedAppearance(bool value); + +protected: + void sliderChange(SliderChange change) Q_DECL_OVERRIDE; + void mouseMoveEvent(QMouseEvent *event) Q_DECL_OVERRIDE; + void mousePressEvent(QMouseEvent *event) Q_DECL_OVERRIDE; + void mouseReleaseEvent(QMouseEvent *event) Q_DECL_OVERRIDE; + void leaveEvent(QEvent *event) Q_DECL_OVERRIDE; + + void updateThumbOffset(); + + const QScopedPointer d_ptr; + +private: + Q_DISABLE_COPY(QtMaterialSlider) + Q_DECLARE_PRIVATE(QtMaterialSlider) +}; + +#endif // QTMATERIALSLIDER_H diff --git a/components/qtmaterialslider_internal.cpp b/components/qtmaterialslider_internal.cpp new file mode 100644 index 0000000..fd2d50b --- /dev/null +++ b/components/qtmaterialslider_internal.cpp @@ -0,0 +1,408 @@ +#include "qtmaterialslider_internal.h" +#include +#include +#include +#include +#include +#include +#include "qtmaterialslider.h" +#include "lib/qtmaterialstyle.h" +#include "lib/qtmaterialstatetransition.h" + +/*! + * \class QtMaterialSliderStateMachine + * \internal + */ + +QtMaterialSliderStateMachine::QtMaterialSliderStateMachine( + QtMaterialSlider *slider, + QtMaterialSliderThumb *thumb, + QtMaterialSliderTrack *track) + : QStateMachine(slider), + m_slider(slider), + m_thumb(thumb), + m_track(track), + m_topState(new QState(QState::ParallelStates)), + m_fstState(new QState(m_topState)), + m_sndState(new QState(m_topState)), + m_inactiveState(new QState(m_fstState)), + m_focusState(new QState(m_fstState)), + m_slidingState(new QState(m_fstState)), + m_pulseOutState(new QState(m_focusState)), + m_pulseInState(new QState(m_focusState)), + m_minState(new QState(m_sndState)), + m_normalState(new QState(m_sndState)) +{ + addState(m_topState); + setInitialState(m_topState); + + m_fstState->setInitialState(m_inactiveState); + m_focusState->setInitialState(m_pulseOutState); + + m_inactiveState->assignProperty(thumb, "haloSize", 0); + m_slidingState->assignProperty(thumb, "haloSize", 0); + + m_pulseOutState->assignProperty(thumb, "haloSize", 35); + m_pulseInState->assignProperty(thumb, "haloSize", 28); + + m_inactiveState->assignProperty(thumb, "diameter", 11); + m_focusState->assignProperty(thumb, "diameter", 11); + m_slidingState->assignProperty(thumb, "diameter", 17); + + QAbstractTransition *transition; + QtMaterialStateTransition *customTransition; + QPropertyAnimation *animation; + + // Show halo on mouse enter + + customTransition = new QtMaterialStateTransition(SliderNoFocusMouseEnter); + customTransition->setTargetState(m_focusState); + + animation = new QPropertyAnimation(thumb, "haloSize", this); + animation->setEasingCurve(QEasingCurve::InOutSine); + customTransition->addAnimation(animation); + customTransition->addAnimation(new QPropertyAnimation(track, "fillColor", this)); + m_inactiveState->addTransition(customTransition); + + // Show halo on focus in + + transition = new QEventTransition(slider, QEvent::FocusIn); + transition->setTargetState(m_focusState); + + animation = new QPropertyAnimation(thumb, "haloSize", this); + animation->setEasingCurve(QEasingCurve::InOutSine); + transition->addAnimation(animation); + transition->addAnimation(new QPropertyAnimation(track, "fillColor", this)); + m_inactiveState->addTransition(transition); + + // Hide halo on focus out + + transition = new QEventTransition(slider, QEvent::FocusOut); + transition->setTargetState(m_inactiveState); + + animation = new QPropertyAnimation(thumb, "haloSize", this); + animation->setEasingCurve(QEasingCurve::InOutSine); + transition->addAnimation(animation); + transition->addAnimation(new QPropertyAnimation(track, "fillColor", this)); + m_focusState->addTransition(transition); + + // Hide halo on mouse leave, except if widget has focus + + customTransition = new QtMaterialStateTransition(SliderNoFocusMouseLeave); + customTransition->setTargetState(m_inactiveState); + + animation = new QPropertyAnimation(thumb, "haloSize", this); + animation->setEasingCurve(QEasingCurve::InOutSine); + customTransition->addAnimation(animation); + customTransition->addAnimation(new QPropertyAnimation(track, "fillColor", this)); + m_focusState->addTransition(customTransition); + + // Pulse in + + transition = new QSignalTransition(m_pulseOutState, SIGNAL(propertiesAssigned())); + transition->setTargetState(m_pulseInState); + + animation = new QPropertyAnimation(thumb, "haloSize", this); + animation->setEasingCurve(QEasingCurve::InOutSine); + animation->setDuration(1000); + transition->addAnimation(animation); + m_pulseOutState->addTransition(transition); + + // Pulse out + + transition = new QSignalTransition(m_pulseInState, SIGNAL(propertiesAssigned())); + transition->setTargetState(m_pulseOutState); + + animation = new QPropertyAnimation(thumb, "haloSize", this); + animation->setEasingCurve(QEasingCurve::InOutSine); + animation->setDuration(1000); + transition->addAnimation(animation); + m_pulseInState->addTransition(transition); + + // Slider pressed + + transition = new QSignalTransition(slider, SIGNAL(sliderPressed())); + transition->setTargetState(m_slidingState); + animation = new QPropertyAnimation(thumb, "diameter", this); + animation->setDuration(70); + transition->addAnimation(animation); + + animation = new QPropertyAnimation(thumb, "haloSize", this); + animation->setEasingCurve(QEasingCurve::InOutSine); + transition->addAnimation(animation); + m_focusState->addTransition(transition); + + // Slider released + + transition = new QSignalTransition(slider, SIGNAL(sliderReleased())); + transition->setTargetState(m_focusState); + animation = new QPropertyAnimation(thumb, "diameter", this); + animation->setDuration(70); + transition->addAnimation(animation); + + animation = new QPropertyAnimation(thumb, "haloSize", this); + animation->setEasingCurve(QEasingCurve::InOutSine); + transition->addAnimation(animation); + m_slidingState->addTransition(transition); + + // Min. value transitions + + m_minState->assignProperty(thumb, "borderWidth", 2); + m_normalState->assignProperty(thumb, "borderWidth", 0); + + m_sndState->setInitialState(m_minState); + + customTransition = new QtMaterialStateTransition(SliderChangedFromMinimum); + customTransition->setTargetState(m_normalState); + + animation = new QPropertyAnimation(thumb, "fillColor", this); + animation->setDuration(200); + customTransition->addAnimation(animation); + + animation = new QPropertyAnimation(thumb, "haloColor", this); + animation->setDuration(300); + customTransition->addAnimation(animation); + + animation = new QPropertyAnimation(thumb, "borderColor", this); + animation->setDuration(200); + customTransition->addAnimation(animation); + + animation = new QPropertyAnimation(thumb, "borderWidth", this); + animation->setDuration(200); + customTransition->addAnimation(animation); + + m_minState->addTransition(customTransition); + + customTransition = new QtMaterialStateTransition(SliderChangedToMinimum); + customTransition->setTargetState(m_minState); + + animation = new QPropertyAnimation(thumb, "fillColor", this); + animation->setDuration(200); + customTransition->addAnimation(animation); + + animation = new QPropertyAnimation(thumb, "haloColor", this); + animation->setDuration(300); + customTransition->addAnimation(animation); + + animation = new QPropertyAnimation(thumb, "borderColor", this); + animation->setDuration(200); + customTransition->addAnimation(animation); + + animation = new QPropertyAnimation(thumb, "borderWidth", this); + animation->setDuration(200); + customTransition->addAnimation(animation); + + m_normalState->addTransition(customTransition); + + setupProperties(); +} + +QtMaterialSliderStateMachine::~QtMaterialSliderStateMachine() +{ +} + +void QtMaterialSliderStateMachine::setupProperties() +{ + QColor trackColor = m_slider->trackColor(); + QColor thumbColor = m_slider->thumbColor(); + + m_inactiveState->assignProperty(m_track, "fillColor", trackColor.lighter(130)); + m_slidingState->assignProperty(m_track, "fillColor", trackColor); + m_focusState->assignProperty(m_track, "fillColor", trackColor); + + QColor holeColor = m_slider->palette().color(QPalette::Base); + + if (m_slider->parentWidget()) { + holeColor = m_slider->parentWidget()->palette().color(m_slider->backgroundRole()); + } + + m_minState->assignProperty(m_thumb, "fillColor", holeColor); + + m_minState->assignProperty(m_thumb, "haloColor", trackColor); + m_minState->assignProperty(m_thumb, "borderColor", trackColor); + + m_normalState->assignProperty(m_thumb, "fillColor", thumbColor); + m_normalState->assignProperty(m_thumb, "haloColor", thumbColor); + m_normalState->assignProperty(m_thumb, "borderColor", thumbColor); + + m_slider->update(); +} + +/*! + * \class QtMaterialSliderThumb + * \internal + */ + +QtMaterialSliderThumb::QtMaterialSliderThumb(QtMaterialSlider *slider) + : QtMaterialOverlayWidget(slider->parentWidget()), + m_slider(slider), + m_diameter(11), + m_borderWidth(2), + m_haloSize(0), + m_offset(0) +{ + slider->installEventFilter(this); + + setAttribute(Qt::WA_TransparentForMouseEvents, true); +} + +QtMaterialSliderThumb::~QtMaterialSliderThumb() +{ +} + +bool QtMaterialSliderThumb::eventFilter(QObject *obj, QEvent *event) +{ + if (QEvent::ParentChange == event->type()) { + setParent(m_slider->parentWidget()); + } + + return QtMaterialOverlayWidget::eventFilter(obj, event); +} + +void QtMaterialSliderThumb::paintEvent(QPaintEvent *event) +{ + Q_UNUSED(event) + + QPainter painter(this); + painter.setRenderHint(QPainter::Antialiasing); + + // Halo + + QBrush brush; + brush.setStyle(Qt::SolidPattern); + brush.setColor(m_haloColor); + painter.setBrush(brush); + painter.setPen(Qt::NoPen); + + QPointF disp = Qt::Horizontal == m_slider->orientation() + ? QPointF(QT_MATERIAL_SLIDER_MARGIN + m_offset, m_slider->height()/2) + : QPointF(m_slider->width()/2, QT_MATERIAL_SLIDER_MARGIN + m_offset); + + QRectF halo((m_slider->pos() - QPointF(m_haloSize, m_haloSize)/2) + disp, + QSizeF(m_haloSize, m_haloSize)); + + painter.setOpacity(0.15); + painter.drawEllipse(halo); + + // Knob + + const bool isMin = m_slider->value() == m_slider->minimum(); + + brush.setColor(m_slider->isEnabled() + ? m_fillColor + : m_slider->disabledColor()); + painter.setBrush(!m_slider->isEnabled() && isMin + ? Qt::NoBrush + : brush); + + if (m_slider->isEnabled() || isMin) { + QPen pen; + pen.setColor(m_borderColor); + pen.setWidthF((isMin && !m_slider->isEnabled()) ? 1.7 : m_borderWidth); + painter.setPen(pen); + } else { + painter.setPen(Qt::NoPen); + } + + QRectF geometry = Qt::Horizontal == m_slider->orientation() + ? QRectF(m_offset, m_slider->height()/2 - QT_MATERIAL_SLIDER_MARGIN, + QT_MATERIAL_SLIDER_MARGIN*2, QT_MATERIAL_SLIDER_MARGIN*2).translated(m_slider->pos()) + : QRectF(m_slider->width()/2 - QT_MATERIAL_SLIDER_MARGIN, m_offset, + QT_MATERIAL_SLIDER_MARGIN*2, QT_MATERIAL_SLIDER_MARGIN*2).translated(m_slider->pos()); + + qreal s = m_slider->isEnabled() ? m_diameter : 7; + + QRectF thumb(0, 0, s, s); + + thumb.moveCenter(geometry.center()); + + painter.setOpacity(1); + painter.drawEllipse(thumb); +} + +/*! + * \class QtMaterialSliderTrack + * \internal + */ + +QtMaterialSliderTrack::QtMaterialSliderTrack(QtMaterialSliderThumb *thumb, QtMaterialSlider *slider) + : QtMaterialOverlayWidget(slider->parentWidget()), + m_slider(slider), + m_thumb(thumb), + m_trackWidth(2) +{ + slider->installEventFilter(this); + + setAttribute(Qt::WA_TransparentForMouseEvents, true); + + connect(slider, SIGNAL(sliderMoved(int)), this, SLOT(update())); +} + +QtMaterialSliderTrack::~QtMaterialSliderTrack() +{ +} + +bool QtMaterialSliderTrack::eventFilter(QObject *obj, QEvent *event) +{ + if (QEvent::ParentChange == event->type()) { + setParent(m_slider->parentWidget()); + } + + return QtMaterialOverlayWidget::eventFilter(obj, event); +} + +void QtMaterialSliderTrack::paintEvent(QPaintEvent *event) +{ + Q_UNUSED(event) + + QPainter painter(this); + painter.setRenderHint(QPainter::Antialiasing); + + QBrush fg; + fg.setStyle(Qt::SolidPattern); + fg.setColor(m_slider->isEnabled() ? m_slider->thumbColor() + : m_slider->disabledColor()); + QBrush bg; + bg.setStyle(Qt::SolidPattern); + bg.setColor(m_slider->isEnabled() ? m_fillColor + : m_slider->disabledColor()); + + qreal offset = m_thumb->offset(); + + if (Qt::Horizontal == m_slider->orientation()) { + painter.translate(m_slider->x() + QT_MATERIAL_SLIDER_MARGIN, + m_slider->y() + m_slider->height()/2 + - static_cast(m_trackWidth)/2); + } else { + painter.translate(m_slider->x() + m_slider->width()/2 + - static_cast(m_trackWidth)/2, + m_slider->y() + QT_MATERIAL_SLIDER_MARGIN); + } + + QRectF geometry = Qt::Horizontal == m_slider->orientation() + ? QRectF(0, 0, m_slider->width() - QT_MATERIAL_SLIDER_MARGIN*2, m_trackWidth) + : QRectF(0, 0, m_trackWidth, m_slider->height() - QT_MATERIAL_SLIDER_MARGIN*2); + + QRectF bgRect; + QRectF fgRect; + + if (Qt::Horizontal == m_slider->orientation()) { + fgRect = QRectF(0, 0, offset, m_trackWidth); + bgRect = QRectF(offset, 0, m_slider->width(), m_trackWidth).intersected(geometry); + } else { + fgRect = QRectF(0, 0, m_trackWidth, offset); + bgRect = QRectF(0, offset, m_trackWidth, m_slider->height()).intersected(geometry); + } + + if (!m_slider->isEnabled()) { + fgRect = fgRect.width() < 9 ? QRectF() : fgRect.adjusted(0, 0, -6, 0); + bgRect = bgRect.width() < 9 ? QRectF() : bgRect.adjusted(6, 0, 0, 0); + } + + if (m_slider->invertedAppearance()) { + qSwap(bgRect, fgRect); + } + + painter.fillRect(bgRect, bg); + painter.fillRect(fgRect, fg); +} diff --git a/components/qtmaterialslider_internal.h b/components/qtmaterialslider_internal.h new file mode 100644 index 0000000..eab83bf --- /dev/null +++ b/components/qtmaterialslider_internal.h @@ -0,0 +1,228 @@ +#ifndef QTMATERIALSLIDER_INTERNAL_H +#define QTMATERIALSLIDER_INTERNAL_H + +#include +#include "lib/qtmaterialoverlaywidget.h" + +class QtMaterialSlider; +class QtMaterialSliderThumb; +class QtMaterialSliderTrack; + +class QtMaterialSliderStateMachine : public QStateMachine +{ + Q_OBJECT + +public: + QtMaterialSliderStateMachine(QtMaterialSlider *slider, + QtMaterialSliderThumb *thumb, + QtMaterialSliderTrack *track); + ~QtMaterialSliderStateMachine(); + + void setupProperties(); + +//signals: +// void changedToMinimum(); +// void changedFromMinimum(); +// void noFocusMouseEnter(); +// void noFocusMouseLeave(); + +private: + Q_DISABLE_COPY(QtMaterialSliderStateMachine) + + QtMaterialSlider *const m_slider; + QtMaterialSliderThumb *const m_thumb; + QtMaterialSliderTrack *const m_track; + QState *const m_topState; + QState *const m_fstState; + QState *const m_sndState; + QState *const m_inactiveState; + QState *const m_focusState; + QState *const m_slidingState; + QState *const m_pulseOutState; + QState *const m_pulseInState; + QState *const m_minState; + QState *const m_normalState; +}; + +class QtMaterialSliderThumb : public QtMaterialOverlayWidget +{ + Q_OBJECT + + Q_PROPERTY(qreal diameter WRITE setDiameter READ diameter) + Q_PROPERTY(qreal borderWidth WRITE setBorderWidth READ borderWidth) + Q_PROPERTY(QColor borderColor WRITE setBorderColor READ borderColor) + Q_PROPERTY(QColor fillColor WRITE setFillColor READ fillColor) + Q_PROPERTY(qreal haloSize WRITE setHaloSize READ haloSize) + Q_PROPERTY(QColor haloColor WRITE setHaloColor READ haloColor) + +public: + explicit QtMaterialSliderThumb(QtMaterialSlider *slider); + ~QtMaterialSliderThumb(); + + inline void setDiameter(qreal diameter); + inline qreal diameter() const; + + inline void setBorderWidth(qreal width); + inline qreal borderWidth() const; + + inline void setBorderColor(const QColor &color); + inline QColor borderColor() const; + + inline void setFillColor(const QColor &color); + inline QColor fillColor() const; + + inline void setHaloSize(qreal size); + inline qreal haloSize() const; + + inline void setHaloColor(const QColor &color); + inline QColor haloColor() const; + + inline void setOffset(int offset); + inline int offset() const; + +protected: + bool eventFilter(QObject *obj, QEvent *event) Q_DECL_OVERRIDE; + void paintEvent(QPaintEvent *event) Q_DECL_OVERRIDE; + +private: + Q_DISABLE_COPY(QtMaterialSliderThumb) + + const QtMaterialSlider *const m_slider; + QColor m_borderColor; + QColor m_fillColor; + QColor m_haloColor; + qreal m_diameter; + qreal m_borderWidth; + qreal m_haloSize; + int m_offset; +}; + +inline void QtMaterialSliderThumb::setDiameter(qreal diameter) +{ + m_diameter = diameter; + update(); +} + +inline qreal QtMaterialSliderThumb::diameter() const +{ + return m_diameter; +} + +inline void QtMaterialSliderThumb::setBorderWidth(qreal width) +{ + m_borderWidth = width; + update(); +} + +inline qreal QtMaterialSliderThumb::borderWidth() const +{ + return m_borderWidth; +} + +inline void QtMaterialSliderThumb::setBorderColor(const QColor &color) +{ + m_borderColor = color; + update(); +} + +inline QColor QtMaterialSliderThumb::borderColor() const +{ + return m_borderColor; +} + +inline void QtMaterialSliderThumb::setFillColor(const QColor &color) +{ + m_fillColor = color; + update(); +} + +inline QColor QtMaterialSliderThumb::fillColor() const +{ + return m_fillColor; +} + +inline void QtMaterialSliderThumb::setHaloSize(qreal size) +{ + m_haloSize = size; + update(); +} + +inline qreal QtMaterialSliderThumb::haloSize() const +{ + return m_haloSize; +} + +inline void QtMaterialSliderThumb::setHaloColor(const QColor &color) +{ + m_haloColor = color; + update(); +} + +inline QColor QtMaterialSliderThumb::haloColor() const +{ + return m_haloColor; +} + +inline void QtMaterialSliderThumb::setOffset(int offset) +{ + m_offset = offset; + update(); +} + +inline int QtMaterialSliderThumb::offset() const +{ + return m_offset; +} + +class QtMaterialSliderTrack : public QtMaterialOverlayWidget +{ + Q_OBJECT + + Q_PROPERTY(QColor fillColor WRITE setFillColor READ fillColor) + +public: + explicit QtMaterialSliderTrack(QtMaterialSliderThumb *thumb, QtMaterialSlider *slider); + ~QtMaterialSliderTrack(); + + inline void setFillColor(const QColor &color); + inline QColor fillColor() const; + + inline void setTrackWidth(int width); + inline int trackWidth() const; + +protected: + bool eventFilter(QObject *obj, QEvent *event) Q_DECL_OVERRIDE; + void paintEvent(QPaintEvent *event) Q_DECL_OVERRIDE; + +private: + Q_DISABLE_COPY(QtMaterialSliderTrack) + + const QtMaterialSlider *const m_slider; + QtMaterialSliderThumb *const m_thumb; + QColor m_fillColor; + int m_trackWidth; +}; + +inline void QtMaterialSliderTrack::setFillColor(const QColor &color) +{ + m_fillColor = color; + update(); +} + +inline QColor QtMaterialSliderTrack::fillColor() const +{ + return m_fillColor; +} + +void QtMaterialSliderTrack::setTrackWidth(int width) +{ + m_trackWidth = width; + update(); +} + +int QtMaterialSliderTrack::trackWidth() const +{ + return m_trackWidth; +} + +#endif // QTMATERIALSLIDER_INTERNAL_H diff --git a/components/qtmaterialslider_p.h b/components/qtmaterialslider_p.h new file mode 100644 index 0000000..ff6b7ca --- /dev/null +++ b/components/qtmaterialslider_p.h @@ -0,0 +1,49 @@ +#ifndef QTMATERIALSLIDER_P_H +#define QTMATERIALSLIDER_P_H + +#include +#include +#include + +class QtMaterialSlider; +class QtMaterialSliderThumb; +class QtMaterialSliderTrack; +class QtMaterialSliderStateMachine; + +class QtMaterialSliderPrivate +{ + Q_DISABLE_COPY(QtMaterialSliderPrivate) + Q_DECLARE_PUBLIC(QtMaterialSlider) + +public: + QtMaterialSliderPrivate(QtMaterialSlider *q); + ~QtMaterialSliderPrivate(); + + void init(); + + QRectF trackBoundingRect() const; + QRectF thumbBoundingRect() const; + + int valueFromPosition(const QPoint &pos) const; + + void setHovered(bool status); + + QtMaterialSlider *const q_ptr; + QtMaterialSliderThumb *thumb; + QtMaterialSliderTrack *track; + QtMaterialSliderStateMachine *stateMachine; + QColor thumbColor; + QColor trackColor; + QColor disabledColor; + int stepTo; + int oldValue; + int trackWidth; + bool hoverTrack; + bool hoverThumb; + bool hover; + bool step; + bool pageStepMode; + bool useThemeColors; +}; + +#endif // QTMATERIALSLIDER_P_H diff --git a/examples/examples.pro b/examples/examples.pro index f69e771..1e8768c 100644 --- a/examples/examples.pro +++ b/examples/examples.pro @@ -10,7 +10,8 @@ SOURCES = mainwindow.cpp \ flatbuttonsettingseditor.cpp \ iconbuttonsettingseditor.cpp \ progresssettingseditor.cpp \ - circularprogresssettingseditor.cpp + circularprogresssettingseditor.cpp \ + slidersettingseditor.cpp HEADERS = mainwindow.h \ avatarsettingseditor.h \ badgesettingseditor.h \ @@ -20,7 +21,8 @@ HEADERS = mainwindow.h \ flatbuttonsettingseditor.h \ iconbuttonsettingseditor.h \ progresssettingseditor.h \ - circularprogresssettingseditor.h + circularprogresssettingseditor.h \ + slidersettingseditor.h LIBS += ../components/libcomponents.a INCLUDEPATH += ../components/ TARGET = ../examples-exe @@ -36,4 +38,5 @@ FORMS += \ flatbuttonsettingsform.ui \ iconbuttonsettingsform.ui \ progresssettingsform.ui \ - circularprogresssettingsform.ui + circularprogresssettingsform.ui \ + slidersettingsform.ui diff --git a/examples/mainwindow.cpp b/examples/mainwindow.cpp index 0d784ac..71dc2a0 100644 --- a/examples/mainwindow.cpp +++ b/examples/mainwindow.cpp @@ -11,6 +11,7 @@ #include "iconbuttonsettingseditor.h" #include "progresssettingseditor.h" #include "circularprogresssettingseditor.h" +#include "slidersettingseditor.h" MainWindow::MainWindow(QWidget *parent) : QMainWindow(parent) @@ -39,6 +40,7 @@ MainWindow::MainWindow(QWidget *parent) IconButtonSettingsEditor *iconButton = new IconButtonSettingsEditor; ProgressSettingsEditor *progress = new ProgressSettingsEditor; CircularProgressSettingsEditor *circularProgress = new CircularProgressSettingsEditor; + SliderSettingsEditor *slider = new SliderSettingsEditor; stack->addWidget(avatar); stack->addWidget(badge); @@ -49,6 +51,7 @@ MainWindow::MainWindow(QWidget *parent) stack->addWidget(iconButton); stack->addWidget(progress); stack->addWidget(raisedButton); + stack->addWidget(slider); list->addItem("Avatar"); list->addItem("Badge"); @@ -59,6 +62,7 @@ MainWindow::MainWindow(QWidget *parent) list->addItem("Icon Button"); list->addItem("Progress"); list->addItem("Raised Button"); + list->addItem("Slider"); list->setCurrentRow(0); diff --git a/examples/slidersettingseditor.cpp b/examples/slidersettingseditor.cpp new file mode 100644 index 0000000..c0b9236 --- /dev/null +++ b/examples/slidersettingseditor.cpp @@ -0,0 +1,98 @@ +#include "slidersettingseditor.h" +#include +#include +#include "qtmaterialslider.h" + +SliderSettingsEditor::SliderSettingsEditor(QWidget *parent) + : QWidget(parent), + ui(new Ui::SliderSettingsForm), + m_slider(new QtMaterialSlider) +{ + 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); + canvas->setMaximumHeight(300); + layout->addWidget(m_slider); + layout->setAlignment(m_slider, Qt::AlignHCenter); + + setupForm(); + + connect(ui->disabledCheckBox, SIGNAL(toggled(bool)), this, SLOT(updateWidget())); + connect(ui->valueLineEdit, SIGNAL(textChanged(QString)), this, SLOT(updateWidget())); + connect(ui->orientationComboBox, SIGNAL(currentIndexChanged(int)), this, SLOT(updateWidget())); + connect(ui->invertedCheckBox, SIGNAL(toggled(bool)), this, SLOT(updateWidget())); + + connect(m_slider, SIGNAL(valueChanged(int)), this, SLOT(setupForm())); +} + +SliderSettingsEditor::~SliderSettingsEditor() +{ + delete ui; +} + +void SliderSettingsEditor::setupForm() +{ + switch (m_slider->orientation()) + { + case Qt::Horizontal: + ui->orientationComboBox->setCurrentIndex(0); + break; + case Qt::Vertical: + ui->orientationComboBox->setCurrentIndex(1); + break; + default: + break; + } + + ui->disabledCheckBox->setChecked(!m_slider->isEnabled()); + ui->valueLineEdit->setText(QString::number(m_slider->value())); + ui->invertedCheckBox->setChecked(m_slider->invertedAppearance()); +} + +void SliderSettingsEditor::updateWidget() +{ + switch (ui->orientationComboBox->currentIndex()) + { + case 0: + m_slider->setOrientation(Qt::Horizontal); + break; + case 1: + m_slider->setOrientation(Qt::Vertical); + break; + default: + break; + } + + m_slider->setDisabled(ui->disabledCheckBox->isChecked()); + m_slider->setValue(ui->valueLineEdit->text().toInt()); + m_slider->setInvertedAppearance(ui->invertedCheckBox->isChecked()); +} + +void SliderSettingsEditor::selectColor() +{ + QColorDialog dialog; + if (dialog.exec()) { + QColor color = dialog.selectedColor(); + QString senderName = sender()->objectName(); + //if ("textColorToolButton" == senderName) { + // m_avatar->setTextColor(color); + // ui->textColorLineEdit->setText(color.name(QColor::HexRgb)); + //} else if ("backgroundColorToolButton" == senderName) { + // m_avatar->setBackgroundColor(color); + // ui->backgroundColorLineEdit->setText(color.name(QColor::HexRgb)); + //} + } + setupForm(); +} diff --git a/examples/slidersettingseditor.h b/examples/slidersettingseditor.h new file mode 100644 index 0000000..07ca0df --- /dev/null +++ b/examples/slidersettingseditor.h @@ -0,0 +1,27 @@ +#ifndef SLIDERSETTINGSEDITOR_H +#define SLIDERSETTINGSEDITOR_H + +#include +#include "ui_slidersettingsform.h" + +class QtMaterialSlider; + +class SliderSettingsEditor : public QWidget +{ + Q_OBJECT + +public: + explicit SliderSettingsEditor(QWidget *parent = 0); + ~SliderSettingsEditor(); + +protected slots: + void setupForm(); + void updateWidget(); + void selectColor(); + +private: + Ui::SliderSettingsForm *const ui; + QtMaterialSlider *const m_slider; +}; + +#endif // SLIDERSETTINGSEDITOR_H diff --git a/examples/slidersettingsform.ui b/examples/slidersettingsform.ui new file mode 100644 index 0000000..11d2ffe --- /dev/null +++ b/examples/slidersettingsform.ui @@ -0,0 +1,82 @@ + + + SliderSettingsForm + + + + 0 + 0 + 474 + 387 + + + + Form + + + + + 0 + 0 + 361 + 331 + + + + + + + Disabled + + + + + + + + + + Value + + + + + + + + + + Orientation + + + + + + + + Horizontal + + + + + Vertical + + + + + + + + Inverted + + + + + + + + + + + +