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#include "qquickkeyframedatautils_p.h"
8
9#include <QtCore/qdebug.h>
10#include <QtCore/QVariantAnimation>
11#include <QtCore/qmath.h>
12#include <QtGui/qpainter.h>
13#include <QtQuick/private/qquickitem_p.h>
14#include <QtQml/QQmlProperty>
15#include <QtQml/QQmlFile>
16#include <QtQml/QQmlContext>
17#include <QtQml/QQmlEngine>
18#include <QtCore/QFile>
19#include <QtCore/QCborStreamReader>
20
21#include <private/qvariantanimation_p.h>
22
23#include <algorithm>
24
25QT_BEGIN_NAMESPACE
26
27class QQuickKeyframeGroupPrivate : public QObjectPrivate
28{
29 Q_DECLARE_PUBLIC(QQuickKeyframeGroup)
30public:
31 QQuickKeyframeGroupPrivate() = default;
32
33 QObject *target = nullptr;
34 QString propertyName;
35 QUrl keyframeSource;
36 QByteArray keyframeData;
37 bool componentComplete = false;
38 int userType = -1;
39
40protected:
41 void setupKeyframes();
42 void loadKeyframes(bool fromBinary = false);
43
44 static void append_keyframe(QQmlListProperty<QQuickKeyframe> *list, QQuickKeyframe *a);
45 static qsizetype keyframe_count(QQmlListProperty<QQuickKeyframe> *list);
46 static QQuickKeyframe* keyframe_at(QQmlListProperty<QQuickKeyframe> *list, qsizetype pos);
47 static void clear_keyframes(QQmlListProperty<QQuickKeyframe> *list);
48
49 QList<QQuickKeyframe *> keyframes;
50 QList<QQuickKeyframe *> sortedKeyframes;
51
52 QVariant originalValue;
53 QVariant lastValue;
54};
55
56void QQuickKeyframeGroupPrivate::setupKeyframes()
57{
58 sortedKeyframes = keyframes;
59 std::sort(first: sortedKeyframes.begin(), last: sortedKeyframes.end(), comp: [](const QQuickKeyframe *first, const QQuickKeyframe *second) {
60 return first->frame() < second->frame();
61 });
62}
63
64void QQuickKeyframeGroupPrivate::loadKeyframes(bool fromBinary)
65{
66 Q_Q(QQuickKeyframeGroup);
67
68 QCborStreamReader reader;
69 QFile dataFile;
70 if (!fromBinary) {
71 // Resolve URL similar to QQuickImage source
72 QUrl loadUrl = keyframeSource;
73 QQmlContext *context = qmlContext(q);
74 if (context)
75 loadUrl = context->resolvedUrl(keyframeSource);
76 QString dataFilePath = QQmlFile::urlToLocalFileOrQrc(loadUrl);
77
78 dataFile.setFileName(dataFilePath);
79 if (!dataFile.open(flags: QIODevice::ReadOnly)) {
80 // Invalid file
81 qWarning() << "Unable to open keyframeSource:" << dataFilePath;
82 qDeleteAll(c: keyframes);
83 keyframes.clear();
84 return;
85 }
86 reader.setDevice(&dataFile);
87 } else {
88 reader.addData(data: keyframeData);
89 }
90
91 // Check that file is standard keyframes CBOR and get the version
92 int version = readKeyframesHeader(reader);
93
94 if (version == -1) {
95 // Invalid file
96 qWarning() << "Invalid keyframeSource version:" << version;
97 return;
98 }
99
100 QMetaType::Type propertyType = QMetaType::UnknownType;
101 if (reader.isInteger()) {
102 propertyType = static_cast<QMetaType::Type>(reader.toInteger());
103 reader.next();
104 }
105
106 // Start keyframes array
107 reader.enterContainer();
108
109 while (reader.lastError() == QCborError::NoError && reader.hasNext()) {
110 auto keyframe = new QQuickKeyframe(q);
111 keyframe->setFrame(readReal(reader));
112 keyframe->setEasing(QEasingCurve(static_cast<QEasingCurve::Type>(reader.toInteger())));
113 reader.next();
114 keyframe->setValue(readValue(reader, type: propertyType));
115 keyframes.append(t: keyframe);
116 }
117 // Leave keyframes array
118 reader.leaveContainer();
119
120 // Leave root array
121 reader.leaveContainer();
122
123}
124
125void QQuickKeyframeGroupPrivate::append_keyframe(QQmlListProperty<QQuickKeyframe> *list, QQuickKeyframe *a)
126{
127 auto q = static_cast<QQuickKeyframeGroup *>(list->object);
128 q->d_func()->keyframes.append(t: a);
129 q->d_func()->setupKeyframes();
130 q->reset();
131}
132
133qsizetype QQuickKeyframeGroupPrivate::keyframe_count(QQmlListProperty<QQuickKeyframe> *list)
134{
135 auto q = static_cast<QQuickKeyframeGroup *>(list->object);
136 return q->d_func()->keyframes.size();
137}
138
139QQuickKeyframe* QQuickKeyframeGroupPrivate::keyframe_at(QQmlListProperty<QQuickKeyframe> *list, qsizetype pos)
140{
141 auto q = static_cast<QQuickKeyframeGroup *>(list->object);
142 return q->d_func()->keyframes.at(i: pos);
143}
144
145void QQuickKeyframeGroupPrivate::clear_keyframes(QQmlListProperty<QQuickKeyframe> *list)
146{
147 auto q = static_cast<QQuickKeyframeGroup *>(list->object);
148 q->d_func()->keyframes.clear();
149 q->d_func()->setupKeyframes();
150}
151
152class QQuickKeyframePrivate : public QObjectPrivate
153{
154 Q_DECLARE_PUBLIC(QQuickKeyframe)
155public:
156 QQuickKeyframePrivate() = default;
157
158 qreal frame = 0;
159 QEasingCurve easingCurve;
160 QVariant value;
161};
162
163/*!
164 \qmltype Keyframe
165 \inherits QtObject
166 \instantiates QQuickKeyframe
167 \inqmlmodule QtQuick.Timeline
168 \ingroup qtqmltypes
169
170 \brief A keyframe.
171
172 The value of a keyframe on a timeline.
173
174 An easing curve can be attached to the keyframe.
175*/
176
177/*!
178 \qmlproperty double Keyframe::frame
179
180 The position of the keyframe on the timeline.
181*/
182
183/*!
184 \qmlproperty var Keyframe::easing
185
186 The easing curve attached to the keyframe.
187*/
188
189/*!
190 \qmlproperty var Keyframe::value
191
192 The value of the keyframe.
193*/
194
195/*!
196 \qmlsignal Keyframe::easingCurveChanged
197
198 This signal is emitted when the easing curve attached to the keyframe
199 changes.
200*/
201
202QQuickKeyframe::QQuickKeyframe(QObject *parent)
203 : QObject(*(new QQuickKeyframePrivate), parent)
204{
205}
206
207qreal QQuickKeyframe::frame() const
208{
209 Q_D(const QQuickKeyframe);
210 return d->frame;
211}
212
213void QQuickKeyframe::setFrame(qreal f)
214{
215 Q_D(QQuickKeyframe);
216 if (d->frame == f)
217 return;
218 d->frame = f;
219
220 reset();
221
222 emit frameChanged();
223}
224
225void QQuickKeyframe::reset()
226{
227 auto keyframes = qobject_cast<QQuickKeyframeGroup*>(object: parent());
228 if (keyframes)
229 keyframes->reset();
230}
231
232QQuickKeyframe::QQuickKeyframe(QQuickKeyframePrivate &dd, QObject *parent)
233 : QObject(dd, parent)
234{
235}
236
237/*!
238 \qmltype KeyframeGroup
239 \inherits QtObject
240 \instantiates QQuickKeyframeGroup
241 \inqmlmodule QtQuick.Timeline
242 \ingroup qtqmltypes
243
244 \brief A keyframe group.
245
246 A keyframe group contains all keyframes for a specific property of an item
247 and always belongs to a timeline.
248*/
249
250/*!
251 \qmlproperty var KeyframeGroup::target
252
253 The item that is targeted by the keyframe group.
254*/
255
256/*!
257 \qmlproperty string KeyframeGroup::property
258
259 The property that is targeted by the keyframe group.
260*/
261
262/*!
263 \qmlproperty list KeyframeGroup::keyframes
264 \readonly
265
266 A list of keyframes that belong to the keyframe group.
267*/
268
269/*!
270 \qmlproperty url KeyframeGroup::keyframeSource
271
272 The URL to a file containing binary keyframe data.
273
274 \note KeyframeGroup should either set this property or contain
275 Keyframe child elements. Using both can lead to an undefined behavior.
276
277 \since 1.1
278*/
279
280QQuickKeyframeGroup::QQuickKeyframeGroup(QObject *parent)
281 : QObject(*(new QQuickKeyframeGroupPrivate), parent)
282{
283
284}
285
286QQmlListProperty<QQuickKeyframe> QQuickKeyframeGroup::keyframes()
287{
288 Q_D(QQuickKeyframeGroup);
289
290 return { this, &d->keyframes, QQuickKeyframeGroupPrivate::append_keyframe,
291 QQuickKeyframeGroupPrivate::keyframe_count,
292 QQuickKeyframeGroupPrivate::keyframe_at,
293 QQuickKeyframeGroupPrivate::clear_keyframes };
294}
295
296QObject *QQuickKeyframeGroup::target() const
297{
298 Q_D(const QQuickKeyframeGroup);
299 return d->target;
300}
301
302void QQuickKeyframeGroup::setTargetObject(QObject *o)
303{
304 Q_D(QQuickKeyframeGroup);
305 if (d->target == o)
306 return;
307 d->target = o;
308
309 if (!property().isEmpty())
310 init();
311
312 emit targetChanged();
313}
314
315QString QQuickKeyframeGroup::property() const
316{
317 Q_D(const QQuickKeyframeGroup);
318 return d->propertyName;
319}
320
321void QQuickKeyframeGroup::setProperty(const QString &n)
322{
323 Q_D(QQuickKeyframeGroup);
324 if (d->propertyName == n)
325 return;
326 d->propertyName = n;
327
328 if (target())
329 init();
330
331 emit propertyChanged();
332}
333
334QUrl QQuickKeyframeGroup::keyframeSource() const
335{
336 Q_D(const QQuickKeyframeGroup);
337 return d->keyframeSource;
338}
339
340void QQuickKeyframeGroup::setKeyframeSource(const QUrl &source)
341{
342 Q_D(QQuickKeyframeGroup);
343 if (d->keyframeSource == source)
344 return;
345
346 if (d->keyframes.size() > 0) {
347 // Remove possible previously loaded keyframes
348 qDeleteAll(c: d->keyframes);
349 d->keyframes.clear();
350 d->keyframeData.clear();
351 }
352
353 d->keyframeSource = source;
354 d->loadKeyframes();
355 d->setupKeyframes();
356 reset();
357
358 emit keyframeSourceChanged();
359}
360
361const QByteArray QQuickKeyframeGroup::keyframeData() const
362{
363 Q_D(const QQuickKeyframeGroup);
364 return d->keyframeData;
365}
366
367void QQuickKeyframeGroup::setKeyframeData(const QByteArray &data)
368{
369 Q_D(QQuickKeyframeGroup);
370 if (d->keyframeData == data)
371 return;
372
373 if (d->keyframes.size() > 0) {
374 // Remove possible previously loaded keyframes
375 qDeleteAll(c: d->keyframes);
376 d->keyframes.clear();
377 d->keyframeSource.clear();
378 }
379
380 d->keyframeData = data;
381 d->loadKeyframes(fromBinary: true);
382 d->setupKeyframes();
383 reset();
384
385 emit keyframeSourceChanged();
386}
387
388QVariant QQuickKeyframeGroup::evaluate(qreal frame) const
389{
390 Q_D(const QQuickKeyframeGroup);
391
392 if (d->sortedKeyframes.isEmpty())
393 return QVariant();
394
395 static QQuickKeyframe dummy;
396 auto timeline = qobject_cast<QQuickTimeline*>(object: parent());
397 if (timeline)
398 dummy.setFrame(timeline->startFrame() - 0.0001);
399 dummy.setValue(d->originalValue);
400
401 QQuickKeyframe *lastFrame = &dummy;
402
403 for (auto keyFrame : std::as_const(t: d->sortedKeyframes)) {
404 if (qFuzzyCompare(p1: frame, p2: keyFrame->frame()) || frame < keyFrame->frame())
405 return keyFrame->evaluate(pre: lastFrame, frame, userType: d->userType);
406 lastFrame = keyFrame;
407 }
408
409 return lastFrame->value();
410}
411
412void QQuickKeyframeGroup::setProperty(qreal frame)
413{
414 Q_D(QQuickKeyframeGroup);
415
416 if (target()) {
417 QQmlProperty qmlProperty(target(), property());
418
419 d->lastValue = evaluate(frame);
420
421 if (!qmlProperty.write(d->lastValue))
422 qWarning() << "Cannot set property" << property();
423 }
424}
425
426void QQuickKeyframeGroup::init()
427{
428 Q_D(QQuickKeyframeGroup);
429 if (target()) {
430 d->originalValue = QQmlProperty::read(target(), property());
431 d->userType = QQmlProperty(target(), property()).property().userType();
432 if (property().contains(c: QLatin1Char('.'))) {
433 if (d->userType == QMetaType::QVector2D
434 || d->userType == QMetaType::QVector3D
435 || d->userType == QMetaType::QVector4D
436 || d->userType == QMetaType::QQuaternion)
437 d->userType = QMetaType::Double;
438 }
439 }
440}
441
442void QQuickKeyframeGroup::resetDefaultValue()
443{
444 Q_D(QQuickKeyframeGroup);
445
446 if (QQmlProperty::read(target(), property()) == d->lastValue)
447 QQmlProperty::write(target(), property(), d->originalValue);
448}
449
450void QQuickKeyframeGroup::reset()
451{
452 Q_D(QQuickKeyframeGroup);
453 if (!d->componentComplete)
454 return;
455
456 auto *timeline = qobject_cast<QQuickTimeline*>(object: parent());
457 if (timeline)
458 timeline->reevaluate();
459}
460
461void QQuickKeyframeGroup::setupKeyframes()
462{
463 Q_D(QQuickKeyframeGroup);
464
465 if (d->componentComplete)
466 d->setupKeyframes();
467}
468
469void QQuickKeyframeGroup::classBegin()
470{
471 Q_D(QQuickKeyframeGroup);
472 d->componentComplete = false;
473}
474
475void QQuickKeyframeGroup::componentComplete()
476{
477 Q_D(QQuickKeyframeGroup);
478 d->componentComplete = true;
479 setupKeyframes();
480}
481
482QEasingCurve QQuickKeyframe::easing() const
483{
484 Q_D(const QQuickKeyframe);
485 return d->easingCurve;
486}
487
488void QQuickKeyframe::setEasing(const QEasingCurve &e)
489{
490 Q_D(QQuickKeyframe);
491 if (d->easingCurve == e)
492 return;
493
494 d->easingCurve = e;
495
496 reset();
497
498 emit easingCurveChanged();
499}
500
501QVariant QQuickKeyframe::value() const
502{
503 Q_D(const QQuickKeyframe);
504 return d->value;
505}
506
507void QQuickKeyframe::setValue(const QVariant &v)
508{
509 Q_D(QQuickKeyframe);
510 if (d->value == v)
511 return;
512 d->value = v;
513
514 reset();
515
516 emit valueChanged();
517}
518
519QVariant QQuickKeyframe::evaluate(QQuickKeyframe *pre, qreal frametime, int userType) const
520{
521 QVariantAnimation::Interpolator interpolator = QVariantAnimationPrivate::getInterpolator(interpolationType: userType);
522 if (!pre)
523 return value();
524
525 QVariant preValue = pre->value();
526 qreal preFrame = pre->frame();
527
528 qreal duration = frame() - preFrame;
529 qreal offset = frametime - preFrame;
530
531 qreal progress = easing().valueForProgress(progress: offset / duration);
532
533 const QMetaType targetType(userType);
534 preValue.convert(type: targetType);
535 QVariant convertedValue = value();
536 convertedValue.convert(type: targetType);
537
538 if (!interpolator) {
539 if (progress < 1.0)
540 return preValue;
541
542 return convertedValue;
543 }
544
545 if (preValue.isValid() && convertedValue.isValid())
546 return interpolator(preValue.constData(), convertedValue.constData(), progress);
547
548 qWarning() << "invalid keyframe target" << preValue << convertedValue << userType;
549
550 return QVariant();
551}
552
553QT_END_NAMESPACE
554
555
556

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