QAbstractAnimationJob* QQuickParentAnimation::transition(QQuickStateActions &actions, QQmlProperties &modified, TransitionDirection direction, QObject *defaultTarget) { Q_D(QQuickParentAnimation); struct QQuickParentAnimationData : public QAbstractAnimationAction { QQuickParentAnimationData() : reverse(false) {} ~QQuickParentAnimationData() { qDeleteAll(pc); } QQuickStateActions actions; //### reverse should probably apply on a per-action basis bool reverse; QList<QQuickParentChange *> pc; virtual void doAction() { for (int ii = 0; ii < actions.count(); ++ii) { const QQuickAction &action = actions.at(ii); if (reverse) action.event->reverse(); else action.event->execute(); } } }; QQuickParentAnimationData *data = new QQuickParentAnimationData; QQuickParentAnimationData *viaData = new QQuickParentAnimationData; bool hasExplicit = false; if (d->target && d->newParent) { data->reverse = false; QQuickAction myAction; QQuickParentChange *pc = new QQuickParentChange; pc->setObject(d->target); pc->setParent(d->newParent); myAction.event = pc; data->pc << pc; data->actions << myAction; hasExplicit = true; if (d->via) { viaData->reverse = false; QQuickAction myVAction; QQuickParentChange *vpc = new QQuickParentChange; vpc->setObject(d->target); vpc->setParent(d->via); myVAction.event = vpc; viaData->pc << vpc; viaData->actions << myVAction; } //### once actions have concept of modified, // loop to match appropriate ParentChanges and mark as modified } if (!hasExplicit) for (int i = 0; i < actions.size(); ++i) { QQuickAction &action = actions[i]; if (action.event && action.event->type() == QQuickActionEvent::ParentChange && (!d->target || static_cast<QQuickParentChange*>(action.event)->object() == d->target)) { QQuickParentChange *pc = static_cast<QQuickParentChange*>(action.event); QQuickAction myAction = action; data->reverse = action.reverseEvent; //### this logic differs from PropertyAnimation // (probably a result of modified vs. done) if (d->newParent) { QQuickParentChange *epc = new QQuickParentChange; epc->setObject(static_cast<QQuickParentChange*>(action.event)->object()); epc->setParent(d->newParent); myAction.event = epc; data->pc << epc; data->actions << myAction; pc = epc; } else { action.actionDone = true; data->actions << myAction; } if (d->via) { viaData->reverse = false; QQuickAction myAction; QQuickParentChange *vpc = new QQuickParentChange; vpc->setObject(pc->object()); vpc->setParent(d->via); myAction.event = vpc; viaData->pc << vpc; viaData->actions << myAction; QQuickAction dummyAction; QQuickAction &xAction = pc->xIsSet() && i < actions.size()-1 ? actions[++i] : dummyAction; QQuickAction &yAction = pc->yIsSet() && i < actions.size()-1 ? actions[++i] : dummyAction; QQuickAction &sAction = pc->scaleIsSet() && i < actions.size()-1 ? actions[++i] : dummyAction; QQuickAction &rAction = pc->rotationIsSet() && i < actions.size()-1 ? actions[++i] : dummyAction; QQuickItem *target = pc->object(); QQuickItem *targetParent = action.reverseEvent ? pc->originalParent() : pc->parent(); //### this mirrors the logic in QQuickParentChange. bool ok; const QTransform &transform = targetParent->itemTransform(d->via, &ok); if (transform.type() >= QTransform::TxShear || !ok) { qmlInfo(this) << QQuickParentAnimation::tr("Unable to preserve appearance under complex transform"); ok = false; } qreal scale = 1; qreal rotation = 0; bool isRotate = (transform.type() == QTransform::TxRotate) || (transform.m11() < 0); if (ok && !isRotate) { if (transform.m11() == transform.m22()) scale = transform.m11(); else { qmlInfo(this) << QQuickParentAnimation::tr("Unable to preserve appearance under non-uniform scale"); ok = false; } } else if (ok && isRotate) { if (transform.m11() == transform.m22()) scale = qSqrt(transform.m11()*transform.m11() + transform.m12()*transform.m12()); else { qmlInfo(this) << QQuickParentAnimation::tr("Unable to preserve appearance under non-uniform scale"); ok = false; } if (scale != 0) rotation = atan2(transform.m12()/scale, transform.m11()/scale) * 180/M_PI; else { qmlInfo(this) << QQuickParentAnimation::tr("Unable to preserve appearance under scale of 0"); ok = false; } } const QPointF &point = transform.map(QPointF(xAction.toValue.toReal(),yAction.toValue.toReal())); qreal x = point.x(); qreal y = point.y(); if (ok && target->transformOrigin() != QQuickItem::TopLeft) { qreal w = target->width(); qreal h = target->height(); if (pc->widthIsSet() && i < actions.size() - 1) w = actions[++i].toValue.toReal(); if (pc->heightIsSet() && i < actions.size() - 1) h = actions[++i].toValue.toReal(); const QPointF &transformOrigin = d->computeTransformOrigin(target->transformOrigin(), w,h); qreal tempxt = transformOrigin.x(); qreal tempyt = transformOrigin.y(); QTransform t; t.translate(-tempxt, -tempyt); t.rotate(rotation); t.scale(scale, scale); t.translate(tempxt, tempyt); const QPointF &offset = t.map(QPointF(0,0)); x += offset.x(); y += offset.y(); } if (ok) { //qDebug() << x << y << rotation << scale; xAction.toValue = x; yAction.toValue = y; sAction.toValue = sAction.toValue.toReal() * scale; rAction.toValue = rAction.toValue.toReal() + rotation; } } } } if (data->actions.count()) { QSequentialAnimationGroupJob *topLevelGroup = new QSequentialAnimationGroupJob; QActionAnimation *viaAction = d->via ? new QActionAnimation : 0; QActionAnimation *targetAction = new QActionAnimation; //we'll assume the common case by far is to have children, and always create ag QParallelAnimationGroupJob *ag = new QParallelAnimationGroupJob; if (d->via) viaAction->setAnimAction(viaData); targetAction->setAnimAction(data); //take care of any child animations bool valid = d->defaultProperty.isValid(); QAbstractAnimationJob* anim; for (int ii = 0; ii < d->animations.count(); ++ii) { if (valid) d->animations.at(ii)->setDefaultTarget(d->defaultProperty); anim = d->animations.at(ii)->transition(actions, modified, direction, defaultTarget); if (anim) ag->appendAnimation(anim); } //TODO: simplify/clarify logic bool forwards = direction == QQuickAbstractAnimation::Forward; if (forwards) { topLevelGroup->appendAnimation(d->via ? viaAction : targetAction); topLevelGroup->appendAnimation(ag); if (d->via) topLevelGroup->appendAnimation(targetAction); } else { if (d->via) topLevelGroup->appendAnimation(targetAction); topLevelGroup->appendAnimation(ag); topLevelGroup->appendAnimation(d->via ? viaAction : targetAction); } return initInstance(topLevelGroup); } else { delete data; delete viaData; } return 0; }
void tst_QPauseAnimationJob::multipleSequentialGroups() { EnableConsistentTiming enabled; QParallelAnimationGroupJob group; group.setLoopCount(2); QSequentialAnimationGroupJob subgroup1; group.appendAnimation(&subgroup1); TestableGenericAnimation animation(300); subgroup1.appendAnimation(&animation); TestablePauseAnimation pause(200); subgroup1.appendAnimation(&pause); QSequentialAnimationGroupJob subgroup2; group.appendAnimation(&subgroup2); TestableGenericAnimation animation2(200); subgroup2.appendAnimation(&animation2); TestablePauseAnimation pause2(250); subgroup2.appendAnimation(&pause2); QSequentialAnimationGroupJob subgroup3; group.appendAnimation(&subgroup3); TestablePauseAnimation pause3(400); subgroup3.appendAnimation(&pause3); TestableGenericAnimation animation3(200); subgroup3.appendAnimation(&animation3); QSequentialAnimationGroupJob subgroup4; group.appendAnimation(&subgroup4); TestablePauseAnimation pause4(310); subgroup4.appendAnimation(&pause4); TestablePauseAnimation pause5(60); subgroup4.appendAnimation(&pause5); group.start(); QVERIFY(group.state() == QAbstractAnimationJob::Running); QVERIFY(subgroup1.state() == QAbstractAnimationJob::Running); QVERIFY(subgroup2.state() == QAbstractAnimationJob::Running); QVERIFY(subgroup3.state() == QAbstractAnimationJob::Running); QVERIFY(subgroup4.state() == QAbstractAnimationJob::Running); // This is a pretty long animation so it tends to get rather out of sync // when using the consistent timer, so run for an extra half second for good // measure... QTest::qWait(group.totalDuration() + 500); #ifdef Q_OS_WIN if (group.state() != QAbstractAnimationJob::Stopped) QEXPECT_FAIL("", winTimerError, Abort); #endif QTRY_VERIFY(group.state() == QAbstractAnimationJob::Stopped); #ifdef Q_OS_WIN if (subgroup1.state() != QAbstractAnimationJob::Stopped) QEXPECT_FAIL("", winTimerError, Abort); #endif QVERIFY(subgroup1.state() == QAbstractAnimationJob::Stopped); #ifdef Q_OS_WIN if (subgroup2.state() != QAbstractAnimationJob::Stopped) QEXPECT_FAIL("", winTimerError, Abort); #endif QVERIFY(subgroup2.state() == QAbstractAnimationJob::Stopped); #ifdef Q_OS_WIN if (subgroup3.state() != QAbstractAnimationJob::Stopped) QEXPECT_FAIL("", winTimerError, Abort); #endif QVERIFY(subgroup3.state() == QAbstractAnimationJob::Stopped); #ifdef Q_OS_WIN if (subgroup4.state() != QAbstractAnimationJob::Stopped) QEXPECT_FAIL("", winTimerError, Abort); #endif QVERIFY(subgroup4.state() == QAbstractAnimationJob::Stopped); #ifdef Q_OS_WIN if (pause5.m_updateCurrentTimeCount != 4) QEXPECT_FAIL("", winTimerError, Abort); #endif QCOMPARE(pause5.m_updateCurrentTimeCount, 4); }