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

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