diff --git a/kstyle/animations/breezemultistatedata.h b/kstyle/animations/breezemultistatedata.h index 50dc64c4..c40b6a1f 100644 --- a/kstyle/animations/breezemultistatedata.h +++ b/kstyle/animations/breezemultistatedata.h @@ -1,367 +1,376 @@ #ifndef breezemultistatedata_h #define breezemultistatedata_h /************************************************************************* * Copyright (C) 2014 by Hugo Pereira Da Costa * * * * This program is free software; you can redistribute it and/or modify * * it under the terms of the GNU General Public License as published by * * the Free Software Foundation; either version 2 of the License, or * * (at your option) any later version. * * * * This program is distributed in the hope that it will be useful, * * but WITHOUT ANY WARRANTY; without even the implied warranty of * * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * * GNU General Public License for more details. * * * * You should have received a copy of the GNU General Public License * * along with this program; if not, write to the * * Free Software Foundation, Inc., * * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA . * *************************************************************************/ #include "breezegenericdata.h" namespace Breeze { //// //// //// //// /// /// /// // // / / / / class PropertyWrapperBase { public: PropertyWrapperBase(QObject *object, QByteArray name): _object(object), _name(std::move(name)) {} PropertyWrapperBase(const PropertyWrapperBase &other) { *this = other; } PropertyWrapperBase & operator =(const PropertyWrapperBase &other) = default; virtual ~PropertyWrapperBase() = default; const QByteArray & name() const { return _name; } QObject * object() const { return _object; } operator QVariant() const { return _object->property(_name); } virtual PropertyWrapperBase & operator =(const QVariant &value) { const QVariant oldValue = _object->property(_name); if (oldValue.isValid() && oldValue.type() != value.type()) { QVariant converted = value; bool ok = converted.convert(oldValue.type()); if(!ok) { qDebug("property \"%s\": new value type does not match previous value type (new: %s, old: %s) - trying to cast to original type.", qPrintable(_name), value.typeName(), _object->property(_name).typeName()); Q_ASSERT(ok); } _object->setProperty(_name, converted); } else { _object->setProperty(_name, value); } return *this; } protected: QObject *_object; QByteArray _name; }; -template -class PropertyWrapper: public PropertyWrapperBase { -public: - PropertyWrapper(QObject *object_, const QByteArray &name_): PropertyWrapperBase(object_, name_) - { - QVariant value = object()->property(name()); - if (!value.isValid()) { - object()->setProperty(name(), T()); - } - } - explicit PropertyWrapper(const PropertyWrapper &other) : PropertyWrapperBase(other._object, other._name) {} - PropertyWrapper & operator =(const PropertyWrapper &) = delete; - PropertyWrapperBase & operator =(const PropertyWrapperBase &other) = delete; - - operator T() const { return _object->property(_name).template value(); } - PropertyWrapper & operator =(const T &value) { _object->setProperty(_name, value); return *this; } - PropertyWrapper & operator =(const QVariant &value) override { *this = value.value(); return *this; } -}; - -struct AbstractVariantInterpolator { - virtual ~AbstractVariantInterpolator() = default; - virtual QVariant interpolated(const QVariant &from, const QVariant &to, qreal progress) const = 0; -}; +//// //// //// //// /// /// /// // // / / / / -// QPropertyAnimation with support for passing custom interpolators as parameter and single -// PropertyWrapper instead of object + property parameters. -class CustomPropertyAnimation: public QPropertyAnimation { -public: - CustomPropertyAnimation(const PropertyWrapperBase &property, QObject *parent = nullptr): - QPropertyAnimation(property.object(), property.name(), parent), _interpolator(nullptr) {} + static constexpr const qreal qrealQNaN {std::numeric_limits::quiet_NaN()}; // TODO: remove if unused anywhere but below + static constexpr const QPointF invalidPointF {qrealQNaN, qrealQNaN}; + static const auto isInvalidPointF = [](const QPointF &point) { return std::isnan(point.x()) && std::isnan(point.y()); }; - CustomPropertyAnimation(const PropertyWrapperBase &property, - AbstractVariantInterpolator *interpolator, - QObject *parent = nullptr) - : QPropertyAnimation(property.object(), property.name(), parent) - , _interpolator(interpolator) - {} + class Timeline { + public: + struct Entry { /**************************************/ + Entry(float relStartTime, float relDuration, unsigned dataId, QVariant from, QVariant to, QEasingCurve easingCurve) + : relStartTime(relStartTime) + , state(nullptr) + , dataId(dataId) + , from(std::move(from)) + , to(std::move(to)) + , relDuration(relDuration) + , easingCurve(std::move(easingCurve)) + {} - ~CustomPropertyAnimation() override { delete _interpolator; } + Entry(float relStartTime, unsigned member, QVariant to) + : relStartTime(relStartTime) + , state(nullptr) + , dataId(member) + , from(QVariant()) + , to(std::move(to)) + , relDuration(0) + {} -protected: - QVariant interpolated(const QVariant &from, const QVariant &to, qreal progress) const override { - if (_interpolator != nullptr) { - return _interpolator->interpolated(from, to, progress); - } - return QPropertyAnimation::interpolated(from, to, progress); - } + Entry(float relStartTime, const QVector *state) + : relStartTime(relStartTime) + , state(q_check_ptr(state)) + , relDuration(0) + {} -private: - AbstractVariantInterpolator *_interpolator; -}; + float relStartTime; + const QVector * state; + unsigned dataId; + QVariant from; + QVariant to; + float relDuration; + QEasingCurve easingCurve; -//// //// //// //// /// /// /// // // / / / / + inline bool isSetter() const { return qFuzzyIsNull(relDuration) && !from.isValid(); } + inline bool isStartingFromPreviousValue() const { return !from.isValid() && to.isValid(); } + }; /*******************************************************/ + using EntryList = QVector; -#if 0 - class ValueAnimator: public QAbstractAnimation - { public: - ValueAnimator(QVariant *value, const QVariant &from, const QVariant &to = QVariant(), - qreal duration = qQNaN(), QEasingCurve curve = QEasingCurve::Linear); - - qreal normalizedDuration() const; - - int duration() const override + Timeline(QVector *data, const EntryList *transitions = nullptr) + : _data(q_check_ptr(data)) { + setTransitions(transitions); } - protected: - void updateCurrentTime(int currentTime) override - { - } - - private: - QVariant *_value; - QVariant _from; - QVariant _to; - qreal _duration; - QEasingCurve _curve; - }; - -#endif - class AnimationTimeline: public QAbstractAnimation - { - Q_OBJECT - - public: - struct Entry { - Entry(qreal normalizedStartTime, const PropertyWrapperBase &property, - QVariant from, QVariant to, qreal normalizedDuration, - QEasingCurve easingCurve = QEasingCurve::Linear) - : normalizedStartTime(normalizedStartTime) - , property(property) - , from(std::move(from)) - , to(std::move(to)) - , normalizedDuration(normalizedDuration) - , easingCurve(std::move(easingCurve)) - {} - - Entry(qreal normalizedStartTime, const PropertyWrapperBase &property, QVariant value) - : normalizedStartTime(normalizedStartTime) - , property(property) - , from(QVariant()) - , to(std::move(value)) - , normalizedDuration(0) - { + void setTransitions(const EntryList *transitions) { + _transitions = transitions; + if(transitions != nullptr) { + _transitionStates = QVector(transitions->size()); + } else { + _transitionStates.clear(); } - - inline bool isSetter() const { return qFuzzyIsNull(normalizedDuration) && !from.isValid(); } - inline bool isStartingFromCurrentValue() const { return !qFuzzyIsNull(normalizedDuration) && !from.isValid(); } - - qreal normalizedStartTime; - PropertyWrapperBase property; - QVariant from; - QVariant to; - qreal normalizedDuration; - QEasingCurve easingCurve; - }; - using EntryList = QList; - - AnimationTimeline(QObject *parent, int duration = 0, EntryList entries = {}) - : QAbstractAnimation(parent) - , _entries(std::move(entries)) - , _states(QVector(_entries.length())) - , _durationMs(duration) - {} - - void setDuration(int durationMs) { _durationMs = durationMs; } - int duration() const override { return _durationMs; } - - // XXX: remove - bool isfirst() { - static void *_loggedobj = parent(); - return _loggedobj == parent(); + reset(); } - Q_SIGNALS: - void valueChanged(); + bool updateProgress(qreal progress) { + if(_transitions == nullptr) { + return false; + } - protected: - #define dbg(...) if(isfirst()) { qDebug(__VA_ARGS__); } else {} - void updateCurrentTime(int currentTime) override { bool changed = false; - - for(int i = 0; i < _entries.length(); ++i) { - auto &entry = _entries[i]; - auto &state = _states[i]; - - if(state.processed) { + for (int i = 0; i < _transitions->size(); ++i) { + const Entry &transition = (*_transitions)[i]; + TransitionState &state = _transitionStates[i]; + if (state.processed) { continue; } - int startTime = _durationMs * entry.normalizedStartTime; - int duration = _durationMs * entry.normalizedDuration; - int endTime = startTime + duration; + float relEndTime = transition.relStartTime + transition.relDuration; - if (currentTime < startTime) { - // Too early for this and all following entries - break; + // State setter entry + if (transition.state != nullptr) { + if (relEndTime <= progress) { + *_data = *transition.state; + state.processed = true; + } + continue; } + // Value setter (animated or not) - if (currentTime < endTime) { - if (entry.isStartingFromCurrentValue() && !state.from.isValid()) { - state.from = entry.property; - } - - // We're here only when endTime > startTime => duration > 0 - Q_ASSERT(duration > 0); - qreal progress = qreal(currentTime - startTime) / duration; + Q_ASSERT(transition.dataId < _data->size()); + QVariant &value = (*_data)[transition.dataId]; - const QVariant &from = entry.isStartingFromCurrentValue() ? state.from : entry.from; - entry.property = interpolate(from, entry.to, entry.easingCurve.valueForProgress(progress)); + if (relEndTime < progress) { + // Already ended + if (value != transition.to) { + value = transition.to; + changed = true; + } + state.processed = true; + } else if (transition.relStartTime <= progress) { + // Is running + if (transition.isStartingFromPreviousValue() && !state.previousValue.isValid()) { + state.previousValue = value; + } - dbg("t=%4d; i=%2d; start=%4d; duration=%4d; end=%4d; progress=% 4f", currentTime, i, startTime, duration, endTime, progress); + const qreal transitionProgress = (progress - transition.relStartTime) / transition.relDuration; + const QVariant &from = transition.isStartingFromPreviousValue() ? state.previousValue : transition.from; + const QVariant newValue = interpolate(from, transition.to, transition.easingCurve.valueForProgress(transitionProgress)); + if (value != newValue) { + value = newValue; + changed = true; + } } else { - entry.property = entry.to; - state.processed = true; - dbg("t=%4d; i=%2d; start=%4d; duration=%4d; end=%4d", currentTime, i, startTime, duration, endTime); + // Too early + break; } - changed = true; } - if (changed) { - emit valueChanged(); - } + return changed; } - void updateState(QAbstractAnimation::State newState, QAbstractAnimation::State oldState) override - { - Q_UNUSED(oldState); - dbg("updateState: %d", newState); - - switch(newState) { - case Running: - dbg("Timings for %dms:", _durationMs); - for (int i = 0; i < _states.length(); ++i) { - auto &entry = _entries[i]; - auto &state = _states[i]; - - state.processed = false; - if (entry.isStartingFromCurrentValue()) { - state.from = QVariant(); - } - - int startTime = _durationMs * entry.normalizedStartTime; - int duration = _durationMs * entry.normalizedDuration; - int endTime = startTime + duration; - dbg(" * % 4d → % 4d; % 4d [%s]", startTime, endTime, duration, qPrintable(entry.property.name())); - } - default: break; + void reset() { + for(auto &state: _transitionStates) { + state = TransitionState(); } } private: - template - static T interpolateGeneric(const QVariant &from, const QVariant &to, qreal progress) { - const T a = from.value(); - const T b = to.value(); + template + static ValueType interpolateGeneric(const QVariant &from, const QVariant &to, qreal progress) { + const auto a = from.value(); + const auto b = to.value(); return a * (1.0 - progress) + b * progress; } static QVariant interpolate(const QVariant &from, const QVariant &to, qreal progress) { switch(QMetaType::Type(from.type())) { case QMetaType::Int: return interpolateGeneric(from, to, progress); case QMetaType::UInt: return interpolateGeneric(from, to, progress); case QMetaType::LongLong: return interpolateGeneric(from, to, progress); case QMetaType::ULongLong: return interpolateGeneric(from, to, progress); case QMetaType::Float: return interpolateGeneric(from, to, progress); case QMetaType::Double: return interpolateGeneric(from, to, progress); case QMetaType::QPoint: return interpolateGeneric(from, to, progress); case QMetaType::QPointF: return interpolateGeneric(from, to, progress); default: qWarning("Interpolation not supported for type %s", from.typeName()); return to; } } - struct EntryState { - QVariant from {QVariant()}; + struct TransitionState { + QVariant previousValue {QVariant()}; bool processed {false}; }; - EntryList _entries; - QVector _states; + QVector *_data; + const EntryList *_transitions; + QVector _transitionStates; + }; + class TimelineAnimation: public QAbstractAnimation { + Q_OBJECT + + public: + TimelineAnimation(QObject *parent, int durationMs, Timeline *timeline) + : QAbstractAnimation(parent) + , _durationMs(durationMs) + , _timeline(q_check_ptr(timeline)) + {} + + void setDuration(int durationMs) { _durationMs = durationMs; } + int duration() const override { return _durationMs; } + + inline Timeline * timeline() { return _timeline; } + + Q_SIGNALS: + void valueChanged(); + + protected: + void updateCurrentTime(int currentTime) override { + Q_ASSERT(_timeline); + + const bool changed = _timeline->updateProgress(qreal(currentTime)/_durationMs); + if (changed) { + emit valueChanged(); + } + } + + void updateState(QAbstractAnimation::State newState, QAbstractAnimation::State oldState) override + { + Q_UNUSED(oldState); + + switch(newState) { + case Running: + Q_ASSERT(_timeline); + + _timeline->reset(); + break; + default: break; + } + } + + private: int _durationMs; + Timeline *_timeline; }; +// template +// class FixedTypeVariant: public QVariant { +// public: +// FixedTypeVariant(): QVariant(qMetaTypeId(), nullptr) { +// static_assert (sizeof(FixedTypeVariant) == sizeof(QVariant)); +// } +// FixedTypeVariant(const T &val): QVariant(val) {} + +// operator T() const { return value(); } +// FixedTypeVariant & operator =(const T &value) { setValue(value); return *this; } +// }; -//////////////////////////////////////////////////////////////////////////////////////////////////// + class CheckMarkRenderer { +// Q_OBJECT + public: + enum DataId: unsigned { + Position, + LinePointPosition_0, + LinePointPosition_1, + LinePointPosition_2, + PointPosition_0, + PointPosition_1, + PointPosition_2, + PointRadius_0, + PointRadius_1, + PointRadius_2, + + DataIdCount, + + LinePointPosition = LinePointPosition_0, + LinePointPosition_Last = LinePointPosition_2, + + PointPosition = PointPosition_0, + PointPosition_Last = PointPosition_2, + + PointRadius = PointRadius_0, + PointRadius_Last = PointRadius_2, + }; + + void setState(CheckBoxState newState); + void render(QPainter &painter) {} + }; //* Tracks arbitrary states (e.g. tri-state checkbox check state) class MultiStateData: public GenericData { - Q_OBJECT public: //* constructor MultiStateData( QObject* parent, QWidget* target, int duration, QVariant state = QVariant() ): GenericData( parent, target, duration ), _initialized( false ), _state( state ), - _previousState( state ) - {} + _previousState( state ), + timeline(new Timeline(&variables)), + anim(new TimelineAnimation(this, 250, timeline)) + { + connect(anim, &TimelineAnimation::valueChanged, target, QOverload<>::of(&QWidget::update)); + } //* destructor - virtual ~MultiStateData() - {} + ~MultiStateData() override + { + anim->stop(); + delete timeline; + } /** returns true if state has changed and starts timer accordingly */ virtual bool updateState( const QVariant &value ); virtual QVariant state() const { return _state; } virtual QVariant previousState() const { return _previousState; } - QMap anims; + QVector variables; + Timeline *timeline; + TimelineAnimation *anim; private: bool _initialized; QVariant _state; QVariant _previousState; }; } #endif diff --git a/kstyle/checkbox.cpp b/kstyle/checkbox.cpp index 2c637340..e7fa9abb 100644 --- a/kstyle/checkbox.cpp +++ b/kstyle/checkbox.cpp @@ -1,355 +1,323 @@ #include "breezestyle.h" #include "breeze.h" #include "breezeanimations.h" #include "breezemultistatedata.h" #include #include #include #include #include #include #include +#include + namespace Breeze { + using Id = CheckMarkRenderer::DataId; + + namespace { + const QVector offStateData { + /* Position */ QPointF(0, 0), + /* LinePointPosition */ invalidPointF, invalidPointF, invalidPointF, + /* PointPosition */ invalidPointF, invalidPointF, invalidPointF, + /* PointRadius */ 0.0f, 0.0f, 0.0f + }; + const QVector onStateData = { + /* Position */ QPointF(-1, 3), + /* LinePointPosition */ QPointF(-3, -3), QPointF(0, 0), QPointF(5, -5), + /* PointPosition */ invalidPointF, invalidPointF, invalidPointF, + /* PointRadius */ 0.0f, 0.0f, 0.0f, + }; + const QVector partialStateData = { + /* Position */ QPointF(0, 0), + /* LinePointPosition */ invalidPointF, invalidPointF, invalidPointF, + /* PointPosition */ QPointF(-4, 0), QPointF( 0, 0), QPointF(4, 0), + /* PointRadius */ 1.0f, 1.0f, 1.0f, + }; + + const Timeline::EntryList offToOnTransition { + {0.0f, &offStateData}, + {0.0f, Id::Position, onStateData[Id::Position]}, + {0.0f, Id::LinePointPosition_0, onStateData[Id::LinePointPosition_0]}, + {0.0f, 0.4f, Id::LinePointPosition_1, onStateData[Id::LinePointPosition_0], onStateData[Id::LinePointPosition_1], QEasingCurve::InOutCubic}, + {0.5f, 0.5f, Id::LinePointPosition_2, onStateData[Id::LinePointPosition_1], onStateData[Id::LinePointPosition_2], QEasingCurve::InOutCubic}, + {1.0f, &onStateData}, + }; + const Timeline::EntryList onToOffTransition { + {0.0f, &onStateData}, + {0.0f, 0.5f, Id::LinePointPosition_0, onStateData[Id::LinePointPosition_0], onStateData[Id::LinePointPosition_1], QEasingCurve::InOutCubic}, + {0.6f, 0.4f, Id::LinePointPosition_0, onStateData[Id::LinePointPosition_1], onStateData[Id::LinePointPosition_2], QEasingCurve::InOutCubic}, + {0.6f, 0.4f, Id::LinePointPosition_1, onStateData[Id::LinePointPosition_1], onStateData[Id::LinePointPosition_2], QEasingCurve::InOutCubic}, + {1.0f, &offStateData}, + }; + + const Timeline::EntryList offToPartialTransition { + {0.0f, &offStateData}, + {0.0f, Id::PointPosition_0, partialStateData[Id::PointPosition_0]}, + {0.0f, Id::PointPosition_1, partialStateData[Id::PointPosition_1]}, + {0.0f, Id::PointPosition_2, partialStateData[Id::PointPosition_2]}, + {0.0f, 0.6f, Id::PointRadius_0, QVariant(), partialStateData[Id::PointRadius_0], QEasingCurve::OutCubic}, + {0.2f, 0.6f, Id::PointRadius_1, QVariant(), partialStateData[Id::PointRadius_1], QEasingCurve::OutCubic}, + {0.4f, 0.6f, Id::PointRadius_2, QVariant(), partialStateData[Id::PointRadius_2], QEasingCurve::OutCubic}, + {1.0f, &partialStateData}, + }; + const Timeline::EntryList partialToOffTransition { + {0.0f, &partialStateData}, + {0.0f, 0.6f, Id::PointRadius_0, partialStateData[Id::PointRadius_0], offStateData[Id::PointRadius_0], QEasingCurve::InCubic}, + {0.2f, 0.6f, Id::PointRadius_1, partialStateData[Id::PointRadius_1], offStateData[Id::PointRadius_1], QEasingCurve::InCubic}, + {0.4f, 0.6f, Id::PointRadius_2, partialStateData[Id::PointRadius_2], offStateData[Id::PointRadius_2], QEasingCurve::InCubic}, + {1.0f, &offStateData}, + }; + + const float partialPointRadiusSqrt2 = partialStateData[Id::PointRadius_0].toFloat() * sqrtf(2); + const float partialPointRadiusSqrt3 = partialStateData[Id::PointRadius_0].toFloat() * sqrtf(3); + const QPointF onAbsLinePointPosition_2 = onStateData[Id::LinePointPosition_2].toPointF() + onStateData[Id::Position].toPointF(); + + const Timeline::EntryList partialToOnTransition { + {0.0f, &partialStateData}, + {0.0f, Id::Position, onStateData[Id::Position]}, + {0.0f, Id::LinePointPosition_0, onStateData[Id::LinePointPosition_0]}, + + {0.0f, 0.4f, Id::LinePointPosition_1, onStateData[Id::LinePointPosition_0], onStateData[Id::LinePointPosition_1], QEasingCurve::InOutCubic}, + {0.0f, 0.4f, Id::PointRadius_0, QVariant(), onStateData[Id::PointRadius_0], QEasingCurve::InOutCubic}, + {0.0f, 0.5f, Id::PointPosition_1, QVariant(), onStateData[Id::Position], QEasingCurve::InOutCubic}, + {0.0f, 0.5f, Id::PointPosition_2, QVariant(), onStateData[Id::Position], QEasingCurve::InOutCubic}, + {0.0f, 0.5f, Id::PointRadius_1, QVariant(), partialPointRadiusSqrt2, QEasingCurve::InOutCubic}, + {0.0f, 0.5f, Id::PointRadius_2, QVariant(), partialPointRadiusSqrt2, QEasingCurve::InOutCubic}, + + {0.5f, 0.5f, Id::LinePointPosition_2, onStateData[Id::LinePointPosition_1], onStateData[Id::LinePointPosition_2], QEasingCurve::InOutCubic}, + {0.5f, 0.5f, Id::PointPosition_2, QVariant(), onAbsLinePointPosition_2, QEasingCurve::InOutCubic}, + {0.5f, 0.5f, Id::PointRadius_1, QVariant(), onStateData[Id::PointRadius_1], QEasingCurve::InOutCubic}, + {0.5f, 0.5f, Id::PointRadius_2, QVariant(), onStateData[Id::PointRadius_2], QEasingCurve::InOutCubic}, + {1.0f, &onStateData}, + }; + const Timeline::EntryList onToPartialTransition { + {0.0f, &onStateData}, + {0.0f, 0.4f, Id::Position, QVariant(), partialStateData[Id::Position], QEasingCurve::InOutCubic}, + {0.0f, 0.4f, Id::LinePointPosition_0, QVariant(), onStateData[Id::LinePointPosition_1], QEasingCurve::InOutCubic}, + {0.0f, 0.4f, Id::LinePointPosition_2, QVariant(), onStateData[Id::LinePointPosition_1], QEasingCurve::InOutCubic}, + {0.0f, 0.4f, Id::PointPosition_1, onStateData[Id::Position], partialStateData[Id::PointPosition_1], QEasingCurve::InOutCubic}, + {0.0f, 0.4f, Id::PointRadius_1, QVariant(), partialPointRadiusSqrt3, QEasingCurve::InOutCubic}, + + {0.5f, 0.5f, Id::PointPosition_0, partialStateData[Id::PointPosition_1], partialStateData[Id::PointPosition_0], QEasingCurve::InOutCubic}, + {0.5f, 0.5f, Id::PointPosition_2, partialStateData[Id::PointPosition_1], partialStateData[Id::PointPosition_2], QEasingCurve::InOutCubic}, + {0.5f, 0.5f, Id::PointRadius_0, partialPointRadiusSqrt3, partialStateData[Id::PointRadius_0], QEasingCurve::InOutCubic}, + {0.5f, 0.5f, Id::PointRadius_1, partialPointRadiusSqrt3, partialStateData[Id::PointRadius_1], QEasingCurve::InOutCubic}, + {0.5f, 0.5f, Id::PointRadius_2, partialPointRadiusSqrt3, partialStateData[Id::PointRadius_2], QEasingCurve::InOutCubic}, + {1.0f, &partialStateData}, + }; + } + //___________________________________________________________________________________ void Style::drawChoicePrimitive(const QStyleOption *option, QPainter *painter, const QWidget* widget, bool isRadioButton) const { // copy rect and palette const auto& rect( option->rect ); const auto& palette( option->palette ); // copy state const State& state( option->state ); const bool enabled( state & State_Enabled ); const bool mouseOver( enabled && ( state & State_MouseOver ) ); const bool hasFocus( enabled && ( state & State_HasFocus ) ); // FIXME (mglb): move to some animation engine QVariant lastStateVariant = widget ? widget->property("_breeze_lastState") : QVariant(); int lastState; if(lastStateVariant.isValid()) { lastState = lastStateVariant.toInt(); } else { lastState = option->state & (State_On | State_NoChange | State_Off); } if(widget) { auto *widgetRw = const_cast(widget); widgetRw->setProperty("_breeze_lastState", QVariant(option->state & (State_On | State_NoChange | State_Off))); } // focus takes precedence over mouse over _animations->widgetStateEngine().updateState( widget, AnimationFocus, hasFocus ); _animations->widgetStateEngine().updateState( widget, AnimationHover, mouseOver ); // retrieve animation mode and opacity const AnimationMode mode( _animations->widgetStateEngine().frameAnimationMode( widget ) ); const qreal opacity( _animations->widgetStateEngine().frameOpacity( widget ) ); // Render background and frame // Foreground and background color const auto &normalBackground = palette.color(QPalette::Base); const auto &normalForeground = palette.color(QPalette::Text); const auto &focusBackground = palette.color(QPalette::Highlight); const auto &focusForeground = palette.color(QPalette::HighlightedText); const auto focusOpacityOrInvalid = _animations->widgetStateEngine().opacity(widget, AnimationFocus); const auto focusOpacity = focusOpacityOrInvalid != AnimationData::OpacityInvalid ? focusOpacityOrInvalid : 1.0 * int(hasFocus); const auto background = KColorUtils::mix(normalBackground, focusBackground, focusOpacity); const auto foreground = hasFocus ? focusForeground : normalForeground; // Frame color - hover priority QColor outline = _helper->frameOutlineColor(palette, mouseOver, hasFocus, opacity, mode); _helper->renderFrame( painter, rect.adjusted(0, 0, -0, -0), background, outline , isRadioButton); // Render mark static const auto inQuadEasingCurve = [](qreal v) { return v*v; }; static const auto outQuadEasingCurve = [](qreal v) { return 1.0-inQuadEasingCurve(1.0-v); }; painter->setRenderHint( QPainter::Antialiasing, true ); if(isRadioButton) { RadioButtonState radioButtonState = state & State_On ? RadioOn : RadioOff; _animations->widgetStateEngine().updateState( widget, AnimationPressed, radioButtonState != RadioOff ); if( _animations->widgetStateEngine().isAnimated( widget, AnimationPressed ) ) { radioButtonState = radioButtonState == RadioOn ? RadioOffToOn : RadioOnToOff; } const qreal animation = _animations->widgetStateEngine().opacity( widget, AnimationPressed ); if(radioButtonState == RadioOff) { return; } /* painter->setBrush( Qt::NoBrush ); painter->setPen( QPen(foreground, 4, Qt::SolidLine, Qt::RoundCap) ); */ const QPointF center = {rect.x() + rect.width() / 2.0, rect.y() + rect.height() / 2.0}; const qreal fullRadius = 4.0; if(radioButtonState == RadioOn) { painter->setBrush( foreground ); painter->setPen( Qt::NoPen ); painter->drawEllipse(center, fullRadius, fullRadius); } else { qreal radius; QColor color = foreground; if(radioButtonState == RadioOffToOn) { radius = outQuadEasingCurve(animation) * fullRadius; color.setAlphaF(outQuadEasingCurve(animation)); painter->setBrush(color); painter->setPen( Qt::NoPen ); } else { qreal penWidth = fullRadius * inQuadEasingCurve(animation); radius = fullRadius / 2.0 + ((rect.width() - fullRadius) / 2 - 2) * outQuadEasingCurve(1.0-animation); color.setAlphaF(inQuadEasingCurve(animation)); painter->setBrush(Qt::NoBrush); painter->setPen(QPen(color, penWidth)); } painter->drawEllipse(center, radius, radius); } } else { const CheckBoxState checkBoxState = state & State_NoChange ? CheckPartial : state & State_On ? CheckOn : CheckOff; bool startAnim = (checkBoxState != _animations->multiStateEngine().state(widget).value()); _animations->multiStateEngine().updateState(widget, checkBoxState); const QVariant lastStateVariant = _animations->multiStateEngine().previousState(widget); const CheckBoxState previousCheckBoxState = lastStateVariant.isValid() ? lastStateVariant.value() : CheckBoxState::CheckOff; qreal progress = _animations->multiStateEngine().opacity(widget); if(!_animations->multiStateEngine().isAnimated(widget)) { progress = 1.0; } const QPoint centerOffset = {rect.width()/2 + rect.x(), rect.height()/2 + rect.y()}; - static const auto makePropertyAnimation = - [](const PropertyWrapperBase &property, const QVariant &start, - const QVariant &end = QVariant(), unsigned duration = 0, - const QEasingCurve &easing = QEasingCurve::Linear, - AbstractVariantInterpolator *interpolator = nullptr, - QPropertyAnimation **outPtr = nullptr) - { - QPropertyAnimation *p = new CustomPropertyAnimation(property, interpolator); - p->setStartValue(start); - p->setEndValue(end.isValid() ? end : start); - p->setDuration(duration); - p->setEasingCurve(easing); - if (outPtr != nullptr) { - *outPtr = p; - } - return p; - }; - static const auto makeParallelAnimationGroup = [](std::initializer_list animations) - { - auto *group = new QParallelAnimationGroup(); - for(auto *animation: animations) { - group->addAnimation(animation); - } - return group; - }; - static const auto makeSequentialAnimationGroup = [](std::initializer_list animations) - { - auto *group = new QSequentialAnimationGroup(); - for(auto *animation: animations) { - group->addAnimation(animation); - } - return group; - }; - static const auto connectToWidget = [](QAbstractAnimation *animation, const QWidget *widget) - { - auto *timeline = qobject_cast(animation); - if(timeline != nullptr) { - connect(timeline, &AnimationTimeline::valueChanged, - const_cast(widget), QOverload<>::of(&QWidget::update)); - } - }; + DataMap::Value dataPtr = _animations->multiStateEngine().data(widget); - static const QPointF invalidPointF(qQNaN(), qQNaN()); - // FIXME: use duratoin from the new engine after the code is moved - static const unsigned totalDuration = 1000;//_animations->multiStateEngine().duration(); + static const auto stateToData = [](CheckBoxState state) -> const QVector * { + switch(state) { + case CheckOff: return &offStateData; + case CheckOn: return &onStateData; + case CheckPartial: return &partialStateData; + }; + return nullptr; + }; - DataMap::Value dataPtr = _animations->multiStateEngine().data(widget); + const QVector *vars = nullptr; if (dataPtr.isNull()) { - // TODO: draw static mark when animation not supported - return; + vars = stateToData(checkBoxState); + Q_CHECK_PTR(vars); + } else { + MultiStateData *data = dataPtr.data(); + vars = &data->variables; + if(data->variables.isEmpty()) { + // First rendering. Don't animate, it is initial state. + data->variables = *q_check_ptr(stateToData(checkBoxState)); + } else { + if (startAnim) { + data->anim->stop(); + if (previousCheckBoxState == CheckOff && checkBoxState == CheckOn) { data->timeline->setTransitions(&offToOnTransition); } + if (previousCheckBoxState == CheckOn && checkBoxState == CheckOff) { data->timeline->setTransitions(&onToOffTransition); } + if (previousCheckBoxState == CheckOff && checkBoxState == CheckPartial) { data->timeline->setTransitions(&offToPartialTransition); } + if (previousCheckBoxState == CheckPartial && checkBoxState == CheckOff) { data->timeline->setTransitions(&partialToOffTransition); } + if (previousCheckBoxState == CheckPartial && checkBoxState == CheckOn) { data->timeline->setTransitions(&partialToOnTransition); } + if (previousCheckBoxState == CheckOn && checkBoxState == CheckPartial) { data->timeline->setTransitions(&onToPartialTransition); } + data->anim->start(); + } + } } - MultiStateData *data = dataPtr.data(); - static const auto comparePointF = [](const QPointF &a, const QPointF &b) { - return qAbs(b.x() - a.x()) < 0.1 && qAbs(b.y() - a.y()) < 0.1; - }; + painter->setBrush(Qt::NoBrush); + painter->setPen(QPen(foreground, 2, Qt::SolidLine, Qt::RoundCap, Qt::RoundJoin)); + QPainterPath pp; - //////////////////////////////////////////////////////////////////////////////// - - std::array, 3> pPos = {{ - {data, "p0.pos"}, - {data, "p1.pos"}, - {data, "p2.pos"} - }}; - std::array, 3> pRadius = {{ - {data, "p0.radius"}, - {data, "p1.radius"}, - {data, "p2.radius"} - }}; - std::array, 3> lPPos = {{ - {data, "lp0.pos"}, - {data, "lp1.pos"}, - {data, "lp2.pos"} - }}; - PropertyWrapper checkPos = {data, "cp.pos"}; - - static const std::array refLPPos = {{{-4, 0}, {-1, 3}, {4, -2}}}; - static const std::array refPPos = {{{-4, 0}, {0, 0}, {4, 0}}}; - - static const std::array startRadius = {{0, 0, 0}}; - static const std::array endRadius = {{1, 1, 1}}; - - enum AnimationId { - OffToOn, - OnToOff, - - OffToPartial, - PartialToOff, - - PartialToOn, - OnToPartial, + const QPointF pos = (*vars)[Id::Position].toPointF(); + const QPointF linePointPos[] = { + (*vars)[Id::LinePointPosition_0].toPointF(), + (*vars)[Id::LinePointPosition_1].toPointF(), + (*vars)[Id::LinePointPosition_2].toPointF(), + }; + const QPointF pointPos[] = { + (*vars)[Id::PointPosition_0].toPointF(), + (*vars)[Id::PointPosition_1].toPointF(), + (*vars)[Id::PointPosition_2].toPointF(), + }; + const float pointRadius[] = { + (*vars)[Id::PointRadius_0].toFloat(), + (*vars)[Id::PointRadius_1].toFloat(), + (*vars)[Id::PointRadius_2].toFloat(), }; - if (!data->anims.contains(OffToOn)) { - data->anims[OnToOff] = new AnimationTimeline(data, totalDuration, { - {0.0, pPos[0], QPointF{0,0}}, - {0.0, pPos[1], QPointF{0,0}}, - {0.0, pPos[2], QPointF{0,0}}, - {0.0, pRadius[0], 0.0}, - {0.0, pRadius[1], 0.0}, - {0.0, pRadius[2], 0.0}, - - {0.0, lPPos[0], refLPPos[0], refLPPos[1], 0.5, QEasingCurve::InOutCubic}, - {0.6, lPPos[0], refLPPos[1], refLPPos[2], 0.4, QEasingCurve::InOutCubic}, - {0.6, lPPos[1], refLPPos[1], refLPPos[2], 0.4, QEasingCurve::InOutCubic}, - }); - - data->anims[OffToOn] = new AnimationTimeline(data, totalDuration, { - {0.0, lPPos[0], refLPPos[0]}, - {0.0, lPPos[2], refLPPos[0]}, - {0.0, lPPos[1], refLPPos[0], refLPPos[1], 0.4, QEasingCurve::InOutCubic}, - {0.5, lPPos[2], refLPPos[1], refLPPos[2], 0.5, QEasingCurve::InOutCubic}, - }); - - data->anims[OffToPartial] = new AnimationTimeline(data, totalDuration, { - {0.0, pPos[0], refPPos[0]}, - {0.0, pPos[1], refPPos[1]}, - {0.0, pPos[2], refPPos[2]}, - {0.0, pRadius[0], startRadius[0], endRadius[0], 0.6, QEasingCurve::OutCubic}, - {0.2, pRadius[1], startRadius[1], endRadius[1], 0.6, QEasingCurve::OutCubic}, - {0.4, pRadius[2], startRadius[2], endRadius[2], 0.6, QEasingCurve::OutCubic}, - }); - - data->anims[PartialToOff] = new AnimationTimeline(data, totalDuration, { - {0.0, lPPos[0], QPointF{0,0}}, - {0.0, lPPos[1], QPointF{0,0}}, - {0.0, lPPos[2], QPointF{0,0}}, - - {0.0, pRadius[0], endRadius[0], startRadius[0], 0.6, QEasingCurve::InCubic}, - {0.2, pRadius[1], endRadius[1], startRadius[1], 0.6, QEasingCurve::InCubic}, - {0.4, pRadius[2], endRadius[2], startRadius[2], 0.6, QEasingCurve::InCubic}, - {0.6, pPos[0], QPointF{0,0}}, - {0.8, pPos[1], QPointF{0,0}}, - {1.0, pPos[2], QPointF{0,0}}, - }); - - data->anims[PartialToOn] = new AnimationTimeline(data, totalDuration, { - {0.0, lPPos[0], refLPPos[0]}, - {0.0, lPPos[2], refLPPos[0]}, - {0.0, pPos[0], refPPos[0]}, - {0.0, pPos[1], refPPos[1]}, - {0.0, pPos[2], refPPos[2]}, - - {0.0, lPPos[1], refLPPos[0], refLPPos[1], 0.4, QEasingCurve::InOutCubic}, - {0.0, pPos[1], refPPos[1], refLPPos[1], 0.5, QEasingCurve::InOutCubic}, - {0.0, pPos[2], refPPos[2], refLPPos[1], 0.5, QEasingCurve::InOutCubic}, - {0.0, pRadius[1], endRadius[1], endRadius[1] * sqrt(2), 0.5, QEasingCurve::InOutCubic}, - {0.0, pRadius[2], endRadius[2], endRadius[2] * sqrt(2), 0.5, QEasingCurve::InOutCubic}, - - {0.5, lPPos[2], refLPPos[1], refLPPos[2], 0.5, QEasingCurve::InOutCubic}, - {0.5, pRadius[1], endRadius[1] * sqrt(2), endRadius[1], 0.5, QEasingCurve::InOutCubic}, - {0.5, pRadius[2], endRadius[2] * sqrt(2), endRadius[2], 0.5, QEasingCurve::InOutCubic}, - {0.5, pPos[2], refLPPos[1], refLPPos[2], 0.5, QEasingCurve::InOutCubic}, - - {1.0, pRadius[0], 0.0}, - {1.0, pRadius[1], 0.0}, - {1.0, pRadius[2], 0.0}, - }); - - data->anims[OnToPartial] = new AnimationTimeline(data, totalDuration, { - {0.0, pRadius[0], 0.0}, - {0.0, pRadius[2], 0.0}, - - {0.0, lPPos[0], refLPPos[0], refLPPos[1], 0.4, QEasingCurve::InOutCubic}, - {0.0, lPPos[2], refLPPos[2], refLPPos[1], 0.4, QEasingCurve::InOutCubic}, - {0.0, checkPos, QPointF{0,0}, refPPos[1] - refLPPos[1], 0.4, QEasingCurve::InOutCubic}, - {0.0, pPos[1], refLPPos[1], refPPos[1], 0.4, QEasingCurve::InOutCubic}, - {0.0, pRadius[1], startRadius[1], endRadius[1] * sqrt(3), 0.4, QEasingCurve::InOutCubic}, - - {0.5, pRadius[1], endRadius[1] * sqrt(3), endRadius[0], 0.5, QEasingCurve::InOutCubic}, - {0.5, pRadius[0], endRadius[0] * sqrt(3), endRadius[0], 0.5, QEasingCurve::InOutCubic}, - {0.5, pPos[0], refPPos[1], refPPos[0], 0.5, QEasingCurve::InOutCubic}, - {0.5, pRadius[2], endRadius[2] * sqrt(3), endRadius[0], 0.5, QEasingCurve::InOutCubic}, - {0.5, pPos[2], refPPos[1], refPPos[2], 0.5, QEasingCurve::InOutCubic}, - {1.0, checkPos, QPointF{0,0}}, - }); - - connectToWidget(data->anims[OnToOff], widget); - connectToWidget(data->anims[OffToOn], widget); - connectToWidget(data->anims[OffToPartial], widget); - connectToWidget(data->anims[PartialToOff], widget); - connectToWidget(data->anims[PartialToOn], widget); - connectToWidget(data->anims[OnToPartial], widget); + int i = 0; + for(; i < 3; ++i) { + if(!isInvalidPointF(linePointPos[i])) { + pp.moveTo(linePointPos[i]); + break; + } } - - if (startAnim) { - if (previousCheckBoxState == CheckOff && checkBoxState == CheckOn) { data->anims[OffToOn]->start(); } - if (previousCheckBoxState == CheckOn && checkBoxState == CheckOff) { data->anims[OnToOff]->start(); } - if (previousCheckBoxState == CheckOff && checkBoxState == CheckPartial) { data->anims[OffToPartial]->start(); } - if (previousCheckBoxState == CheckPartial && checkBoxState == CheckOff) { data->anims[PartialToOff]->start(); } - if (previousCheckBoxState == CheckPartial && checkBoxState == CheckOn) { data->anims[PartialToOn]->start(); } - if (previousCheckBoxState == CheckOn && checkBoxState == CheckPartial) { data->anims[OnToPartial]->start(); } + for(; i < 3; ++i) { + if(!isInvalidPointF(linePointPos[i])) { + pp.lineTo(linePointPos[i]); + } } - - painter->setBrush(Qt::NoBrush); - painter->setPen(QPen(foreground, 2, Qt::SolidLine, Qt::RoundCap, Qt::RoundJoin)); - QPainterPath pp; - pp.moveTo(lPPos[0] + centerOffset); - if (!comparePointF(lPPos[1], lPPos[0])) { pp.lineTo(lPPos[1] + centerOffset); } - if (!comparePointF(lPPos[2], lPPos[0])) { pp.lineTo(lPPos[2] + centerOffset); } - pp.translate(checkPos); + pp.translate(pos + centerOffset); painter->drawPath(pp); painter->setPen(Qt::NoPen); for (int i = 0; i < 3; ++i) { + if (isInvalidPointF(pointPos[i]) || qFuzzyIsNull(pointRadius[i])) { + continue; + } painter->setBrush(foreground); - painter->drawEllipse(pPos[i] + centerOffset, pRadius[i], pRadius[i]); + painter->drawEllipse(pointPos[i] + centerOffset, pointRadius[i], pointRadius[i]); } } } -} \ No newline at end of file +}