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 #include <QtGui/qmatrix.h>
91 
92 #include <algorithm>
93 
94 QT_BEGIN_NAMESPACE
95 
check_step_valid(qreal step,const char * method)96 static inline bool check_step_valid(qreal step, const char *method)
97 {
98     if (!(step >= 0 && step <= 1)) {
99         qWarning("QGraphicsItemAnimation::%s: invalid step = %f", method, step);
100         return false;
101     }
102     return true;
103 }
104 
105 class QGraphicsItemAnimationPrivate
106 {
107 public:
QGraphicsItemAnimationPrivate()108     inline QGraphicsItemAnimationPrivate()
109         : q(nullptr), timeLine(nullptr), item(nullptr), step(0)
110     { }
111 
112     QGraphicsItemAnimation *q;
113 
114     QPointer<QTimeLine> timeLine;
115     QGraphicsItem *item;
116 
117     QPointF startPos;
118     QTransform startTransform;
119 
120     qreal step;
121 
122     struct Pair {
operator <QGraphicsItemAnimationPrivate::Pair123         bool operator <(const Pair &other) const
124         { return step < other.step; }
operator ==QGraphicsItemAnimationPrivate::Pair125         bool operator==(const Pair &other) const
126         { return step == other.step; }
127         qreal step;
128         qreal value;
129     };
130     QVector<Pair> xPosition;
131     QVector<Pair> yPosition;
132     QVector<Pair> rotation;
133     QVector<Pair> verticalScale;
134     QVector<Pair> horizontalScale;
135     QVector<Pair> verticalShear;
136     QVector<Pair> horizontalShear;
137     QVector<Pair> xTranslation;
138     QVector<Pair> yTranslation;
139 
140     qreal linearValueForStep(qreal step, const QVector<Pair> &source, qreal defaultValue = 0);
141     void insertUniquePair(qreal step, qreal value, QVector<Pair> *binList, const char* method);
142 };
143 Q_DECLARE_TYPEINFO(QGraphicsItemAnimationPrivate::Pair, Q_PRIMITIVE_TYPE);
144 
linearValueForStep(qreal step,const QVector<Pair> & source,qreal defaultValue)145 qreal QGraphicsItemAnimationPrivate::linearValueForStep(qreal step, const QVector<Pair> &source, 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 
insertUniquePair(qreal step,qreal value,QVector<Pair> * binList,const char * method)175 void QGraphicsItemAnimationPrivate::insertUniquePair(qreal step, qreal value, QVector<Pair> *binList, const char* method)
176 {
177     if (!check_step_valid(step, method))
178         return;
179 
180     const Pair pair = { step, value };
181 
182     const QVector<Pair>::iterator result = std::lower_bound(binList->begin(), binList->end(), pair);
183     if (result == binList->end() || pair < *result)
184         binList->insert(result, pair);
185     else
186         result->value = value;
187 }
188 
189 /*!
190   Constructs an animation object with the given \a parent.
191 */
QGraphicsItemAnimation(QObject * parent)192 QGraphicsItemAnimation::QGraphicsItemAnimation(QObject *parent)
193     : QObject(parent), d(new QGraphicsItemAnimationPrivate)
194 {
195     d->q = this;
196 }
197 
198 /*!
199   Destroys the animation object.
200 */
~QGraphicsItemAnimation()201 QGraphicsItemAnimation::~QGraphicsItemAnimation()
202 {
203     delete d;
204 }
205 
206 /*!
207   Returns the item on which the animation object operates.
208 
209   \sa setItem()
210 */
item() const211 QGraphicsItem *QGraphicsItemAnimation::item() const
212 {
213     return d->item;
214 }
215 
216 /*!
217   Sets the specified \a item to be used in the animation.
218 
219   \sa item()
220 */
setItem(QGraphicsItem * item)221 void QGraphicsItemAnimation::setItem(QGraphicsItem *item)
222 {
223     d->item = item;
224     d->startPos = d->item->pos();
225 }
226 
227 /*!
228   Returns the timeline object used to control the rate at which the animation
229   occurs.
230 
231   \sa setTimeLine()
232 */
timeLine() const233 QTimeLine *QGraphicsItemAnimation::timeLine() const
234 {
235     return d->timeLine;
236 }
237 
238 /*!
239   Sets the timeline object used to control the rate of animation to the \a timeLine
240   specified.
241 
242   \sa timeLine()
243 */
setTimeLine(QTimeLine * timeLine)244 void QGraphicsItemAnimation::setTimeLine(QTimeLine *timeLine)
245 {
246     if (d->timeLine == timeLine)
247         return;
248     if (d->timeLine)
249         delete d->timeLine;
250     if (!timeLine)
251         return;
252     d->timeLine = timeLine;
253     connect(timeLine, SIGNAL(valueChanged(qreal)), this, SLOT(setStep(qreal)));
254 }
255 
256 /*!
257   Returns the position of the item at the given \a step value.
258 
259   \sa setPosAt()
260 */
posAt(qreal step) const261 QPointF QGraphicsItemAnimation::posAt(qreal step) const
262 {
263     check_step_valid(step, "posAt");
264     return QPointF(d->linearValueForStep(step, d->xPosition, d->startPos.x()),
265                    d->linearValueForStep(step, d->yPosition, d->startPos.y()));
266 }
267 
268 /*!
269   \fn void QGraphicsItemAnimation::setPosAt(qreal step, const QPointF &point)
270 
271   Sets the position of the item at the given \a step value to the \a point specified.
272 
273   \sa posAt()
274 */
setPosAt(qreal step,const QPointF & pos)275 void QGraphicsItemAnimation::setPosAt(qreal step, const QPointF &pos)
276 {
277     d->insertUniquePair(step, pos.x(), &d->xPosition, "setPosAt");
278     d->insertUniquePair(step, pos.y(), &d->yPosition, "setPosAt");
279 }
280 
281 /*!
282   Returns all explicitly inserted positions.
283 
284   \sa posAt(), setPosAt()
285 */
posList() const286 QList<QPair<qreal, QPointF> > QGraphicsItemAnimation::posList() const
287 {
288     QList<QPair<qreal, QPointF> > list;
289     const int xPosCount = d->xPosition.size();
290     list.reserve(xPosCount);
291     for (int i = 0; i < xPosCount; ++i)
292         list << QPair<qreal, QPointF>(d->xPosition.at(i).step, QPointF(d->xPosition.at(i).value, d->yPosition.at(i).value));
293 
294     return list;
295 }
296 
297 #if QT_DEPRECATED_SINCE(5, 14)
298 /*!
299   Returns the matrix used to transform the item at the specified \a step value.
300 
301   \obsolete Use transformAt() instead
302 */
matrixAt(qreal step) const303 QMatrix QGraphicsItemAnimation::matrixAt(qreal step) const
304 {
305     check_step_valid(step, "matrixAt");
306     return transformAt(step).toAffine();
307 }
308 #endif
309 
310 /*!
311   Returns the transform used for the item at the specified \a step value.
312 
313   \since 5.14
314 */
transformAt(qreal step) const315 QTransform QGraphicsItemAnimation::transformAt(qreal step) const
316 {
317     check_step_valid(step, "transformAt");
318 
319     QTransform transform;
320     if (!d->rotation.isEmpty())
321         transform.rotate(rotationAt(step));
322     if (!d->verticalScale.isEmpty())
323         transform.scale(horizontalScaleAt(step), verticalScaleAt(step));
324     if (!d->verticalShear.isEmpty())
325         transform.shear(horizontalShearAt(step), verticalShearAt(step));
326     if (!d->xTranslation.isEmpty())
327         transform.translate(xTranslationAt(step), yTranslationAt(step));
328     return transform;
329 }
330 
331 /*!
332   Returns the angle at which the item is rotated at the specified \a step value.
333 
334   \sa setRotationAt()
335 */
rotationAt(qreal step) const336 qreal QGraphicsItemAnimation::rotationAt(qreal step) const
337 {
338     check_step_valid(step, "rotationAt");
339     return d->linearValueForStep(step, d->rotation);
340 }
341 
342 /*!
343   Sets the rotation of the item at the given \a step value to the \a angle specified.
344 
345   \sa rotationAt()
346 */
setRotationAt(qreal step,qreal angle)347 void QGraphicsItemAnimation::setRotationAt(qreal step, qreal angle)
348 {
349     d->insertUniquePair(step, angle, &d->rotation, "setRotationAt");
350 }
351 
352 /*!
353   Returns all explicitly inserted rotations.
354 
355   \sa rotationAt(), setRotationAt()
356 */
rotationList() const357 QList<QPair<qreal, qreal> > QGraphicsItemAnimation::rotationList() const
358 {
359     QList<QPair<qreal, qreal> > list;
360     const int numRotations = d->rotation.size();
361     list.reserve(numRotations);
362     for (int i = 0; i < numRotations; ++i)
363         list << QPair<qreal, qreal>(d->rotation.at(i).step, d->rotation.at(i).value);
364 
365     return list;
366 }
367 
368 /*!
369   Returns the horizontal translation of the item at the specified \a step value.
370 
371   \sa setTranslationAt()
372 */
xTranslationAt(qreal step) const373 qreal QGraphicsItemAnimation::xTranslationAt(qreal step) const
374 {
375     check_step_valid(step, "xTranslationAt");
376     return d->linearValueForStep(step, d->xTranslation);
377 }
378 
379 /*!
380   Returns the vertical translation of the item at the specified \a step value.
381 
382   \sa setTranslationAt()
383 */
yTranslationAt(qreal step) const384 qreal QGraphicsItemAnimation::yTranslationAt(qreal step) const
385 {
386     check_step_valid(step, "yTranslationAt");
387     return d->linearValueForStep(step, d->yTranslation);
388 }
389 
390 /*!
391   Sets the translation of the item at the given \a step value using the horizontal
392   and vertical coordinates specified by \a dx and \a dy.
393 
394   \sa xTranslationAt(), yTranslationAt()
395 */
setTranslationAt(qreal step,qreal dx,qreal dy)396 void QGraphicsItemAnimation::setTranslationAt(qreal step, qreal dx, qreal dy)
397 {
398     d->insertUniquePair(step, dx, &d->xTranslation, "setTranslationAt");
399     d->insertUniquePair(step, dy, &d->yTranslation, "setTranslationAt");
400 }
401 
402 /*!
403   Returns all explicitly inserted translations.
404 
405   \sa xTranslationAt(), yTranslationAt(), setTranslationAt()
406 */
translationList() const407 QList<QPair<qreal, QPointF> > QGraphicsItemAnimation::translationList() const
408 {
409     QList<QPair<qreal, QPointF> > list;
410     const int numTranslations = d->xTranslation.size();
411     list.reserve(numTranslations);
412     for (int i = 0; i < numTranslations; ++i)
413         list << QPair<qreal, QPointF>(d->xTranslation.at(i).step, QPointF(d->xTranslation.at(i).value, d->yTranslation.at(i).value));
414 
415     return list;
416 }
417 
418 /*!
419   Returns the vertical scale for the item at the specified \a step value.
420 
421   \sa setScaleAt()
422 */
verticalScaleAt(qreal step) const423 qreal QGraphicsItemAnimation::verticalScaleAt(qreal step) const
424 {
425     check_step_valid(step, "verticalScaleAt");
426 
427     return d->linearValueForStep(step, d->verticalScale, 1);
428 }
429 
430 /*!
431   Returns the horizontal scale for the item at the specified \a step value.
432 
433   \sa setScaleAt()
434 */
horizontalScaleAt(qreal step) const435 qreal QGraphicsItemAnimation::horizontalScaleAt(qreal step) const
436 {
437     check_step_valid(step, "horizontalScaleAt");
438     return d->linearValueForStep(step, d->horizontalScale, 1);
439 }
440 
441 /*!
442   Sets the scale of the item at the given \a step value using the horizontal and
443   vertical scale factors specified by \a sx and \a sy.
444 
445   \sa verticalScaleAt(), horizontalScaleAt()
446 */
setScaleAt(qreal step,qreal sx,qreal sy)447 void QGraphicsItemAnimation::setScaleAt(qreal step, qreal sx, qreal sy)
448 {
449     d->insertUniquePair(step, sx, &d->horizontalScale, "setScaleAt");
450     d->insertUniquePair(step, sy, &d->verticalScale, "setScaleAt");
451 }
452 
453 /*!
454   Returns all explicitly inserted scales.
455 
456   \sa verticalScaleAt(), horizontalScaleAt(), setScaleAt()
457 */
scaleList() const458 QList<QPair<qreal, QPointF> > QGraphicsItemAnimation::scaleList() const
459 {
460     QList<QPair<qreal, QPointF> > list;
461     const int numScales = d->horizontalScale.size();
462     list.reserve(numScales);
463     for (int i = 0; i < numScales; ++i)
464         list << QPair<qreal, QPointF>(d->horizontalScale.at(i).step, QPointF(d->horizontalScale.at(i).value, d->verticalScale.at(i).value));
465 
466     return list;
467 }
468 
469 /*!
470   Returns the vertical shear for the item at the specified \a step value.
471 
472   \sa setShearAt()
473 */
verticalShearAt(qreal step) const474 qreal QGraphicsItemAnimation::verticalShearAt(qreal step) const
475 {
476     check_step_valid(step, "verticalShearAt");
477     return d->linearValueForStep(step, d->verticalShear, 0);
478 }
479 
480 /*!
481   Returns the horizontal shear for the item at the specified \a step value.
482 
483   \sa setShearAt()
484 */
horizontalShearAt(qreal step) const485 qreal QGraphicsItemAnimation::horizontalShearAt(qreal step) const
486 {
487     check_step_valid(step, "horizontalShearAt");
488     return d->linearValueForStep(step, d->horizontalShear, 0);
489 }
490 
491 /*!
492   Sets the shear of the item at the given \a step value using the horizontal and
493   vertical shear factors specified by \a sh and \a sv.
494 
495   \sa verticalShearAt(), horizontalShearAt()
496 */
setShearAt(qreal step,qreal sh,qreal sv)497 void QGraphicsItemAnimation::setShearAt(qreal step, qreal sh, qreal sv)
498 {
499     d->insertUniquePair(step, sh, &d->horizontalShear, "setShearAt");
500     d->insertUniquePair(step, sv, &d->verticalShear, "setShearAt");
501 }
502 
503 /*!
504   Returns all explicitly inserted shears.
505 
506   \sa verticalShearAt(), horizontalShearAt(), setShearAt()
507 */
shearList() const508 QList<QPair<qreal, QPointF> > QGraphicsItemAnimation::shearList() const
509 {
510     QList<QPair<qreal, QPointF> > list;
511     const int numShears = d->horizontalShear.size();
512     list.reserve(numShears);
513     for (int i = 0; i < numShears; ++i)
514         list << QPair<qreal, QPointF>(d->horizontalShear.at(i).step, QPointF(d->horizontalShear.at(i).value, d->verticalShear.at(i).value));
515 
516     return list;
517 }
518 
519 /*!
520   Clears the scheduled transformations used for the animation, but
521   retains the item and timeline.
522 */
clear()523 void QGraphicsItemAnimation::clear()
524 {
525     d->xPosition.clear();
526     d->yPosition.clear();
527     d->rotation.clear();
528     d->verticalScale.clear();
529     d->horizontalScale.clear();
530     d->verticalShear.clear();
531     d->horizontalShear.clear();
532     d->xTranslation.clear();
533     d->yTranslation.clear();
534 }
535 
536 /*!
537   \fn void QGraphicsItemAnimation::setStep(qreal step)
538 
539   Sets the current \a step value for the animation, causing the
540   transformations scheduled at this step to be performed.
541 */
setStep(qreal step)542 void QGraphicsItemAnimation::setStep(qreal step)
543 {
544     if (!check_step_valid(step, "setStep"))
545         return;
546 
547     beforeAnimationStep(step);
548 
549     d->step = step;
550     if (d->item) {
551         if (!d->xPosition.isEmpty() || !d->yPosition.isEmpty())
552             d->item->setPos(posAt(step));
553         if (!d->rotation.isEmpty()
554             || !d->verticalScale.isEmpty()
555             || !d->horizontalScale.isEmpty()
556             || !d->verticalShear.isEmpty()
557             || !d->horizontalShear.isEmpty()
558             || !d->xTranslation.isEmpty()
559             || !d->yTranslation.isEmpty()) {
560             d->item->setTransform(d->startTransform * transformAt(step));
561         }
562     }
563 
564     afterAnimationStep(step);
565 }
566 
567 #if QT_DEPRECATED_SINCE(5, 13)
568 /*!
569     Resets the item to its starting position and transformation.
570 
571     \obsolete
572 
573     You can call setStep(0) instead.
574 */
reset()575 void QGraphicsItemAnimation::reset()
576 {
577     if (!d->item)
578         return;
579     d->startPos = d->item->pos();
580     d->startTransform = d->item->transform();
581 }
582 #endif
583 
584 /*!
585   \fn void QGraphicsItemAnimation::beforeAnimationStep(qreal step)
586 
587   This method is meant to be overridden by subclassed that needs to
588   execute additional code before a new step takes place. The
589   animation \a step is provided for use in cases where the action
590   depends on its value.
591 */
beforeAnimationStep(qreal step)592 void QGraphicsItemAnimation::beforeAnimationStep(qreal step)
593 {
594     Q_UNUSED(step);
595 }
596 
597 /*!
598   \fn void QGraphicsItemAnimation::afterAnimationStep(qreal step)
599 
600   This method is meant to be overridden in subclasses that need to
601   execute additional code after a new step has taken place. The
602   animation \a step is provided for use in cases where the action
603   depends on its value.
604 */
afterAnimationStep(qreal step)605 void QGraphicsItemAnimation::afterAnimationStep(qreal step)
606 {
607     Q_UNUSED(step);
608 }
609 
610 QT_END_NAMESPACE
611 
612 #include "moc_qgraphicsitemanimation.cpp"
613