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