diff options
-rw-r--r-- | src/imports/controls/doc/images/qtquickcontrols2-dial-inputMode.svgz | bin | 0 -> 2753 bytes | |||
-rw-r--r-- | src/imports/controls/doc/images/qtquickcontrols2-dial-inputmode.png | bin | 0 -> 12922 bytes | |||
-rw-r--r-- | src/imports/controls/doc/src/includes/qquickdial.qdocinc | 13 | ||||
-rw-r--r-- | src/imports/controls/doc/src/qtquickcontrols2-input.qdoc | 10 | ||||
-rw-r--r-- | src/imports/templates/qtquicktemplates2plugin.cpp | 1 | ||||
-rw-r--r-- | src/quicktemplates2/qquickdial.cpp | 76 | ||||
-rw-r--r-- | src/quicktemplates2/qquickdial_p.h | 15 | ||||
-rw-r--r-- | tests/auto/controls/data/tst_dial.qml | 99 |
8 files changed, 209 insertions, 5 deletions
diff --git a/src/imports/controls/doc/images/qtquickcontrols2-dial-inputMode.svgz b/src/imports/controls/doc/images/qtquickcontrols2-dial-inputMode.svgz Binary files differnew file mode 100644 index 00000000..005ab7b3 --- /dev/null +++ b/src/imports/controls/doc/images/qtquickcontrols2-dial-inputMode.svgz diff --git a/src/imports/controls/doc/images/qtquickcontrols2-dial-inputmode.png b/src/imports/controls/doc/images/qtquickcontrols2-dial-inputmode.png Binary files differnew file mode 100644 index 00000000..27694ee3 --- /dev/null +++ b/src/imports/controls/doc/images/qtquickcontrols2-dial-inputmode.png diff --git a/src/imports/controls/doc/src/includes/qquickdial.qdocinc b/src/imports/controls/doc/src/includes/qquickdial.qdocinc new file mode 100644 index 00000000..3370b3d8 --- /dev/null +++ b/src/imports/controls/doc/src/includes/qquickdial.qdocinc @@ -0,0 +1,13 @@ +//! [inputMode] +Dial supports three \l {inputMode}{input modes}: \c Dial.Circular, +\c Dial.Horizontal and \c Dial.Vertical. The circular input mode operates on an +absolute input system, where the position of the cursor within the dial +directly reflects its value. The horizontal and vertical input modes use a +relative input system, where changes in the cursor's position are "added" to +the value of the dial. + +The following image illustrates the directions in which the various input modes +track movement: + +\image qtquickcontrols2-dial-inputmode.png +//! [inputMode] diff --git a/src/imports/controls/doc/src/qtquickcontrols2-input.qdoc b/src/imports/controls/doc/src/qtquickcontrols2-input.qdoc index 71de6104..60cd586d 100644 --- a/src/imports/controls/doc/src/qtquickcontrols2-input.qdoc +++ b/src/imports/controls/doc/src/qtquickcontrols2-input.qdoc @@ -67,6 +67,16 @@ The dial is rotated by clicking and dragging, with the handle indicating the value of the dial. + For applications where fast input is important, the circular + \l {Dial::inputMode}{input mode} is useful, as clicking on the dial will + move it directly to that position. + + For applications where precise input is important, the horizontal and + vertical input modes are recommended, as these allow small adjustments to + be made relative to where the dial is clicked. These modes are also better + for dials where large jumps in values could be unsafe, such as a dial that + controls audio volume. + \b {See also} \l {Tumbler Control}. \section1 TextArea Control diff --git a/src/imports/templates/qtquicktemplates2plugin.cpp b/src/imports/templates/qtquicktemplates2plugin.cpp index 5b91c234..ef7a646b 100644 --- a/src/imports/templates/qtquicktemplates2plugin.cpp +++ b/src/imports/templates/qtquicktemplates2plugin.cpp @@ -328,6 +328,7 @@ void QtQuickTemplates2Plugin::registerTypes(const char *uri) qmlRegisterType<QQuickComboBox, 5>(uri, 2, 5, "ComboBox"); qmlRegisterType<QQuickControl, 5>(uri, 2, 5, "Control"); qmlRegisterType<QQuickContainer, 5>(uri, 2, 5, "Container"); + qmlRegisterType<QQuickDial, 5>(uri, 2, 5, "Dial"); qmlRegisterType<QQuickDialog, 5>(uri, 2, 5, "Dialog"); qmlRegisterType<QQuickGroupBox, 5>(uri, 2, 5, "GroupBox"); qmlRegisterType<QQuickLabel, 5>(uri, 2, 5, "Label"); diff --git a/src/quicktemplates2/qquickdial.cpp b/src/quicktemplates2/qquickdial.cpp index a4124678..123f9b1b 100644 --- a/src/quicktemplates2/qquickdial.cpp +++ b/src/quicktemplates2/qquickdial.cpp @@ -75,6 +75,8 @@ QT_BEGIN_NAMESPACE \row \li Set \l value to \l to \li \c Qt.Key_End \endtable + \include qquickdial.qdocinc inputMode + \sa {Customizing Dial}, {Input Controls} */ @@ -99,9 +101,12 @@ public: qreal valueAt(qreal position) const; qreal snapPosition(qreal position) const; qreal positionAt(const QPointF &point) const; + qreal circularPositionAt(const QPointF &point) const; + qreal linearPositionAt(const QPointF &point) const; void setPosition(qreal position); void updatePosition(); bool isLargeChange(const QPointF &eventPos, qreal proposedPosition) const; + bool isHorizontalOrVertical() const; void handlePress(const QPointF &point) override; void handleMove(const QPointF &point) override; @@ -119,7 +124,9 @@ public: qreal stepSize = 0; bool pressed = false; QPointF pressPoint; + qreal positionBeforePress = 0; QQuickDial::SnapMode snapMode = QQuickDial::NoSnap; + QQuickDial::InputMode inputMode = QQuickDial::Circular; bool wrap = false; bool live = true; QQuickDeferredPointer<QQuickItem> handle; @@ -145,6 +152,11 @@ qreal QQuickDialPrivate::snapPosition(qreal position) const qreal QQuickDialPrivate::positionAt(const QPointF &point) const { + return inputMode == QQuickDial::Circular ? circularPositionAt(point) : linearPositionAt(point); +} + +qreal QQuickDialPrivate::circularPositionAt(const QPointF &point) const +{ qreal yy = height / 2.0 - point.y(); qreal xx = point.x() - width / 2.0; qreal angle = (xx || yy) ? std::atan2(yy, xx) : 0; @@ -156,6 +168,32 @@ qreal QQuickDialPrivate::positionAt(const QPointF &point) const return normalizedAngle; } +qreal QQuickDialPrivate::linearPositionAt(const QPointF &point) const +{ + // This value determines the range (either horizontal or vertical) + // within which the dial can be dragged. + // The larger this value is, the further the drag distance + // must be to go from a position of e.g. 0.0 to 1.0. + qreal dragArea = 0; + + // The linear input mode uses a "relative" input system, + // where the distance from the press point is used to calculate + // the change in position. Moving the mouse above the press + // point increases the position (when inputMode is Vertical), + // and vice versa. This prevents the dial from jumping when clicked. + qreal dragDistance = 0; + + if (inputMode == QQuickDial::Horizontal) { + dragArea = width * 2; + dragDistance = pressPoint.x() - point.x(); + } else { + dragArea = height * 2; + dragDistance = point.y() - pressPoint.y(); + } + const qreal normalisedDifference = dragDistance / dragArea; + return qBound(0.0, positionBeforePress - normalisedDifference, 1.0); +} + void QQuickDialPrivate::setPosition(qreal pos) { Q_Q(QQuickDial); @@ -184,11 +222,17 @@ bool QQuickDialPrivate::isLargeChange(const QPointF &eventPos, qreal proposedPos return qAbs(proposedPosition - position) >= 0.5 && eventPos.y() >= height / 2; } +bool QQuickDialPrivate::isHorizontalOrVertical() const +{ + return inputMode == QQuickDial::Horizontal || inputMode == QQuickDial::Vertical; +} + void QQuickDialPrivate::handlePress(const QPointF &point) { Q_Q(QQuickDial); QQuickControlPrivate::handlePress(point); pressPoint = point; + positionBeforePress = position; q->setPressed(true); } @@ -201,7 +245,7 @@ void QQuickDialPrivate::handleMove(const QPointF &point) if (snapMode == QQuickDial::SnapAlways) pos = snapPosition(pos); - if (wrap || (!wrap && !isLargeChange(point, pos))) { + if (wrap || (!wrap && (isHorizontalOrVertical() || !isLargeChange(point, pos)))) { if (live) q->setValue(valueAt(pos)); else @@ -221,7 +265,7 @@ void QQuickDialPrivate::handleRelease(const QPointF &point) if (snapMode != QQuickDial::NoSnap) pos = snapPosition(pos); - if (wrap || (!wrap && !isLargeChange(point, pos))) + if (wrap || (!wrap && (isHorizontalOrVertical() || !isLargeChange(point, pos)))) q->setValue(valueAt(pos)); if (!qFuzzyCompare(pos, oldPos)) emit q->moved(); @@ -232,6 +276,7 @@ void QQuickDialPrivate::handleRelease(const QPointF &point) q->setPressed(false); pressPoint = QPointF(); + positionBeforePress = 0; } void QQuickDialPrivate::handleUngrab() @@ -239,6 +284,7 @@ void QQuickDialPrivate::handleUngrab() Q_Q(QQuickDial); QQuickControlPrivate::handleUngrab(); pressPoint = QPointF(); + positionBeforePress = 0; q->setPressed(false); } @@ -454,6 +500,32 @@ void QQuickDial::setSnapMode(SnapMode mode) } /*! + \since QtQuick.Controls 2.5 (Qt 5.12) + \qmlproperty enumeration QtQuick.Controls::Dial::inputMode + + This property holds the input mode. + + \include qquickdial.qdocinc inputMode + + The default value is \c Dial.Circular. +*/ +QQuickDial::InputMode QQuickDial::inputMode() const +{ + Q_D(const QQuickDial); + return d->inputMode; +} + +void QQuickDial::setInputMode(QQuickDial::InputMode mode) +{ + Q_D(QQuickDial); + if (d->inputMode == mode) + return; + + d->inputMode = mode; + emit inputModeChanged(); +} + +/*! \qmlproperty bool QtQuick.Controls::Dial::wrap This property holds whether the dial wraps when dragged. diff --git a/src/quicktemplates2/qquickdial_p.h b/src/quicktemplates2/qquickdial_p.h index d2caccfc..cc641c78 100644 --- a/src/quicktemplates2/qquickdial_p.h +++ b/src/quicktemplates2/qquickdial_p.h @@ -72,6 +72,8 @@ class Q_QUICKTEMPLATES2_PRIVATE_EXPORT QQuickDial : public QQuickControl Q_PROPERTY(QQuickItem *handle READ handle WRITE setHandle NOTIFY handleChanged FINAL) // 2.2 (Qt 5.9) Q_PROPERTY(bool live READ live WRITE setLive NOTIFY liveChanged FINAL REVISION 2) + // 2.5 (Qt 5.12) + Q_PROPERTY(InputMode inputMode READ inputMode WRITE setInputMode NOTIFY inputModeChanged FINAL REVISION 5) Q_CLASSINFO("DeferredPropertyNames", "background,handle") public: @@ -103,6 +105,13 @@ public: SnapMode snapMode() const; void setSnapMode(SnapMode mode); + enum InputMode { + Circular, + Horizontal, + Vertical, + }; + Q_ENUM(InputMode) + bool wrap() const; void setWrap(bool wrap); @@ -116,6 +125,10 @@ public: bool live() const; void setLive(bool live); + // 2.5 (Qt 5.12) + InputMode inputMode() const; + void setInputMode(InputMode mode); + public Q_SLOTS: void increase(); void decrease(); @@ -134,6 +147,8 @@ Q_SIGNALS: // 2.2 (Qt 5.9) Q_REVISION(2) void moved(); Q_REVISION(2) void liveChanged(); + // 2.5 (Qt 5.12) + Q_REVISION(5) void inputModeChanged(); protected: void keyPressEvent(QKeyEvent *event) override; diff --git a/tests/auto/controls/data/tst_dial.qml b/tests/auto/controls/data/tst_dial.qml index 33b0dbea..a2d32347 100644 --- a/tests/auto/controls/data/tst_dial.qml +++ b/tests/auto/controls/data/tst_dial.qml @@ -54,15 +54,19 @@ import QtQuick.Controls 2.2 TestCase { id: testCase - width: 200 - height: 200 + width: 450 + height: 450 visible: true when: windowShown name: "Dial" Component { id: dialComponent - Dial {} + Dial { + width: 100 + height: 100 + anchors.centerIn: parent + } } Component { @@ -592,4 +596,93 @@ TestCase { mouseRelease(control) compare(control.pressed, false) } + + function move(inputEventType, control, x, y) { + if (inputEventType === "mouseInput") { + mouseMove(control, x, y); + } else { + var touch = touchEvent(control); + touch.move(0, control, x, y).commit(); + } + } + + function press(inputEventType, control, x, y) { + if (inputEventType === "mouseInput") { + mousePress(control, x, y); + } else { + var touch = touchEvent(control); + touch.press(0, control, x, y).commit(); + } + } + + function release(inputEventType, control, x, y) { + if (inputEventType === "mouseInput") { + mouseRelease(control, x, y); + } else { + var touch = touchEvent(control); + touch.release(0, control, x, y).commit(); + } + } + + function test_horizontalAndVertical_data() { + var data = [ + { eventType: "mouseInput", inputMode: Dial.Vertical, moveToX: 0.5, moveToY: 0.25, expectedPosition: 0.125 }, + // Horizontal movement should have no effect on a vertical dial. + { eventType: "mouseInput", inputMode: Dial.Vertical, moveToX: 2.0, moveToY: 0.25, expectedPosition: 0.125 }, + { eventType: "mouseInput", inputMode: Dial.Vertical, moveToX: 0.5, moveToY: 0.0, expectedPosition: 0.25 }, + { eventType: "mouseInput", inputMode: Dial.Vertical, moveToX: 0.5, moveToY: -1.5, expectedPosition: 1.0 }, + // Going above the drag area shouldn't make the position higher than 1.0. + { eventType: "mouseInput", inputMode: Dial.Vertical, moveToX: 0.5, moveToY: -2.0, expectedPosition: 1.0 }, + // Try to decrease the position by moving the mouse down. + // The dial's position is 0 before the press event, so nothing should happen. + { eventType: "mouseInput", inputMode: Dial.Vertical, moveToX: 0.5, moveToY: 1.25, expectedPosition: 0.0 }, + + { eventType: "mouseInput", inputMode: Dial.Horizontal, moveToX: 0.75, moveToY: 0.5, expectedPosition: 0.125 }, + // Vertical movement should have no effect on a horizontal dial. + { eventType: "mouseInput", inputMode: Dial.Horizontal, moveToX: 0.75, moveToY: 2.0, expectedPosition: 0.125 }, + { eventType: "mouseInput", inputMode: Dial.Horizontal, moveToX: 1.0, moveToY: 0.5, expectedPosition: 0.25 }, + { eventType: "mouseInput", inputMode: Dial.Horizontal, moveToX: 1.5, moveToY: 0.5, expectedPosition: 0.5 }, + { eventType: "mouseInput", inputMode: Dial.Horizontal, moveToX: 2.5, moveToY: 0.5, expectedPosition: 1.0 }, + // Going above the drag area shouldn't make the position higher than 1.0. + { eventType: "mouseInput", inputMode: Dial.Horizontal, moveToX: 2.525, moveToY: 0.5, expectedPosition: 1.0 }, + // Try to decrease the position by moving the mouse to the left. + // The dial's position is 0 before the press event, so nothing should happen. + { eventType: "mouseInput", inputMode: Dial.Vertical, moveToX: 0.25, moveToY: 0.5, expectedPosition: 0.0 } + ]; + + // Do the same tests for touch by copying the mouse tests and adding them to the end of the array. + var mouseTestCount = data.length; + for (var i = mouseTestCount; i < mouseTestCount * 2; ++i) { + // Shallow-copy the object. + data[i] = JSON.parse(JSON.stringify(data[i - mouseTestCount])); + data[i].eventType = "touchInput"; + } + + for (i = 0; i < data.length; ++i) { + var row = data[i]; + row.tag = "eventType=" + row.eventType + ", " + + "inputMode=" + (row.inputMode === Dial.Vertical ? "Vertical" : "Horizontal") + ", " + + "moveToX=" + row.moveToX + ", moveToY=" + row.moveToY + ", " + + "expectedPosition=" + row.expectedPosition; + } + + return data; + } + + function test_horizontalAndVertical(data) { + var control = createTemporaryObject(dialComponent, testCase, { inputMode: data.inputMode }); + verify(control); + + press(data.eventType, control); + compare(control.pressed, true); + // The position shouldn't change until the mouse has actually moved. + compare(control.position, 0); + + move(data.eventType, control, control.width * data.moveToX, control.width * data.moveToY); + compare(control.position, data.expectedPosition); + + release(data.eventType, control, control.width * data.moveToX, control.width * data.moveToY); + compare(control.pressed, false); + compare(control.position, data.expectedPosition); + } } |