Flickable: add horizontal/verticalOvershoot properties

[ChangeLog][QtQuick][Flickable] Added horizontalOvershoot and
verticalOvershoot properties that can be used for implementing
boundary actions and effects.

Task-number: QTBUG-38515
Change-Id: I06379348a67d03507b56788d6fc7020bbb2d375f
Reviewed-by: Robin Burchell <robin.burchell@viroteck.net>
This commit is contained in:
J-P Nurmi 2016-06-25 12:16:25 +02:00
parent 9a272ad185
commit bb594453f9
7 changed files with 420 additions and 3 deletions

View File

@ -1570,12 +1570,52 @@ void QQuickFlickablePrivate::replayDelayedPress()
//XXX pixelAligned ignores the global position of the Flickable, i.e. assumes Flickable itself is pixel aligned.
void QQuickFlickablePrivate::setViewportX(qreal x)
{
contentItem->setX(pixelAligned ? -Round(-x) : x);
Q_Q(QQuickFlickable);
if (pixelAligned)
x = -Round(-x);
contentItem->setX(x);
if (contentItem->x() != x)
return; // reentered
const qreal maxX = q->maxXExtent();
const qreal minX = q->minXExtent();
qreal overshoot = 0.0;
if (x <= maxX)
overshoot = maxX - x;
else if (x >= minX)
overshoot = minX - x;
if (overshoot != hData.overshoot) {
hData.overshoot = overshoot;
emit q->horizontalOvershootChanged();
}
}
void QQuickFlickablePrivate::setViewportY(qreal y)
{
contentItem->setY(pixelAligned ? -Round(-y) : y);
Q_Q(QQuickFlickable);
if (pixelAligned)
y = -Round(-y);
contentItem->setY(y);
if (contentItem->y() != y)
return; // reentered
const qreal maxY = q->maxYExtent();
const qreal minY = q->minYExtent();
qreal overshoot = 0.0;
if (y <= maxY)
overshoot = maxY - y;
else if (y >= minY)
overshoot = minY - y;
if (overshoot != vData.overshoot) {
vData.overshoot = overshoot;
emit q->verticalOvershootChanged();
}
}
void QQuickFlickable::timerEvent(QTimerEvent *event)
@ -1841,6 +1881,8 @@ QQmlListProperty<QQuickItem> QQuickFlickable::flickableChildren()
beyond the boundary of the Flickable, and can overshoot the
boundary when flicked.
\endlist
\sa horizontalOvershoot, verticalOvershoot
*/
QQuickFlickable::BoundsBehavior QQuickFlickable::boundsBehavior() const
{
@ -2638,4 +2680,38 @@ void QQuickFlickablePrivate::updateVelocity()
emit q->verticalVelocityChanged();
}
/*!
\qmlproperty real QtQuick::Flickable::horizontalOvershoot
\since 5.9
This property holds the horizontal overshoot, that is, the horizontal distance by
which the contents has been dragged or flicked past the bounds of the flickable.
The value is negative when the content is dragged or flicked beyond the beginning,
and positive when beyond the end; \c 0.0 otherwise.
\sa verticalOvershoot, boundsBehavior
*/
qreal QQuickFlickable::horizontalOvershoot() const
{
Q_D(const QQuickFlickable);
return d->hData.overshoot;
}
/*!
\qmlproperty real QtQuick::Flickable::verticalOvershoot
\since 5.9
This property holds the vertical overshoot, that is, the vertical distance by
which the contents has been dragged or flicked past the bounds of the flickable.
The value is negative when the content is dragged or flicked beyond the beginning,
and positive when beyond the end; \c 0.0 otherwise.
\sa horizontalOvershoot, boundsBehavior
*/
qreal QQuickFlickable::verticalOvershoot() const
{
Q_D(const QQuickFlickable);
return d->vData.overshoot;
}
QT_END_NAMESPACE

View File

