/**************************************************************************** ** ** Copyright (C) 2016 The Qt Company Ltd. ** Contact: https://www.qt.io/licensing/ ** ** This file is part of the $MODULE$ 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$ ** ****************************************************************************/ #include #include #include #include // #include class tst_QScrollerWidget : public QWidget { public: tst_QScrollerWidget() : QWidget() { reset(); } void reset() { receivedPrepare = false; receivedScroll = false; receivedFirst = false; receivedLast = false; receivedOvershoot = false; } bool event(QEvent *e) { switch (e->type()) { case QEvent::Gesture: e->setAccepted(false); // better reject the event or QGestureManager will make trouble return false; case QEvent::ScrollPrepare: { receivedPrepare = true; QScrollPrepareEvent *se = static_cast(e); se->setViewportSize(QSizeF(100,100)); se->setContentPosRange(scrollArea); se->setContentPos(scrollPosition); se->accept(); return true; } case QEvent::Scroll: { receivedScroll = true; QScrollEvent *se = static_cast(e); // qDebug() << "Scroll for"<scrollPos()<<"ov"<overshoot()<<"first"<isFirst()<<"last"<isLast(); if (se->scrollState() == QScrollEvent::ScrollStarted) receivedFirst = true; if (se->scrollState() == QScrollEvent::ScrollFinished) receivedLast = true; currentPos = se->contentPos(); overshoot = se->overshootDistance(); if (!qFuzzyCompare( overshoot.x() + 1.0, 1.0 ) || !qFuzzyCompare( overshoot.y() + 1.0, 1.0 )) receivedOvershoot = true; return true; } default: return QObject::event(e); } } QRectF scrollArea; QPointF scrollPosition; bool receivedPrepare; bool receivedScroll; bool receivedFirst; bool receivedLast; bool receivedOvershoot; QPointF currentPos; QPointF overshoot; }; class tst_QScroller : public QObject { Q_OBJECT public: tst_QScroller() { } ~tst_QScroller() { } private: void kineticScroll( tst_QScrollerWidget *sw, QPointF from, QPoint touchStart, QPoint touchUpdate, QPoint touchEnd); void kineticScrollNoTest( tst_QScrollerWidget *sw, QPointF from, QPoint touchStart, QPoint touchUpdate, QPoint touchEnd); private slots: void staticScrollers(); void scrollerProperties(); void scrollTo(); void scroll(); void overshoot(); }; /*! \internal Generates touchBegin, touchUpdate and touchEnd events to trigger scrolling. Tests some in between states but does not wait until scrolling is finished. */ void tst_QScroller::kineticScroll( tst_QScrollerWidget *sw, QPointF from, QPoint touchStart, QPoint touchUpdate, QPoint touchEnd) { sw->scrollPosition = from; sw->currentPos= from; QScroller *s1 = QScroller::scroller(sw); QCOMPARE( s1->state(), QScroller::Inactive ); QScrollerProperties sp1 = QScroller::scroller(sw)->scrollerProperties(); QTouchEvent::TouchPoint rawTouchPoint; rawTouchPoint.setId(0); // send the touch begin event QTouchEvent::TouchPoint touchPoint(0); touchPoint.setState(Qt::TouchPointPressed); touchPoint.setPos(touchStart); touchPoint.setScenePos(touchStart); touchPoint.setScreenPos(touchStart); QTouchDevice *device = new QTouchDevice; device->setType(QTouchDevice::TouchScreen); QWindowSystemInterface::registerTouchDevice(device); QTouchEvent touchEvent1(QEvent::TouchBegin, device, Qt::NoModifier, Qt::TouchPointPressed, (QList() << touchPoint)); QApplication::sendEvent(sw, &touchEvent1); QCOMPARE( s1->state(), QScroller::Pressed ); // send the touch update far enough to trigger a scroll QTest::qWait(200); // we need to wait a little or else the speed would be infinite. now we have around 500 pixel per second. touchPoint.setPos(touchUpdate); touchPoint.setScenePos(touchUpdate); touchPoint.setScreenPos(touchUpdate); QTouchEvent touchEvent2(QEvent::TouchUpdate, device, Qt::NoModifier, Qt::TouchPointMoved, (QList() << touchPoint)); QApplication::sendEvent(sw, &touchEvent2); QCOMPARE( s1->state(), QScroller::Dragging ); QCOMPARE( sw->receivedPrepare, true ); QTRY_COMPARE( sw->receivedFirst, true ); QCOMPARE( sw->receivedScroll, true ); QCOMPARE( sw->receivedOvershoot, false ); // note that the scrolling goes in a different direction than the mouse move QPoint calculatedPos = from.toPoint() - touchUpdate - touchStart; QVERIFY(qAbs(sw->currentPos.x() - calculatedPos.x()) < 1.0); QVERIFY(qAbs(sw->currentPos.y() - calculatedPos.y()) < 1.0); // send the touch end touchPoint.setPos(touchEnd); touchPoint.setScenePos(touchEnd); touchPoint.setScreenPos(touchEnd); QTouchEvent touchEvent5(QEvent::TouchEnd, device, Qt::NoModifier, Qt::TouchPointReleased, (QList() << touchPoint)); QApplication::sendEvent(sw, &touchEvent5); } /*! \internal Generates touchBegin, touchUpdate and touchEnd events to trigger scrolling. This function does not have any in between tests, it does not expect the scroller to actually scroll. */ void tst_QScroller::kineticScrollNoTest( tst_QScrollerWidget *sw, QPointF from, QPoint touchStart, QPoint touchUpdate, QPoint touchEnd) { sw->scrollPosition = from; sw->currentPos = from; QScroller *s1 = QScroller::scroller(sw); QCOMPARE( s1->state(), QScroller::Inactive ); QScrollerProperties sp1 = s1->scrollerProperties(); int fps = 60; QTouchEvent::TouchPoint rawTouchPoint; rawTouchPoint.setId(0); // send the touch begin event QTouchEvent::TouchPoint touchPoint(0); touchPoint.setState(Qt::TouchPointPressed); touchPoint.setPos(touchStart); touchPoint.setScenePos(touchStart); touchPoint.setScreenPos(touchStart); QTouchDevice *device = new QTouchDevice; device->setType(QTouchDevice::TouchScreen); QWindowSystemInterface::registerTouchDevice(device); QTouchEvent touchEvent1(QEvent::TouchBegin, device, Qt::NoModifier, Qt::TouchPointPressed, (QList() << touchPoint)); QApplication::sendEvent(sw, &touchEvent1); // send the touch update far enough to trigger a scroll QTest::qWait(200); // we need to wait a little or else the speed would be infinite. now we have around 500 pixel per second. touchPoint.setPos(touchUpdate); touchPoint.setScenePos(touchUpdate); touchPoint.setScreenPos(touchUpdate); QTouchEvent touchEvent2(QEvent::TouchUpdate, device, Qt::NoModifier, Qt::TouchPointMoved, (QList() << touchPoint)); QApplication::sendEvent(sw, &touchEvent2); QTest::qWait(1000 / fps * 2); // wait until the first scroll move // send the touch end touchPoint.setPos(touchEnd); touchPoint.setScenePos(touchEnd); touchPoint.setScreenPos(touchEnd); QTouchEvent touchEvent5(QEvent::TouchEnd, device, Qt::NoModifier, Qt::TouchPointReleased, (QList() << touchPoint)); QApplication::sendEvent(sw, &touchEvent5); } void tst_QScroller::staticScrollers() { // scrollers { QObject *o1 = new QObject(this); QObject *o2 = new QObject(this); // get scroller for object QScroller *s1 = QScroller::scroller(o1); QScroller *s2 = QScroller::scroller(o2); QVERIFY(s1); QVERIFY(s2); QVERIFY(s1 != s2); QVERIFY(!QScroller::scroller(static_cast(0))); QCOMPARE(QScroller::scroller(o1), s1); delete o1; delete o2; } // the same for properties { QObject *o1 = new QObject(this); QObject *o2 = new QObject(this); // get scroller for object QScrollerProperties sp1 = QScroller::scroller(o1)->scrollerProperties(); QScrollerProperties sp2 = QScroller::scroller(o2)->scrollerProperties(); // default properties should be the same QCOMPARE(sp1, sp2); QCOMPARE(QScroller::scroller(o1)->scrollerProperties(), sp1); delete o1; delete o2; } } void tst_QScroller::scrollerProperties() { QObject *o1 = new QObject(this); QScrollerProperties sp1 = QScroller::scroller(o1)->scrollerProperties(); QScrollerProperties::ScrollMetric metrics[] = { QScrollerProperties::MousePressEventDelay, // qreal [s] QScrollerProperties::DragStartDistance, // qreal [m] QScrollerProperties::DragVelocitySmoothingFactor, // qreal [0..1/s] (complex calculation involving time) v = v_new* DASF + v_old * (1-DASF) QScrollerProperties::AxisLockThreshold, // qreal [0..1] atan(|min(dx,dy)|/|max(dx,dy)|) QScrollerProperties::DecelerationFactor, // slope of the curve QScrollerProperties::MinimumVelocity, // qreal [m/s] QScrollerProperties::MaximumVelocity, // qreal [m/s] QScrollerProperties::MaximumClickThroughVelocity, // qreal [m/s] QScrollerProperties::AcceleratingFlickMaximumTime, // qreal [s] QScrollerProperties::AcceleratingFlickSpeedupFactor, // qreal [1..] QScrollerProperties::SnapPositionRatio, // qreal [0..1] QScrollerProperties::SnapTime, // qreal [s] QScrollerProperties::OvershootDragResistanceFactor, // qreal [0..1] QScrollerProperties::OvershootDragDistanceFactor, // qreal [0..1] QScrollerProperties::OvershootScrollDistanceFactor, // qreal [0..1] QScrollerProperties::OvershootScrollTime, // qreal [s] }; for (unsigned int i = 0; i < sizeof(metrics) / sizeof(metrics[0]); i++) { sp1.setScrollMetric(metrics[i], 0.9); QCOMPARE(sp1.scrollMetric(metrics[i]).toDouble(), 0.9); } sp1.setScrollMetric(QScrollerProperties::ScrollingCurve, QEasingCurve(QEasingCurve::OutQuart)); QCOMPARE(sp1.scrollMetric(QScrollerProperties::ScrollingCurve).toEasingCurve().type(), QEasingCurve::OutQuart); sp1.setScrollMetric(QScrollerProperties::HorizontalOvershootPolicy, QVariant::fromValue(QScrollerProperties::OvershootAlwaysOff)); QCOMPARE(sp1.scrollMetric(QScrollerProperties::HorizontalOvershootPolicy).value(), QScrollerProperties::OvershootAlwaysOff); sp1.setScrollMetric(QScrollerProperties::VerticalOvershootPolicy, QVariant::fromValue(QScrollerProperties::OvershootAlwaysOn)); QCOMPARE(sp1.scrollMetric(QScrollerProperties::VerticalOvershootPolicy).value(), QScrollerProperties::OvershootAlwaysOn); sp1.setScrollMetric(QScrollerProperties::FrameRate, QVariant::fromValue(QScrollerProperties::Fps20)); QCOMPARE(sp1.scrollMetric(QScrollerProperties::FrameRate).value(), QScrollerProperties::Fps20); } void tst_QScroller::scrollTo() { #ifdef Q_OS_MAC QSKIP("Flakey test - QTBUG-29950"); #endif { tst_QScrollerWidget *sw = new tst_QScrollerWidget(); sw->scrollArea = QRectF( 0, 0, 1000, 1000 ); sw->scrollPosition = QPointF( 500, 500 ); QScroller *s1 = QScroller::scroller(sw); QCOMPARE( s1->state(), QScroller::Inactive ); // a normal scroll s1->scrollTo(QPointF(100,100), 100); QTest::qWait(200); QCOMPARE( sw->receivedPrepare, true ); QCOMPARE( sw->receivedScroll, true ); QCOMPARE( sw->receivedFirst, true ); QCOMPARE( sw->receivedLast, true ); QCOMPARE( sw->receivedOvershoot, false ); QVERIFY(qFuzzyCompare( sw->currentPos.x(), 100 )); QVERIFY(qFuzzyCompare( sw->currentPos.y(), 100 )); delete sw; } } void tst_QScroller::scroll() { #ifdef Q_OS_MAC QSKIP("Flakey test - QTBUG-30133"); #endif #ifndef QT_NO_GESTURES // -- good case. normal scroll tst_QScrollerWidget *sw = new tst_QScrollerWidget(); sw->scrollArea = QRectF(0, 0, 1000, 1000); QScroller::grabGesture(sw, QScroller::TouchGesture); sw->setGeometry(100, 100, 400, 300); QScroller *s1 = QScroller::scroller(sw); kineticScroll(sw, QPointF(500, 500), QPoint(0, 0), QPoint(100, 100), QPoint(200, 200)); // now we should be scrolling QTRY_COMPARE( s1->state(), QScroller::Scrolling ); // wait until finished, check that no further first scroll is sent sw->receivedFirst = false; sw->receivedScroll = false; while (s1->state() == QScroller::Scrolling) QTest::qWait(100); QCOMPARE( sw->receivedFirst, false ); QCOMPARE( sw->receivedScroll, true ); QCOMPARE( sw->receivedLast, true ); QVERIFY(sw->currentPos.x() < 400); QVERIFY(sw->currentPos.y() < 400); // -- try to scroll when nothing to scroll sw->reset(); sw->scrollArea = QRectF(0, 0, 0, 1000); kineticScrollNoTest(sw, QPointF(0, 500), QPoint(0, 0), QPoint(100, 0), QPoint(200, 0)); while (s1->state() != QScroller::Inactive) QTest::qWait(20); QCOMPARE(sw->currentPos.x(), 0.0); QCOMPARE(sw->currentPos.y(), 500.0); delete sw; #endif } void tst_QScroller::overshoot() { #ifdef Q_OS_MAC QSKIP("Flakey test - QTBUG-29950"); #endif #ifndef QT_NO_GESTURES tst_QScrollerWidget *sw = new tst_QScrollerWidget(); sw->scrollArea = QRectF(0, 0, 1000, 1000); QScroller::grabGesture(sw, QScroller::TouchGesture); sw->setGeometry(100, 100, 400, 300); QScroller *s1 = QScroller::scroller(sw); QScrollerProperties sp1 = s1->scrollerProperties(); sp1.setScrollMetric(QScrollerProperties::OvershootDragResistanceFactor, 0.5); sp1.setScrollMetric(QScrollerProperties::OvershootDragDistanceFactor, 0.2); sp1.setScrollMetric(QScrollerProperties::OvershootScrollDistanceFactor, 0.2); // -- try to scroll with overshoot (when scrollable good case) sp1.setScrollMetric(QScrollerProperties::HorizontalOvershootPolicy, QVariant::fromValue(QScrollerProperties::OvershootWhenScrollable)); s1->setScrollerProperties(sp1); kineticScrollNoTest(sw, QPointF(500, 500), QPoint(0, 0), QPoint(400, 0), QPoint(490, 0)); while (s1->state() != QScroller::Inactive) QTest::qWait(20); //qDebug() << "Overshoot fuzzy: "<currentPos; QVERIFY(qFuzzyCompare( sw->currentPos.x(), 0 )); QVERIFY(qFuzzyCompare( sw->currentPos.y(), 500 )); QCOMPARE( sw->receivedOvershoot, true ); // -- try to scroll with overshoot (when scrollable bad case) sw->reset(); sw->scrollArea = QRectF(0, 0, 0, 1000); sp1.setScrollMetric(QScrollerProperties::HorizontalOvershootPolicy, QVariant::fromValue(QScrollerProperties::OvershootWhenScrollable)); s1->setScrollerProperties(sp1); kineticScrollNoTest(sw, QPointF(0, 500), QPoint(0, 0), QPoint(400, 0), QPoint(490, 0)); while (s1->state() != QScroller::Inactive) QTest::qWait(20); //qDebug() << "Overshoot fuzzy: "<currentPos; QVERIFY(qFuzzyCompare( sw->currentPos.x(), 0 )); QVERIFY(qFuzzyCompare( sw->currentPos.y(), 500 )); QCOMPARE( sw->receivedOvershoot, false ); // -- try to scroll with overshoot (always on) sw->reset(); sw->scrollArea = QRectF(0, 0, 0, 1000); sp1.setScrollMetric(QScrollerProperties::HorizontalOvershootPolicy, QVariant::fromValue(QScrollerProperties::OvershootAlwaysOn)); s1->setScrollerProperties(sp1); kineticScrollNoTest(sw, QPointF(0, 500), QPoint(0, 0), QPoint(400, 0), QPoint(490, 0)); while (s1->state() != QScroller::Inactive) QTest::qWait(20); //qDebug() << "Overshoot fuzzy: "<currentPos; QVERIFY(qFuzzyCompare( sw->currentPos.x(), 0 )); QVERIFY(qFuzzyCompare( sw->currentPos.y(), 500 )); QCOMPARE( sw->receivedOvershoot, true ); // -- try to scroll with overshoot (always off) sw->reset(); sw->scrollArea = QRectF(0, 0, 1000, 1000); sp1.setScrollMetric(QScrollerProperties::HorizontalOvershootPolicy, QVariant::fromValue(QScrollerProperties::OvershootAlwaysOff)); s1->setScrollerProperties(sp1); kineticScrollNoTest(sw, QPointF(500, 500), QPoint(0, 0), QPoint(400, 0), QPoint(490, 0)); while (s1->state() != QScroller::Inactive) QTest::qWait(20); QVERIFY(qFuzzyCompare( sw->currentPos.x(), 0 )); QVERIFY(qFuzzyCompare( sw->currentPos.y(), 500 )); QCOMPARE( sw->receivedOvershoot, false ); // -- try to scroll with overshoot (always on but max overshoot = 0) sp1.setScrollMetric(QScrollerProperties::OvershootDragDistanceFactor, 0.0); sp1.setScrollMetric(QScrollerProperties::OvershootScrollDistanceFactor, 0.0); sw->reset(); sw->scrollArea = QRectF(0, 0, 1000, 1000); sp1.setScrollMetric(QScrollerProperties::HorizontalOvershootPolicy, QVariant::fromValue(QScrollerProperties::OvershootAlwaysOn)); s1->setScrollerProperties(sp1); kineticScrollNoTest(sw, QPointF(500, 500), QPoint(0, 0), QPoint(400, 0), QPoint(490, 0)); while (s1->state() != QScroller::Inactive) QTest::qWait(20); QVERIFY(qFuzzyCompare( sw->currentPos.x(), 0 )); QVERIFY(qFuzzyCompare( sw->currentPos.y(), 500 )); QCOMPARE( sw->receivedOvershoot, false ); delete sw; #endif } QTEST_MAIN(tst_QScroller) #include "tst_qscroller.moc"