1/****************************************************************************
2**
3** Copyright (C) 2016 The Qt Company Ltd.
4** Contact: https://www.qt.io/licensing/
5**
6** This file is part of the QtWidgets module of the Qt Toolkit.
7**
8** $QT_BEGIN_LICENSE:LGPL$
9** Commercial License Usage
10** Licensees holding valid commercial Qt licenses may use this file in
11** accordance with the commercial license agreement provided with the
12** Software or, alternatively, in accordance with the terms contained in
13** a written agreement between you and The Qt Company. For licensing terms
14** and conditions see https://www.qt.io/terms-conditions. For further
15** information use the contact form at https://www.qt.io/contact-us.
16**
17** GNU Lesser General Public License Usage
18** Alternatively, this file may be used under the terms of the GNU Lesser
19** General Public License version 3 as published by the Free Software
20** Foundation and appearing in the file LICENSE.LGPL3 included in the
21** packaging of this file. Please review the following information to
22** ensure the GNU Lesser General Public License version 3 requirements
23** will be met: https://www.gnu.org/licenses/lgpl-3.0.html.
24**
25** GNU General Public License Usage
26** Alternatively, this file may be used under the terms of the GNU
27** General Public License version 2.0 or (at your option) the GNU General
28** Public license version 3 or any later version approved by the KDE Free
29** Qt Foundation. The licenses are as published by the Free Software
30** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3
31** included in the packaging of this file. Please review the following
32** information to ensure the GNU General Public License requirements will
33** be met: https://www.gnu.org/licenses/gpl-2.0.html and
34** https://www.gnu.org/licenses/gpl-3.0.html.
35**
36** $QT_END_LICENSE$
37**
38****************************************************************************/
39
40/*!
41 \class QGraphicsItemAnimation
42 \brief The QGraphicsItemAnimation class provides simple animation
43 support for QGraphicsItem.
44 \since 4.2
45 \ingroup graphicsview-api
46 \inmodule QtWidgets
47 \deprecated
48
49 The QGraphicsItemAnimation class animates a QGraphicsItem. You can
50 schedule changes to the item's transformation matrix at
51 specified steps. The QGraphicsItemAnimation class has a
52 current step value. When this value changes the transformations
53 scheduled at that step are performed. The current step of the
54 animation is set with the \c setStep() function.
55
56 QGraphicsItemAnimation will do a simple linear interpolation
57 between the nearest adjacent scheduled changes to calculate the
58 matrix. For instance, if you set the position of an item at values
59 0.0 and 1.0, the animation will show the item moving in a straight
60 line between these positions. The same is true for scaling and
61 rotation.
62
63 It is usual to use the class with a QTimeLine. The timeline's
64 \l{QTimeLine::}{valueChanged()} signal is then connected to the
65 \c setStep() slot. For example, you can set up an item for rotation
66 by calling \c setRotationAt() for different step values.
67 The animations timeline is set with the setTimeLine() function.
68
69 An example animation with a timeline follows:
70
71 \snippet timeline/main.cpp 0
72
73 Note that steps lie between 0.0 and 1.0. It may be necessary to use
74 \l{QTimeLine::}{setUpdateInterval()}. The default update interval
75 is 40 ms. A scheduled transformation cannot be removed when set,
76 so scheduling several transformations of the same kind (e.g.,
77 rotations) at the same step is not recommended.
78
79 \sa QTimeLine, {Graphics View Framework}
80*/
81
82#include "qgraphicsitemanimation.h"
83
84#include "qgraphicsitem.h"
85
86#include <QtCore/qtimeline.h>
87#include <QtCore/qpoint.h>
88#include <QtCore/qpointer.h>
89#include <QtCore/qpair.h>
90
91#include <algorithm>
92
93QT_BEGIN_NAMESPACE
94
95static inline bool check_step_valid(qreal step, const char *method)
96{
97 if (!(step >= 0 && step <= 1)) {
98 qWarning("QGraphicsItemAnimation::%s: invalid step = %f", method, step);
99 return false;
100 }
101 return true;
102}
103
104class QGraphicsItemAnimationPrivate
105{
106public:
107 inline QGraphicsItemAnimationPrivate()
108 : q(nullptr), timeLine(nullptr), item(nullptr), step(0)
109 { }
110
111 QGraphicsItemAnimation *q;
112
113 QPointer<QTimeLine> timeLine;
114 QGraphicsItem *item;
115
116 QPointF startPos;
117 QTransform startTransform;
118
119 qreal step;
120
121 struct Pair {
122 bool operator <(const Pair &other) const
123 { return step < other.step; }
124 bool operator==(const Pair &other) const
125 { return step == other.step; }
126 qreal step;
127 qreal value;
128 };
129 QList<Pair> xPosition;
130 QList<Pair> yPosition;
131 QList<Pair> rotation;
132 QList<Pair> verticalScale;
133 QList<Pair> horizontalScale;
134 QList<Pair> verticalShear;
135 QList<Pair> horizontalShear;
136 QList<Pair> xTranslation;
137 QList<Pair> yTranslation;
138
139 qreal linearValueForStep(qreal step, const QList<Pair> &source, qreal defaultValue = 0);
140 void insertUniquePair(qreal step, qreal value, QList<Pair> *binList, const char *method);
141};
142Q_DECLARE_TYPEINFO(QGraphicsItemAnimationPrivate::Pair, Q_PRIMITIVE_TYPE);
143
144qreal QGraphicsItemAnimationPrivate::linearValueForStep(qreal step, const QList<Pair> &source,
145 qreal defaultValue)
146{
147 if (source.isEmpty())
148 return defaultValue;
149 step = qMin<qreal>(qMax<qreal>(step, 0), 1);
150
151 if (step == 1)
152 return source.back().value;
153
154 qreal stepBefore = 0;
155 qreal stepAfter = 1;
156 qreal valueBefore = source.front().step == 0 ? source.front().value : defaultValue;
157 qreal valueAfter = source.back().value;
158
159 // Find the closest step and value before the given step.
160 for (int i = 0; i < source.size() && step >= source[i].step; ++i) {
161 stepBefore = source[i].step;
162 valueBefore = source[i].value;
163 }
164
165 // Find the closest step and value after the given step.
166 for (int i = source.size() - 1; i >= 0 && step < source[i].step; --i) {
167 stepAfter = source[i].step;
168 valueAfter = source[i].value;
169 }
170
171 // Do a simple linear interpolation.
172 return valueBefore + (valueAfter - valueBefore) * ((step - stepBefore) / (stepAfter - stepBefore));
173}
174
175void QGraphicsItemAnimationPrivate::insertUniquePair(qreal step, qreal value, QList<Pair> *binList,
176 const char *method)
177{
178 if (!check_step_valid(step, method))
179 return;
180
181 const Pair pair = { step, value };
182
183 const QList<Pair>::iterator result = std::lower_bound(binList->begin(), binList->end(), pair);
184 if (result == binList->end() || pair < *result)
185 binList->insert(result, pair);
186 else
187 result->value = value;
188}
189
190/*!
191 Constructs an animation object with the given \a parent.
192*/
193QGraphicsItemAnimation::QGraphicsItemAnimation(QObject *parent)
194 : QObject(parent), d(new QGraphicsItemAnimationPrivate)
195{
196 d->q = this;
197}
198
199/*!
200 Destroys the animation object.
201*/
202QGraphicsItemAnimation::~QGraphicsItemAnimation()
203{
204 delete d;
205}
206
207/*!
208 Returns the item on which the animation object operates.
209
210 \sa setItem()
211*/
212QGraphicsItem *QGraphicsItemAnimation::item() const
213{
214 return d->item;
215}
216
217/*!
218 Sets the specified \a item to be used in the animation.
219
220 \sa item()
221*/
222void QGraphicsItemAnimation::setItem(QGraphicsItem *item)
223{
224 d->item = item;
225 d->startPos = d->item->pos();
226}
227
228/*!
229 Returns the timeline object used to control the rate at which the animation
230 occurs.
231
232 \sa setTimeLine()
233*/
234QTimeLine *QGraphicsItemAnimation::timeLine() const
235{
236 return d->timeLine;
237}
238
239/*!
240 Sets the timeline object used to control the rate of animation to the \a timeLine
241 specified.
242
243 \sa timeLine()
244*/
245void QGraphicsItemAnimation::setTimeLine(QTimeLine *timeLine)
246{
247 if (d->timeLine == timeLine)
248 return;
249 if (d->timeLine)
250 delete d->timeLine;
251 if (!timeLine)
252 return;
253 d->timeLine = timeLine;
254 connect(timeLine, SIGNAL(valueChanged(qreal)), this, SLOT(setStep(qreal)));
255}
256
257/*!
258 Returns the position of the item at the given \a step value.
259
260 \sa setPosAt()
261*/
262QPointF QGraphicsItemAnimation::posAt(qreal step) const
263{
264 check_step_valid(step, "posAt");
265 return QPointF(d->linearValueForStep(step, d->xPosition, d->startPos.x()),
266 d->linearValueForStep(step, d->yPosition, d->startPos.y()));
267}
268
269/*!
270 \fn void QGraphicsItemAnimation::setPosAt(qreal step, const QPointF &point)
271
272 Sets the position of the item at the given \a step value to the \a point specified.
273
274 \sa posAt()
275*/
276void QGraphicsItemAnimation::setPosAt(qreal step, const QPointF &pos)
277{
278 d->insertUniquePair(step, pos.x(), &d->xPosition, "setPosAt");
279 d->insertUniquePair(step, pos.y(), &d->yPosition, "setPosAt");
280}
281
282/*!
283 Returns all explicitly inserted positions.
284
285 \sa posAt(), setPosAt()
286*/
287QList<QPair<qreal, QPointF> > QGraphicsItemAnimation::posList() const
288{
289 QList<QPair<qreal, QPointF> > list;
290 const int xPosCount = d->xPosition.size();
291 list.reserve(xPosCount);
292 for (int i = 0; i < xPosCount; ++i)
293 list << QPair<qreal, QPointF>(d->xPosition.at(i).step, QPointF(d->xPosition.at(i).value, d->yPosition.at(i).value));
294
295 return list;
296}
297
298/*!
299 Returns the transform used for the item at the specified \a step value.
300
301 \since 5.14
302*/
303QTransform QGraphicsItemAnimation::transformAt(qreal step) const
304{
305 check_step_valid(step, "transformAt");
306
307 QTransform transform;
308 if (!d->rotation.isEmpty())
309 transform.rotate(rotationAt(step));
310 if (!d->verticalScale.isEmpty())
311 transform.scale(horizontalScaleAt(step), verticalScaleAt(step));
312 if (!d->verticalShear.isEmpty())
313 transform.shear(horizontalShearAt(step), verticalShearAt(step));
314 if (!d->xTranslation.isEmpty())
315 transform.translate(xTranslationAt(step), yTranslationAt(step));
316 return transform;
317}
318
319/*!
320 Returns the angle at which the item is rotated at the specified \a step value.
321
322 \sa setRotationAt()
323*/
324qreal QGraphicsItemAnimation::rotationAt(qreal step) const
325{
326 check_step_valid(step, "rotationAt");
327 return d->linearValueForStep(step, d->rotation);
328}
329
330/*!
331 Sets the rotation of the item at the given \a step value to the \a angle specified.
332
333 \sa rotationAt()
334*/
335void QGraphicsItemAnimation::setRotationAt(qreal step, qreal angle)
336{
337 d->insertUniquePair(step, angle, &d->rotation, "setRotationAt");
338}
339
340/*!
341 Returns all explicitly inserted rotations.
342
343 \sa rotationAt(), setRotationAt()
344*/
345QList<QPair<qreal, qreal> > QGraphicsItemAnimation::rotationList() const
346{
347 QList<QPair<qreal, qreal> > list;
348 const int numRotations = d->rotation.size();
349 list.reserve(numRotations);
350 for (int i = 0; i < numRotations; ++i)
351 list << QPair<qreal, qreal>(d->rotation.at(i).step, d->rotation.at(i).value);
352
353 return list;
354}
355
356/*!
357 Returns the horizontal translation of the item at the specified \a step value.
358
359 \sa setTranslationAt()
360*/
361qreal QGraphicsItemAnimation::xTranslationAt(qreal step) const
362{
363 check_step_valid(step, "xTranslationAt");
364 return d->linearValueForStep(step, d->xTranslation);
365}
366
367/*!
368 Returns the vertical translation of the item at the specified \a step value.
369
370 \sa setTranslationAt()
371*/
372qreal QGraphicsItemAnimation::yTranslationAt(qreal step) const
373{
374 check_step_valid(step, "yTranslationAt");
375 return d->linearValueForStep(step, d->yTranslation);
376}
377
378/*!
379 Sets the translation of the item at the given \a step value using the horizontal
380 and vertical coordinates specified by \a dx and \a dy.
381
382 \sa xTranslationAt(), yTranslationAt()
383*/
384void QGraphicsItemAnimation::setTranslationAt(qreal step, qreal dx, qreal dy)
385{
386 d->insertUniquePair(step, dx, &d->xTranslation, "setTranslationAt");
387 d->insertUniquePair(step, dy, &d->yTranslation, "setTranslationAt");
388}
389
390/*!
391 Returns all explicitly inserted translations.
392
393 \sa xTranslationAt(), yTranslationAt(), setTranslationAt()
394*/
395QList<QPair<qreal, QPointF> > QGraphicsItemAnimation::translationList() const
396{
397 QList<QPair<qreal, QPointF> > list;
398 const int numTranslations = d->xTranslation.size();
399 list.reserve(numTranslations);
400 for (int i = 0; i < numTranslations; ++i)
401 list << QPair<qreal, QPointF>(d->xTranslation.at(i).step, QPointF(d->xTranslation.at(i).value, d->yTranslation.at(i).value));
402
403 return list;
404}
405
406/*!
407 Returns the vertical scale for the item at the specified \a step value.
408
409 \sa setScaleAt()
410*/
411qreal QGraphicsItemAnimation::verticalScaleAt(qreal step) const
412{
413 check_step_valid(step, "verticalScaleAt");
414
415 return d->linearValueForStep(step, d->verticalScale, 1);
416}
417
418/*!
419 Returns the horizontal scale for the item at the specified \a step value.
420
421 \sa setScaleAt()
422*/
423qreal QGraphicsItemAnimation::horizontalScaleAt(qreal step) const
424{
425 check_step_valid(step, "horizontalScaleAt");
426 return d->linearValueForStep(step, d->horizontalScale, 1);
427}
428
429/*!
430 Sets the scale of the item at the given \a step value using the horizontal and
431 vertical scale factors specified by \a sx and \a sy.
432
433 \sa verticalScaleAt(), horizontalScaleAt()
434*/
435void QGraphicsItemAnimation::setScaleAt(qreal step, qreal sx, qreal sy)
436{
437 d->insertUniquePair(step, sx, &d->horizontalScale, "setScaleAt");
438 d->insertUniquePair(step, sy, &d->verticalScale, "setScaleAt");
439}
440
441/*!
442 Returns all explicitly inserted scales.
443
444 \sa verticalScaleAt(), horizontalScaleAt(), setScaleAt()
445*/
446QList<QPair<qreal, QPointF> > QGraphicsItemAnimation::scaleList() const
447{
448 QList<QPair<qreal, QPointF> > list;
449 const int numScales = d->horizontalScale.size();
450 list.reserve(numScales);
451 for (int i = 0; i < numScales; ++i)
452 list << QPair<qreal, QPointF>(d->horizontalScale.at(i).step, QPointF(d->horizontalScale.at(i).value, d->verticalScale.at(i).value));
453
454 return list;
455}
456
457/*!
458 Returns the vertical shear for the item at the specified \a step value.
459
460 \sa setShearAt()
461*/
462qreal QGraphicsItemAnimation::verticalShearAt(qreal step) const
463{
464 check_step_valid(step, "verticalShearAt");
465 return d->linearValueForStep(step, d->verticalShear, 0);
466}
467
468/*!
469 Returns the horizontal shear for the item at the specified \a step value.
470
471 \sa setShearAt()
472*/
473qreal QGraphicsItemAnimation::horizontalShearAt(qreal step) const
474{
475 check_step_valid(step, "horizontalShearAt");
476 return d->linearValueForStep(step, d->horizontalShear, 0);
477}
478
479/*!
480 Sets the shear of the item at the given \a step value using the horizontal and
481 vertical shear factors specified by \a sh and \a sv.
482
483 \sa verticalShearAt(), horizontalShearAt()
484*/
485void QGraphicsItemAnimation::setShearAt(qreal step, qreal sh, qreal sv)
486{
487 d->insertUniquePair(step, sh, &d->horizontalShear, "setShearAt");
488 d->insertUniquePair(step, sv, &d->verticalShear, "setShearAt");
489}
490
491/*!
492 Returns all explicitly inserted shears.
493
494 \sa verticalShearAt(), horizontalShearAt(), setShearAt()
495*/
496QList<QPair<qreal, QPointF> > QGraphicsItemAnimation::shearList() const
497{
498 QList<QPair<qreal, QPointF> > list;
499 const int numShears = d->horizontalShear.size();
500 list.reserve(numShears);
501 for (int i = 0; i < numShears; ++i)
502 list << QPair<qreal, QPointF>(d->horizontalShear.at(i).step, QPointF(d->horizontalShear.at(i).value, d->verticalShear.at(i).value));
503
504 return list;
505}
506
507/*!
508 Clears the scheduled transformations used for the animation, but
509 retains the item and timeline.
510*/
511void QGraphicsItemAnimation::clear()
512{
513 d->xPosition.clear();
514 d->yPosition.clear();
515 d->rotation.clear();
516 d->verticalScale.clear();
517 d->horizontalScale.clear();
518 d->verticalShear.clear();
519 d->horizontalShear.clear();
520 d->xTranslation.clear();
521 d->yTranslation.clear();
522}
523
524/*!
525 \fn void QGraphicsItemAnimation::setStep(qreal step)
526
527 Sets the current \a step value for the animation, causing the
528 transformations scheduled at this step to be performed.
529*/
530void QGraphicsItemAnimation::setStep(qreal step)
531{
532 if (!check_step_valid(step, "setStep"))
533 return;
534
535 beforeAnimationStep(step);
536
537 d->step = step;
538 if (d->item) {
539 if (!d->xPosition.isEmpty() || !d->yPosition.isEmpty())
540 d->item->setPos(posAt(step));
541 if (!d->rotation.isEmpty()
542 || !d->verticalScale.isEmpty()
543 || !d->horizontalScale.isEmpty()
544 || !d->verticalShear.isEmpty()
545 || !d->horizontalShear.isEmpty()
546 || !d->xTranslation.isEmpty()
547 || !d->yTranslation.isEmpty()) {
548 d->item->setTransform(d->startTransform * transformAt(step));
549 }
550 }
551
552 afterAnimationStep(step);
553}
554
555/*!
556 \fn void QGraphicsItemAnimation::beforeAnimationStep(qreal step)
557
558 This method is meant to be overridden by subclassed that needs to
559 execute additional code before a new step takes place. The
560 animation \a step is provided for use in cases where the action
561 depends on its value.
562*/
563void QGraphicsItemAnimation::beforeAnimationStep(qreal step)
564{
565 Q_UNUSED(step);
566}
567
568/*!
569 \fn void QGraphicsItemAnimation::afterAnimationStep(qreal step)
570
571 This method is meant to be overridden in subclasses that need to
572 execute additional code after a new step has taken place. The
573 animation \a step is provided for use in cases where the action
574 depends on its value.
575*/
576void QGraphicsItemAnimation::afterAnimationStep(qreal step)
577{
578 Q_UNUSED(step);
579}
580
581QT_END_NAMESPACE
582
583#include "moc_qgraphicsitemanimation.cpp"
584