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 | |
93 | QT_BEGIN_NAMESPACE |
94 | |
95 | static 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 | |
104 | class QGraphicsItemAnimationPrivate |
105 | { |
106 | public: |
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 | }; |
142 | Q_DECLARE_TYPEINFO(QGraphicsItemAnimationPrivate::Pair, Q_PRIMITIVE_TYPE); |
143 | |
144 | qreal 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 | |
175 | void 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 | */ |
193 | QGraphicsItemAnimation::QGraphicsItemAnimation(QObject *parent) |
194 | : QObject(parent), d(new QGraphicsItemAnimationPrivate) |
195 | { |
196 | d->q = this; |
197 | } |
198 | |
199 | /*! |
200 | Destroys the animation object. |
201 | */ |
202 | QGraphicsItemAnimation::~QGraphicsItemAnimation() |
203 | { |
204 | delete d; |
205 | } |
206 | |
207 | /*! |
208 | Returns the item on which the animation object operates. |
209 | |
210 | \sa setItem() |
211 | */ |
212 | QGraphicsItem *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 | */ |
222 | void 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 | */ |
234 | QTimeLine *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 | */ |
245 | void 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 | */ |
262 | QPointF 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 | */ |
276 | void 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 | */ |
287 | QList<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 | */ |
303 | QTransform 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 | */ |
324 | qreal 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 | */ |
335 | void 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 | */ |
345 | QList<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 | */ |
361 | qreal 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 | */ |
372 | qreal 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 | */ |
384 | void 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 | */ |
395 | QList<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 | */ |
411 | qreal 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 | */ |
423 | qreal 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 | */ |
435 | void 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 | */ |
446 | QList<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 | */ |
462 | qreal 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 | */ |
473 | qreal 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 | */ |
485 | void 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 | */ |
496 | QList<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 | */ |
511 | void 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 | */ |
530 | void 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 | */ |
563 | void 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 | */ |
576 | void QGraphicsItemAnimation::afterAnimationStep(qreal step) |
577 | { |
578 | Q_UNUSED(step); |
579 | } |
580 | |
581 | QT_END_NAMESPACE |
582 | |
583 | #include "moc_qgraphicsitemanimation.cpp" |
584 | |