1// Copyright (C) 2019 The Qt Company Ltd.
2// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only
3
4#include "qquickkeyframe_p.h"
5
6#include "qquicktimeline_p.h"
7
8#include <QtCore/qdebug.h>
9#include <QtCore/QVariantAnimation>
10#include <QtCore/qmath.h>
11#include <QtGui/qpainter.h>
12#include <QtQml/QQmlProperty>
13#include <QtQml/QQmlFile>
14#include <QtQml/QQmlContext>
15#include <QtQml/QQmlEngine>
16#include <QtCore/QFile>
17#include <QtCore/QCborStreamReader>
18#include <QCborArray>
19#include <QRect>
20#include <QVector2D>
21#include <QVector3D>
22#include <QVector4D>
23#include <QQuaternion>
24#include <QColor>
25
26#include <private/qvariantanimation_p.h>
27#include <private/qqmlproperty_p.h>
28
29#include <algorithm>
30
31QT_BEGIN_NAMESPACE
32
33class QQuickKeyframeGroupPrivate : public QObjectPrivate
34{
35 Q_DECLARE_PUBLIC(QQuickKeyframeGroup)
36public:
37 QQuickKeyframeGroupPrivate() = default;
38
39 QObject *target = nullptr;
40 QString propertyName;
41 QUrl keyframeSource;
42 QByteArray keyframeData;
43 bool componentComplete = false;
44 int userType = -1;
45
46protected:
47 void setupKeyframes();
48 bool loadKeyframes(bool fromBinary = false);
49 void resetKeyframes();
50
51 static void append_keyframe(QQmlListProperty<QQuickKeyframe> *list, QQuickKeyframe *a);
52 static qsizetype keyframe_count(QQmlListProperty<QQuickKeyframe> *list);
53 static QQuickKeyframe* keyframe_at(QQmlListProperty<QQuickKeyframe> *list, qsizetype pos);
54 static void clear_keyframes(QQmlListProperty<QQuickKeyframe> *list);
55
56 QList<QQuickKeyframe *> keyframes;
57 QList<QQuickKeyframe *> sortedKeyframes;
58
59 QVariant originalValue;
60 QVariant lastValue;
61 QQmlAnyBinding originalBinding;
62};
63
64void QQuickKeyframeGroupPrivate::setupKeyframes()
65{
66 sortedKeyframes = keyframes;
67 std::sort(first: sortedKeyframes.begin(), last: sortedKeyframes.end(), comp: [](const QQuickKeyframe *first, const QQuickKeyframe *second) {
68 return first->frame() < second->frame();
69 });
70}
71
72// time, easingType, data
73static int validFrameSize(QMetaType::Type type)
74{
75 switch (type) {
76 case QMetaType::Bool:
77 case QMetaType::Int:
78 case QMetaType::Float:
79 case QMetaType::Double:
80 return 3;
81 case QMetaType::QVector2D:
82 case QMetaType::QPoint:
83 case QMetaType::QPointF:
84 case QMetaType::QSize:
85 case QMetaType::QSizeF:
86 return 4;
87 case QMetaType::QVector3D:
88 return 5;
89 case QMetaType::QVector4D:
90 case QMetaType::QQuaternion:
91 case QMetaType::QColor:
92 case QMetaType::QRect:
93 case QMetaType::QRectF:
94 return 6;
95 default:
96 qWarning() << "Keyframe property type not handled:" << type;
97 return -1;
98 }
99}
100
101// Read property 'type' value from CborArray from index 'id' and return it as QVariant.
102static QVariant qQuickKeyframeReadProperty(const QCborArray &array, qsizetype id, QMetaType::Type type)
103{
104 switch (type) {
105 case QMetaType::QVector2D:
106 {
107 QVector2D v;
108 v.setX(array.at(i: id).toDouble());
109 v.setY(array.at(i: id + 1).toDouble());
110 return QVariant(v);
111 }
112 break;
113 case QMetaType::QVector3D:
114 {
115 QVector3D v;
116 v.setX(array.at(i: id).toDouble());
117 v.setY(array.at(i: id + 1).toDouble());
118 v.setZ(array.at(i: id + 2).toDouble());
119 return QVariant(v);
120 }
121 break;
122 case QMetaType::QVector4D:
123 {
124 QVector4D v;
125 v.setX(array.at(i: id).toDouble());
126 v.setY(array.at(i: id + 1).toDouble());
127 v.setZ(array.at(i: id + 2).toDouble());
128 v.setW(array.at(i: id + 3).toDouble());
129 return QVariant(v);
130 }
131 break;
132 case QMetaType::QQuaternion:
133 {
134 QQuaternion q;
135 q.setScalar(array.at(i: id).toDouble());
136 q.setX(array.at(i: id + 1).toDouble());
137 q.setY(array.at(i: id + 2).toDouble());
138 q.setZ(array.at(i: id + 3).toDouble());
139 return QVariant(q);
140 }
141 break;
142 case QMetaType::QColor:
143 {
144 QColor c;
145 c.setRed(array.at(i: id).toInteger());
146 c.setGreen(array.at(i: id + 1).toInteger());
147 c.setBlue(array.at(i: id + 2).toInteger());
148 c.setAlpha(array.at(i: id + 3).toInteger());
149 return QVariant(c);
150 }
151 break;
152 case QMetaType::QRectF:
153 {
154 QRectF r;
155 r.setX(array.at(i: id).toDouble());
156 r.setY(array.at(i: id + 1).toDouble());
157 r.setWidth(array.at(i: id + 2).toDouble());
158 r.setHeight(array.at(i: id + 3).toDouble());
159 return QVariant(r);
160 }
161 break;
162 case QMetaType::QRect:
163 {
164 QRect r;
165 r.setX(array.at(i: id).toInteger());
166 r.setY(array.at(i: id + 1).toInteger());
167 r.setWidth(array.at(i: id + 2).toInteger());
168 r.setHeight(array.at(i: id + 3).toInteger());
169 return QVariant(r);
170 }
171 break;
172 case QMetaType::QPointF:
173 {
174 QPointF p;
175 p.setX(array.at(i: id).toDouble());
176 p.setY(array.at(i: id + 1).toDouble());
177 return QVariant(p);
178 }
179 break;
180 case QMetaType::QPoint:
181 {
182 QPoint p;
183 p.setX(array.at(i: id).toInteger());
184 p.setY(array.at(i: id + 1).toInteger());
185 return QVariant(p);
186 }
187 break;
188 case QMetaType::QSizeF:
189 {
190 QSizeF s;
191 s.setWidth(array.at(i: id).toDouble());
192 s.setHeight(array.at(i: id + 1).toDouble());
193 return QVariant(s);
194 }
195 break;
196 case QMetaType::QSize:
197 {
198 QSize s;
199 s.setWidth(array.at(i: id).toInteger());
200 s.setHeight(array.at(i: id + 1).toInteger());
201 return QVariant(s);
202 }
203 break;
204 case QMetaType::Bool:
205 case QMetaType::Int:
206 case QMetaType::Float:
207 case QMetaType::Double:
208 {
209 return array.at(i: id).toVariant();
210 }
211
212 default:
213 qWarning() << "Keyframe property type not handled:" << type;
214 }
215
216 return QVariant();
217}
218
219bool QQuickKeyframeGroupPrivate::loadKeyframes(bool fromBinary)
220{
221 Q_Q(QQuickKeyframeGroup);
222
223 QCborStreamReader reader;
224 QFile dataFile;
225 if (!fromBinary) {
226 // Resolve URL similar to QQuickImage source
227 QUrl loadUrl = keyframeSource;
228 QQmlContext *context = qmlContext(q);
229 if (context)
230 loadUrl = context->resolvedUrl(keyframeSource);
231 QString dataFilePath = QQmlFile::urlToLocalFileOrQrc(loadUrl);
232
233 dataFile.setFileName(dataFilePath);
234 if (!dataFile.open(flags: QIODevice::ReadOnly)) {
235 // Invalid file
236 qWarning() << "Unable to open keyframeSource:" << dataFilePath;
237 return false;
238 }
239 reader.setDevice(&dataFile);
240 } else {
241 reader.addData(data: keyframeData);
242 }
243
244 auto error = [&reader](const QString &msg = QString()) {
245 if (!msg.isEmpty())
246 qWarning() << "Corrupt keyframeSource" << msg;
247 else
248 qWarning() << "Corrupt keyframeSource" << reader.lastError().toString();
249 return false;
250 };
251
252 QCborValue kfSrcCborValue = QCborValue::fromCbor(reader);
253 if (reader.lastError() != QCborError::NoError || !kfSrcCborValue.isArray())
254 return error(QStringLiteral("invalid format.(array expected)"));
255
256 QCborArray kfSrcCborArray = kfSrcCborValue.toArray();
257 // [ "QTimelineKeyframes", version(int), property type(int), [...]]
258 if (kfSrcCborArray.size() != 4)
259 return error(QStringLiteral("invalid data size"));
260
261 if (kfSrcCborArray.at(i: 0).toString() != QStringLiteral("QTimelineKeyframes"))
262 return error(QStringLiteral("invalid keyframeSource header string"));
263
264 if (kfSrcCborArray.at(i: 1).toInteger() != 1)
265 return error(QStringLiteral("invalid keyframeSource version %1").arg(a: kfSrcCborArray.at(i: 1).toInteger()));
266
267 // QMetaType::UnknownType = 0;
268 QMetaType::Type propertyType = static_cast<QMetaType::Type>(kfSrcCborArray.at(i: 2).toInteger(defaultValue: 0));
269 const int frameSize = validFrameSize(type: propertyType);
270 if (frameSize < 0)
271 return error(QStringLiteral("unsupported property type"));
272
273 // Start keyframes array
274 QCborArray kfArray = kfSrcCborArray.at(i: 3).toArray();
275 const auto arraySize = kfArray.size();
276 bool validKeyframeData = true;
277 for (qsizetype i = 0; i < arraySize - frameSize; i += frameSize) {
278 auto keyframe = std::make_unique<QQuickKeyframe>(args: q);
279
280 keyframe->setFrame(kfArray.at(i).toDouble());
281
282 QCborValue easingTypeValue = kfArray.at(i: i + 1);
283 if (!easingTypeValue.isInteger()) {
284 validKeyframeData = false;
285 break;
286 }
287 keyframe->setEasing(static_cast<QEasingCurve::Type>(easingTypeValue.toInteger()));
288
289 QVariant value = qQuickKeyframeReadProperty(array: kfArray, id: i + 2, type: propertyType);
290 if (value.isValid()) {
291 keyframe->setValue(value);
292 keyframes.append(t: keyframe.release());
293 } else {
294 validKeyframeData = false;
295 break;
296 }
297 }
298
299 if (!validKeyframeData) {
300 qWarning() << "Invalid keyframe data";
301 resetKeyframes();
302 return false;
303 }
304
305 return true;
306}
307
308void QQuickKeyframeGroupPrivate::resetKeyframes()
309{
310 qDeleteAll(c: keyframes);
311 keyframes.clear();
312}
313
314void QQuickKeyframeGroupPrivate::append_keyframe(QQmlListProperty<QQuickKeyframe> *list, QQuickKeyframe *a)
315{
316 auto q = static_cast<QQuickKeyframeGroup *>(list->object);
317 q->d_func()->keyframes.append(t: a);
318 q->d_func()->setupKeyframes();
319 q->reset();
320}
321
322qsizetype QQuickKeyframeGroupPrivate::keyframe_count(QQmlListProperty<QQuickKeyframe> *list)
323{
324 auto q = static_cast<QQuickKeyframeGroup *>(list->object);
325 return q->d_func()->keyframes.size();
326}
327
328QQuickKeyframe* QQuickKeyframeGroupPrivate::keyframe_at(QQmlListProperty<QQuickKeyframe> *list, qsizetype pos)
329{
330 auto q = static_cast<QQuickKeyframeGroup *>(list->object);
331 return q->d_func()->keyframes.at(i: pos);
332}
333
334void QQuickKeyframeGroupPrivate::clear_keyframes(QQmlListProperty<QQuickKeyframe> *list)
335{
336 auto q = static_cast<QQuickKeyframeGroup *>(list->object);
337 q->d_func()->keyframes.clear();
338 q->d_func()->setupKeyframes();
339}
340
341class QQuickKeyframePrivate : public QObjectPrivate
342{
343 Q_DECLARE_PUBLIC(QQuickKeyframe)
344public:
345 QQuickKeyframePrivate() = default;
346
347 qreal frame = 0;
348 QEasingCurve easingCurve;
349 QVariant value;
350};
351
352/*!
353 \qmltype Keyframe
354 \inherits QtObject
355 \nativetype QQuickKeyframe
356 \inqmlmodule QtQuick.Timeline
357 \ingroup qtqmltypes
358
359 \brief A keyframe.
360
361 The value of a keyframe on a timeline.
362
363 An easing curve can be attached to the keyframe.
364*/
365
366/*!
367 \qmlproperty double Keyframe::frame
368
369 The position of the keyframe on the timeline.
370*/
371
372/*!
373 \qmlproperty var Keyframe::easing
374
375 The easing curve attached to the keyframe.
376*/
377
378/*!
379 \qmlproperty var Keyframe::value
380
381 The value of the keyframe.
382*/
383
384/*!
385 \qmlsignal Keyframe::easingCurveChanged
386
387 This signal is emitted when the easing curve attached to the keyframe
388 changes.
389*/
390
391QQuickKeyframe::QQuickKeyframe(QObject *parent)
392 : QObject(*(new QQuickKeyframePrivate), parent)
393{
394}
395
396qreal QQuickKeyframe::frame() const
397{
398 Q_D(const QQuickKeyframe);
399 return d->frame;
400}
401
402void QQuickKeyframe::setFrame(qreal f)
403{
404 Q_D(QQuickKeyframe);
405 if (d->frame == f)
406 return;
407 d->frame = f;
408
409 reset();
410
411 emit frameChanged();
412}
413
414void QQuickKeyframe::reset()
415{
416 auto keyframes = qobject_cast<QQuickKeyframeGroup*>(object: parent());
417 if (keyframes)
418 keyframes->reset();
419}
420
421QQuickKeyframe::QQuickKeyframe(QQuickKeyframePrivate &dd, QObject *parent)
422 : QObject(dd, parent)
423{
424}
425
426/*!
427 \qmltype KeyframeGroup
428 \inherits QtObject
429 \nativetype QQuickKeyframeGroup
430 \inqmlmodule QtQuick.Timeline
431 \ingroup qtqmltypes
432
433 \brief A keyframe group.
434
435 A keyframe group contains all keyframes for a specific property of an item
436 and always belongs to a timeline.
437*/
438
439/*!
440 \qmlproperty var KeyframeGroup::target
441
442 The item that is targeted by the keyframe group.
443*/
444
445/*!
446 \qmlproperty string KeyframeGroup::property
447
448 The property that is targeted by the keyframe group.
449*/
450
451/*!
452 \qmlproperty list KeyframeGroup::keyframes
453 \readonly
454
455 A list of keyframes that belong to the keyframe group.
456*/
457
458/*!
459 \qmlproperty url KeyframeGroup::keyframeSource
460
461 The URL to a file containing binary keyframe data.
462
463 \note KeyframeGroup should either set this property or contain
464 Keyframe child elements. Using both can lead to an undefined behavior.
465
466 \since 1.1
467*/
468
469QQuickKeyframeGroup::QQuickKeyframeGroup(QObject *parent)
470 : QObject(*(new QQuickKeyframeGroupPrivate), parent)
471{
472
473}
474
475QQmlListProperty<QQuickKeyframe> QQuickKeyframeGroup::keyframes()
476{
477 Q_D(QQuickKeyframeGroup);
478
479 return { this, &d->keyframes, QQuickKeyframeGroupPrivate::append_keyframe,
480 QQuickKeyframeGroupPrivate::keyframe_count,
481 QQuickKeyframeGroupPrivate::keyframe_at,
482 QQuickKeyframeGroupPrivate::clear_keyframes };
483}
484
485QObject *QQuickKeyframeGroup::target() const
486{
487 Q_D(const QQuickKeyframeGroup);
488 return d->target;
489}
490
491void QQuickKeyframeGroup::setTargetObject(QObject *o)
492{
493 Q_D(QQuickKeyframeGroup);
494 if (d->target == o)
495 return;
496 d->target = o;
497
498 if (!property().isEmpty())
499 init();
500
501 emit targetChanged();
502}
503
504QString QQuickKeyframeGroup::property() const
505{
506 Q_D(const QQuickKeyframeGroup);
507 return d->propertyName;
508}
509
510void QQuickKeyframeGroup::setProperty(const QString &n)
511{
512 Q_D(QQuickKeyframeGroup);
513 if (d->propertyName == n)
514 return;
515 d->propertyName = n;
516
517 if (target())
518 init();
519
520 emit propertyChanged();
521}
522
523QUrl QQuickKeyframeGroup::keyframeSource() const
524{
525 Q_D(const QQuickKeyframeGroup);
526 return d->keyframeSource;
527}
528
529void QQuickKeyframeGroup::setKeyframeSource(const QUrl &source)
530{
531 Q_D(QQuickKeyframeGroup);
532 if (d->keyframeSource == source)
533 return;
534
535 if (d->keyframes.size() > 0) {
536 // Remove possible previously loaded keyframes
537 d->resetKeyframes();
538 d->keyframeData.clear();
539 }
540
541 d->keyframeSource = source;
542 if (d->loadKeyframes())
543 d->setupKeyframes();
544 reset();
545
546 emit keyframeSourceChanged();
547}
548
549const QByteArray QQuickKeyframeGroup::keyframeData() const
550{
551 Q_D(const QQuickKeyframeGroup);
552 return d->keyframeData;
553}
554
555void QQuickKeyframeGroup::setKeyframeData(const QByteArray &data)
556{
557 Q_D(QQuickKeyframeGroup);
558 if (d->keyframeData == data)
559 return;
560
561 if (d->keyframes.size() > 0) {
562 // Remove possible previously loaded keyframes
563 d->resetKeyframes();
564 d->keyframeSource.clear();
565 }
566
567 d->keyframeData = data;
568 if (d->loadKeyframes(fromBinary: true))
569 d->setupKeyframes();
570 reset();
571
572 emit keyframeSourceChanged();
573}
574
575QVariant QQuickKeyframeGroup::evaluate(qreal frame) const
576{
577 Q_D(const QQuickKeyframeGroup);
578
579 if (d->sortedKeyframes.isEmpty())
580 return QVariant();
581
582 static QQuickKeyframe dummy;
583 auto timeline = qobject_cast<QQuickTimeline*>(object: parent());
584 if (timeline)
585 dummy.setFrame(timeline->startFrame() - 0.0001);
586 dummy.setValue(d->originalValue);
587
588 QQuickKeyframe *lastFrame = &dummy;
589
590 for (auto keyFrame : std::as_const(t: d->sortedKeyframes)) {
591 if (qFuzzyCompare(p1: frame, p2: keyFrame->frame()) || frame < keyFrame->frame())
592 return keyFrame->evaluate(pre: lastFrame, frame, userType: d->userType);
593 lastFrame = keyFrame;
594 }
595
596 return lastFrame->value();
597}
598
599void QQuickKeyframeGroup::setProperty(qreal frame)
600{
601 Q_D(QQuickKeyframeGroup);
602
603 if (target()) {
604 QQmlProperty qmlProperty(target(), property());
605
606 d->lastValue = evaluate(frame);
607
608 if (!qmlProperty.write(d->lastValue))
609 qWarning() << "Cannot set property" << property();
610 }
611}
612
613void QQuickKeyframeGroup::init()
614{
615 Q_D(QQuickKeyframeGroup);
616 if (target()) {
617 QQmlProperty qmlProperty(target(), property());
618 d->originalValue = QQmlProperty::read(target(), property());
619 d->userType = qmlProperty.property().userType();
620 if (d->originalBinding)
621 d->originalBinding = nullptr;
622 d->originalBinding = QQmlAnyBinding::ofProperty(prop: qmlProperty);
623 if (property().contains(c: QLatin1Char('.'))) {
624 if (d->userType == QMetaType::QVector2D
625 || d->userType == QMetaType::QVector3D
626 || d->userType == QMetaType::QVector4D
627 || d->userType == QMetaType::QQuaternion)
628 d->userType = QMetaType::Double;
629 }
630 }
631}
632
633void QQuickKeyframeGroup::resetDefaultValue()
634{
635 Q_D(QQuickKeyframeGroup);
636
637 if (QQmlProperty::read(target(), property()) == d->lastValue) {
638 if (d->originalBinding) {
639 QQmlProperty qmlProperty(target(), property());
640 d->originalBinding.installOn(target: qmlProperty);
641 d->originalBinding = nullptr;
642 } else {
643 QQmlProperty::write(target(), property(), d->originalValue);
644 }
645 }
646}
647
648void QQuickKeyframeGroup::reset()
649{
650 Q_D(QQuickKeyframeGroup);
651 if (!d->componentComplete)
652 return;
653
654 auto *timeline = qobject_cast<QQuickTimeline*>(object: parent());
655 if (timeline)
656 timeline->reevaluate();
657}
658
659void QQuickKeyframeGroup::setupKeyframes()
660{
661 Q_D(QQuickKeyframeGroup);
662
663 if (d->componentComplete)
664 d->setupKeyframes();
665}
666
667void QQuickKeyframeGroup::classBegin()
668{
669 Q_D(QQuickKeyframeGroup);
670 d->componentComplete = false;
671}
672
673void QQuickKeyframeGroup::componentComplete()
674{
675 Q_D(QQuickKeyframeGroup);
676 d->componentComplete = true;
677 setupKeyframes();
678}
679
680QEasingCurve QQuickKeyframe::easing() const
681{
682 Q_D(const QQuickKeyframe);
683 return d->easingCurve;
684}
685
686void QQuickKeyframe::setEasing(const QEasingCurve &e)
687{
688 Q_D(QQuickKeyframe);
689 if (d->easingCurve == e)
690 return;
691
692 d->easingCurve = e;
693
694 reset();
695
696 emit easingCurveChanged();
697}
698
699QVariant QQuickKeyframe::value() const
700{
701 Q_D(const QQuickKeyframe);
702 return d->value;
703}
704
705void QQuickKeyframe::setValue(const QVariant &v)
706{
707 Q_D(QQuickKeyframe);
708 if (d->value == v)
709 return;
710 d->value = v;
711
712 reset();
713
714 emit valueChanged();
715}
716
717QVariant QQuickKeyframe::evaluate(QQuickKeyframe *pre, qreal frametime, int userType) const
718{
719 QVariantAnimation::Interpolator interpolator = QVariantAnimationPrivate::getInterpolator(interpolationType: userType);
720 if (!pre)
721 return value();
722
723 QVariant preValue = pre->value();
724 qreal preFrame = pre->frame();
725
726 qreal duration = frame() - preFrame;
727 qreal offset = frametime - preFrame;
728
729 qreal progress = easing().valueForProgress(progress: offset / duration);
730
731 const QMetaType targetType(userType);
732 preValue.convert(type: targetType);
733 QVariant convertedValue = value();
734 convertedValue.convert(type: targetType);
735
736 if (!interpolator) {
737 if (progress < 1.0)
738 return preValue;
739
740 return convertedValue;
741 }
742
743 if (preValue.isValid() && convertedValue.isValid())
744 return interpolator(preValue.constData(), convertedValue.constData(), progress);
745
746 qWarning() << "invalid keyframe target" << preValue << convertedValue << userType;
747
748 return QVariant();
749}
750
751QT_END_NAMESPACE
752
753
754

source code of qtquicktimeline/src/timeline/qquickkeyframe.cpp