aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--src/imports/controls/doc/images/qtquickcontrols2-dial-inputMode.svgzbin0 -> 2753 bytes
-rw-r--r--src/imports/controls/doc/images/qtquickcontrols2-dial-inputmode.pngbin0 -> 12922 bytes
-rw-r--r--src/imports/controls/doc/src/includes/qquickdial.qdocinc13
-rw-r--r--src/imports/controls/doc/src/qtquickcontrols2-input.qdoc10
-rw-r--r--src/imports/templates/qtquicktemplates2plugin.cpp1
-rw-r--r--src/quicktemplates2/qquickdial.cpp76
-rw-r--r--src/quicktemplates2/qquickdial_p.h15
-rw-r--r--tests/auto/controls/data/tst_dial.qml99
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
new file mode 100644
index 00000000..005ab7b3
--- /dev/null
+++ b/src/imports/controls/doc/images/qtquickcontrols2-dial-inputMode.svgz
Binary files differ
diff --git a/src/imports/controls/doc/images/qtquickcontrols2-dial-inputmode.png b/src/imports/controls/doc/images/qtquickcontrols2-dial-inputmode.png
new file mode 100644
index 00000000..27694ee3
--- /dev/null
+++ b/src/imports/controls/doc/images/qtquickcontrols2-dial-inputmode.png
Binary files differ
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);
+ }
}