@ -106,6 +106,9 @@ class Q_QUICK_PRIVATE_EXPORT QQuickFlickable : public QQuickItem
Q_PROPERTY(bool pixelAligned READ pixelAligned WRITE setPixelAligned NOTIFY pixelAlignedChanged)
Q_PROPERTY(qreal horizontalOvershoot READ horizontalOvershoot NOTIFY horizontalOvershootChanged REVISION 9)
Q_PROPERTY(qreal verticalOvershoot READ verticalOvershoot NOTIFY verticalOvershootChanged REVISION 9)
Q_PROPERTY(QQmlListProperty<QObject> flickableData READ flickableData)
Q_PROPERTY(QQmlListProperty<QQuickItem> flickableChildren READ flickableChildren)
Q_CLASSINFO("DefaultProperty", "flickableData")
@ -201,6 +204,9 @@ public:
bool pixelAligned() const;
void setPixelAligned(bool align);
qreal horizontalOvershoot() const;
qreal verticalOvershoot() const;
Q_INVOKABLE void resizeContent(qreal w, qreal h, QPointF center);
Q_INVOKABLE void returnToBounds();
Q_INVOKABLE void flick(qreal xVelocity, qreal yVelocity);
@ -243,6 +249,8 @@ Q_SIGNALS:
void dragStarted();
void dragEnded();
void pixelAlignedChanged();
Q_REVISION(9) void horizontalOvershootChanged();
Q_REVISION(9) void verticalOvershootChanged();
protected:
bool childMouseEventFilter(QQuickItem *, QEvent *) Q_DECL_OVERRIDE;
@ -286,6 +294,7 @@ protected:
private:
Q_DISABLE_COPY(QQuickFlickable)
Q_DECLARE_PRIVATE(QQuickFlickable)
friend class QQuickFlickableContentItem;
friend class QQuickFlickableVisibleArea;
friend class QQuickFlickableReboundTransition;
};

View File

@ -101,7 +101,7 @@ public:
: move(fp, func)
, transitionToBounds(0)
, viewSize(-1), lastPos(0), previousDragDelta(0), velocity(0), startMargin(0), endMargin(0)
, origin(0)
, origin(0), overshoot(0)
, transitionTo(0)
, continuousFlickVelocity(0), velocityTime(), vTime(0)
, smoothVelocity(fp), atEnd(false), atBeginning(true)
@ -148,6 +148,7 @@ public:
qreal startMargin;
qreal endMargin;
qreal origin;
qreal overshoot;
qreal transitionTo;
qreal continuousFlickVelocity;
QElapsedTimer velocityTime;

View File

@ -374,6 +374,7 @@ static void qt_quickitems_defineModule(const char *uri, int major, int minor)
qmlRegisterType<QQuickBorderImageMesh>("QtQuick", 2, 8, "BorderImageMesh");
#endif
qmlRegisterType<QQuickFlickable, 9>(uri, 2, 9, "Flickable");
qmlRegisterType<QQuickMouseArea, 9>(uri, 2, 9, "MouseArea");
}

View File

@ -0,0 +1,79 @@
/****************************************************************************
**
** Copyright (C) 2016 The Qt Company Ltd.
** Contact: https://www.qt.io/licensing/
**
** This file is part of the test suite of the Qt Toolkit.
**
** $QT_BEGIN_LICENSE:GPL-EXCEPT$
** Commercial License Usage
** Licensees holding valid commercial Qt licenses may use this file in
** accordance with the commercial license agreement provided with the
** Software or, alternatively, in accordance with the terms contained in
** a written agreement between you and The Qt Company. For licensing terms
** and conditions see https://www.qt.io/terms-conditions. For further
** information use the contact form at https://www.qt.io/contact-us.
**
** GNU General Public License Usage
** Alternatively, this file may be used under the terms of the GNU
** General Public License version 3 as published by the Free Software
** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT
** included in the packaging of this file. Please review the following
** information to ensure the GNU General Public License requirements will
** be met: https://www.gnu.org/licenses/gpl-3.0.html.
**
** $QT_END_LICENSE$
**
****************************************************************************/
import QtQuick 2.9
Flickable {
width: 200; height: 200
contentWidth: rect.width; contentHeight: rect.height
property real minContentY: 0
property real maxContentY: 0
onContentYChanged: {
minContentY = Math.min(contentY, minContentY)
maxContentY = Math.max(contentY, maxContentY)
}
property real minContentX: 0
property real maxContentX: 0
onContentXChanged: {
minContentX = Math.min(contentX, minContentX)
maxContentX = Math.max(contentX, maxContentX)
}
property real minVerticalOvershoot: 0
property real maxVerticalOvershoot: 0
onVerticalOvershootChanged: {
minVerticalOvershoot = Math.min(verticalOvershoot, minVerticalOvershoot)
maxVerticalOvershoot = Math.max(verticalOvershoot, maxVerticalOvershoot)
}
property real minHorizontalOvershoot: 0
property real maxHorizontalOvershoot: 0
onHorizontalOvershootChanged: {
minHorizontalOvershoot = Math.min(horizontalOvershoot, minHorizontalOvershoot)
maxHorizontalOvershoot = Math.max(horizontalOvershoot, maxHorizontalOvershoot)
}
function reset() {
minContentY = contentY
maxContentY = contentY
minContentX = contentX
maxContentX = contentX
minVerticalOvershoot = 0
maxVerticalOvershoot = 0
minHorizontalOvershoot = 0
maxHorizontalOvershoot = 0
}
Rectangle {
id: rect
color: "red"
width: 400; height: 400
}
}

