1 | // Copyright (C) 2017 The Qt Company Ltd. |
---|---|
2 | // SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only |
3 | |
4 | #include "qquickdial_p.h" |
5 | #include "qquickdeferredexecute_p_p.h" |
6 | |
7 | #include <QtCore/qmath.h> |
8 | #include <QtQuick/private/qquickflickable_p.h> |
9 | #include <QtQuickTemplates2/private/qquickcontrol_p_p.h> |
10 | |
11 | #include <cmath> |
12 | |
13 | QT_BEGIN_NAMESPACE |
14 | |
15 | /*! |
16 | \qmltype Dial |
17 | \inherits Control |
18 | //! \nativetype QQuickDial |
19 | \inqmlmodule QtQuick.Controls |
20 | \since 5.7 |
21 | \ingroup qtquickcontrols-input |
22 | \brief Circular dial that is rotated to set a value. |
23 | |
24 | The Dial is similar to a traditional dial knob that is found on devices |
25 | such as stereos or industrial equipment. It allows the user to specify a |
26 | value within a range. |
27 | |
28 | \image qtquickcontrols-dial-no-wrap.gif |
29 | |
30 | The value of the dial is set with the \l value property. The range is |
31 | set with the \l from and \l to properties. To enable or disable wrapping, |
32 | use the \l wrap property. |
33 | |
34 | The dial can be manipulated with a keyboard. It supports the following |
35 | actions: |
36 | |
37 | \table |
38 | \header \li \b {Action} \li \b {Key} |
39 | \row \li Decrease \l value by \l stepSize \li \c Qt.Key_Left |
40 | \row \li Decrease \l value by \l stepSize \li \c Qt.Key_Down |
41 | \row \li Set \l value to \l from \li \c Qt.Key_Home |
42 | \row \li Increase \l value by \l stepSize \li \c Qt.Key_Right |
43 | \row \li Increase \l value by \l stepSize \li \c Qt.Key_Up |
44 | \row \li Set \l value to \l to \li \c Qt.Key_End |
45 | \endtable |
46 | |
47 | \include qquickdial.qdocinc inputMode |
48 | |
49 | \sa {Customizing Dial}, {Input Controls} |
50 | */ |
51 | |
52 | /*! |
53 | \since QtQuick.Controls 2.2 (Qt 5.9) |
54 | \qmlsignal QtQuick.Controls::Dial::moved() |
55 | |
56 | This signal is emitted when the dial has been interactively moved |
57 | by the user by either touch, mouse, or keys. |
58 | */ |
59 | |
60 | /*! |
61 | \qmlsignal QtQuick.Controls::Dial::wrapped(Dial.WrapDirection direction) |
62 | \since 6.6 |
63 | |
64 | This signal is emitted when the dial wraps around, i.e. goes beyond its |
65 | maximum value to its minimum value, or vice versa. It is only emitted when |
66 | \l wrap is \c true. |
67 | The \a direction argument specifies the direction of the full rotation and |
68 | will be one of the following arguments: |
69 | |
70 | \value Dial.Clockwise The dial wrapped in clockwise direction. |
71 | \value Dial.CounterClockwise The dial wrapped in counterclockwise direction. |
72 | */ |
73 | |
74 | // The user angle is the clockwise angle between the position and the vertical |
75 | // y-axis (12 o clock position). |
76 | // Using radians for logic (atan2(...)) and degree for user interface |
77 | constexpr qreal toUserAngleDeg(qreal logicAngleRad) { |
78 | // minus to turn clockwise, add 90 deg clockwise |
79 | return -logicAngleRad / M_PI * 180. + 90; |
80 | } |
81 | |
82 | static const qreal defaultStartAngle = -140; |
83 | static const qreal defaultEndAngle = 140; |
84 | |
85 | class QQuickDialPrivate : public QQuickControlPrivate |
86 | { |
87 | Q_DECLARE_PUBLIC(QQuickDial) |
88 | |
89 | public: |
90 | qreal valueAt(qreal position) const; |
91 | qreal snapPosition(qreal position) const; |
92 | qreal positionAt(const QPointF &point) const; |
93 | qreal circularPositionAt(const QPointF &point) const; |
94 | qreal linearPositionAt(const QPointF &point) const; |
95 | void setPosition(qreal position); |
96 | void updatePosition(); |
97 | bool isLargeChange(qreal proposedPosition) const; |
98 | bool isHorizontalOrVertical() const; |
99 | |
100 | bool handlePress(const QPointF &point, ulong timestamp) override; |
101 | bool handleMove(const QPointF &point, ulong timestamp) override; |
102 | bool handleRelease(const QPointF &point, ulong timestamp) override; |
103 | void handleUngrab() override; |
104 | |
105 | void cancelHandle(); |
106 | void executeHandle(bool complete = false); |
107 | |
108 | void updateAllValuesAreInteger(); |
109 | |
110 | void maybeEmitWrapAround(qreal pos); |
111 | |
112 | qreal from = 0; |
113 | qreal to = 1; |
114 | qreal value = 0; |
115 | qreal position = 0; |
116 | qreal startAngle = defaultStartAngle; |
117 | qreal endAngle = defaultEndAngle; |
118 | qreal angle = startAngle; |
119 | qreal stepSize = 0; |
120 | QPointF pressPoint; |
121 | qreal positionBeforePress = 0; |
122 | QQuickDial::SnapMode snapMode = QQuickDial::NoSnap; |
123 | QQuickDial::InputMode inputMode = QQuickDial::Circular; |
124 | QQuickDeferredPointer<QQuickItem> handle; |
125 | bool wrap = false; |
126 | bool live = true; |
127 | bool pressed = false; |
128 | bool allValuesAreInteger = false; |
129 | }; |
130 | |
131 | qreal QQuickDialPrivate::valueAt(qreal position) const |
132 | { |
133 | qreal value = from + (to - from) * position; |
134 | |
135 | /* play nice with users expecting that integer from, to and stepSize leads to |
136 | integer values - given that we are using floating point internally (and in |
137 | the API of value), this does not hold, but it is easy enough to handle |
138 | */ |
139 | if (allValuesAreInteger) |
140 | value = qRound(d: value); |
141 | |
142 | return value; |
143 | } |
144 | |
145 | qreal QQuickDialPrivate::snapPosition(qreal position) const |
146 | { |
147 | const qreal range = to - from; |
148 | if (qFuzzyIsNull(d: range)) |
149 | return position; |
150 | |
151 | const qreal effectiveStep = stepSize / range; |
152 | if (qFuzzyIsNull(d: effectiveStep)) |
153 | return position; |
154 | |
155 | return qRound(d: position / effectiveStep) * effectiveStep; |
156 | } |
157 | |
158 | qreal QQuickDialPrivate::positionAt(const QPointF &point) const |
159 | { |
160 | return inputMode == QQuickDial::Circular ? circularPositionAt(point) : linearPositionAt(point); |
161 | } |
162 | |
163 | qreal QQuickDialPrivate::circularPositionAt(const QPointF &point) const |
164 | { |
165 | qreal yy = height / 2.0 - point.y(); |
166 | qreal xx = point.x() - width / 2.0; |
167 | qreal alpha = (xx || yy) ? toUserAngleDeg(logicAngleRad: std::atan2(y: yy, x: xx)) : 0; |
168 | |
169 | // Move around the circle to reach the interval. |
170 | if (alpha < startAngle && alpha + 360. < endAngle) |
171 | alpha += 360.; |
172 | else if (alpha >= endAngle && alpha - 360. >= startAngle) |
173 | alpha -= 360.; |
174 | |
175 | // If wrap is on and we are out of the interval [startAngle, endAngle], |
176 | // we want to jump to the closest border to make it feel nice and responsive |
177 | if ((alpha < startAngle || alpha > endAngle) && wrap) { |
178 | if (abs(x: alpha - startAngle) > abs(x: endAngle - alpha - 360.)) |
179 | alpha += 360.; |
180 | else if (abs(x: alpha - startAngle - 360.) < abs(x: endAngle - alpha)) |
181 | alpha -= 360.; |
182 | } |
183 | |
184 | // If wrap is off, |
185 | // we want to stay as close as possible to the current angle. |
186 | // This is important to allow easy setting of boundary values (0,1) |
187 | if (!wrap) { |
188 | if (abs(x: angle - alpha) > abs(x: angle - (alpha + 360.))) |
189 | alpha += 360.; |
190 | if (abs(x: angle - alpha) > abs(x: angle - (alpha - 360.))) |
191 | alpha -= 360.; |
192 | } |
193 | |
194 | return (alpha - startAngle) / (endAngle - startAngle); |
195 | } |
196 | |
197 | qreal QQuickDialPrivate::linearPositionAt(const QPointF &point) const |
198 | { |
199 | // This value determines the range (either horizontal or vertical) |
200 | // within which the dial can be dragged. |
201 | // The larger this value is, the further the drag distance |
202 | // must be to go from a position of e.g. 0.0 to 1.0. |
203 | qreal dragArea = 0; |
204 | |
205 | // The linear input mode uses a "relative" input system, |
206 | // where the distance from the press point is used to calculate |
207 | // the change in position. Moving the mouse above the press |
208 | // point increases the position (when inputMode is Vertical), |
209 | // and vice versa. This prevents the dial from jumping when clicked. |
210 | qreal dragDistance = 0; |
211 | |
212 | if (inputMode == QQuickDial::Horizontal) { |
213 | dragArea = width * 2; |
214 | dragDistance = pressPoint.x() - point.x(); |
215 | } else { |
216 | dragArea = height * 2; |
217 | dragDistance = point.y() - pressPoint.y(); |
218 | } |
219 | const qreal normalisedDifference = dragDistance / dragArea; |
220 | return qBound(min: qreal(0), val: positionBeforePress - normalisedDifference, max: qreal(1)); |
221 | } |
222 | |
223 | void QQuickDialPrivate::setPosition(qreal pos) |
224 | { |
225 | Q_Q(QQuickDial); |
226 | pos = qBound<qreal>(min: qreal(0), val: pos, max: qreal(1)); |
227 | const qreal alpha = startAngle + pos * qAbs(t: endAngle - startAngle); |
228 | if (qFuzzyCompare(p1: position, p2: pos) && qFuzzyCompare(p1: angle, p2: alpha)) |
229 | return; |
230 | |
231 | angle = alpha; |
232 | position = pos; |
233 | |
234 | |
235 | emit q->positionChanged(); |
236 | emit q->angleChanged(); |
237 | } |
238 | |
239 | void QQuickDialPrivate::updatePosition() |
240 | { |
241 | qreal pos = 0; |
242 | if (!qFuzzyCompare(p1: from, p2: to)) |
243 | pos = (value - from) / (to - from); |
244 | setPosition(pos); |
245 | } |
246 | |
247 | bool QQuickDialPrivate::isLargeChange(qreal proposedPosition) const |
248 | { |
249 | if (endAngle - startAngle < 180.0) |
250 | return false; |
251 | return qAbs(t: proposedPosition - position) > qreal(0.5); |
252 | } |
253 | |
254 | bool QQuickDialPrivate::isHorizontalOrVertical() const |
255 | { |
256 | return inputMode == QQuickDial::Horizontal || inputMode == QQuickDial::Vertical; |
257 | } |
258 | |
259 | bool QQuickDialPrivate::handlePress(const QPointF &point, ulong timestamp) |
260 | { |
261 | Q_Q(QQuickDial); |
262 | QQuickControlPrivate::handlePress(point, timestamp); |
263 | pressPoint = point; |
264 | positionBeforePress = position; |
265 | q->setPressed(true); |
266 | return true; |
267 | } |
268 | |
269 | bool QQuickDialPrivate::handleMove(const QPointF &point, ulong timestamp) |
270 | { |
271 | Q_Q(QQuickDial); |
272 | QQuickControlPrivate::handleMove(point, timestamp); |
273 | const qreal oldPos = position; |
274 | qreal pos = qBound(min: 0.0, val: positionAt(point), max: 1.0); |
275 | if (snapMode == QQuickDial::SnapAlways) |
276 | pos = snapPosition(position: pos); |
277 | |
278 | maybeEmitWrapAround(pos); |
279 | |
280 | if (wrap || isHorizontalOrVertical() || !isLargeChange(proposedPosition: pos)) { |
281 | if (live) |
282 | q->setValue(valueAt(position: pos)); |
283 | else |
284 | setPosition(pos); |
285 | if (!qFuzzyCompare(p1: pos, p2: oldPos)) |
286 | emit q->moved(); |
287 | } |
288 | return true; |
289 | } |
290 | |
291 | bool QQuickDialPrivate::handleRelease(const QPointF &point, ulong timestamp) |
292 | { |
293 | Q_Q(QQuickDial); |
294 | QQuickControlPrivate::handleRelease(point, timestamp); |
295 | if (q->keepMouseGrab() || q->keepTouchGrab()) { |
296 | const qreal oldPos = position; |
297 | qreal pos = positionAt(point); |
298 | if (snapMode != QQuickDial::NoSnap) |
299 | pos = snapPosition(position: pos); |
300 | |
301 | maybeEmitWrapAround(pos); |
302 | |
303 | if (wrap || isHorizontalOrVertical() || !isLargeChange(proposedPosition: pos)) |
304 | q->setValue(valueAt(position: pos)); |
305 | if (!qFuzzyCompare(p1: pos, p2: oldPos)) |
306 | emit q->moved(); |
307 | |
308 | q->setKeepMouseGrab(false); |
309 | q->setKeepTouchGrab(false); |
310 | } |
311 | |
312 | q->setPressed(false); |
313 | pressPoint = QPointF(); |
314 | positionBeforePress = 0; |
315 | return true; |
316 | } |
317 | |
318 | void QQuickDialPrivate::handleUngrab() |
319 | { |
320 | Q_Q(QQuickDial); |
321 | QQuickControlPrivate::handleUngrab(); |
322 | pressPoint = QPointF(); |
323 | positionBeforePress = 0; |
324 | q->setPressed(false); |
325 | } |
326 | |
327 | void QQuickDialPrivate::cancelHandle() |
328 | { |
329 | Q_Q(QQuickDial); |
330 | quickCancelDeferred(object: q, property: handleName()); |
331 | } |
332 | |
333 | void QQuickDialPrivate::executeHandle(bool complete) |
334 | { |
335 | Q_Q(QQuickDial); |
336 | if (handle.wasExecuted()) |
337 | return; |
338 | |
339 | if (!handle || complete) |
340 | quickBeginDeferred(object: q, property: handleName(), delegate&: handle); |
341 | if (complete) |
342 | quickCompleteDeferred(object: q, property: handleName(), delegate&: handle); |
343 | } |
344 | |
345 | template<typename ...Real> |
346 | static bool areRepresentableAsInteger(Real... numbers) { |
347 | auto check = [](qreal number) -> bool { return std::nearbyint(x: number) == number; }; |
348 | return (... && check(numbers)); |
349 | } |
350 | |
351 | void QQuickDialPrivate::updateAllValuesAreInteger() |
352 | { |
353 | allValuesAreInteger = areRepresentableAsInteger(numbers: to, numbers: from, numbers: stepSize) && stepSize != 0.0; |
354 | } |
355 | |
356 | void QQuickDialPrivate::maybeEmitWrapAround(qreal pos) |
357 | { |
358 | Q_Q(QQuickDial); |
359 | |
360 | if (wrap && isLargeChange(proposedPosition: pos)) |
361 | emit q->wrapped((pos < q->position()) ? QQuickDial::Clockwise : QQuickDial::CounterClockwise); |
362 | } |
363 | |
364 | QQuickDial::QQuickDial(QQuickItem *parent) |
365 | : QQuickControl(*(new QQuickDialPrivate), parent) |
366 | { |
367 | setActiveFocusOnTab(true); |
368 | setAcceptedMouseButtons(Qt::LeftButton); |
369 | #if QT_CONFIG(quicktemplates2_multitouch) |
370 | setAcceptTouchEvents(true); |
371 | #endif |
372 | #if QT_CONFIG(cursor) |
373 | setCursor(Qt::ArrowCursor); |
374 | #endif |
375 | Q_D(QQuickDial); |
376 | d->setSizePolicy(horizontalPolicy: QLayoutPolicy::Preferred, verticalPolicy: QLayoutPolicy::Preferred); |
377 | } |
378 | |
379 | /*! |
380 | \qmlproperty real QtQuick.Controls::Dial::from |
381 | |
382 | This property holds the starting value for the range. The default value is \c 0.0. |
383 | |
384 | \sa to, value |
385 | */ |
386 | qreal QQuickDial::from() const |
387 | { |
388 | Q_D(const QQuickDial); |
389 | return d->from; |
390 | } |
391 | |
392 | void QQuickDial::setFrom(qreal from) |
393 | { |
394 | Q_D(QQuickDial); |
395 | if (qFuzzyCompare(p1: d->from, p2: from)) |
396 | return; |
397 | |
398 | d->from = from; |
399 | emit fromChanged(); |
400 | d->updateAllValuesAreInteger(); |
401 | if (isComponentComplete()) { |
402 | setValue(d->value); |
403 | d->updatePosition(); |
404 | } |
405 | } |
406 | |
407 | /*! |
408 | \qmlproperty real QtQuick.Controls::Dial::to |
409 | |
410 | This property holds the end value for the range. The default value is |
411 | \c 1.0. |
412 | |
413 | \sa from, value |
414 | */ |
415 | qreal QQuickDial::to() const |
416 | { |
417 | Q_D(const QQuickDial); |
418 | return d->to; |
419 | } |
420 | |
421 | void QQuickDial::setTo(qreal to) |
422 | { |
423 | Q_D(QQuickDial); |
424 | if (qFuzzyCompare(p1: d->to, p2: to)) |
425 | return; |
426 | |
427 | d->to = to; |
428 | d->updateAllValuesAreInteger(); |
429 | emit toChanged(); |
430 | if (isComponentComplete()) { |
431 | setValue(d->value); |
432 | d->updatePosition(); |
433 | } |
434 | } |
435 | |
436 | /*! |
437 | \qmlproperty real QtQuick.Controls::Dial::value |
438 | |
439 | This property holds the value in the range \c from - \c to. The default |
440 | value is \c 0.0. |
441 | |
442 | \sa position, live |
443 | */ |
444 | qreal QQuickDial::value() const |
445 | { |
446 | Q_D(const QQuickDial); |
447 | return d->value; |
448 | } |
449 | |
450 | void QQuickDial::setValue(qreal value) |
451 | { |
452 | Q_D(QQuickDial); |
453 | if (isComponentComplete()) |
454 | value = d->from > d->to ? qBound(min: d->to, val: value, max: d->from) : qBound(min: d->from, val: value, max: d->to); |
455 | |
456 | if (qFuzzyCompare(p1: d->value, p2: value)) |
457 | return; |
458 | |
459 | d->value = value; |
460 | d->updatePosition(); |
461 | emit valueChanged(); |
462 | } |
463 | |
464 | /*! |
465 | \qmlproperty real QtQuick.Controls::Dial::position |
466 | \readonly |
467 | |
468 | This property holds the logical position of the handle. |
469 | |
470 | The position is expressed as a fraction of the control's angle range (the |
471 | range within which the handle can be moved) in the range \c {0.0 - 1.0}. |
472 | |
473 | \sa value, angle |
474 | */ |
475 | qreal QQuickDial::position() const |
476 | { |
477 | Q_D(const QQuickDial); |
478 | return d->position; |
479 | } |
480 | |
481 | /*! |
482 | \qmlproperty real QtQuick.Controls::Dial::angle |
483 | \readonly |
484 | |
485 | This property holds the clockwise angle of the handle in degrees. |
486 | |
487 | The angle is zero at the 12 o'clock position and the range is from |
488 | \l startAngle to \c endAngle. |
489 | |
490 | \sa position, startAngle, endAngle |
491 | */ |
492 | qreal QQuickDial::angle() const |
493 | { |
494 | Q_D(const QQuickDial); |
495 | return d->angle; |
496 | } |
497 | |
498 | /*! |
499 | \qmlproperty real QtQuick.Controls::Dial::stepSize |
500 | |
501 | This property holds the step size. |
502 | |
503 | The step size determines the amount by which the dial's value |
504 | is increased and decreased when interacted with via the keyboard. |
505 | For example, a step size of \c 0.2, will result in the dial's |
506 | value increasing and decreasing in increments of \c 0.2. |
507 | |
508 | The step size is only respected for touch and mouse interaction |
509 | when \l snapMode is set to a value other than \c Dial.NoSnap. |
510 | |
511 | The default value is \c 0.0, which results in an effective step |
512 | size of \c 0.1 for keyboard interaction. |
513 | |
514 | \sa snapMode, increase(), decrease() |
515 | */ |
516 | qreal QQuickDial::stepSize() const |
517 | { |
518 | Q_D(const QQuickDial); |
519 | return d->stepSize; |
520 | } |
521 | |
522 | void QQuickDial::setStepSize(qreal step) |
523 | { |
524 | Q_D(QQuickDial); |
525 | if (qFuzzyCompare(p1: d->stepSize, p2: step)) |
526 | return; |
527 | |
528 | d->stepSize = step; |
529 | d->updateAllValuesAreInteger(); |
530 | emit stepSizeChanged(); |
531 | } |
532 | |
533 | |
534 | /*! |
535 | \qmlproperty real QtQuick.Controls::Dial::startAngle |
536 | \since 6.6 |
537 | |
538 | This property holds the starting angle of the dial in degrees. |
539 | |
540 | This is the \l angle the dial will have for its minimum value, i.e. \l from. |
541 | The \l startAngle has to be smaller than the \l endAngle, larger than -360 |
542 | and larger or equal to the \l endAngle - 360 degrees. |
543 | |
544 | \sa endAngle, angle |
545 | */ |
546 | qreal QQuickDial::startAngle() const |
547 | { |
548 | Q_D(const QQuickDial); |
549 | return d->startAngle; |
550 | } |
551 | |
552 | void QQuickDial::setStartAngle(qreal startAngle) |
553 | { |
554 | Q_D(QQuickDial); |
555 | if (!d->componentComplete) { |
556 | // Binding evaluation order can cause warnings with certain combinations |
557 | // of start and end angles, so delay the actual setting until after component completion. |
558 | // Store the requested value in the existing member to avoid the need for an extra one. |
559 | d->startAngle = startAngle; |
560 | return; |
561 | } |
562 | |
563 | if (qFuzzyCompare(p1: d->startAngle, p2: startAngle)) |
564 | return; |
565 | |
566 | // do not allow to change direction |
567 | if (startAngle >= d->endAngle) { |
568 | qmlWarning(me: this) << "startAngle ("<< startAngle |
569 | << ") cannot be greater than or equal to endAngle ("<< d->endAngle << ")"; |
570 | return; |
571 | } |
572 | |
573 | // Keep the interval around 0 |
574 | if (startAngle <= -360.) { |
575 | qmlWarning(me: this) << "startAngle ("<< startAngle << ") cannot be less than or equal to -360"; |
576 | return; |
577 | } |
578 | |
579 | // keep the interval [startAngle, endAngle] unique |
580 | if (startAngle < d->endAngle - 360.) { |
581 | qmlWarning(me: this) << "Difference between startAngle ("<< startAngle |
582 | << ") and endAngle ("<< d->endAngle << ") cannot be greater than 360." |
583 | << " Changing endAngle to avoid overlaps."; |
584 | d->endAngle = startAngle + 360.; |
585 | emit endAngleChanged(); |
586 | } |
587 | |
588 | d->startAngle = startAngle; |
589 | // changing the startAngle will change the angle |
590 | // if the value is kept constant |
591 | d->updatePosition(); |
592 | emit startAngleChanged(); |
593 | } |
594 | |
595 | /*! |
596 | \qmlproperty real QtQuick.Controls::Dial::endAngle |
597 | \since 6.6 |
598 | |
599 | This property holds the end angle of the dial in degrees. |
600 | |
601 | This is the \l angle the dial will have for its maximum value, i.e. \l to. |
602 | The \l endAngle has to be bigger than the \l startAngle, smaller than 720 |
603 | and smaller or equal than the \l startAngle + 360 degrees. |
604 | |
605 | \sa endAngle, angle |
606 | */ |
607 | qreal QQuickDial::endAngle() const |
608 | { |
609 | Q_D(const QQuickDial); |
610 | return d->endAngle; |
611 | |
612 | } |
613 | |
614 | void QQuickDial::setEndAngle(qreal endAngle) |
615 | { |
616 | Q_D(QQuickDial); |
617 | if (!d->componentComplete) { |
618 | // Binding evaluation order can cause warnings with certain combinations |
619 | // of start and end angles, so delay the actual setting until after component completion. |
620 | // Store the requested value in the existing member to avoid the need for an extra one. |
621 | d->endAngle = endAngle; |
622 | return; |
623 | } |
624 | |
625 | if (qFuzzyCompare(p1: d->endAngle, p2: endAngle)) |
626 | return; |
627 | |
628 | if (endAngle <= d->startAngle) { |
629 | qmlWarning(me: this) << "endAngle ("<< endAngle |
630 | << ") cannot be less than or equal to startAngle ("<< d->startAngle << ")"; |
631 | return; |
632 | } |
633 | |
634 | // Keep the interval around 0 |
635 | if (endAngle >= 720.) { |
636 | qmlWarning(me: this) << "endAngle ("<< endAngle << ") cannot be greater than or equal to 720"; |
637 | return; |
638 | } |
639 | |
640 | // keep the interval [startAngle, endAngle] unique |
641 | if (endAngle > d->startAngle + 360.) { |
642 | qmlWarning(me: this) << "Difference between startAngle ("<< d->startAngle |
643 | << ") and endAngle ("<< endAngle << ") cannot be greater than 360." |
644 | << " Changing startAngle to avoid overlaps."; |
645 | d->startAngle = endAngle - 360.; |
646 | emit startAngleChanged(); |
647 | } |
648 | |
649 | d->endAngle = endAngle; |
650 | // changing the startAngle will change the angle |
651 | // if the value is kept constant |
652 | d->updatePosition(); |
653 | emit endAngleChanged(); |
654 | } |
655 | |
656 | /*! |
657 | \qmlproperty enumeration QtQuick.Controls::Dial::snapMode |
658 | |
659 | This property holds the snap mode. |
660 | |
661 | The snap mode works with the \l stepSize to allow the handle to snap to |
662 | certain points along the dial. |
663 | |
664 | Possible values: |
665 | \value Dial.NoSnap The dial does not snap (default). |
666 | \value Dial.SnapAlways The dial snaps while the handle is dragged. |
667 | \value Dial.SnapOnRelease The dial does not snap while being dragged, but only after the handle is released. |
668 | |
669 | \sa stepSize |
670 | */ |
671 | QQuickDial::SnapMode QQuickDial::snapMode() const |
672 | { |
673 | Q_D(const QQuickDial); |
674 | return d->snapMode; |
675 | } |
676 | |
677 | void QQuickDial::setSnapMode(SnapMode mode) |
678 | { |
679 | Q_D(QQuickDial); |
680 | if (d->snapMode == mode) |
681 | return; |
682 | |
683 | d->snapMode = mode; |
684 | emit snapModeChanged(); |
685 | } |
686 | |
687 | /*! |
688 | \since QtQuick.Controls 2.5 (Qt 5.12) |
689 | \qmlproperty enumeration QtQuick.Controls::Dial::inputMode |
690 | |
691 | This property holds the input mode. |
692 | |
693 | \include qquickdial.qdocinc inputMode |
694 | |
695 | The default value is \c Dial.Circular. |
696 | */ |
697 | QQuickDial::InputMode QQuickDial::inputMode() const |
698 | { |
699 | Q_D(const QQuickDial); |
700 | return d->inputMode; |
701 | } |
702 | |
703 | void QQuickDial::setInputMode(QQuickDial::InputMode mode) |
704 | { |
705 | Q_D(QQuickDial); |
706 | if (d->inputMode == mode) |
707 | return; |
708 | |
709 | d->inputMode = mode; |
710 | emit inputModeChanged(); |
711 | } |
712 | |
713 | /*! |
714 | \qmlproperty bool QtQuick.Controls::Dial::wrap |
715 | |
716 | This property holds whether the dial wraps when dragged. |
717 | |
718 | For example, when this property is set to \c true, dragging the dial past |
719 | the \l to position will result in the handle being positioned at the |
720 | \l from position, and vice versa: |
721 | |
722 | \image qtquickcontrols-dial-wrap.gif |
723 | |
724 | When this property is \c false, it's not possible to drag the dial across |
725 | the from and to values. |
726 | |
727 | \image qtquickcontrols-dial-no-wrap.gif |
728 | |
729 | The default value is \c false. |
730 | */ |
731 | bool QQuickDial::wrap() const |
732 | { |
733 | Q_D(const QQuickDial); |
734 | return d->wrap; |
735 | } |
736 | |
737 | void QQuickDial::setWrap(bool wrap) |
738 | { |
739 | Q_D(QQuickDial); |
740 | if (d->wrap == wrap) |
741 | return; |
742 | |
743 | d->wrap = wrap; |
744 | emit wrapChanged(); |
745 | } |
746 | |
747 | /*! |
748 | \qmlproperty bool QtQuick.Controls::Dial::pressed |
749 | |
750 | This property holds whether the dial is pressed. |
751 | |
752 | The dial will be pressed when either the mouse is pressed over it, or a key |
753 | such as \c Qt.Key_Left is held down. If you'd prefer not to have the dial |
754 | be pressed upon key presses (due to styling reasons, for example), you can |
755 | use the \l {Keys}{Keys attached property}: |
756 | |
757 | \code |
758 | Dial { |
759 | Keys.onLeftPressed: {} |
760 | } |
761 | \endcode |
762 | |
763 | This will result in pressed only being \c true upon mouse presses. |
764 | */ |
765 | bool QQuickDial::isPressed() const |
766 | { |
767 | Q_D(const QQuickDial); |
768 | return d->pressed; |
769 | } |
770 | |
771 | void QQuickDial::setPressed(bool pressed) |
772 | { |
773 | Q_D(QQuickDial); |
774 | if (d->pressed == pressed) |
775 | return; |
776 | |
777 | d->pressed = pressed; |
778 | setAccessibleProperty(propertyName: "pressed", value: pressed); |
779 | emit pressedChanged(); |
780 | } |
781 | |
782 | /*! |
783 | \qmlproperty Item QtQuick.Controls::Dial::handle |
784 | |
785 | This property holds the handle of the dial. |
786 | |
787 | The handle acts as a visual indicator of the position of the dial. |
788 | |
789 | \sa {Customizing Dial} |
790 | */ |
791 | QQuickItem *QQuickDial::handle() const |
792 | { |
793 | QQuickDialPrivate *d = const_cast<QQuickDialPrivate *>(d_func()); |
794 | if (!d->handle) |
795 | d->executeHandle(); |
796 | return d->handle; |
797 | } |
798 | |
799 | void QQuickDial::setHandle(QQuickItem *handle) |
800 | { |
801 | Q_D(QQuickDial); |
802 | if (handle == d->handle) |
803 | return; |
804 | |
805 | QQuickControlPrivate::warnIfCustomizationNotSupported(control: this, item: handle, QStringLiteral("handle")); |
806 | |
807 | if (!d->handle.isExecuting()) |
808 | d->cancelHandle(); |
809 | |
810 | QQuickControlPrivate::hideOldItem(item: d->handle); |
811 | d->handle = handle; |
812 | if (d->handle && !d->handle->parentItem()) |
813 | d->handle->setParentItem(this); |
814 | if (!d->handle.isExecuting()) |
815 | emit handleChanged(); |
816 | } |
817 | |
818 | /*! |
819 | \since QtQuick.Controls 2.2 (Qt 5.9) |
820 | \qmlproperty bool QtQuick.Controls::Dial::live |
821 | |
822 | This property holds whether the dial provides live updates for the \l value |
823 | property while the handle is dragged. |
824 | |
825 | The default value is \c true. |
826 | |
827 | \sa value |
828 | */ |
829 | bool QQuickDial::live() const |
830 | { |
831 | Q_D(const QQuickDial); |
832 | return d->live; |
833 | } |
834 | |
835 | void QQuickDial::setLive(bool live) |
836 | { |
837 | Q_D(QQuickDial); |
838 | if (d->live == live) |
839 | return; |
840 | |
841 | d->live = live; |
842 | emit liveChanged(); |
843 | } |
844 | |
845 | /*! |
846 | \qmlmethod void QtQuick.Controls::Dial::increase() |
847 | |
848 | Increases the value by \l stepSize, or \c 0.1 if stepSize is not defined. |
849 | |
850 | \sa stepSize |
851 | */ |
852 | void QQuickDial::increase() |
853 | { |
854 | Q_D(QQuickDial); |
855 | qreal step = qFuzzyIsNull(d: d->stepSize) ? 0.1 : d->stepSize; |
856 | setValue(d->value + step); |
857 | } |
858 | |
859 | /*! |
860 | \qmlmethod void QtQuick.Controls::Dial::decrease() |
861 | |
862 | Decreases the value by \l stepSize, or \c 0.1 if stepSize is not defined. |
863 | |
864 | \sa stepSize |
865 | */ |
866 | void QQuickDial::decrease() |
867 | { |
868 | Q_D(QQuickDial); |
869 | qreal step = qFuzzyIsNull(d: d->stepSize) ? 0.1 : d->stepSize; |
870 | setValue(d->value - step); |
871 | } |
872 | |
873 | void QQuickDial::keyPressEvent(QKeyEvent *event) |
874 | { |
875 | Q_D(QQuickDial); |
876 | const qreal oldValue = d->value; |
877 | switch (event->key()) { |
878 | case Qt::Key_Left: |
879 | case Qt::Key_Down: |
880 | setPressed(true); |
881 | if (isMirrored()) |
882 | increase(); |
883 | else |
884 | decrease(); |
885 | break; |
886 | |
887 | case Qt::Key_Right: |
888 | case Qt::Key_Up: |
889 | setPressed(true); |
890 | if (isMirrored()) |
891 | decrease(); |
892 | else |
893 | increase(); |
894 | break; |
895 | |
896 | case Qt::Key_Home: |
897 | setPressed(true); |
898 | setValue(isMirrored() ? d->to : d->from); |
899 | break; |
900 | |
901 | case Qt::Key_End: |
902 | setPressed(true); |
903 | setValue(isMirrored() ? d->from : d->to); |
904 | break; |
905 | |
906 | default: |
907 | event->ignore(); |
908 | QQuickControl::keyPressEvent(event); |
909 | break; |
910 | } |
911 | if (!qFuzzyCompare(p1: d->value, p2: oldValue)) |
912 | emit moved(); |
913 | } |
914 | |
915 | void QQuickDial::keyReleaseEvent(QKeyEvent *event) |
916 | { |
917 | QQuickControl::keyReleaseEvent(event); |
918 | setPressed(false); |
919 | } |
920 | |
921 | void QQuickDial::mousePressEvent(QMouseEvent *event) |
922 | { |
923 | Q_D(QQuickDial); |
924 | QQuickControl::mousePressEvent(event); |
925 | d->handleMove(point: event->position(), timestamp: event->timestamp()); |
926 | setKeepMouseGrab(true); |
927 | } |
928 | |
929 | #if QT_CONFIG(quicktemplates2_multitouch) |
930 | void QQuickDial::touchEvent(QTouchEvent *event) |
931 | { |
932 | Q_D(QQuickDial); |
933 | switch (event->type()) { |
934 | case QEvent::TouchUpdate: |
935 | for (const QTouchEvent::TouchPoint &point : event->points()) { |
936 | if (!d->acceptTouch(point)) |
937 | continue; |
938 | |
939 | switch (point.state()) { |
940 | case QEventPoint::Updated: |
941 | if (!keepTouchGrab()) { |
942 | bool overXDragThreshold = QQuickWindowPrivate::dragOverThreshold(d: point.position().x() - d->pressPoint.x(), axis: Qt::XAxis, tp: &point); |
943 | setKeepTouchGrab(overXDragThreshold); |
944 | |
945 | if (!overXDragThreshold) { |
946 | bool overYDragThreshold = QQuickWindowPrivate::dragOverThreshold(d: point.position().y() - d->pressPoint.y(), axis: Qt::YAxis, tp: &point); |
947 | setKeepTouchGrab(overYDragThreshold); |
948 | } |
949 | } |
950 | if (keepTouchGrab()) |
951 | d->handleMove(point: point.position(), timestamp: event->timestamp()); |
952 | break; |
953 | |
954 | default: |
955 | QQuickControl::touchEvent(event); |
956 | break; |
957 | } |
958 | } |
959 | break; |
960 | |
961 | default: |
962 | QQuickControl::touchEvent(event); |
963 | break; |
964 | } |
965 | } |
966 | #endif |
967 | |
968 | #if QT_CONFIG(wheelevent) |
969 | void QQuickDial::wheelEvent(QWheelEvent *event) |
970 | { |
971 | Q_D(QQuickDial); |
972 | QQuickControl::wheelEvent(event); |
973 | if (d->wheelEnabled) { |
974 | const qreal oldValue = d->value; |
975 | const QPointF angle = event->angleDelta(); |
976 | const qreal delta = (qFuzzyIsNull(d: angle.y()) ? angle.x() : (event->inverted() ? -angle.y() : angle.y())) / int(QWheelEvent::DefaultDeltasPerStep); |
977 | const qreal step = qFuzzyIsNull(d: d->stepSize) ? 0.1 : d->stepSize; |
978 | setValue(oldValue + step * delta); |
979 | event->setAccepted(!qFuzzyCompare(p1: d->value, p2: oldValue)); |
980 | } |
981 | } |
982 | #endif |
983 | |
984 | void QQuickDial::mirrorChange() |
985 | { |
986 | QQuickControl::mirrorChange(); |
987 | emit angleChanged(); |
988 | } |
989 | |
990 | void QQuickDial::componentComplete() |
991 | { |
992 | Q_D(QQuickDial); |
993 | d->executeHandle(complete: true); |
994 | QQuickControl::componentComplete(); |
995 | |
996 | // Set the (delayed) start and end angles, if necessary (see the setters for more info). |
997 | if (!qFuzzyCompare(p1: d->startAngle, p2: defaultStartAngle)) { |
998 | const qreal startAngle = d->startAngle; |
999 | // Temporarily set it to something else so that it sees that it has changed. |
1000 | d->startAngle = defaultStartAngle; |
1001 | setStartAngle(startAngle); |
1002 | } |
1003 | |
1004 | if (!qFuzzyCompare(p1: d->endAngle, p2: defaultEndAngle)) { |
1005 | const qreal endAngle = d->endAngle; |
1006 | d->endAngle = defaultEndAngle; |
1007 | setEndAngle(endAngle); |
1008 | } |
1009 | |
1010 | setValue(d->value); |
1011 | d->updatePosition(); |
1012 | } |
1013 | |
1014 | #if QT_CONFIG(accessibility) |
1015 | void QQuickDial::accessibilityActiveChanged(bool active) |
1016 | { |
1017 | QQuickControl::accessibilityActiveChanged(active); |
1018 | |
1019 | Q_D(QQuickDial); |
1020 | if (active) |
1021 | setAccessibleProperty(propertyName: "pressed", value: d->pressed); |
1022 | } |
1023 | |
1024 | QAccessible::Role QQuickDial::accessibleRole() const |
1025 | { |
1026 | return QAccessible::Dial; |
1027 | } |
1028 | #endif |
1029 | |
1030 | QT_END_NAMESPACE |
1031 | |
1032 | #include "moc_qquickdial_p.cpp" |
1033 |
Definitions
- toUserAngleDeg
- defaultStartAngle
- defaultEndAngle
- QQuickDialPrivate
- valueAt
- snapPosition
- positionAt
- circularPositionAt
- linearPositionAt
- setPosition
- updatePosition
- isLargeChange
- isHorizontalOrVertical
- handlePress
- handleMove
- handleRelease
- handleUngrab
- cancelHandle
- executeHandle
- areRepresentableAsInteger
- updateAllValuesAreInteger
- maybeEmitWrapAround
- QQuickDial
- from
- setFrom
- to
- setTo
- value
- setValue
- position
- angle
- stepSize
- setStepSize
- startAngle
- setStartAngle
- endAngle
- setEndAngle
- snapMode
- setSnapMode
- inputMode
- setInputMode
- wrap
- setWrap
- isPressed
- setPressed
- handle
- setHandle
- live
- setLive
- increase
- decrease
- keyPressEvent
- keyReleaseEvent
- mousePressEvent
- touchEvent
- wheelEvent
- mirrorChange
- componentComplete
- accessibilityActiveChanged
Start learning QML with our Intro Training
Find out more