diff options
author | J-P Nurmi <jpnurmi@qt.io> | 2017-01-06 16:48:38 +0100 |
---|---|---|
committer | J-P Nurmi <jpnurmi@qt.io> | 2017-01-10 14:09:44 +0000 |
commit | 04502964550874bef7cb35d3a6f642f8ab7c61c2 (patch) | |
tree | 32b3858baf78ee826d9e3fd80cd9b421586a0b3f | |
parent | eee9e6e1184f96f22aca17e02b66d58d87b2e06e (diff) |
Add ScrollBar::snapMode
[ChangeLog][Controls][ScrollBar] Added snapMode property incremental
or discrete scrolling.
Task-number: QTBUG-56569
Change-Id: Id0d463b85063a62b7df6307af8fe8b203155a5de
Reviewed-by: Mitch Curtis <mitch.curtis@qt.io>
-rw-r--r-- | src/imports/controls/doc/images/qtquickcontrols2-scrollbar-nosnap.gif | bin | 0 -> 5271 bytes | |||
-rw-r--r-- | src/imports/controls/doc/images/qtquickcontrols2-scrollbar-snapalways.gif | bin | 0 -> 4707 bytes | |||
-rw-r--r-- | src/imports/controls/doc/images/qtquickcontrols2-scrollbar-snaponrelease.gif | bin | 0 -> 5646 bytes | |||
-rw-r--r-- | src/imports/templates/qtquicktemplates2plugin.cpp | 1 | ||||
-rw-r--r-- | src/quicktemplates2/qquickscrollbar.cpp | 65 | ||||
-rw-r--r-- | src/quicktemplates2/qquickscrollbar_p.h | 12 | ||||
-rw-r--r-- | tests/auto/controls/data/tst_scrollbar.qml | 73 | ||||
-rw-r--r-- | tests/manual/gifs/data/qtquickcontrols2-scrollbar-snap.qml | 61 | ||||
-rw-r--r-- | tests/manual/gifs/tst_gifs.cpp | 52 |
9 files changed, 259 insertions, 5 deletions
diff --git a/src/imports/controls/doc/images/qtquickcontrols2-scrollbar-nosnap.gif b/src/imports/controls/doc/images/qtquickcontrols2-scrollbar-nosnap.gif Binary files differnew file mode 100644 index 00000000..f61ac5b4 --- /dev/null +++ b/src/imports/controls/doc/images/qtquickcontrols2-scrollbar-nosnap.gif diff --git a/src/imports/controls/doc/images/qtquickcontrols2-scrollbar-snapalways.gif b/src/imports/controls/doc/images/qtquickcontrols2-scrollbar-snapalways.gif Binary files differnew file mode 100644 index 00000000..438d4a33 --- /dev/null +++ b/src/imports/controls/doc/images/qtquickcontrols2-scrollbar-snapalways.gif diff --git a/src/imports/controls/doc/images/qtquickcontrols2-scrollbar-snaponrelease.gif b/src/imports/controls/doc/images/qtquickcontrols2-scrollbar-snaponrelease.gif Binary files differnew file mode 100644 index 00000000..c2fa67b0 --- /dev/null +++ b/src/imports/controls/doc/images/qtquickcontrols2-scrollbar-snaponrelease.gif diff --git a/src/imports/templates/qtquicktemplates2plugin.cpp b/src/imports/templates/qtquicktemplates2plugin.cpp index 1f00a99f..d8dce4c9 100644 --- a/src/imports/templates/qtquicktemplates2plugin.cpp +++ b/src/imports/templates/qtquicktemplates2plugin.cpp @@ -230,6 +230,7 @@ void QtQuickTemplates2Plugin::registerTypes(const char *uri) qmlRegisterType<QQuickDial, 2>(uri, 2, 2, "Dial"); qmlRegisterType<QQuickDrawer, 2>(uri, 2, 2, "Drawer"); qmlRegisterType<QQuickRangeSlider, 2>(uri, 2, 2, "RangeSlider"); + qmlRegisterType<QQuickScrollBar, 2>(uri, 2, 2, "ScrollBar"); qmlRegisterType<QQuickSlider, 2>(uri, 2, 2, "Slider"); qmlRegisterType<QQuickSpinBox, 2>(uri, 2, 2, "SpinBox"); qmlRegisterType<QQuickSwipeDelegate, 2>(uri, 2, 2, "SwipeDelegate"); diff --git a/src/quicktemplates2/qquickscrollbar.cpp b/src/quicktemplates2/qquickscrollbar.cpp index a4ed68bf..c862002b 100644 --- a/src/quicktemplates2/qquickscrollbar.cpp +++ b/src/quicktemplates2/qquickscrollbar.cpp @@ -158,7 +158,7 @@ class QQuickScrollBarPrivate : public QQuickControlPrivate public: QQuickScrollBarPrivate() : size(0), position(0), stepSize(0), offset(0), active(false), pressed(false), moving(false), - orientation(Qt::Vertical) + orientation(Qt::Vertical), snapMode(QQuickScrollBar::NoSnap) { } @@ -167,6 +167,7 @@ public: return bar->d_func(); } + qreal snapPosition(qreal position) const; qreal positionAt(const QPointF &point) const; void updateActive(); void resizeContent() override; @@ -184,8 +185,18 @@ public: bool pressed; bool moving; Qt::Orientation orientation; + QQuickScrollBar::SnapMode snapMode; }; +qreal QQuickScrollBarPrivate::snapPosition(qreal position) const +{ + const qreal effectiveStep = stepSize * (1.0 - size); + if (qFuzzyIsNull(effectiveStep)) + return position; + + return qRound(position / effectiveStep) * effectiveStep; +} + qreal QQuickScrollBarPrivate::positionAt(const QPointF &point) const { Q_Q(const QQuickScrollBar); @@ -228,13 +239,19 @@ void QQuickScrollBarPrivate::handlePress(const QPointF &point) void QQuickScrollBarPrivate::handleMove(const QPointF &point) { Q_Q(QQuickScrollBar); - q->setPosition(qBound<qreal>(0.0, positionAt(point) - offset, 1.0 - size)); + qreal pos = qBound<qreal>(0.0, positionAt(point) - offset, 1.0 - size); + if (snapMode == QQuickScrollBar::SnapAlways) + pos = snapPosition(pos); + q->setPosition(pos); } void QQuickScrollBarPrivate::handleRelease(const QPointF &point) { Q_Q(QQuickScrollBar); - q->setPosition(qBound<qreal>(0.0, positionAt(point) - offset, 1.0 - size)); + qreal pos = qBound<qreal>(0.0, positionAt(point) - offset, 1.0 - size); + if (snapMode != QQuickScrollBar::NoSnap) + pos = snapPosition(pos); + q->setPosition(pos); offset = 0.0; q->setPressed(false); } @@ -325,7 +342,7 @@ void QQuickScrollBar::setPosition(qreal position) This property holds the step size. The default value is \c 0.0. - \sa increase(), decrease() + \sa snapMode, increase(), decrease() */ qreal QQuickScrollBar::stepSize() const { @@ -425,6 +442,46 @@ void QQuickScrollBar::setOrientation(Qt::Orientation orientation) } /*! + \since QtQuick.Controls 2.2 + \qmlproperty enumeration QtQuick.Controls::ScrollBar::snapMode + + This property holds the snap mode. + + Possible values: + \value ScrollBar.NoSnap The scrollbar does not snap (default). + \value ScrollBar.SnapAlways The scrollbar snaps while dragged. + \value ScrollBar.SnapOnRelease The scrollbar does not snap while being dragged, but only after released. + + In the following table, the various modes are illustrated with animations. + The movement and the \l stepSize (\c 0.25) are identical in each animation. + + \table + \header + \row \li \b Value \li \b Example + \row \li \c ScrollBar.NoSnap \li \image qtquickcontrols2-scrollbar-nosnap.gif + \row \li \c ScrollBar.SnapAlways \li \image qtquickcontrols2-scrollbar-snapalways.gif + \row \li \c ScrollBar.SnapOnRelease \li \image qtquickcontrols2-scrollbar-snaponrelease.gif + \endtable + + \sa stepSize +*/ +QQuickScrollBar::SnapMode QQuickScrollBar::snapMode() const +{ + Q_D(const QQuickScrollBar); + return d->snapMode; +} + +void QQuickScrollBar::setSnapMode(SnapMode mode) +{ + Q_D(QQuickScrollBar); + if (d->snapMode == mode) + return; + + d->snapMode = mode; + emit snapModeChanged(); +} + +/*! \qmlmethod void QtQuick.Controls::ScrollBar::increase() Increases the position by \l stepSize or \c 0.1 if stepSize is \c 0.0. diff --git a/src/quicktemplates2/qquickscrollbar_p.h b/src/quicktemplates2/qquickscrollbar_p.h index 4f90af47..7632e565 100644 --- a/src/quicktemplates2/qquickscrollbar_p.h +++ b/src/quicktemplates2/qquickscrollbar_p.h @@ -65,6 +65,7 @@ class Q_QUICKTEMPLATES2_PRIVATE_EXPORT QQuickScrollBar : public QQuickControl Q_PROPERTY(bool active READ isActive WRITE setActive NOTIFY activeChanged FINAL) Q_PROPERTY(bool pressed READ isPressed WRITE setPressed NOTIFY pressedChanged FINAL) Q_PROPERTY(Qt::Orientation orientation READ orientation WRITE setOrientation NOTIFY orientationChanged FINAL) + Q_PROPERTY(SnapMode snapMode READ snapMode WRITE setSnapMode NOTIFY snapModeChanged FINAL REVISION 2) public: explicit QQuickScrollBar(QQuickItem *parent = nullptr); @@ -86,6 +87,16 @@ public: Qt::Orientation orientation() const; void setOrientation(Qt::Orientation orientation); + enum SnapMode { + NoSnap, + SnapAlways, + SnapOnRelease + }; + Q_ENUM(SnapMode) + + SnapMode snapMode() const; + void setSnapMode(SnapMode mode); + public Q_SLOTS: void increase(); void decrease(); @@ -99,6 +110,7 @@ Q_SIGNALS: void activeChanged(); void pressedChanged(); void orientationChanged(); + void snapModeChanged(); protected: void mousePressEvent(QMouseEvent *event) override; diff --git a/tests/auto/controls/data/tst_scrollbar.qml b/tests/auto/controls/data/tst_scrollbar.qml index 810fbcce..5f87d1a6 100644 --- a/tests/auto/controls/data/tst_scrollbar.qml +++ b/tests/auto/controls/data/tst_scrollbar.qml @@ -40,7 +40,7 @@ import QtQuick 2.2 import QtTest 1.0 -import QtQuick.Controls 2.1 +import QtQuick.Controls 2.2 TestCase { id: testCase @@ -339,4 +339,75 @@ TestCase { compare(control.hovered, false) compare(control.active, false) } + + function test_snapMode_data() { + return [ + { tag: "NoSnap", snapMode: ScrollBar.NoSnap, stepSize: 0.1, size: 0.2, width: 100, steps: 80 }, /* 0.8*100 */ + { tag: "NoSnap2", snapMode: ScrollBar.NoSnap, stepSize: 0.2, size: 0.1, width: 200, steps: 180 }, /* 0.9*200 */ + + { tag: "SnapAlways", snapMode: ScrollBar.SnapAlways, stepSize: 0.1, size: 0.2, width: 100, steps: 10 }, + { tag: "SnapAlways2", snapMode: ScrollBar.SnapAlways, stepSize: 0.2, size: 0.1, width: 200, steps: 5 }, + + { tag: "SnapOnRelease", snapMode: ScrollBar.SnapOnRelease, stepSize: 0.1, size: 0.2, width: 100, steps: 80 }, /* 0.8*100 */ + { tag: "SnapOnRelease2", snapMode: ScrollBar.SnapOnRelease, stepSize: 0.2, size: 0.1, width: 200, steps: 180 }, /* 0.9*200 */ + ] + } + + function test_snapMode_mouse_data() { + return test_snapMode_data() + } + + function test_snapMode_mouse(data) { + var control = createTemporaryObject(scrollBar, testCase, {snapMode: data.snapMode, orientation: Qt.Horizontal, stepSize: data.stepSize, size: data.size, width: data.width}) + verify(control) + + function snappedPosition(pos) { + var effectiveStep = control.stepSize * (1.0 - control.size) + return Math.round(pos / effectiveStep) * effectiveStep + } + + function boundPosition(pos) { + return Math.max(0, Math.min(pos, 1.0 - control.size)) + } + + mousePress(control, 0, 0) + compare(control.position, 0) + + mouseMove(control, control.width * 0.3, 0) + var expectedMovePos = 0.3 + if (control.snapMode === ScrollBar.SnapAlways) { + expectedMovePos = snappedPosition(expectedMovePos) + verify(expectedMovePos !== 0.3) + } + compare(control.position, expectedMovePos) + + mouseRelease(control, control.width * 0.75, 0) + var expectedReleasePos = 0.75 + if (control.snapMode !== ScrollBar.NoSnap) { + expectedReleasePos = snappedPosition(expectedReleasePos) + verify(expectedReleasePos !== 0.75) + } + compare(control.position, expectedReleasePos) + + control.position = 0 + mousePress(control, 0, 0) + + var steps = 0 + var prevPos = 0 + + for (var x = 0; x < control.width; ++x) { + mouseMove(control, x, 0) + expectedMovePos = boundPosition(x / control.width) + if (control.snapMode === ScrollBar.SnapAlways) + expectedMovePos = snappedPosition(expectedMovePos) + compare(control.position, expectedMovePos) + + if (control.position !== prevPos) + ++steps + prevPos = control.position + } + compare(steps, data.steps) + + mouseRelease(control, control.width - 1, 0) + } } diff --git a/tests/manual/gifs/data/qtquickcontrols2-scrollbar-snap.qml b/tests/manual/gifs/data/qtquickcontrols2-scrollbar-snap.qml new file mode 100644 index 00000000..cfb26696 --- /dev/null +++ b/tests/manual/gifs/data/qtquickcontrols2-scrollbar-snap.qml @@ -0,0 +1,61 @@ +/**************************************************************************** +** +** Copyright (C) 2017 The Qt Company Ltd. +** Contact: http://www.qt.io/licensing/ +** +** This file is part of the test suite of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:BSD$ +** You may use this file under the terms of the BSD license as follows: +** +** "Redistribution and use in source and binary forms, with or without +** modification, are permitted provided that the following conditions are +** met: +** * Redistributions of source code must retain the above copyright +** notice, this list of conditions and the following disclaimer. +** * Redistributions in binary form must reproduce the above copyright +** notice, this list of conditions and the following disclaimer in +** the documentation and/or other materials provided with the +** distribution. +** * Neither the name of The Qt Company Ltd nor the names of its +** contributors may be used to endorse or promote products derived +** from this software without specific prior written permission. +** +** +** THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +** "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +** LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +** A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +** OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +** SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +** LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +** DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +** THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +** (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +** OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE." +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +import QtQuick 2.7 +import QtQuick.Window 2.0 +import QtQuick.Controls 2.0 + +Window { + width: 100 + maximumHeight: 20 + visible: true + + property alias scrollbar: scrollbar + + ScrollBar { + id: scrollbar + size: 0.2 + stepSize: 0.25 + active: true + width: parent.width + anchors.centerIn: parent + orientation: Qt.Horizontal + } +} diff --git a/tests/manual/gifs/tst_gifs.cpp b/tests/manual/gifs/tst_gifs.cpp index a5fc41fe..37c4c8fc 100644 --- a/tests/manual/gifs/tst_gifs.cpp +++ b/tests/manual/gifs/tst_gifs.cpp @@ -69,6 +69,8 @@ private slots: void dial_data(); void dial(); void scrollBar(); + void scrollBarSnap_data(); + void scrollBarSnap(); void scrollIndicator(); void progressBar_data(); void progressBar(); @@ -862,6 +864,56 @@ void tst_Gifs::scrollBar() gifRecorder.waitForFinish(); } +void tst_Gifs::scrollBarSnap_data() +{ + QTest::addColumn<QString>("gifBaseName"); + QTest::addColumn<int>("snapMode"); + QTest::newRow("NoSnap") << "qtquickcontrols2-scrollbar-nosnap" << 0; + QTest::newRow("SnapAlways") << "qtquickcontrols2-scrollbar-snapalways" << 1; + QTest::newRow("SnapOnRelease") << "qtquickcontrols2-scrollbar-snaponrelease" << 2; +} + +void tst_Gifs::scrollBarSnap() +{ + QFETCH(QString, gifBaseName); + QFETCH(int, snapMode); + + GifRecorder gifRecorder; + gifRecorder.setDataDirPath(dataDirPath); + gifRecorder.setOutputDir(outputDir); + gifRecorder.setRecordingDuration(8); + gifRecorder.setHighQuality(true); + gifRecorder.setQmlFileName("qtquickcontrols2-scrollbar-snap.qml"); + gifRecorder.setOutputFileBaseName(gifBaseName); + + gifRecorder.start(); + + QQuickWindow *window = gifRecorder.window(); + QQuickItem *scrollbar = window->property("scrollbar").value<QQuickItem*>(); + QVERIFY(scrollbar); + QVERIFY(scrollbar->setProperty("snapMode", QVariant(snapMode))); + QCOMPARE(scrollbar->property("snapMode").toInt(), snapMode); + + const QPoint startPos(scrollbar->property("leftPadding").toReal(), scrollbar->y() + scrollbar->height() / 2); + const int availableWidth = scrollbar->property("availableWidth").toReal(); + + QTest::mousePress(window, Qt::LeftButton, Qt::NoModifier, startPos, 200); + const QPoint pos1 = startPos + QPoint(availableWidth * 0.3, 0); + moveSmoothly(window, startPos, pos1, pos1.x() - startPos.x(), QEasingCurve::OutQuint, 30); + QTest::mouseRelease(window, Qt::LeftButton, Qt::NoModifier, pos1, 0); + + QTest::mousePress(window, Qt::LeftButton, Qt::NoModifier, pos1, 400); + const QPoint pos2 = startPos + QPoint(availableWidth * 0.6, 0); + moveSmoothly(window, pos1, pos2, pos2.x() - pos1.x(), QEasingCurve::OutQuint, 30); + QTest::mouseRelease(window, Qt::LeftButton, Qt::NoModifier, pos2, 0); + + QTest::mousePress(window, Qt::LeftButton, Qt::NoModifier, pos2, 400); + moveSmoothly(window, pos2, startPos, pos2.x() - startPos.x(), QEasingCurve::OutQuint, 30); + QTest::mouseRelease(window, Qt::LeftButton, Qt::NoModifier, startPos, 0); + + gifRecorder.waitForFinish(); +} + void tst_Gifs::scrollIndicator() { GifRecorder gifRecorder; |