View File

@ -0,0 +1,55 @@
/****************************************************************************
**
** Copyright (C) 2016 The Qt Company Ltd.
** Contact: https://www.qt.io/licensing/
**
** This file is part of the test suite of the Qt Toolkit.
**
** $QT_BEGIN_LICENSE:GPL-EXCEPT$
** Commercial License Usage
** Licensees holding valid commercial Qt licenses may use this file in
** accordance with the commercial license agreement provided with the
** Software or, alternatively, in accordance with the terms contained in
** a written agreement between you and The Qt Company. For licensing terms
** and conditions see https://www.qt.io/terms-conditions. For further
** information use the contact form at https://www.qt.io/contact-us.
**
** GNU General Public License Usage
** Alternatively, this file may be used under the terms of the GNU
** General Public License version 3 as published by the Free Software
** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT
** included in the packaging of this file. Please review the following
** information to ensure the GNU General Public License requirements will
** be met: https://www.gnu.org/licenses/gpl-3.0.html.
**
** $QT_END_LICENSE$
**
****************************************************************************/
import QtQuick 2.9
Flickable {
width: 200; height: 200
contentWidth: rect.width; contentHeight: rect.height
property real contentPosAdjustment: 0.0
onContentXChanged: {
var adjustment = contentPosAdjustment
contentPosAdjustment = 0.0
contentX += adjustment
}
onContentYChanged: {
var adjustment = contentPosAdjustment
contentPosAdjustment = 0.0
contentY += adjustment
}
Rectangle {
id: rect
border.color: "red"
border.width: 5
width: 400; height: 400
}
}

View File

