From 48e333595fd81192a8cc2befa509407cc7a43381 Mon Sep 17 00:00:00 2001 From: Michael Brasser Date: Wed, 7 Sep 2011 12:39:58 +1000 Subject: PathAnimation updates. Allow smooth orientation changes, smooth interruptions, and implicit "from" in PathAnimation. Change-Id: I2191f6df89ec25d78b1d498827281803a07129c9 Reviewed-on: http://codereview.qt-project.org/4378 Reviewed-by: Qt Sanity Bot Reviewed-by: Martin Jones --- src/declarative/items/qsganimation.cpp | 148 +++++++++++++++++++++-- src/declarative/items/qsganimation_p.h | 15 +++ src/declarative/items/qsganimation_p_p.h | 25 +++- src/declarative/util/qdeclarativeanimation_p_p.h | 4 + src/declarative/util/qdeclarativepath.cpp | 47 +++---- src/declarative/util/qdeclarativepath_p.h | 1 + 6 files changed, 200 insertions(+), 40 deletions(-) (limited to 'src') diff --git a/src/declarative/items/qsganimation.cpp b/src/declarative/items/qsganimation.cpp index eda5629eb9..59c9fe383b 100644 --- a/src/declarative/items/qsganimation.cpp +++ b/src/declarative/items/qsganimation.cpp @@ -554,6 +554,53 @@ void QSGPathAnimation::setAnchorPoint(const QPointF &point) emit anchorPointChanged(point); } +qreal QSGPathAnimation::orientationEntryInterval() const +{ + Q_D(const QSGPathAnimation); + return d->entryInterval; +} + +void QSGPathAnimation::setOrientationEntryInterval(qreal interval) +{ + Q_D(QSGPathAnimation); + if (d->entryInterval == interval) + return; + d->entryInterval = interval; + emit orientationEntryIntervalChanged(interval); +} + +qreal QSGPathAnimation::orientationExitInterval() const +{ + Q_D(const QSGPathAnimation); + return d->exitInterval; +} + +void QSGPathAnimation::setOrientationExitInterval(qreal interval) +{ + Q_D(QSGPathAnimation); + if (d->exitInterval == interval) + return; + d->exitInterval = interval; + emit orientationExitIntervalChanged(interval); +} + +qreal QSGPathAnimation::endRotation() const +{ + Q_D(const QSGPathAnimation); + return d->endRotation.isNull ? qreal(0) : d->endRotation.value; +} + +void QSGPathAnimation::setEndRotation(qreal rotation) +{ + Q_D(QSGPathAnimation); + if (!d->endRotation.isNull && d->endRotation == rotation) + return; + + d->endRotation = rotation; + emit endRotationChanged(d->endRotation); +} + + QAbstractAnimation *QSGPathAnimation::qtAnimation() { Q_D(QSGPathAnimation); @@ -569,9 +616,13 @@ void QSGPathAnimation::transition(QDeclarativeStateActions &actions, data->orientation = d->orientation; data->anchorPoint = d->anchorPoint; + data->entryInterval = d->entryInterval; + data->exitInterval = d->exitInterval; + data->endRotation = d->endRotation; data->reverse = direction == Backward ? true : false; data->fromSourced = false; - data->fromDefined = d->path ? !d->path->hasStartX() || !d->path->hasStartY() ? false : true : false; //### handle x/y separately, as well as endpoint specification? + data->fromDefined = (d->path && d->path->hasStartX() && d->path->hasStartY()) ? true : false; + data->toDefined = d->path ? d->path->hasEnd() : false; int origModifiedSize = modified.count(); for (int i = 0; i < actions.count(); ++i) { @@ -591,7 +642,7 @@ void QSGPathAnimation::transition(QDeclarativeStateActions &actions, } if (d->target && d->path && - (modified.count() > origModifiedSize || data->fromDefined)) { + (modified.count() > origModifiedSize || data->toDefined)) { data->target = d->target; data->path = d->path; if (!d->rangeIsSet) { @@ -599,6 +650,34 @@ void QSGPathAnimation::transition(QDeclarativeStateActions &actions, d->pa->setEndValue(qreal(1)); d->rangeIsSet = true; } + /* + NOTE: The following block relies on the fact that the previous value hasn't + yet been deleted, and has the same target, etc, which may be a bit fragile. + */ + if (d->pa->getAnimValue()) { + QSGPathAnimationUpdater *prevData = static_cast(d->pa->getAnimValue()); + + // get the original start angle that was used (so we can exactly reverse). + data->startRotation = prevData->startRotation; + + // treat interruptions specially, otherwise we end up with strange paths + if ((data->reverse || prevData->reverse) && prevData->currentV > 0 && prevData->currentV < 1) { + if (!data->fromDefined && !data->toDefined && !prevData->painterPath.isEmpty()) { + QPointF pathPos = QDeclarativePath::sequentialPointAt(prevData->painterPath, prevData->pathLength, prevData->attributePoints, prevData->prevBez, prevData->currentV); + if (!prevData->anchorPoint.isNull()) + pathPos -= prevData->anchorPoint; + if (pathPos == data->target->pos()) { //only treat as interruption if we interrupted ourself + data->painterPath = prevData->painterPath; + data->toDefined = data->fromDefined = data->fromSourced = true; + data->prevBez.isValid = false; + data->interruptStart = prevData->currentV; + data->startRotation = prevData->startRotation; + data->pathLength = prevData->pathLength; + data->attributePoints = prevData->attributePoints; + } + } + } + } d->pa->setFromSourcedValue(&data->fromSourced); d->pa->setAnimValue(data, QAbstractAnimation::DeleteWhenStopped); } else { @@ -610,7 +689,15 @@ void QSGPathAnimation::transition(QDeclarativeStateActions &actions, void QSGPathAnimationUpdater::setValue(qreal v) { - if (!fromSourced && !fromDefined) { //### check if !toDefined? + if (interruptStart.isValid()) { + if (reverse) + v = 1 - v; + qreal end = reverse ? 0.0 : 1.0; + v = interruptStart + v * (end-interruptStart); + } + currentV = v; + bool atStart = ((reverse && v == 1.0) || (!reverse && v == 0.0)); + if (!fromSourced && (!fromDefined || !toDefined)) { qreal startX = reverse ? toX + anchorPoint.x() : target->x() + anchorPoint.x(); qreal startY = reverse ? toY + anchorPoint.y() : target->y() + anchorPoint.y(); qreal endX = reverse ? target->x() + anchorPoint.x() : toX + anchorPoint.x(); @@ -628,13 +715,13 @@ void QSGPathAnimationUpdater::setValue(qreal v) //adjust position according to anchor point if (!anchorPoint.isNull()) { currentPos -= anchorPoint; - if ((reverse && v == 1.0) || (!reverse && v == 0.0)) { + if (atStart) { if (!anchorPoint.isNull() && !fixed) target->setTransformOriginPoint(anchorPoint); } } - //### too expensive to reconstruct properties each time? + //### could cache properties rather than reconstructing each time QDeclarativePropertyPrivate::write(QDeclarativeProperty(target, "x"), currentPos.x(), QDeclarativePropertyPrivate::BypassInterceptor | QDeclarativePropertyPrivate::DontRemoveBinding); QDeclarativePropertyPrivate::write(QDeclarativeProperty(target, "y"), currentPos.y(), QDeclarativePropertyPrivate::BypassInterceptor | QDeclarativePropertyPrivate::DontRemoveBinding); @@ -644,27 +731,62 @@ void QSGPathAnimationUpdater::setValue(qreal v) case QSGPathAnimation::RightFirst: angle = -angle; break; + case QSGPathAnimation::TopFirst: + angle = -angle + 90; + break; case QSGPathAnimation::LeftFirst: angle = -angle + 180; break; case QSGPathAnimation::BottomFirst: angle = -angle + 270; break; - case QSGPathAnimation::TopFirst: - angle = -angle + 450; - break; default: angle = 0; break; } + + if (atStart && !reverse) { + startRotation = target->rotation(); + + //shortest distance to correct orientation + qreal diff = angle - startRotation; + while (diff > 180.0) { + startRotation.value += 360.0; + diff -= 360.0; + } + while (diff < -180.0) { + startRotation.value -= 360.0; + diff += 360.0; + } + } + + //smoothly transition to the desired orientation + if (startRotation.isValid()) { + if (reverse && v == 0.0) + angle = startRotation; + else if (v < entryInterval) + angle = angle * v / entryInterval + startRotation * (entryInterval - v) / entryInterval; + } + if (endRotation.isValid()) { + qreal exitStart = 1 - exitInterval; + if (!reverse && v == 1.0) + angle = endRotation; + else if (v > exitStart) + angle = endRotation * (v - exitStart) / exitInterval + angle * (exitInterval - (v - exitStart)) / exitInterval; + } QDeclarativePropertyPrivate::write(QDeclarativeProperty(target, "rotation"), angle, QDeclarativePropertyPrivate::BypassInterceptor | QDeclarativePropertyPrivate::DontRemoveBinding); } - //### resetting transform causes visual jump if ending on an angle -// if ((reverse && v == 0.0) || (!reverse && v == 1.0)) { -// if (!anchorPoint.isNull() && !fixed) -// target->setTransformOriginPoint(QPointF()); -// } + /* + NOTE: we don't always reset the transform origin, as it can cause a + visual jump if ending on an angle. This means that in some cases + (anchor point and orientation both specified, and ending at an angle) + the transform origin will always be set after running the path animation. + */ + if ((reverse && v == 0.0) || (!reverse && v == 1.0)) { + if (!anchorPoint.isNull() && !fixed && qFuzzyIsNull(angle)) + target->setTransformOriginPoint(QPointF()); + } } QT_END_NAMESPACE diff --git a/src/declarative/items/qsganimation_p.h b/src/declarative/items/qsganimation_p.h index 6406b863a0..af6279aad0 100644 --- a/src/declarative/items/qsganimation_p.h +++ b/src/declarative/items/qsganimation_p.h @@ -136,6 +136,9 @@ class Q_AUTOTEST_EXPORT QSGPathAnimation : public QDeclarativeAbstractAnimation Q_PROPERTY(QSGItem *target READ target WRITE setTarget NOTIFY targetChanged) Q_PROPERTY(Orientation orientation READ orientation WRITE setOrientation NOTIFY orientationChanged) Q_PROPERTY(QPointF anchorPoint READ anchorPoint WRITE setAnchorPoint NOTIFY anchorPointChanged) + Q_PROPERTY(qreal orientationEntryInterval READ orientationEntryInterval WRITE setOrientationEntryInterval NOTIFY orientationEntryIntervalChanged) + Q_PROPERTY(qreal orientationExitInterval READ orientationExitInterval WRITE setOrientationExitInterval NOTIFY orientationExitIntervalChanged) + Q_PROPERTY(qreal endRotation READ endRotation WRITE setEndRotation NOTIFY endRotationChanged) public: QSGPathAnimation(QObject *parent=0); @@ -168,6 +171,15 @@ public: QPointF anchorPoint() const; void setAnchorPoint(const QPointF &point); + qreal orientationEntryInterval() const; + void setOrientationEntryInterval(qreal); + + qreal orientationExitInterval() const; + void setOrientationExitInterval(qreal); + + qreal endRotation() const; + void setEndRotation(qreal); + protected: virtual void transition(QDeclarativeStateActions &actions, QDeclarativeProperties &modified, @@ -181,6 +193,9 @@ Q_SIGNALS: void targetChanged(); void orientationChanged(Orientation); void anchorPointChanged(const QPointF &); + void orientationEntryIntervalChanged(qreal); + void orientationExitIntervalChanged(qreal); + void endRotationChanged(qreal); }; QT_END_NAMESPACE diff --git a/src/declarative/items/qsganimation_p_p.h b/src/declarative/items/qsganimation_p_p.h index e26dbbd9fb..276efc5eb4 100644 --- a/src/declarative/items/qsganimation_p_p.h +++ b/src/declarative/items/qsganimation_p_p.h @@ -96,6 +96,14 @@ public: class QSGPathAnimationUpdater : public QDeclarativeBulkValueUpdater { public: + QSGPathAnimationUpdater() : path(0), target(0), reverse(false), + fromSourced(false), fromDefined(false), toDefined(false), + toX(0), toY(0), currentV(0), orientation(QSGPathAnimation::Fixed), + entryInterval(0), exitInterval(0) {} + ~QSGPathAnimationUpdater() {} + + void setValue(qreal v); + QDeclarativePath *path; QPainterPath painterPath; @@ -107,14 +115,18 @@ public: bool reverse; bool fromSourced; bool fromDefined; + bool toDefined; qreal toX; qreal toY; + qreal currentV; + QDeclarativeNullableValue interruptStart; + //TODO: bundle below into common struct QSGPathAnimation::Orientation orientation; QPointF anchorPoint; - QSGPathAnimationUpdater() : path(0), target(0), reverse(false), - fromSourced(false), fromDefined(false), toX(0), toY(0), orientation(QSGPathAnimation::Fixed) {} - ~QSGPathAnimationUpdater() {} - void setValue(qreal v); + qreal entryInterval; + qreal exitInterval; + QDeclarativeNullableValue endRotation; + QDeclarativeNullableValue startRotation; }; class QSGPathAnimationPrivate : public QDeclarativeAbstractAnimationPrivate @@ -122,13 +134,16 @@ class QSGPathAnimationPrivate : public QDeclarativeAbstractAnimationPrivate Q_DECLARE_PUBLIC(QSGPathAnimation) public: QSGPathAnimationPrivate() : path(0), target(0), - rangeIsSet(false), orientation(QSGPathAnimation::Fixed), pa(0) {} + rangeIsSet(false), orientation(QSGPathAnimation::Fixed), entryInterval(0), exitInterval(0), pa(0) {} QDeclarativePath *path; QSGItem *target; bool rangeIsSet; QSGPathAnimation::Orientation orientation; QPointF anchorPoint; + qreal entryInterval; + qreal exitInterval; + QDeclarativeNullableValue endRotation; QDeclarativeBulkValueAnimator *pa; }; diff --git a/src/declarative/util/qdeclarativeanimation_p_p.h b/src/declarative/util/qdeclarativeanimation_p_p.h index 104ebde4be..17caa9a52f 100644 --- a/src/declarative/util/qdeclarativeanimation_p_p.h +++ b/src/declarative/util/qdeclarativeanimation_p_p.h @@ -156,6 +156,10 @@ public: animValue = value; policy = p; } + QDeclarativeBulkValueUpdater *getAnimValue() const + { + return animValue; + } void setFromSourcedValue(bool *value) { fromSourced = value; diff --git a/src/declarative/util/qdeclarativepath.cpp b/src/declarative/util/qdeclarativepath.cpp index 57387f5ddd..f923413e29 100644 --- a/src/declarative/util/qdeclarativepath.cpp +++ b/src/declarative/util/qdeclarativepath.cpp @@ -149,6 +149,20 @@ bool QDeclarativePath::isClosed() const return d->closed; } +bool QDeclarativePath::hasEnd() const +{ + Q_D(const QDeclarativePath); + for (int i = d->_pathElements.count() - 1; i > -1; --i) { + if (QDeclarativeCurve *curve = qobject_cast(d->_pathElements.at(i))) { + if ((!curve->hasX() && !curve->hasRelativeX()) || (!curve->hasY() && !curve->hasRelativeY())) + return false; + else + return true; + } + } + return hasStartX() && hasStartY(); +} + /*! \qmlproperty list QtQuick2::Path::pathElements This property holds the objects composing the path. @@ -400,10 +414,11 @@ QStringList QDeclarativePath::attributes() const return d->_attributes; } -static inline QBezier nextBezier(const QPainterPath &path, int *from, qreal *bezLength, bool reverse = false) +static inline QBezier nextBezier(const QPainterPath &path, int *current, qreal *bezLength, bool reverse = false) { const int lastElement = reverse ? 0 : path.elementCount() - 1; - for (int i=*from; reverse ? i >= lastElement : i <= lastElement; reverse ? --i : ++i) { + const int start = reverse ? *current - 1 : *current + 1; + for (int i=start; reverse ? i >= lastElement : i <= lastElement; reverse ? --i : ++i) { const QPainterPath::Element &e = path.elementAt(i); switch (e.type) { @@ -415,7 +430,7 @@ static inline QBezier nextBezier(const QPainterPath &path, int *from, qreal *bez *bezLength = line.length(); QPointF a = path.elementAt(i-1); QPointF delta = e - a; - *from = reverse ? i-1 : i+1; + *current = i; return QBezier::fromPoints(a, a + delta / 3, a + 2 * delta / 3, e); } case QPainterPath::CurveToElement: @@ -425,14 +440,14 @@ static inline QBezier nextBezier(const QPainterPath &path, int *from, qreal *bez path.elementAt(i+1), path.elementAt(i+2)); *bezLength = b.length(); - *from = reverse ? i-1 : i+3; + *current = i; return b; } default: break; } } - *from = lastElement; + *current = lastElement; *bezLength = 0; return QBezier(); } @@ -455,7 +470,7 @@ void QDeclarativePath::createPointCache() const const int lastElement = d->_path.elementCount() - 1; d->_pointCache.resize(points+1); - int currElement = 0; + int currElement = -1; qreal bezLength = 0; QBezier currBez = nextBezier(d->_path, &currElement, &bezLength); qreal currLength = bezLength; @@ -518,7 +533,7 @@ QPointF QDeclarativePath::forwardsPointAt(const QPainterPath &path, const qreal const int lastElement = path.elementCount() - 1; bool haveCachedBez = prevBez.isValid; - int currElement = haveCachedBez ? prevBez.element : 0; + int currElement = haveCachedBez ? prevBez.element : -1; qreal bezLength = haveCachedBez ? prevBez.bezLength : 0; QBezier currBez = haveCachedBez ? prevBez.bezier : nextBezier(path, &currElement, &bezLength); qreal currLength = haveCachedBez ? prevBez.currLength : bezLength; @@ -536,14 +551,8 @@ QPointF QDeclarativePath::forwardsPointAt(const QPainterPath &path, const qreal qreal spc = prevOrigPercent + elementPercent * point.scale; while (spc > epc) { - if (currElement > lastElement) - break; + Q_ASSERT(!(currElement > lastElement)); currBez = nextBezier(path, &currElement, &bezLength); - /*if (bezLength == 0.0) { - currLength = pathLength; - epc = 1.0; - break; - }*/ currLength += bezLength; epc = currLength / pathLength; } @@ -579,7 +588,7 @@ QPointF QDeclarativePath::backwardsPointAt(const QPainterPath &path, const qreal const int firstElement = 0; bool haveCachedBez = prevBez.isValid; - int currElement = haveCachedBez ? prevBez.element : path.elementCount() - 1; + int currElement = haveCachedBez ? prevBez.element : path.elementCount(); qreal bezLength = haveCachedBez ? prevBez.bezLength : 0; QBezier currBez = haveCachedBez ? prevBez.bezier : nextBezier(path, &currElement, &bezLength, true /*reverse*/); qreal currLength = haveCachedBez ? prevBez.currLength : pathLength; @@ -596,14 +605,8 @@ QPointF QDeclarativePath::backwardsPointAt(const QPainterPath &path, const qreal qreal spc = prevPoint.origpercent + elementPercent * point.scale; while (spc < epc) { - if (currElement < firstElement) - break; + Q_ASSERT(!(currElement < firstElement)); currBez = nextBezier(path, &currElement, &bezLength, true /*reverse*/); - /*if (bezLength == 0.0) { - currLength = 0; - epc = 0.0; - break; - }*/ currLength = prevLength; epc = (currLength - bezLength) / pathLength; } diff --git a/src/declarative/util/qdeclarativepath_p.h b/src/declarative/util/qdeclarativepath_p.h index 4ce1bcf827..e9972404da 100644 --- a/src/declarative/util/qdeclarativepath_p.h +++ b/src/declarative/util/qdeclarativepath_p.h @@ -380,6 +380,7 @@ public: bool hasStartY() const; bool isClosed() const; + bool hasEnd() const; QPainterPath path() const; QStringList attributes() const; -- cgit v1.2.3