diff --git a/kstyle/animations/breezemultistatedata.h b/kstyle/animations/breezemultistatedata.h index 6a97009c..1f59a95f 100644 --- a/kstyle/animations/breezemultistatedata.h +++ b/kstyle/animations/breezemultistatedata.h @@ -1,329 +1,341 @@ #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()) { 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()); QVariant converted = value; bool ok = converted.convert(oldValue.type()); 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) {} CustomPropertyAnimation(const PropertyWrapperBase &property, AbstractVariantInterpolator *interpolator, QObject *parent = nullptr) : QPropertyAnimation(property.object(), property.name(), parent) , _interpolator(interpolator) {} ~CustomPropertyAnimation() override { delete _interpolator; } 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); } private: AbstractVariantInterpolator *_interpolator; }; //// //// //// //// /// /// /// // // / / / / #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 { } 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, QByteArray property, QVariant from, QVariant to, qreal normalizedDuration, QEasingCurve easingCurve = QEasingCurve::Linear) + Entry(qreal normalizedStartTime, const PropertyWrapperBase &property, QVariant from, QVariant to = QVariant(), qreal normalizedDuration = 0.0, QEasingCurve easingCurve = QEasingCurve::Linear) : normalizedStartTime(normalizedStartTime) - , property(std::move(property)) + , property(property) , from(std::move(from)) , to(std::move(to)) , normalizedDuration(normalizedDuration) , easingCurve(std::move(easingCurve)) - {} - Entry(qreal normalizedStartTime, QByteArray property, QVariant to, qreal normalizedDuration = 0.0, QEasingCurve easingCurve = QEasingCurve::Linear) - : normalizedStartTime(normalizedStartTime) - , property(std::move(property)) - , from(QVariant()) - , to(std::move(to)) - , normalizedDuration(normalizedDuration) - , easingCurve(std::move(easingCurve)) - {} + { + // Just to not introduce separate constructor for simple setter entry + if(!this->to.isValid()) { + std::swap(this->from, this->to); + } + } qreal normalizedStartTime; - QByteArray property; + PropertyWrapperBase property; QVariant from; QVariant to; qreal normalizedDuration; QEasingCurve easingCurve; }; + using EntryList = QList; - AnimationTimeline(QObject *parent, int duration = 0, const QList &entries = {}) + AnimationTimeline(QObject *parent, int duration = 0, const EntryList &entries = {}) : QAbstractAnimation(parent) , _entries(entries) , _firstNotStartedIndex(0) , _durationMs(duration) { for(int i = 0; i < _entries.count(); ++i) { auto animation = animationFromEntry(_entries[i]); updateAnimationDuration(animation, _entries[i]); + if (animation != nullptr) { + connect(animation, &QPropertyAnimation::valueChanged, this, &AnimationTimeline::valueChanged); + } _animations.append(animation); } } void setDuration(int durationMs) { _durationMs = durationMs; for(int i = 0; i < _entries.count(); ++i) { updateAnimationDuration(_animations[i], _entries[i]); } } int duration() const override { return _durationMs; } - void *loggedobj() { - static void *_loggedobj = this; - return _loggedobj; + // XXX: remove + bool isfirst() { + static void *_loggedobj = parent(); + return _loggedobj == parent(); } -#define dbg(...) if(this == loggedobj()) { qDebug(__VA_ARGS__); } else {} +Q_SIGNALS: + void valueChanged(); + +#define dbg(...) if(isfirst()) { qDebug(__VA_ARGS__); } else {} protected: void updateCurrentTime(int currentTime) override { - dbg("time: %4d: ", currentTime); + bool changed = false; for(int i = _firstNotStartedIndex; i < _entries.length(); ++i) { - int startTime = _durationMs * _entries[i].normalizedStartTime; - int duration = _durationMs * _entries[i].normalizedDuration; - int endTime = startTime + duration; + auto entry = _entries[i]; + auto animation = _animations[i]; - int startDelay = currentTime - startTime; // < 0 ? OK : started, set current time to diff - int remaining = endTime - currentTime; // > 0 ? OK : already ended!! + int startTime = _durationMs * entry.normalizedStartTime; + int duration = _durationMs * entry.normalizedDuration; + int endTime = startTime + duration; - dbg(" i=%2d; start=%4d; duration=%4d; end=%4d; delay=% 4d; remaining=% 4d", - i, startTime, duration, endTime, startDelay, remaining); + int startDelay = currentTime - startTime; + int remaining = endTime - currentTime; if (startDelay < 0) { // Too early for this and all following entries break; } if (remaining > 0) { - _animations[i]->setCurrentTime(startDelay); - _animations[i]->start(); - dbg(" start now"); + animation->setCurrentTime(startDelay); + animation->start(); + dbg("t=%4d; i=%2d; start=%4d; duration=%4d; end=%4d; delay=% 4d; remaining=% 4d - start", + currentTime, i, startTime, duration, endTime, startDelay, remaining); } else { // missed animation or durationless entry, just apply final state - parent()->setProperty(_entries[i].property, _entries[i].to); - dbg(" set final value"); + entry.property = entry.to; + changed = true; + dbg("t=%4d; i=%2d; start=%4d; duration=%4d; end=%4d; delay=% 4d; remaining=% 4d - set final", + currentTime, i, startTime, duration, endTime, startDelay, remaining); } _firstNotStartedIndex = i + 1; } + if (changed) { + emit valueChanged(); + } } void updateState(QAbstractAnimation::State newState, QAbstractAnimation::State oldState) override { Q_UNUSED(oldState); dbg("updateState: %d", newState); switch(newState) { case Running: for (int i = 0; i < _entries.length(); ++i) { if (!_entries[i].from.isValid() && !qFuzzyIsNull(_entries[i].normalizedDuration)) { - _animations[i]->setStartValue(parent()->property(_entries[i].property)); + dbg(" set start value for %s", qPrintable(_entries[i].property.name())); + _animations[i]->setStartValue(_entries[i].property); } } _firstNotStartedIndex = 0; break; case Stopped: for (int i = 0; i < _entries.length(); ++i) { if (_animations[i] != nullptr) { _animations[i]->stop(); } } break; default: break; } } private: inline QPropertyAnimation *animationFromEntry(const Entry &entry) { if(qFuzzyIsNull(entry.normalizedDuration)) { // this is just a setter TODO: handle in updateCurrentTime return nullptr; } - auto animation = new QPropertyAnimation(parent(), entry.property, this); + auto animation = new QPropertyAnimation(parent(), entry.property.name(), this); animation->setStartValue(entry.from); animation->setEndValue(entry.to); animation->setEasingCurve(entry.easingCurve); return animation; } void updateAnimationDuration(QPropertyAnimation *animation, const Entry &entry) { if(animation == nullptr) { return; } animation->setDuration(_durationMs * entry.normalizedDuration); } - - QList _entries; + public: // TODO: remove + EntryList _entries; QList _animations; int _firstNotStartedIndex; int _durationMs; }; //* 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 ) {} //* destructor virtual ~MultiStateData() {} /** 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; private: bool _initialized; QVariant _state; QVariant _previousState; }; } #endif diff --git a/kstyle/checkbox.cpp b/kstyle/checkbox.cpp index 765fdff1..812c484a 100644 --- a/kstyle/checkbox.cpp +++ b/kstyle/checkbox.cpp @@ -1,388 +1,382 @@ #include "breezestyle.h" #include "breeze.h" #include "breezeanimations.h" #include "breezemultistatedata.h" #include #include #include #include #include #include #include namespace Breeze { //___________________________________________________________________________________ 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 *propertyAnimation = qobject_cast(animation); - if(propertyAnimation != nullptr) { - connect(propertyAnimation, &QPropertyAnimation::valueChanged, + auto *timeline = qobject_cast(animation); + if(timeline != nullptr) { + connect(timeline, &AnimationTimeline::valueChanged, const_cast(widget), QOverload<>::of(&QWidget::update)); - } else { - const auto propertyAnimations = animation->findChildren(); - for(auto *propertyAnimation: propertyAnimations) { - connect(propertyAnimation, &QPropertyAnimation::valueChanged, - const_cast(widget), QOverload<>::of(&QWidget::update)); - } } }; 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(); DataMap::Value dataPtr = _animations->multiStateEngine().data(widget); if (dataPtr.isNull()) { // TODO: draw static mark when animation not supported return; } 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; }; //////////////////////////////////////////////////////////////////////////////// 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, }; if (!data->anims.contains(OffToOn)) { // <<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<< const QList onAnimationEntries = { {0.0, lPPos[0].name(), refLPPos[0]}, {0.0, lPPos[2].name(), refLPPos[0]}, {0.0, lPPos[1].name(), refLPPos[0], refLPPos[1], 0.4, QEasingCurve::InOutCubic}, {0.5, lPPos[2].name(), refLPPos[1], refLPPos[2], 0.5, QEasingCurve::InOutCubic} }; auto *onAnimation = new AnimationTimeline(data, 1000, onAnimationEntries); // data->anims[1000] = a; connectToWidget(onAnimation, widget); onAnimation->setParent(data); data->anims[OffToOn] = onAnimation; auto *offAnimation = makeSequentialAnimationGroup({ makePropertyAnimation(lPPos[0], refLPPos[0], refLPPos[1], 0.5 * totalDuration, QEasingCurve::InOutCubic), new QPauseAnimation(0.1 * totalDuration), makeParallelAnimationGroup({ makePropertyAnimation(lPPos[0], refLPPos[1], refLPPos[2], 0.4 * totalDuration, QEasingCurve::InOutCubic), makePropertyAnimation(lPPos[1], refLPPos[1], refLPPos[2], 0.4 * totalDuration, QEasingCurve::InOutCubic), }) }); connectToWidget(offAnimation, widget); offAnimation->setParent(data); data->anims[OnToOff] = offAnimation; } if (!data->anims.contains(OffToPartial)) { // <<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<< auto *onAnimation = makeSequentialAnimationGroup({ makePropertyAnimation(pPos[0], refPPos[0]), makePropertyAnimation(pPos[1], refPPos[1]), makePropertyAnimation(pPos[2], refPPos[2]), makeParallelAnimationGroup({ makePropertyAnimation(pRadius[0], startRadius[0], endRadius[0], 0.6 * totalDuration, QEasingCurve::OutCubic), makeSequentialAnimationGroup({ new QPauseAnimation(0.2 * totalDuration), makePropertyAnimation(pRadius[1], startRadius[1], endRadius[1], 0.6 * totalDuration, QEasingCurve::OutCubic), }), makeSequentialAnimationGroup({ new QPauseAnimation(0.4 * totalDuration), makePropertyAnimation(pRadius[2], startRadius[2], endRadius[2], 0.6 * totalDuration, QEasingCurve::OutCubic), }), }), }); connectToWidget(onAnimation, widget); onAnimation->setParent(data); data->anims[OffToPartial] = onAnimation; auto *offAnimation = makeParallelAnimationGroup({ makePropertyAnimation(pRadius[0], endRadius[0], startRadius[0], 0.6 * totalDuration, QEasingCurve::OutCubic), makeSequentialAnimationGroup({ new QPauseAnimation(0.2 * totalDuration), makePropertyAnimation(pRadius[1], endRadius[1], startRadius[1], 0.6 * totalDuration, QEasingCurve::OutCubic), }), makeSequentialAnimationGroup({ new QPauseAnimation(0.4 * totalDuration), makePropertyAnimation(pRadius[2], endRadius[2], startRadius[2], 0.6 * totalDuration, QEasingCurve::OutCubic), }), }); connectToWidget(offAnimation, widget); offAnimation->setParent(data); data->anims[PartialToOff] = offAnimation; } if (!data->anims.contains(PartialToOn)) { // <<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<< auto *onAnimation = makeSequentialAnimationGroup({ makePropertyAnimation(lPPos[0], refLPPos[0]), makePropertyAnimation(lPPos[2], refLPPos[0]), makePropertyAnimation(pPos[0], refPPos[0]), makePropertyAnimation(pPos[1], refPPos[1]), makePropertyAnimation(pPos[2], refPPos[2]), makeParallelAnimationGroup({ makePropertyAnimation(lPPos[1], refLPPos[0], refLPPos[1], 0.4 * totalDuration, QEasingCurve::InOutCubic), makePropertyAnimation(pPos[1], refPPos[1], refLPPos[1], 0.5 * totalDuration, QEasingCurve::InOutCubic), makePropertyAnimation(pPos[2], refPPos[2], refLPPos[1], 0.5 * totalDuration, QEasingCurve::InOutCubic), makePropertyAnimation(pRadius[1], endRadius[1], endRadius[1] * sqrt(2), 0.5 * totalDuration, QEasingCurve::InOutCubic), makePropertyAnimation(pRadius[2], endRadius[2], endRadius[2] * sqrt(2), 0.5 * totalDuration, QEasingCurve::InOutCubic), }), makeParallelAnimationGroup({ makePropertyAnimation(lPPos[2], refLPPos[1], refLPPos[2], 0.5 * totalDuration, QEasingCurve::InOutCubic), makePropertyAnimation(pRadius[1], endRadius[1] * sqrt(2), endRadius[1], 0.5 * totalDuration, QEasingCurve::InOutCubic), makePropertyAnimation(pRadius[2], endRadius[2] * sqrt(2), endRadius[2], 0.5 * totalDuration, QEasingCurve::InOutCubic), makePropertyAnimation(pPos[2], refLPPos[1], refLPPos[2], 0.5 * totalDuration, QEasingCurve::InOutCubic), }), makePropertyAnimation(pRadius[0], 0), makePropertyAnimation(pRadius[1], 0), makePropertyAnimation(pRadius[2], 0), }); connectToWidget(onAnimation, widget); onAnimation->setParent(data); data->anims[PartialToOn] = onAnimation; auto *offAnimation = makeSequentialAnimationGroup({ makePropertyAnimation(pRadius[0], 0), makePropertyAnimation(pRadius[2], 0), makeParallelAnimationGroup({ makePropertyAnimation(lPPos[0], refLPPos[0], refLPPos[1], 0.4 * totalDuration, QEasingCurve::InOutCubic), makePropertyAnimation(lPPos[2], refLPPos[2], refLPPos[1], 0.4 * totalDuration, QEasingCurve::InOutCubic), makePropertyAnimation(checkPos, QPointF{0,0}, refPPos[1] - refLPPos[1], 0.4 * totalDuration, QEasingCurve::InOutCubic), makePropertyAnimation(pPos[1], refLPPos[1], refPPos[1], 0.4 * totalDuration, QEasingCurve::InOutCubic), makePropertyAnimation(pRadius[1], startRadius[1], endRadius[1] * sqrt(3), 0.4 * totalDuration, QEasingCurve::InOutCubic), }), new QPauseAnimation(0.1 * totalDuration), makeParallelAnimationGroup({ makePropertyAnimation(pRadius[0], endRadius[0] * sqrt(3), endRadius[0], 0.5 * totalDuration, QEasingCurve::InOutCubic), makePropertyAnimation(pRadius[1], endRadius[1] * sqrt(3), endRadius[0], 0.5 * totalDuration, QEasingCurve::InOutCubic), makePropertyAnimation(pRadius[2], endRadius[2] * sqrt(3), endRadius[0], 0.5 * totalDuration, QEasingCurve::InOutCubic), makePropertyAnimation(pPos[0], refPPos[1], refPPos[0], 0.5 * totalDuration, QEasingCurve::InOutCubic), makePropertyAnimation(pPos[2], refPPos[1], refPPos[2], 0.5 * totalDuration, QEasingCurve::InOutCubic), }), makePropertyAnimation(checkPos, QPointF{0,0}), }); connectToWidget(offAnimation, widget); offAnimation->setParent(data); data->anims[OnToPartial] = offAnimation; } 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(); } } 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); painter->drawPath(pp); painter->setPen(Qt::NoPen); for (int i = 0; i < 3; ++i) { painter->setBrush(foreground); painter->drawEllipse(pPos[i] + centerOffset, pRadius[i], pRadius[i]); } } } } \ No newline at end of file