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#include "qquickflipable_p.h"
5#include "qquickitem_p.h"
6#include "qquicktranslate_p.h"
7
8#include <QtQml/qqmlinfo.h>
9
10#include <QtCore/qpointer.h>
11
12QT_BEGIN_NAMESPACE
13
14// XXX todo - i think this needs work and a bit of a re-think
15
16class QQuickLocalTransform : public QQuickTransform
17{
18 Q_OBJECT
19public:
20 QQuickLocalTransform(QObject *parent) : QQuickTransform(parent) {}
21
22 void setTransform(const QTransform &t) {
23 transform = t;
24 update();
25 }
26 void applyTo(QMatrix4x4 *matrix) const override {
27 *matrix *= transform;
28 }
29private:
30 QTransform transform;
31};
32
33class QQuickFlipablePrivate : public QQuickItemPrivate
34{
35 Q_DECLARE_PUBLIC(QQuickFlipable)
36public:
37 QQuickFlipablePrivate() : current(QQuickFlipable::Front), front(nullptr), back(nullptr), sideDirty(false) {}
38
39 bool transformChanged(QQuickItem *transformedItem) override;
40 void updateSide();
41 void setBackTransform();
42
43 QQuickFlipable::Side current;
44 QPointer<QQuickLocalTransform> backTransform;
45 QPointer<QQuickItem> front;
46 QPointer<QQuickItem> back;
47
48 bool sideDirty;
49 bool wantBackXFlipped;
50 bool wantBackYFlipped;
51};
52
53/*!
54 \qmltype Flipable
55 \nativetype QQuickFlipable
56 \inqmlmodule QtQuick
57 \inherits Item
58 \ingroup qtquick-input
59 \ingroup qtquick-containers
60 \brief Provides a surface that can be flipped.
61
62 Flipable is an item that can be visibly "flipped" between its front and
63 back sides, like a card. It may used together with \l Rotation, \l State
64 and \l Transition types to produce a flipping effect.
65
66 The \l front and \l back properties are used to hold the items that are
67 shown respectively on the front and back sides of the flipable item.
68
69 \section1 Example Usage
70
71 The following example shows a Flipable item that flips whenever it is
72 clicked, rotating about the y-axis.
73
74 This flipable item has a \c flipped boolean property that is toggled
75 whenever the MouseArea within the flipable is clicked. When
76 \c flipped is true, the item changes to the "back" state; in this
77 state, the \c angle of the \l Rotation item is changed to 180
78 degrees to produce the flipping effect. When \c flipped is false, the
79 item reverts to the default state, in which the \c angle value is 0.
80
81 \snippet qml/flipable/flipable.qml 0
82
83 \image flipable.gif
84
85 The \l Transition creates the animation that changes the angle over
86 four seconds. When the item changes between its "back" and
87 default states, the NumberAnimation animates the angle between
88 its old and new values.
89
90 See \l {Qt Quick States} for details on state changes and the default
91 state, and \l {Animation and Transitions in Qt Quick} for more information on how
92 animations work within transitions.
93
94 \sa {customitems/flipable}{UI Components: Flipable Example}
95*/
96QQuickFlipable::QQuickFlipable(QQuickItem *parent)
97: QQuickItem(*(new QQuickFlipablePrivate), parent)
98{
99}
100
101QQuickFlipable::~QQuickFlipable()
102{
103}
104
105/*!
106 \qmlproperty Item QtQuick::Flipable::front
107 \qmlproperty Item QtQuick::Flipable::back
108
109 The front and back sides of the flipable.
110*/
111
112QQuickItem *QQuickFlipable::front() const
113{
114 Q_D(const QQuickFlipable);
115 return d->front;
116}
117
118void QQuickFlipable::setFront(QQuickItem *front)
119{
120 Q_D(QQuickFlipable);
121 if (d->front) {
122 qmlWarning(me: this) << tr(s: "front is a write-once property");
123 return;
124 }
125 d->front = front;
126 d->front->setParentItem(this);
127 if (Back == d->current) {
128 d->front->setOpacity(0.);
129 d->front->setEnabled(false);
130 }
131 emit frontChanged();
132}
133
134QQuickItem *QQuickFlipable::back()
135{
136 Q_D(const QQuickFlipable);
137 return d->back;
138}
139
140void QQuickFlipable::setBack(QQuickItem *back)
141{
142 Q_D(QQuickFlipable);
143 if (d->back) {
144 qmlWarning(me: this) << tr(s: "back is a write-once property");
145 return;
146 }
147 if (back == nullptr)
148 return;
149 d->back = back;
150 d->back->setParentItem(this);
151
152 d->backTransform = new QQuickLocalTransform(d->back);
153 d->backTransform->prependToItem(d->back);
154
155 if (Front == d->current) {
156 d->back->setOpacity(0.);
157 d->back->setEnabled(false);
158 }
159
160 connect(sender: back, SIGNAL(widthChanged()),
161 receiver: this, SLOT(retransformBack()));
162 connect(sender: back, SIGNAL(heightChanged()),
163 receiver: this, SLOT(retransformBack()));
164 emit backChanged();
165}
166
167void QQuickFlipable::retransformBack()
168{
169 Q_D(QQuickFlipable);
170 if (d->current == QQuickFlipable::Back && d->back)
171 d->setBackTransform();
172}
173
174/*!
175 \qmlproperty enumeration QtQuick::Flipable::side
176
177 The side of the Flipable currently visible. Possible values are \c
178 Flipable.Front and \c Flipable.Back.
179*/
180QQuickFlipable::Side QQuickFlipable::side() const
181{
182 Q_D(const QQuickFlipable);
183
184 const_cast<QQuickFlipablePrivate *>(d)->updateSide();
185 return d->current;
186}
187
188bool QQuickFlipablePrivate::transformChanged(QQuickItem *transformedItem)
189{
190 Q_Q(QQuickFlipable);
191
192 if (!sideDirty) {
193 sideDirty = true;
194 q->polish();
195 }
196
197 return QQuickItemPrivate::transformChanged(transformedItem);
198}
199
200void QQuickFlipable::updatePolish()
201{
202 Q_D(QQuickFlipable);
203 d->updateSide();
204}
205
206/*! \internal
207 Flipable must use the complete scene transform to correctly determine the
208 currently visible side.
209
210 It must also be independent of camera distance, in case the contents are
211 too wide: for rotation transforms we simply call QMatrix4x4::rotate(),
212 whereas QQuickRotation::applyTo(QMatrix4x4*) calls
213 QMatrix4x4::projectedRotate() which by default assumes the camera distance
214 is 1024 virtual pixels. So for example if contents inside Flipable are to
215 be flipped around the y axis, and are wider than 1024*2, some of the
216 rendering goes behind the "camera". That's expected for rendering (since we
217 didn't provide API to change camera distance), but not ok for deciding when
218 to flip.
219*/
220void QQuickFlipablePrivate::updateSide()
221{
222 Q_Q(QQuickFlipable);
223
224 if (!sideDirty)
225 return;
226
227 sideDirty = false;
228
229 QMatrix4x4 sceneTransform;
230
231 const qreal tx = x.value();
232 const qreal ty = y.value();
233 if (!qFuzzyIsNull(d: tx) || !qFuzzyIsNull(d: ty))
234 sceneTransform.translate(x: tx, y: ty);
235
236 for (const auto *transform : std::as_const(t&: transforms)) {
237 if (const auto *rot = qobject_cast<const QQuickRotation *>(object: transform)) {
238 // rotation is a special case: we want to call rotate() instead of projectedRotate()
239 const auto angle = rot->angle();
240 const auto axis = rot->axis();
241 if (!(qFuzzyIsNull(d: angle) || axis.isNull())) {
242 sceneTransform.translate(vector: rot->origin());
243 sceneTransform.rotate(angle, x: axis.x(), y: axis.y(), z: axis.z());
244 sceneTransform.translate(vector: -rot->origin());
245 }
246 } else {
247 transform->applyTo(matrix: &sceneTransform);
248 }
249 }
250
251 const bool hasRotation = !qFuzzyIsNull(d: rotation());
252 const bool hasScale = !qFuzzyCompare(p1: scale(), p2: 1);
253 if (hasScale || hasRotation) {
254 QPointF tp = computeTransformOrigin();
255 sceneTransform.translate(x: tp.x(), y: tp.y());
256 if (hasScale)
257 sceneTransform.scale(x: scale(), y: scale());
258 if (hasRotation)
259 sceneTransform.rotate(angle: rotation(), x: 0, y: 0, z: 1);
260 sceneTransform.translate(x: -tp.x(), y: -tp.y());
261 }
262
263 const QVector3D origin(sceneTransform.map(point: QPointF(0, 0)));
264 const QVector3D right = QVector3D(sceneTransform.map(point: QPointF(1, 0))) - origin;
265 const QVector3D top = QVector3D(sceneTransform.map(point: QPointF(0, 1))) - origin;
266
267 wantBackYFlipped = right.x() < 0;
268 wantBackXFlipped = top.y() < 0;
269
270 const QQuickFlipable::Side newSide =
271 QVector3D::crossProduct(v1: top, v2: right).z() > 0 ? QQuickFlipable::Back : QQuickFlipable::Front;
272
273 if (newSide != current) {
274 current = newSide;
275 if (current == QQuickFlipable::Back && back)
276 setBackTransform();
277 if (front) {
278 front->setOpacity((current==QQuickFlipable::Front)?1.:0.);
279 front->setEnabled((current==QQuickFlipable::Front)?true:false);
280 }
281 if (back) {
282 back->setOpacity((current==QQuickFlipable::Back)?1.:0.);
283 back->setEnabled((current==QQuickFlipable::Back)?true:false);
284 }
285 emit q->sideChanged();
286 }
287}
288
289/* Depends on the width/height of the back item, and so needs reevaulating
290 if those change.
291*/
292void QQuickFlipablePrivate::setBackTransform()
293{
294 QTransform mat;
295 mat.translate(dx: back->width()/2,dy: back->height()/2);
296 if (back->width() && wantBackYFlipped)
297 mat.rotate(a: 180, axis: Qt::YAxis);
298 if (back->height() && wantBackXFlipped)
299 mat.rotate(a: 180, axis: Qt::XAxis);
300 mat.translate(dx: -back->width()/2,dy: -back->height()/2);
301
302 if (backTransform)
303 backTransform->setTransform(mat);
304}
305
306QT_END_NAMESPACE
307
308#include "qquickflipable.moc"
309#include "moc_qquickflipable_p.cpp"
310

source code of qtdeclarative/src/quick/items/qquickflipable.cpp