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 QtDeclarative 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 QGraphicsTransform |
42 | \brief The QGraphicsTransform class is an abstract base class for building |
43 | advanced transformations on QGraphicsItems. |
44 | \since 4.6 |
45 | \ingroup graphicsview-api |
46 | \inmodule QtWidgets |
47 | |
48 | As an alternative to QGraphicsItem::transform, QGraphicsTransform lets you |
49 | create and control advanced transformations that can be configured |
50 | independently using specialized properties. |
51 | |
52 | QGraphicsItem allows you to assign any number of QGraphicsTransform |
53 | instances to one QGraphicsItem. Each QGraphicsTransform is applied in |
54 | order, one at a time, to the QGraphicsItem it's assigned to. |
55 | |
56 | QGraphicsTransform is particularly useful for animations. Whereas |
57 | QGraphicsItem::setTransform() lets you assign any transform directly to an |
58 | item, there is no direct way to interpolate between two different |
59 | transformations (e.g., when transitioning between two states, each for |
60 | which the item has a different arbitrary transform assigned). Using |
61 | QGraphicsTransform you can interpolate the property values of each |
62 | independent transformation. The resulting operation is then combined into a |
63 | single transform which is applied to QGraphicsItem. |
64 | |
65 | Transformations are computed in true 3D space using QMatrix4x4. |
66 | When the transformation is applied to a QGraphicsItem, it will be |
67 | projected back to a 2D QTransform. When multiple QGraphicsTransform |
68 | objects are applied to a QGraphicsItem, all of the transformations |
69 | are computed in true 3D space, with the projection back to 2D |
70 | only occurring after the last QGraphicsTransform is applied. |
71 | The exception to this is QGraphicsRotation, which projects back to |
72 | 2D after each rotation to preserve the perspective effect around |
73 | the X and Y axes. |
74 | |
75 | If you want to create your own configurable transformation, you can create |
76 | a subclass of QGraphicsTransform (or any or the existing subclasses), and |
77 | reimplement the pure virtual applyTo() function, which takes a pointer to a |
78 | QMatrix4x4. Each operation you would like to apply should be exposed as |
79 | properties (e.g., customTransform->setVerticalShear(2.5)). Inside you |
80 | reimplementation of applyTo(), you can modify the provided transform |
81 | respectively. |
82 | |
83 | QGraphicsTransform can be used together with QGraphicsItem::setTransform(), |
84 | QGraphicsItem::setRotation(), and QGraphicsItem::setScale(). |
85 | |
86 | \sa QGraphicsItem::transform(), QGraphicsScale, QGraphicsRotation |
87 | */ |
88 | |
89 | #include "qgraphicstransform.h" |
90 | #include "qgraphicsitem_p.h" |
91 | #include "qgraphicstransform_p.h" |
92 | #include <QDebug> |
93 | #include <QtCore/qmath.h> |
94 | #include <QtCore/qnumeric.h> |
95 | |
96 | QT_BEGIN_NAMESPACE |
97 | |
98 | QGraphicsTransformPrivate::~QGraphicsTransformPrivate() |
99 | { |
100 | } |
101 | |
102 | void QGraphicsTransformPrivate::setItem(QGraphicsItem *i) |
103 | { |
104 | if (item == i) |
105 | return; |
106 | |
107 | if (item) { |
108 | Q_Q(QGraphicsTransform); |
109 | QGraphicsItemPrivate *d_ptr = item->d_ptr.data(); |
110 | |
111 | item->prepareGeometryChange(); |
112 | Q_ASSERT(d_ptr->transformData); |
113 | d_ptr->transformData->graphicsTransforms.removeAll(t: q); |
114 | d_ptr->dirtySceneTransform = 1; |
115 | item = nullptr; |
116 | } |
117 | |
118 | item = i; |
119 | } |
120 | |
121 | void QGraphicsTransformPrivate::updateItem(QGraphicsItem *item) |
122 | { |
123 | item->prepareGeometryChange(); |
124 | item->d_ptr->dirtySceneTransform = 1; |
125 | } |
126 | |
127 | /*! |
128 | Constructs a new QGraphicsTransform with the given \a parent. |
129 | */ |
130 | QGraphicsTransform::QGraphicsTransform(QObject *parent) |
131 | : QObject(*new QGraphicsTransformPrivate, parent) |
132 | { |
133 | } |
134 | |
135 | /*! |
136 | Destroys the graphics transform. |
137 | */ |
138 | QGraphicsTransform::~QGraphicsTransform() |
139 | { |
140 | Q_D(QGraphicsTransform); |
141 | d->setItem(nullptr); |
142 | } |
143 | |
144 | /*! |
145 | \internal |
146 | */ |
147 | QGraphicsTransform::QGraphicsTransform(QGraphicsTransformPrivate &p, QObject *parent) |
148 | : QObject(p, parent) |
149 | { |
150 | } |
151 | |
152 | /*! |
153 | \fn void QGraphicsTransform::applyTo(QMatrix4x4 *matrix) const |
154 | |
155 | This pure virtual method has to be reimplemented in derived classes. |
156 | |
157 | It applies this transformation to \a matrix. |
158 | |
159 | \sa QGraphicsItem::transform(), QMatrix4x4::toTransform() |
160 | */ |
161 | |
162 | /*! |
163 | Notifies that this transform operation has changed its parameters in such a |
164 | way that applyTo() will return a different result than before. |
165 | |
166 | When implementing you own custom graphics transform, you must call this |
167 | function every time you change a parameter, to let QGraphicsItem know that |
168 | its transformation needs to be updated. |
169 | |
170 | \sa applyTo() |
171 | */ |
172 | void QGraphicsTransform::update() |
173 | { |
174 | Q_D(QGraphicsTransform); |
175 | if (d->item) |
176 | d->updateItem(item: d->item); |
177 | } |
178 | |
179 | /*! |
180 | \class QGraphicsScale |
181 | \brief The QGraphicsScale class provides a scale transformation. |
182 | \since 4.6 |
183 | \inmodule QtWidgets |
184 | |
185 | QGraphicsScene provides certain parameters to help control how the scale |
186 | should be applied. |
187 | |
188 | The origin is the point that the item is scaled from (i.e., it stays fixed |
189 | relative to the parent as the rest of the item grows). By default the |
190 | origin is QPointF(0, 0). |
191 | |
192 | The parameters xScale, yScale, and zScale describe the scale factors to |
193 | apply in horizontal, vertical, and depth directions. They can take on any |
194 | value, including 0 (to collapse the item to a point) or negative value. |
195 | A negative xScale value will mirror the item horizontally. A negative yScale |
196 | value will flip the item vertically. A negative zScale will flip the |
197 | item end for end. |
198 | |
199 | \sa QGraphicsTransform, QGraphicsItem::setScale(), QTransform::scale() |
200 | */ |
201 | |
202 | class QGraphicsScalePrivate : public QGraphicsTransformPrivate |
203 | { |
204 | public: |
205 | QGraphicsScalePrivate() |
206 | : xScale(1), yScale(1), zScale(1) {} |
207 | QVector3D origin; |
208 | qreal xScale; |
209 | qreal yScale; |
210 | qreal zScale; |
211 | }; |
212 | |
213 | /*! |
214 | Constructs an empty QGraphicsScale object with the given \a parent. |
215 | */ |
216 | QGraphicsScale::QGraphicsScale(QObject *parent) |
217 | : QGraphicsTransform(*new QGraphicsScalePrivate, parent) |
218 | { |
219 | } |
220 | |
221 | /*! |
222 | Destroys the graphics scale. |
223 | */ |
224 | QGraphicsScale::~QGraphicsScale() |
225 | { |
226 | } |
227 | |
228 | /*! |
229 | \property QGraphicsScale::origin |
230 | \brief the origin of the scale in 3D space. |
231 | |
232 | All scaling will be done relative to this point (i.e., this point |
233 | will stay fixed, relative to the parent, when the item is scaled). |
234 | |
235 | \sa xScale, yScale, zScale |
236 | */ |
237 | QVector3D QGraphicsScale::origin() const |
238 | { |
239 | Q_D(const QGraphicsScale); |
240 | return d->origin; |
241 | } |
242 | void QGraphicsScale::setOrigin(const QVector3D &point) |
243 | { |
244 | Q_D(QGraphicsScale); |
245 | if (d->origin == point) |
246 | return; |
247 | d->origin = point; |
248 | update(); |
249 | emit originChanged(); |
250 | } |
251 | |
252 | /*! |
253 | \property QGraphicsScale::xScale |
254 | \brief the horizontal scale factor. |
255 | |
256 | The scale factor can be any real number; the default value is 1.0. If you |
257 | set the factor to 0.0, the item will be collapsed to a single point. If you |
258 | provide a negative value, the item will be mirrored horizontally around its |
259 | origin. |
260 | |
261 | \sa yScale, zScale, origin |
262 | */ |
263 | qreal QGraphicsScale::xScale() const |
264 | { |
265 | Q_D(const QGraphicsScale); |
266 | return d->xScale; |
267 | } |
268 | void QGraphicsScale::setXScale(qreal scale) |
269 | { |
270 | Q_D(QGraphicsScale); |
271 | if (d->xScale == scale) |
272 | return; |
273 | d->xScale = scale; |
274 | update(); |
275 | emit xScaleChanged(); |
276 | emit scaleChanged(); |
277 | } |
278 | |
279 | /*! |
280 | \property QGraphicsScale::yScale |
281 | \brief the vertical scale factor. |
282 | |
283 | The scale factor can be any real number; the default value is 1.0. If you |
284 | set the factor to 0.0, the item will be collapsed to a single point. If you |
285 | provide a negative value, the item will be flipped vertically around its |
286 | origin. |
287 | |
288 | \sa xScale, zScale, origin |
289 | */ |
290 | qreal QGraphicsScale::yScale() const |
291 | { |
292 | Q_D(const QGraphicsScale); |
293 | return d->yScale; |
294 | } |
295 | void QGraphicsScale::setYScale(qreal scale) |
296 | { |
297 | Q_D(QGraphicsScale); |
298 | if (d->yScale == scale) |
299 | return; |
300 | d->yScale = scale; |
301 | update(); |
302 | emit yScaleChanged(); |
303 | emit scaleChanged(); |
304 | } |
305 | |
306 | /*! |
307 | \property QGraphicsScale::zScale |
308 | \brief the depth scale factor. |
309 | |
310 | The scale factor can be any real number; the default value is 1.0. If you |
311 | set the factor to 0.0, the item will be collapsed to a single point. If you |
312 | provide a negative value, the item will be flipped end for end around its |
313 | origin. |
314 | |
315 | \sa xScale, yScale, origin |
316 | */ |
317 | qreal QGraphicsScale::zScale() const |
318 | { |
319 | Q_D(const QGraphicsScale); |
320 | return d->zScale; |
321 | } |
322 | void QGraphicsScale::setZScale(qreal scale) |
323 | { |
324 | Q_D(QGraphicsScale); |
325 | if (d->zScale == scale) |
326 | return; |
327 | d->zScale = scale; |
328 | update(); |
329 | emit zScaleChanged(); |
330 | emit scaleChanged(); |
331 | } |
332 | |
333 | /*! |
334 | \reimp |
335 | */ |
336 | void QGraphicsScale::applyTo(QMatrix4x4 *matrix) const |
337 | { |
338 | Q_D(const QGraphicsScale); |
339 | matrix->translate(vector: d->origin); |
340 | matrix->scale(x: d->xScale, y: d->yScale, z: d->zScale); |
341 | matrix->translate(vector: -d->origin); |
342 | } |
343 | |
344 | /*! |
345 | \fn QGraphicsScale::originChanged() |
346 | |
347 | QGraphicsScale emits this signal when its origin changes. |
348 | |
349 | \sa QGraphicsScale::origin |
350 | */ |
351 | |
352 | /*! |
353 | \fn QGraphicsScale::xScaleChanged() |
354 | \since 4.7 |
355 | |
356 | This signal is emitted whenever the \l xScale property changes. |
357 | */ |
358 | |
359 | /*! |
360 | \fn QGraphicsScale::yScaleChanged() |
361 | \since 4.7 |
362 | |
363 | This signal is emitted whenever the \l yScale property changes. |
364 | */ |
365 | |
366 | /*! |
367 | \fn QGraphicsScale::zScaleChanged() |
368 | \since 4.7 |
369 | |
370 | This signal is emitted whenever the \l zScale property changes. |
371 | */ |
372 | |
373 | /*! |
374 | \fn QGraphicsScale::scaleChanged() |
375 | |
376 | This signal is emitted whenever the xScale, yScale, or zScale |
377 | of the object changes. |
378 | |
379 | \sa QGraphicsScale::xScale, QGraphicsScale::yScale |
380 | \sa QGraphicsScale::zScale |
381 | */ |
382 | |
383 | /*! |
384 | \class QGraphicsRotation |
385 | \brief The QGraphicsRotation class provides a rotation transformation around |
386 | a given axis. |
387 | \since 4.6 |
388 | \inmodule QtWidgets |
389 | |
390 | You can provide the desired axis by assigning a QVector3D to the axis property |
391 | or by passing a member if Qt::Axis to the setAxis convenience function. |
392 | By default the axis is (0, 0, 1) i.e., rotation around the Z axis. |
393 | |
394 | The angle property, which is provided by QGraphicsRotation, now |
395 | describes the number of degrees to rotate around this axis. |
396 | |
397 | QGraphicsRotation provides certain parameters to help control how the |
398 | rotation should be applied. |
399 | |
400 | The origin is the point that the item is rotated around (i.e., it stays |
401 | fixed relative to the parent as the rest of the item is rotated). By |
402 | default the origin is QPointF(0, 0). |
403 | |
404 | The angle property provides the number of degrees to rotate the item |
405 | clockwise around the origin. This value also be negative, indicating a |
406 | counter-clockwise rotation. For animation purposes it may also be useful to |
407 | provide rotation angles exceeding (-360, 360) degrees, for instance to |
408 | animate how an item rotates several times. |
409 | |
410 | Note: the final rotation is the combined effect of a rotation in |
411 | 3D space followed by a projection back to 2D. If several rotations |
412 | are performed in succession, they will not behave as expected unless |
413 | they were all around the Z axis. |
414 | |
415 | \sa QGraphicsTransform, QGraphicsItem::setRotation(), QTransform::rotate() |
416 | */ |
417 | |
418 | class QGraphicsRotationPrivate : public QGraphicsTransformPrivate |
419 | { |
420 | public: |
421 | QGraphicsRotationPrivate() |
422 | : angle(0), axis(0, 0, 1) {} |
423 | QVector3D origin; |
424 | qreal angle; |
425 | QVector3D axis; |
426 | }; |
427 | |
428 | /*! |
429 | Constructs a new QGraphicsRotation with the given \a parent. |
430 | */ |
431 | QGraphicsRotation::QGraphicsRotation(QObject *parent) |
432 | : QGraphicsTransform(*new QGraphicsRotationPrivate, parent) |
433 | { |
434 | } |
435 | |
436 | /*! |
437 | Destroys the graphics rotation. |
438 | */ |
439 | QGraphicsRotation::~QGraphicsRotation() |
440 | { |
441 | } |
442 | |
443 | /*! |
444 | \property QGraphicsRotation::origin |
445 | \brief the origin of the rotation in 3D space. |
446 | |
447 | All rotations will be done relative to this point (i.e., this point |
448 | will stay fixed, relative to the parent, when the item is rotated). |
449 | |
450 | \sa angle |
451 | */ |
452 | QVector3D QGraphicsRotation::origin() const |
453 | { |
454 | Q_D(const QGraphicsRotation); |
455 | return d->origin; |
456 | } |
457 | void QGraphicsRotation::setOrigin(const QVector3D &point) |
458 | { |
459 | Q_D(QGraphicsRotation); |
460 | if (d->origin == point) |
461 | return; |
462 | d->origin = point; |
463 | update(); |
464 | emit originChanged(); |
465 | } |
466 | |
467 | /*! |
468 | \property QGraphicsRotation::angle |
469 | \brief the angle for clockwise rotation, in degrees. |
470 | |
471 | The angle can be any real number; the default value is 0.0. A value of 180 |
472 | will rotate 180 degrees, clockwise. If you provide a negative number, the |
473 | item will be rotated counter-clockwise. Normally the rotation angle will be |
474 | in the range (-360, 360), but you can also provide numbers outside of this |
475 | range (e.g., a angle of 370 degrees gives the same result as 10 degrees). |
476 | Setting the angle to NaN results in no rotation. |
477 | |
478 | \sa origin |
479 | */ |
480 | qreal QGraphicsRotation::angle() const |
481 | { |
482 | Q_D(const QGraphicsRotation); |
483 | return d->angle; |
484 | } |
485 | void QGraphicsRotation::setAngle(qreal angle) |
486 | { |
487 | Q_D(QGraphicsRotation); |
488 | if (d->angle == angle) |
489 | return; |
490 | d->angle = angle; |
491 | update(); |
492 | emit angleChanged(); |
493 | } |
494 | |
495 | /*! |
496 | \fn QGraphicsRotation::originChanged() |
497 | |
498 | This signal is emitted whenever the origin has changed. |
499 | |
500 | \sa QGraphicsRotation::origin |
501 | */ |
502 | |
503 | /*! |
504 | \fn void QGraphicsRotation::angleChanged() |
505 | |
506 | This signal is emitted whenever the angle has changed. |
507 | |
508 | \sa QGraphicsRotation::angle |
509 | */ |
510 | |
511 | /*! |
512 | \property QGraphicsRotation::axis |
513 | \brief a rotation axis, specified by a vector in 3D space. |
514 | |
515 | This can be any axis in 3D space. By default the axis is (0, 0, 1), |
516 | which is aligned with the Z axis. If you provide another axis, |
517 | QGraphicsRotation will provide a transformation that rotates |
518 | around this axis. For example, if you would like to rotate an item |
519 | around its X axis, you could pass (1, 0, 0) as the axis. |
520 | |
521 | \sa QTransform, QGraphicsRotation::angle |
522 | */ |
523 | QVector3D QGraphicsRotation::axis() const |
524 | { |
525 | Q_D(const QGraphicsRotation); |
526 | return d->axis; |
527 | } |
528 | void QGraphicsRotation::setAxis(const QVector3D &axis) |
529 | { |
530 | Q_D(QGraphicsRotation); |
531 | if (d->axis == axis) |
532 | return; |
533 | d->axis = axis; |
534 | update(); |
535 | emit axisChanged(); |
536 | } |
537 | |
538 | /*! |
539 | \fn void QGraphicsRotation::setAxis(Qt::Axis axis) |
540 | |
541 | Convenience function to set the axis to \a axis. |
542 | |
543 | Note: the Qt::YAxis rotation for QTransform is inverted from the |
544 | correct mathematical rotation in 3D space. The QGraphicsRotation |
545 | class implements a correct mathematical rotation. The following |
546 | two sequences of code will perform the same transformation: |
547 | |
548 | \code |
549 | QTransform t; |
550 | t.rotate(45, Qt::YAxis); |
551 | |
552 | QGraphicsRotation r; |
553 | r.setAxis(Qt::YAxis); |
554 | r.setAngle(-45); |
555 | \endcode |
556 | */ |
557 | void QGraphicsRotation::setAxis(Qt::Axis axis) |
558 | { |
559 | switch (axis) |
560 | { |
561 | case Qt::XAxis: |
562 | setAxis(QVector3D(1, 0, 0)); |
563 | break; |
564 | case Qt::YAxis: |
565 | setAxis(QVector3D(0, 1, 0)); |
566 | break; |
567 | case Qt::ZAxis: |
568 | setAxis(QVector3D(0, 0, 1)); |
569 | break; |
570 | } |
571 | } |
572 | |
573 | /*! |
574 | \reimp |
575 | */ |
576 | void QGraphicsRotation::applyTo(QMatrix4x4 *matrix) const |
577 | { |
578 | Q_D(const QGraphicsRotation); |
579 | |
580 | if (d->angle == 0. || d->axis.isNull() || qIsNaN(d: d->angle)) |
581 | return; |
582 | |
583 | matrix->translate(vector: d->origin); |
584 | matrix->projectedRotate(angle: d->angle, x: d->axis.x(), y: d->axis.y(), z: d->axis.z()); |
585 | matrix->translate(vector: -d->origin); |
586 | } |
587 | |
588 | /*! |
589 | \fn void QGraphicsRotation::axisChanged() |
590 | |
591 | This signal is emitted whenever the axis of the object changes. |
592 | |
593 | \sa QGraphicsRotation::axis |
594 | */ |
595 | |
596 | #include "moc_qgraphicstransform.cpp" |
597 | |
598 | QT_END_NAMESPACE |
599 | |