From e253427f93248983d18055c5020094799a2c6b3b Mon Sep 17 00:00:00 2001 From: Mitch Curtis Date: Fri, 15 Apr 2016 16:41:26 +0200 Subject: Dial: add wrap property This works by preventing changes in position that are considered too large. A change is considered too large when the difference between the old and new positions is greater than or equal to 0.5 *and* the position of the mouse is below the center of the dial. This effectively makes it impossible to drag the dial from left to right when the mouse is below its center, but still allows doing so above its center. This is useful for applications such as volume dials, where a large change can be dangerous. Change-Id: I1d7800e5ba16dbe0642974b8e53d8fcd921a01d7 Reviewed-by: J-P Nurmi --- src/quicktemplates2/qquickdial.cpp | 48 ++++++++++++++++++++++++++++++++--- src/quicktemplates2/qquickdial_p.h | 5 ++++ tests/auto/controls/data/tst_dial.qml | 38 +++++++++++++++++++++++++++ 3 files changed, 88 insertions(+), 3 deletions(-) diff --git a/src/quicktemplates2/qquickdial.cpp b/src/quicktemplates2/qquickdial.cpp index 7f73d399..e1084978 100644 --- a/src/quicktemplates2/qquickdial.cpp +++ b/src/quicktemplates2/qquickdial.cpp @@ -94,6 +94,7 @@ public: stepSize(0), pressed(false), snapMode(QQuickDial::NoSnap), + wrap(true), handle(nullptr) { } @@ -103,6 +104,7 @@ public: qreal positionAt(const QPoint &point) const; void setPosition(qreal position); void updatePosition(); + bool isLargeChange(const QPoint &eventPos, qreal proposedPosition) const; qreal from; qreal to; @@ -113,6 +115,7 @@ public: bool pressed; QPoint pressPoint; QQuickDial::SnapMode snapMode; + bool wrap; QQuickItem *handle; }; @@ -164,6 +167,11 @@ void QQuickDialPrivate::updatePosition() setPosition(pos); } +bool QQuickDialPrivate::isLargeChange(const QPoint &eventPos, qreal proposedPosition) const +{ + return qAbs(proposedPosition - position) >= 0.5 && eventPos.y() >= height / 2; +} + QQuickDial::QQuickDial(QQuickItem *parent) : QQuickControl(*(new QQuickDialPrivate), parent) { @@ -348,6 +356,33 @@ void QQuickDial::setSnapMode(SnapMode mode) emit snapModeChanged(); } +/*! + \qmlproperty bool Qt.labs.controls::Dial::wrap + + This property holds whether the dial wraps when dragged. + + For example, when this property is set to \c true, dragging the dial past + the \e "zero" position will result in the handle being positioned at the + opposite side, and vice versa. + + The default value is \c true. +*/ +bool QQuickDial::wrap() const +{ + Q_D(const QQuickDial); + return d->wrap; +} + +void QQuickDial::setWrap(bool wrap) +{ + Q_D(QQuickDial); + if (d->wrap == wrap) + return; + + d->wrap = wrap; + emit wrapChanged(); +} + /*! \qmlproperty bool Qt.labs.controls::Dial::pressed @@ -508,7 +543,9 @@ void QQuickDial::mouseMoveEvent(QMouseEvent *event) qreal pos = d->positionAt(event->pos()); if (d->snapMode == SnapAlways) pos = d->snapPosition(pos); - d->setPosition(pos); + + if (d->wrap || (!d->wrap && !d->isLargeChange(event->pos(), pos))) + d->setPosition(pos); } } @@ -516,15 +553,20 @@ void QQuickDial::mouseReleaseEvent(QMouseEvent *event) { Q_D(QQuickDial); QQuickControl::mouseReleaseEvent(event); - d->pressPoint = QPoint(); + if (keepMouseGrab()) { qreal pos = d->positionAt(event->pos()); if (d->snapMode != NoSnap) pos = d->snapPosition(pos); - setValue(d->valueAt(pos)); + + if (d->wrap || (!d->wrap && !d->isLargeChange(event->pos(), pos))) + setValue(d->valueAt(pos)); + setKeepMouseGrab(false); } + setPressed(false); + d->pressPoint = QPoint(); } void QQuickDial::mouseUngrabEvent() diff --git a/src/quicktemplates2/qquickdial_p.h b/src/quicktemplates2/qquickdial_p.h index 6c83baef..f736e607 100644 --- a/src/quicktemplates2/qquickdial_p.h +++ b/src/quicktemplates2/qquickdial_p.h @@ -67,6 +67,7 @@ class Q_QUICKTEMPLATES2_EXPORT QQuickDial : public QQuickControl Q_PROPERTY(qreal angle READ angle NOTIFY angleChanged FINAL) Q_PROPERTY(qreal stepSize READ stepSize WRITE setStepSize NOTIFY stepSizeChanged FINAL) Q_PROPERTY(SnapMode snapMode READ snapMode WRITE setSnapMode NOTIFY snapModeChanged FINAL) + Q_PROPERTY(bool wrap READ wrap WRITE setWrap NOTIFY wrapChanged FINAL) Q_PROPERTY(bool pressed READ isPressed NOTIFY pressedChanged FINAL) Q_PROPERTY(QQuickItem *handle READ handle WRITE setHandle NOTIFY handleChanged FINAL) @@ -99,6 +100,9 @@ public: SnapMode snapMode() const; void setSnapMode(SnapMode mode); + bool wrap() const; + void setWrap(bool wrap); + bool isPressed() const; void setPressed(bool pressed); @@ -117,6 +121,7 @@ Q_SIGNALS: void angleChanged(); void stepSizeChanged(); void snapModeChanged(); + void wrapChanged(); void pressedChanged(); void handleChanged(); diff --git a/tests/auto/controls/data/tst_dial.qml b/tests/auto/controls/data/tst_dial.qml index e0eba27e..0f7a4f85 100644 --- a/tests/auto/controls/data/tst_dial.qml +++ b/tests/auto/controls/data/tst_dial.qml @@ -179,6 +179,7 @@ TestCase { } function test_dragging(data) { + verify(dial.wrap); dial.from = data.from; dial.to = data.to; @@ -210,6 +211,43 @@ TestCase { valueSpy.clear(); } + function test_nonWrapping() { + dial.wrap = false; + dial.value = 0; + + // Ensure that dragging from bottom left to bottom right doesn't work. + var yPos = dial.height * 0.75; + mousePress(dial, dial.width * 0.25, yPos, Qt.LeftButton); + var positionAtPress = dial.position; + mouseMove(dial, dial.width * 0.5, yPos, Qt.LeftButton); + compare(dial.position, positionAtPress); + mouseMove(dial, dial.width * 0.75, yPos, Qt.LeftButton); + compare(dial.position, positionAtPress); + mouseRelease(dial, dial.width * 0.75, yPos, Qt.LeftButton); + compare(dial.position, positionAtPress); + + // Try the same thing, but a bit higher. + yPos = dial.height * 0.6; + mousePress(dial, dial.width * 0.25, yPos, Qt.LeftButton); + positionAtPress = dial.position; + mouseMove(dial, dial.width * 0.5, yPos, Qt.LeftButton); + compare(dial.position, positionAtPress); + mouseMove(dial, dial.width * 0.75, yPos, Qt.LeftButton); + compare(dial.position, positionAtPress); + mouseRelease(dial, dial.width * 0.75, yPos, Qt.LeftButton); + compare(dial.position, positionAtPress); + + // Going from below the center of the dial to above it should work (once it gets above the center). + mousePress(dial, dial.width * 0.25, dial.height * 0.75, Qt.LeftButton); + positionAtPress = dial.position; + mouseMove(dial, dial.width * 0.5, dial.height * 0.6, Qt.LeftButton); + compare(dial.position, positionAtPress); + mouseMove(dial, dial.width * 0.75, dial.height * 0.4, Qt.LeftButton); + verify(dial.position > positionAtPress); + mouseRelease(dial, dial.width * 0.75, dial.height * 0.3, Qt.LeftButton); + verify(dial.position > positionAtPress); + } + property Component focusTest: Component { FocusScope { signal receivedKeyPress -- cgit v1.2.3