@ -95,6 +95,9 @@ private slots:
void ratios_smallContent();
void contentXYNotTruncatedToInt();
void keepGrab();
void overshoot();
void overshoot_data();
void overshoot_reentrant();
private:
void flickWithTouch(QQuickWindow *window, QTouchDevice *touchDevice, const QPoint &from, const QPoint &to);
@ -2037,6 +2040,199 @@ void tst_qquickflickable::keepGrab()
QVERIFY(flickable->contentY() != 0.0);
}
Q_DECLARE_METATYPE(QQuickFlickable::BoundsBehavior)
void tst_qquickflickable::overshoot()
{
QFETCH(QQuickFlickable::BoundsBehavior, boundsBehavior);
QScopedPointer<QQuickView> window(new QQuickView);
window->setSource(testFileUrl("overshoot.qml"));
window->show();
QVERIFY(QTest::qWaitForWindowExposed(window.data()));
QQuickFlickable *flickable = qobject_cast<QQuickFlickable*>(window->rootObject());
QVERIFY(flickable);
QCOMPARE(flickable->width(), 200.0);
QCOMPARE(flickable->height(), 200.0);
QCOMPARE(flickable->contentWidth(), 400.0);
QCOMPARE(flickable->contentHeight(), 400.0);
flickable->setBoundsBehavior(boundsBehavior);
// drag past the beginning
QTest::mousePress(window.data(), Qt::LeftButton, 0, QPoint(10, 10));
QTest::mouseMove(window.data(), QPoint(20, 20));
QTest::mouseMove(window.data(), QPoint(30, 30));
QTest::mouseMove(window.data(), QPoint(40, 40));
QTest::mouseRelease(window.data(), Qt::LeftButton, 0, QPoint(50, 50));
if (boundsBehavior & QQuickFlickable::DragOverBounds) {
QVERIFY(flickable->property("minVerticalOvershoot").toReal() < 0.0);
QVERIFY(flickable->property("minHorizontalOvershoot").toReal() < 0.0);
QCOMPARE(flickable->property("minContentY").toReal(),
flickable->property("minVerticalOvershoot").toReal());
QCOMPARE(flickable->property("minContentX").toReal(),
flickable->property("minHorizontalOvershoot").toReal());
} else {
QCOMPARE(flickable->property("minContentY").toReal(), 0.0);
QCOMPARE(flickable->property("minContentX").toReal(), 0.0);
QCOMPARE(flickable->property("minVerticalOvershoot").toReal(), 0.0);
QCOMPARE(flickable->property("minHorizontalOvershoot").toReal(), 0.0);
}
QCOMPARE(flickable->property("maxContentY").toReal(), 0.0);
QCOMPARE(flickable->property("maxContentX").toReal(), 0.0);
QCOMPARE(flickable->property("maxVerticalOvershoot").toReal(), 0.0);
QCOMPARE(flickable->property("maxHorizontalOvershoot").toReal(), 0.0);
flickable->setContentX(20.0);
flickable->setContentY(20.0);
QMetaObject::invokeMethod(flickable, "reset");
// flick past the beginning
flick(window.data(), QPoint(10, 10), QPoint(50, 50), 100);
QTRY_VERIFY(!flickable->property("flicking").toBool());
if (boundsBehavior & QQuickFlickable::OvershootBounds) {
QVERIFY(flickable->property("minVerticalOvershoot").toReal() < 0.0);
QVERIFY(flickable->property("minHorizontalOvershoot").toReal() < 0.0);
QCOMPARE(flickable->property("minContentY").toReal(),
flickable->property("minVerticalOvershoot").toReal());
QCOMPARE(flickable->property("minContentX").toReal(),
flickable->property("minHorizontalOvershoot").toReal());
} else {
QCOMPARE(flickable->property("minContentY").toReal(), 0.0);
QCOMPARE(flickable->property("minContentX").toReal(), 0.0);
QCOMPARE(flickable->property("minVerticalOvershoot").toReal(), 0.0);
QCOMPARE(flickable->property("minHorizontalOvershoot").toReal(), 0.0);
}
QCOMPARE(flickable->property("maxContentY").toReal(), 20.0);
QCOMPARE(flickable->property("maxContentX").toReal(), 20.0);
QCOMPARE(flickable->property("maxVerticalOvershoot").toReal(), 0.0);
QCOMPARE(flickable->property("maxHorizontalOvershoot").toReal(), 0.0);
flickable->setContentX(200.0);
flickable->setContentY(200.0);
QMetaObject::invokeMethod(flickable, "reset");
// drag past the end
QTest::mousePress(window.data(), Qt::LeftButton, 0, QPoint(50, 50));
QTest::mouseMove(window.data(), QPoint(40, 40));
QTest::mouseMove(window.data(), QPoint(30, 30));
QTest::mouseMove(window.data(), QPoint(20, 20));
QTest::mouseRelease(window.data(), Qt::LeftButton, 0, QPoint(10, 10));
if (boundsBehavior & QQuickFlickable::DragOverBounds) {
QVERIFY(flickable->property("maxVerticalOvershoot").toReal() > 0.0);
QVERIFY(flickable->property("maxHorizontalOvershoot").toReal() > 0.0);
QCOMPARE(flickable->property("maxContentY").toReal() - 200.0,
flickable->property("maxVerticalOvershoot").toReal());
QCOMPARE(flickable->property("maxContentX").toReal() - 200.0,
flickable->property("maxHorizontalOvershoot").toReal());
} else {
QCOMPARE(flickable->property("maxContentY").toReal(), 200.0);
QCOMPARE(flickable->property("maxContentX").toReal(), 200.0);
QCOMPARE(flickable->property("maxVerticalOvershoot").toReal(), 0.0);
QCOMPARE(flickable->property("maxHorizontalOvershoot").toReal(), 0.0);
}
QCOMPARE(flickable->property("minContentY").toReal(), 200.0);
QCOMPARE(flickable->property("minContentX").toReal(), 200.0);
QCOMPARE(flickable->property("minVerticalOvershoot").toReal(), 0.0);
QCOMPARE(flickable->property("minHorizontalOvershoot").toReal(), 0.0);
flickable->setContentX(180.0);
flickable->setContentY(180.0);
QMetaObject::invokeMethod(flickable, "reset");
// flick past the end
flick(window.data(), QPoint(50, 50), QPoint(10, 10), 100);
QTRY_VERIFY(!flickable->property("flicking").toBool());
if (boundsBehavior & QQuickFlickable::OvershootBounds) {
QVERIFY(flickable->property("maxVerticalOvershoot").toReal() > 0.0);
QVERIFY(flickable->property("maxHorizontalOvershoot").toReal() > 0.0);
QCOMPARE(flickable->property("maxContentY").toReal() - 200.0,
flickable->property("maxVerticalOvershoot").toReal());
QCOMPARE(flickable->property("maxContentX").toReal() - 200.0,
flickable->property("maxHorizontalOvershoot").toReal());
} else {
QCOMPARE(flickable->property("maxContentY").toReal(), 200.0);
QCOMPARE(flickable->property("maxContentX").toReal(), 200.0);
QCOMPARE(flickable->property("maxVerticalOvershoot").toReal(), 0.0);
QCOMPARE(flickable->property("maxHorizontalOvershoot").toReal(), 0.0);
}
QCOMPARE(flickable->property("minContentY").toReal(), 180.0);
QCOMPARE(flickable->property("minContentX").toReal(), 180.0);
QCOMPARE(flickable->property("minVerticalOvershoot").toReal(), 0.0);
QCOMPARE(flickable->property("minHorizontalOvershoot").toReal(), 0.0);
}
void tst_qquickflickable::overshoot_data()
{
QTest::addColumn<QQuickFlickable::BoundsBehavior>("boundsBehavior");
QTest::newRow("StopAtBounds")
<< QQuickFlickable::BoundsBehavior(QQuickFlickable::StopAtBounds);
QTest::newRow("DragOverBounds")
<< QQuickFlickable::BoundsBehavior(QQuickFlickable::DragOverBounds);
QTest::newRow("OvershootBounds")
<< QQuickFlickable::BoundsBehavior(QQuickFlickable::OvershootBounds);
QTest::newRow("DragAndOvershootBounds")
<< QQuickFlickable::BoundsBehavior(QQuickFlickable::DragAndOvershootBounds);
}
void tst_qquickflickable::overshoot_reentrant()
{
QScopedPointer<QQuickView> window(new QQuickView);
window->setSource(testFileUrl("overshoot_reentrant.qml"));
window->show();
QVERIFY(QTest::qWaitForWindowExposed(window.data()));
QQuickFlickable *flickable = qobject_cast<QQuickFlickable*>(window->rootObject());
QVERIFY(flickable);
// horizontal
flickable->setContentX(-10.0);
QCOMPARE(flickable->contentX(), -10.0);
QCOMPARE(flickable->horizontalOvershoot(), -10.0);
flickable->setProperty("contentPosAdjustment", -5.0);
flickable->setContentX(-20.0);
QCOMPARE(flickable->contentX(), -25.0);
QCOMPARE(flickable->horizontalOvershoot(), -25.0);
flickable->setContentX(210);
QCOMPARE(flickable->contentX(), 210.0);
QCOMPARE(flickable->horizontalOvershoot(), 10.0);
flickable->setProperty("contentPosAdjustment", 5.0);
flickable->setContentX(220.0);
QCOMPARE(flickable->contentX(), 225.0);
QCOMPARE(flickable->horizontalOvershoot(), 25.0);
// vertical
flickable->setContentY(-10.0);
QCOMPARE(flickable->contentY(), -10.0);
QCOMPARE(flickable->verticalOvershoot(), -10.0);
flickable->setProperty("contentPosAdjustment", -5.0);
flickable->setContentY(-20.0);
QCOMPARE(flickable->contentY(), -25.0);
QCOMPARE(flickable->verticalOvershoot(), -25.0);
flickable->setContentY(210);
QCOMPARE(flickable->contentY(), 210.0);
QCOMPARE(flickable->verticalOvershoot(), 10.0);
flickable->setProperty("contentPosAdjustment", 5.0);
flickable->setContentY(220.0);
QCOMPARE(flickable->contentY(), 225.0);
QCOMPARE(flickable->verticalOvershoot(), 25.0);
}
QTEST_MAIN(tst_qquickflickable)
#include "tst_qquickflickable.moc"