diff --git a/components/sliderinternal.cpp b/components/sliderinternal.cpp new file mode 100644 index 0000000..2615839 --- /dev/null +++ b/components/sliderinternal.cpp @@ -0,0 +1,434 @@ +#include "sliderinternal.h" +#include +#include +#include +#include +#include +#include +#include "lib/style.h" +#include "slider.h" + +SliderStateMachine::SliderStateMachine(Slider *parent, + SliderThumb *thumb, + SliderTrack *track) + : QStateMachine(parent) +{ + Style &style = Style::instance(); + + QState *topState = new QState(QState::ParallelStates); + + QState *fstState = new QState(topState); + + QState *inactiveState = new QState(fstState); + QState *focusState = new QState(fstState); + QState *slidingState = new QState(fstState); + QState *disabledState = new QState(fstState); + + QState *pulseOutState = new QState(focusState); + QState *pulseInState = new QState(focusState); + + focusState->setInitialState(pulseOutState); + + inactiveState->assignProperty(thumb, "haloSize", 0); + slidingState->assignProperty(thumb, "haloSize", 0); + + pulseOutState->assignProperty(thumb, "haloSize", 35); + pulseInState->assignProperty(thumb, "haloSize", 28); + + disabledState->assignProperty(thumb, "diameter", 7); + disabledState->assignProperty(thumb, "fillColor", style.themeColor("disabled")); + + inactiveState->assignProperty(thumb, "diameter", 11); + focusState->assignProperty(thumb, "diameter", 11); + slidingState->assignProperty(thumb, "diameter", 17); + + QColor fillColor = style.themeColor("primary1"); + + inactiveState->assignProperty(thumb, "fillColor", fillColor); + focusState->assignProperty(thumb, "fillColor", fillColor); + slidingState->assignProperty(thumb, "fillColor", fillColor); + + inactiveState->assignProperty(track, "fillColor", style.themeColor("accent2")); + slidingState->assignProperty(track, "fillColor", style.themeColor("accent3")); + focusState->assignProperty(track, "fillColor", style.themeColor("accent3")); + disabledState->assignProperty(track, "fillColor", style.themeColor("disabled")); + + addState(topState); + + fstState->setInitialState(inactiveState); + + setInitialState(topState); + + QAbstractTransition *transition; + QPropertyAnimation *animation; + + // Add transitions + + transition = new QSignalTransition(this, SIGNAL(sliderDisabled())); + transition->setTargetState(disabledState); + inactiveState->addTransition(transition); + + transition = new QSignalTransition(this, SIGNAL(sliderDisabled())); + transition->setTargetState(disabledState); + focusState->addTransition(transition); + + transition = new QSignalTransition(this, SIGNAL(sliderDisabled())); + transition->setTargetState(disabledState); + slidingState->addTransition(transition); + + transition = new QSignalTransition(this, SIGNAL(sliderEnabled())); + transition->setTargetState(inactiveState); + disabledState->addTransition(transition); + + // Show halo on mouse enter + + transition = new QSignalTransition(this, SIGNAL(noFocusMouseEnter())); + transition->setTargetState(focusState); + + animation = new QPropertyAnimation(thumb, "haloSize"); + animation->setEasingCurve(QEasingCurve::InOutSine); + transition->addAnimation(animation); + transition->addAnimation(new QPropertyAnimation(track, "fillColor")); + inactiveState->addTransition(transition); + + // Show halo on focus in + + transition = new QEventTransition(parent, QEvent::FocusIn); + transition->setTargetState(focusState); + + animation = new QPropertyAnimation(thumb, "haloSize"); + animation->setEasingCurve(QEasingCurve::InOutSine); + transition->addAnimation(animation); + transition->addAnimation(new QPropertyAnimation(track, "fillColor")); + inactiveState->addTransition(transition); + + // Hide halo on focus out + + transition = new QEventTransition(parent, QEvent::FocusOut); + transition->setTargetState(inactiveState); + + animation = new QPropertyAnimation(thumb, "haloSize"); + animation->setEasingCurve(QEasingCurve::InOutSine); + transition->addAnimation(animation); + transition->addAnimation(new QPropertyAnimation(track, "fillColor")); + focusState->addTransition(transition); + + // Hide halo on mouse leave, except if widget has focus + + transition = new QSignalTransition(this, SIGNAL(noFocusMouseLeave())); + transition->setTargetState(inactiveState); + + animation = new QPropertyAnimation(thumb, "haloSize"); + animation->setEasingCurve(QEasingCurve::InOutSine); + transition->addAnimation(animation); + transition->addAnimation(new QPropertyAnimation(track, "fillColor")); + focusState->addTransition(transition); + + // Pulse in + + transition = new QSignalTransition(pulseOutState, SIGNAL(propertiesAssigned())); + transition->setTargetState(pulseInState); + + animation = new QPropertyAnimation(thumb, "haloSize"); + animation->setEasingCurve(QEasingCurve::InOutSine); + animation->setDuration(1000); + transition->addAnimation(animation); + pulseOutState->addTransition(transition); + + // Pulse out + + transition = new QSignalTransition(pulseInState, SIGNAL(propertiesAssigned())); + transition->setTargetState(pulseOutState); + + animation = new QPropertyAnimation(thumb, "haloSize"); + animation->setEasingCurve(QEasingCurve::InOutSine); + animation->setDuration(1000); + transition->addAnimation(animation); + pulseInState->addTransition(transition); + + // Slider pressed + + transition = new QSignalTransition(parent, SIGNAL(sliderPressed())); + transition->setTargetState(slidingState); + animation = new QPropertyAnimation(thumb, "diameter"); + animation->setDuration(70); + transition->addAnimation(animation); + + animation = new QPropertyAnimation(thumb, "haloSize"); + animation->setEasingCurve(QEasingCurve::InOutSine); + transition->addAnimation(animation); + focusState->addTransition(transition); + + // Slider released + + transition = new QSignalTransition(parent, SIGNAL(sliderReleased())); + transition->setTargetState(focusState); + animation = new QPropertyAnimation(thumb, "diameter"); + animation->setDuration(70); + transition->addAnimation(animation); + + animation = new QPropertyAnimation(thumb, "haloSize"); + animation->setEasingCurve(QEasingCurve::InOutSine); + transition->addAnimation(animation); + slidingState->addTransition(transition); + + // Min. value transitions + + QState *sndState = new QState(topState); + + QState *minState = new QState(sndState); + QState *normalState = new QState(sndState); + + QColor minHaloColor = style.themeColor("accent3"); + minHaloColor.setAlphaF(0.15); + + QColor haloColor = style.themeColor("primary1"); + haloColor.setAlphaF(0.15); + + QColor canvasColor = style.themeColor("canvas"); + + minState->assignProperty(thumb, "minFillColor", canvasColor); + minState->assignProperty(thumb, "fillColor", canvasColor); + minState->assignProperty(thumb, "haloColor", minHaloColor); + minState->assignProperty(thumb, "borderWidth", 2); + normalState->assignProperty(thumb, "fillColor", fillColor); + normalState->assignProperty(thumb, "minFillColor", fillColor); + normalState->assignProperty(thumb, "haloColor", haloColor); + normalState->assignProperty(thumb, "borderWidth", 0); + + sndState->setInitialState(minState); + + transition = new QSignalTransition(this, SIGNAL(changedFromMinimum())); + transition->setTargetState(normalState); + + animation = new QPropertyAnimation(thumb, "fillColor"); + animation->setDuration(200); + transition->addAnimation(animation); + + animation = new QPropertyAnimation(thumb, "haloColor"); + animation->setDuration(200); + transition->addAnimation(animation); + + animation = new QPropertyAnimation(thumb, "borderWidth"); + animation->setDuration(400); + transition->addAnimation(animation); + + minState->addTransition(transition); + + transition = new QSignalTransition(this, SIGNAL(changedToMinimum())); + transition->setTargetState(minState); + + animation = new QPropertyAnimation(thumb, "minFillColor"); + animation->setDuration(200); + transition->addAnimation(animation); + + animation = new QPropertyAnimation(thumb, "haloColor"); + animation->setDuration(200); + transition->addAnimation(animation); + + animation = new QPropertyAnimation(thumb, "borderWidth"); + animation->setDuration(400); + transition->addAnimation(animation); + + normalState->addTransition(transition); +} + +SliderStateMachine::~SliderStateMachine() +{ +} + +SliderThumb::SliderThumb(Slider *slider) + : QWidget(slider->parentWidget()), + slider(slider), + _diameter(11), + _borderWidth(2), + _haloSize(0) +{ + slider->installEventFilter(this); + setAttribute(Qt::WA_TransparentForMouseEvents, true); + + connect(slider, SIGNAL(sliderMoved(int)), this, SLOT(update())); + connect(slider, SIGNAL(valueChanged(int)), this, SLOT(update())); +} + +SliderThumb::~SliderThumb() +{ +} + +bool SliderThumb::eventFilter(QObject *obj, QEvent *event) +{ + QEvent::Type type = event->type(); + + if (QEvent::ParentChange == type) { + setParent(slider->parentWidget()); + } else if (QEvent::Resize == type || QEvent::Move == type) { + QWidget *widget; + if ((widget = parentWidget())) { + setGeometry(widget->rect()); + } + } + return QWidget::eventFilter(obj, event); +} + +void SliderThumb::paintEvent(QPaintEvent *event) +{ + QPainter painter(this); + + // Halo + + QBrush brush; + brush.setStyle(Qt::SolidPattern); + brush.setColor(_haloColor); + painter.setBrush(brush); + painter.setPen(Qt::NoPen); + + painter.setRenderHint(QPainter::Antialiasing); + + QPointF disp = Qt::Horizontal == slider->orientation() + ? QPointF(SLIDER_MARGIN + slider->thumbOffset(), slider->height()/2) + : QPointF(slider->width()/2, SLIDER_MARGIN + slider->thumbOffset()); + + QRectF halo((slider->pos() - QPointF(_haloSize, _haloSize)/2) + disp, + QSizeF(_haloSize, _haloSize)); + + painter.drawEllipse(halo); + + // Knob + + brush.setColor(slider->value() > slider->minimum() + ? (slider->isEnabled() + ? _fillColor : Style::instance().themeColor("disabled")) + : _minFillColor); + painter.setBrush(brush); + + if (_borderWidth > 0) { + QPen pen; + pen.setColor(Style::instance().themeColor("accent3")); + pen.setWidthF(_borderWidth); + painter.setPen(pen); + } else { + painter.setPen(Qt::NoPen); + } + + QRectF geometry = Qt::Horizontal == slider->orientation() + ? QRectF(slider->thumbOffset(), slider->height()/2 - SLIDER_MARGIN, + SLIDER_MARGIN*2, SLIDER_MARGIN*2).translated(slider->pos()) + : QRectF(slider->width()/2 - SLIDER_MARGIN, slider->thumbOffset(), + SLIDER_MARGIN*2, SLIDER_MARGIN*2).translated(slider->pos()); + + QRectF thumb(0, 0, _diameter, _diameter); + + thumb.moveCenter(geometry.center()); + + painter.drawEllipse(thumb); + +#ifdef DEBUG_LAYOUT + QPen pen; + pen.setColor(Qt::red); + pen.setWidth(2); + painter.setPen(pen); + painter.setBrush(Qt::NoBrush); + + painter.drawRect(geometry); + + painter.drawRect(rect().adjusted(0, 0, -2, -2)); +#endif + + QWidget::paintEvent(event); +} + +SliderTrack::SliderTrack(Slider *slider) + : QWidget(slider->parentWidget()), + slider(slider), + _width(2) +{ + slider->installEventFilter(this); + setAttribute(Qt::WA_TransparentForMouseEvents, true); + + connect(slider, SIGNAL(sliderMoved(int)), this, SLOT(update())); +} + +SliderTrack::~SliderTrack() +{ +} + +bool SliderTrack::eventFilter(QObject *obj, QEvent *event) +{ + QEvent::Type type = event->type(); + + if (QEvent::ParentChange == type) { + setParent(slider->parentWidget()); + } else if (QEvent::Resize == type || QEvent::Move == type) { + if (parentWidget()) { + setGeometry(parentWidget()->rect()); + } + } + return QWidget::eventFilter(obj, event); +} + +void SliderTrack::paintEvent(QPaintEvent *event) +{ + QPainter painter(this); + Style &style = Style::instance(); + + QBrush fg; + fg.setStyle(Qt::SolidPattern); + fg.setColor(slider->isEnabled() ? style.themeColor("primary1") + : style.themeColor("disabled")); + QBrush bg; + bg.setStyle(Qt::SolidPattern); + bg.setColor(slider->isEnabled() ? _fillColor + : style.themeColor("disabled")); + + painter.setRenderHint(QPainter::Antialiasing); + + qreal offset = slider->thumbOffset(); + + if (Qt::Horizontal == slider->orientation()) { + painter.translate(slider->x() + SLIDER_MARGIN, + slider->y() + slider->height()/2 + - static_cast(_width)/2); + } else { + painter.translate(slider->x() + slider->width()/2 + - static_cast(_width)/2, + slider->y() + SLIDER_MARGIN); + } + + QRectF geometry = Qt::Horizontal == slider->orientation() + ? QRectF(0, 0, slider->width() - SLIDER_MARGIN*2, _width) + : QRectF(0, 0, _width, slider->height() - SLIDER_MARGIN*2); + + QRectF bgRect; + QRectF fgRect; + + if (Qt::Horizontal == slider->orientation()) { + fgRect = QRectF(0, 0, offset, _width); + bgRect = QRectF(offset, 0, slider->width(), _width).intersected(geometry); + } else { + fgRect = QRectF(0, 0, _width, offset); + bgRect = QRectF(0, offset, _width, slider->height()).intersected(geometry); + } + + if (!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 (slider->invertedAppearance()) { + qSwap(bgRect, fgRect); + } + + painter.fillRect(bgRect, bg); + painter.fillRect(fgRect, fg); + +#ifdef DEBUG_LAYOUT + if (slider->hovered()) { + painter.save(); + painter.setPen(Qt::red); + painter.drawRect(geometry); + painter.restore(); + } +#endif + + QWidget::paintEvent(event); +} diff --git a/components/sliderinternal.h b/components/sliderinternal.h new file mode 100644 index 0000000..b2e9a94 --- /dev/null +++ b/components/sliderinternal.h @@ -0,0 +1,173 @@ +#ifndef SLIDERINTERNAL_H +#define SLIDERINTERNAL_H + +#include +#include + +class Slider; +class SliderThumb; +class SliderTrack; + +class SliderStateMachine : public QStateMachine +{ + Q_OBJECT + +public: + SliderStateMachine(Slider *parent, SliderThumb *thumb, SliderTrack *track); + ~SliderStateMachine(); + +signals: + void changedToMinimum(); + void changedFromMinimum(); + void sliderEnabled(); + void sliderDisabled(); + void noFocusMouseEnter(); + void noFocusMouseLeave(); + +private: + Q_DISABLE_COPY(SliderStateMachine) +}; + +class SliderThumb : public QWidget +{ + Q_OBJECT + + Q_PROPERTY(qreal diameter WRITE setDiameter READ diameter) + Q_PROPERTY(qreal borderWidth WRITE setBorderWidth READ borderWidth) + Q_PROPERTY(QColor fillColor WRITE setFillColor READ fillColor) + Q_PROPERTY(QColor minFillColor WRITE setMinFillColor READ minFillColor) + Q_PROPERTY(qreal haloSize WRITE setHaloSize READ haloSize) + Q_PROPERTY(QColor haloColor WRITE setHaloColor READ haloColor) + +public: + explicit SliderThumb(Slider *slider); + ~SliderThumb(); + + inline void setDiameter(qreal diameter) + { + _diameter = diameter; + update(); + } + + inline qreal diameter() const + { + return _diameter; + } + + inline void setBorderWidth(qreal width) + { + _borderWidth = width; + update(); + } + + inline qreal borderWidth() const + { + return _borderWidth; + } + + inline void setFillColor(const QColor &color) + { + _fillColor = color; + update(); + } + + inline QColor fillColor() const + { + return _fillColor; + } + + inline void setMinFillColor(const QColor &color) + { + _minFillColor = color; + update(); + } + + inline QColor minFillColor() const + { + return _minFillColor; + } + + inline void setHaloSize(qreal size) + { + _haloSize = size; + update(); + } + + inline qreal haloSize() const + { + return _haloSize; + } + + inline void setHaloColor(const QColor &color) + { + _haloColor = color; + update(); + } + + inline QColor haloColor() const + { + return _haloColor; + } + +protected: + bool eventFilter(QObject *obj, QEvent *event); + void paintEvent(QPaintEvent *event); + +private: + Q_DISABLE_COPY(SliderThumb) + + const Slider *const slider; + qreal _diameter; + qreal _borderWidth; + qreal _haloSize; + QColor _fillColor; + QColor _minFillColor; + QColor _haloColor; +}; + +class SliderTrack : public QWidget +{ + Q_OBJECT + + Q_PROPERTY(QColor fillColor WRITE setFillColor READ fillColor) + Q_PROPERTY(int trackWidth WRITE setTrackWidth READ trackWidth) + +public: + explicit SliderTrack(Slider *slider); + ~SliderTrack(); + + inline void setFillColor(const QColor &color) + { + _fillColor = color; + update(); + } + + inline QColor fillColor() const + { + return _fillColor; + } + + void setTrackWidth(int width) + { + _width = width; + update(); + } + + int trackWidth() const + { + return _width; + } + +protected: + bool eventFilter(QObject *obj, QEvent *event); + void paintEvent(QPaintEvent *event); + +private: + Q_DISABLE_COPY(SliderTrack) + + const Slider *const slider; + QColor _fillColor; + int _width; +}; + +#endif // SLIDERINTERNAL_H diff --git a/qt-material-widgets.pro b/qt-material-widgets.pro index ba213fb..0cca60d 100644 --- a/qt-material-widgets.pro +++ b/qt-material-widgets.pro @@ -51,11 +51,9 @@ SOURCES += main.cpp\ examples/iconmenuexamples.cpp \ lib/scaleeffect.cpp \ lib/style.cpp \ - components/sliderthumb.cpp \ components/searchfield.cpp \ lib/theme.cpp \ - components/sliderstatemachine.cpp \ - components/slidertrack.cpp + components/sliderinternal.cpp HEADERS += mainwindow.h \ components/appbar.h \ @@ -101,12 +99,10 @@ HEADERS += mainwindow.h \ examples/iconmenuexamples.h \ lib/scaleeffect.h \ lib/style.h \ - components/sliderthumb.h \ components/searchfield.h \ lib/theme.h \ lib/theme_p.h \ - components/sliderstatemachine.h \ - components/slidertrack.h + components/sliderinternal.h RESOURCES += \ resources.qrc