Revision control

Copy as Markdown

// Copyright (C) 2016 The Qt Company Ltd.
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only
// Qt-Security score:significant reason:default
#include "qquickflipable_p.h"
#include "qquickitem_p.h"
#include "qquicktranslate_p.h"
#include <QtQml/qqmlinfo.h>
#include <QtCore/qpointer.h>
QT_BEGIN_NAMESPACE
// XXX todo - i think this needs work and a bit of a re-think
class QQuickLocalTransform : public QQuickTransform
{
Q_OBJECT
public:
QQuickLocalTransform(QObject *parent) : QQuickTransform(parent) {}
void setTransform(const QTransform &t) {
transform = t;
update();
}
void applyTo(QMatrix4x4 *matrix) const override {
*matrix *= transform;
}
private:
QTransform transform;
};
class QQuickFlipablePrivate : public QQuickItemPrivate
{
Q_DECLARE_PUBLIC(QQuickFlipable)
public:
QQuickFlipablePrivate() : current(QQuickFlipable::Front), front(nullptr), back(nullptr), sideDirty(false) {}
bool transformChanged(QQuickItem *transformedItem) override;
void updateSide();
void setBackTransform();
QQuickFlipable::Side current;
QPointer<QQuickLocalTransform> backTransform;
QPointer<QQuickItem> front;
QPointer<QQuickItem> back;
bool sideDirty;
bool wantBackXFlipped;
bool wantBackYFlipped;
};
/*!
\qmltype Flipable
\nativetype QQuickFlipable
\inqmlmodule QtQuick
\inherits Item
\ingroup qtquick-input
\ingroup qtquick-containers
\brief Provides a surface that can be flipped.
Flipable is an item that can be visibly "flipped" between its front and
back sides, like a card. It may used together with \l Rotation, \l State
and \l Transition types to produce a flipping effect.
The \l front and \l back properties are used to hold the items that are
shown respectively on the front and back sides of the flipable item.
\section1 Example Usage
The following example shows a Flipable item that flips whenever it is
clicked, rotating about the y-axis.
This flipable item has a \c flipped boolean property that is toggled
whenever the MouseArea within the flipable is clicked. When
\c flipped is true, the item changes to the "back" state; in this
state, the \c angle of the \l Rotation item is changed to 180
degrees to produce the flipping effect. When \c flipped is false, the
item reverts to the default state, in which the \c angle value is 0.
\snippet qml/flipable/flipable.qml 0
\image flipable.gif {Qt logo flipping between front and back sides
with rotation animation}
The \l Transition creates the animation that changes the angle over
four seconds. When the item changes between its "back" and
default states, the NumberAnimation animates the angle between
its old and new values.
See \l {Qt Quick States} for details on state changes and the default
state, and \l {Animation and Transitions in Qt Quick} for more information on how
animations work within transitions.
\sa {customitems/flipable}{UI Components: Flipable Example}
*/
QQuickFlipable::QQuickFlipable(QQuickItem *parent)
: QQuickItem(*(new QQuickFlipablePrivate), parent)
{
}
QQuickFlipable::~QQuickFlipable()
{
}
/*!
\qmlproperty Item QtQuick::Flipable::front
\qmlproperty Item QtQuick::Flipable::back
The front and back sides of the flipable.
*/
QQuickItem *QQuickFlipable::front() const
{
Q_D(const QQuickFlipable);
return d->front;
}
void QQuickFlipable::setFront(QQuickItem *front)
{
Q_D(QQuickFlipable);
if (d->front) {
qmlWarning(this) << tr("front is a write-once property");
return;
}
d->front = front;
d->front->setParentItem(this);
if (Back == d->current) {
d->front->setOpacity(0.);
d->front->setEnabled(false);
}
emit frontChanged();
}
QQuickItem *QQuickFlipable::back()
{
Q_D(const QQuickFlipable);
return d->back;
}
void QQuickFlipable::setBack(QQuickItem *back)
{
Q_D(QQuickFlipable);
if (d->back) {
qmlWarning(this) << tr("back is a write-once property");
return;
}
if (back == nullptr)
return;
d->back = back;
d->back->setParentItem(this);
d->backTransform = new QQuickLocalTransform(d->back);
d->backTransform->prependToItem(d->back);
if (Front == d->current) {
d->back->setOpacity(0.);
d->back->setEnabled(false);
}
connect(back, SIGNAL(widthChanged()),
this, SLOT(retransformBack()));
connect(back, SIGNAL(heightChanged()),
this, SLOT(retransformBack()));
emit backChanged();
}
void QQuickFlipable::retransformBack()
{
Q_D(QQuickFlipable);
if (d->current == QQuickFlipable::Back && d->back)
d->setBackTransform();
}
/*!
\qmlproperty enumeration QtQuick::Flipable::side
The side of the Flipable currently visible. Possible values are \c
Flipable.Front and \c Flipable.Back.
*/
QQuickFlipable::Side QQuickFlipable::side() const
{
Q_D(const QQuickFlipable);
const_cast<QQuickFlipablePrivate *>(d)->updateSide();
return d->current;
}
bool QQuickFlipablePrivate::transformChanged(QQuickItem *transformedItem)
{
Q_Q(QQuickFlipable);
if (!sideDirty) {
sideDirty = true;
q->polish();
}
return QQuickItemPrivate::transformChanged(transformedItem);
}
void QQuickFlipable::updatePolish()
{
Q_D(QQuickFlipable);
d->updateSide();
}
/*! \internal
Flipable must use the complete scene transform to correctly determine the
currently visible side.
It must also be independent of camera distance, in case the contents are
too wide: for rotation transforms we simply call QMatrix4x4::rotate(),
whereas QQuickRotation::applyTo(QMatrix4x4*) calls
QMatrix4x4::projectedRotate() which by default assumes the camera distance
is 1024 virtual pixels. So for example if contents inside Flipable are to
be flipped around the y axis, and are wider than 1024*2, some of the
rendering goes behind the "camera". That's expected for rendering (since we
didn't provide API to change camera distance), but not ok for deciding when
to flip.
*/
void QQuickFlipablePrivate::updateSide()
{
Q_Q(QQuickFlipable);
if (!sideDirty)
return;
sideDirty = false;
QMatrix4x4 sceneTransform;
const qreal tx = x.value();
const qreal ty = y.value();
if (!qFuzzyIsNull(tx) || !qFuzzyIsNull(ty))
sceneTransform.translate(tx, ty);
for (const auto *transform : std::as_const(transforms)) {
if (const auto *rot = qobject_cast<const QQuickRotation *>(transform)) {
// rotation is a special case: we want to call rotate() instead of projectedRotate()
const auto angle = rot->angle();
const auto axis = rot->axis();
if (!(qFuzzyIsNull(angle) || axis.isNull())) {
sceneTransform.translate(rot->origin());
sceneTransform.rotate(angle, axis.x(), axis.y(), axis.z());
sceneTransform.translate(-rot->origin());
}
} else {
transform->applyTo(&sceneTransform);
}
}
const bool hasRotation = !qFuzzyIsNull(rotation());
const bool hasScale = !qFuzzyCompare(scale(), 1);
if (hasScale || hasRotation) {
QPointF tp = computeTransformOrigin();
sceneTransform.translate(tp.x(), tp.y());
if (hasScale)
sceneTransform.scale(scale(), scale());
if (hasRotation)
sceneTransform.rotate(rotation(), 0, 0, 1);
sceneTransform.translate(-tp.x(), -tp.y());
}
const QVector3D origin(sceneTransform.map(QPointF(0, 0)));
const QVector3D right = QVector3D(sceneTransform.map(QPointF(1, 0))) - origin;
const QVector3D top = QVector3D(sceneTransform.map(QPointF(0, 1))) - origin;
wantBackYFlipped = right.x() < 0;
wantBackXFlipped = top.y() < 0;
const QQuickFlipable::Side newSide =
QVector3D::crossProduct(top, right).z() > 0 ? QQuickFlipable::Back : QQuickFlipable::Front;
if (newSide != current) {
current = newSide;
if (current == QQuickFlipable::Back && back)
setBackTransform();
if (front) {
front->setOpacity((current==QQuickFlipable::Front)?1.:0.);
front->setEnabled((current==QQuickFlipable::Front)?true:false);
}
if (back) {
back->setOpacity((current==QQuickFlipable::Back)?1.:0.);
back->setEnabled((current==QQuickFlipable::Back)?true:false);
}
emit q->sideChanged();
}
}
/* Depends on the width/height of the back item, and so needs reevaulating
if those change.
*/
void QQuickFlipablePrivate::setBackTransform()
{
QTransform mat;
mat.translate(back->width()/2,back->height()/2);
if (back->width() && wantBackYFlipped)
mat.rotate(180, Qt::YAxis);
if (back->height() && wantBackXFlipped)
mat.rotate(180, Qt::XAxis);
mat.translate(-back->width()/2,-back->height()/2);
if (backTransform)
backTransform->setTransform(mat);
}
QT_END_NAMESPACE
#include "qquickflipable.moc"
#include "moc_qquickflipable_p.cpp"