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 "qquickrangeslider_p.h"
5#include "qquickcontrol_p_p.h"
6#include "qquickdeferredexecute_p_p.h"
7
8#include <QtCore/qscopedpointer.h>
9#include <QtQuick/private/qquickwindow_p.h>
10
11QT_BEGIN_NAMESPACE
12
13/*!
14 \qmltype RangeSlider
15 \inherits Control
16//! \instantiates QQuickRangeSlider
17 \inqmlmodule QtQuick.Controls
18 \since 5.7
19 \ingroup qtquickcontrols-input
20 \ingroup qtquickcontrols-focusscopes
21 \brief Used to select a range of values by sliding two handles along a track.
22
23 \image qtquickcontrols-rangeslider.gif
24
25 RangeSlider is used to select a range specified by two values, by sliding
26 each handle along a track.
27
28 In the example below, custom \l from and \l to values are set, and the
29 initial positions of the \l first and \l second handles are set:
30
31 \code
32 RangeSlider {
33 from: 1
34 to: 100
35 first.value: 25
36 second.value: 75
37 }
38 \endcode
39
40 In order to perform an action when the value for a particular handle changes,
41 use the following syntax:
42
43 \code
44 first.onMoved: console.log("first.value changed to " + first.value)
45 \endcode
46
47 The \l {first.position} and \l {second.position} properties are expressed as
48 fractions of the control's size, in the range \c {0.0 - 1.0}.
49 The \l {first.visualPosition} and \l {second.visualPosition} properties are
50 the same, except that they are reversed in a
51 \l {Right-to-left User Interfaces}{right-to-left} application.
52 The \c visualPosition is useful for positioning the handles when styling
53 RangeSlider. In the example above, \l {first.visualPosition} will be \c 0.24
54 in a left-to-right application, and \c 0.76 in a right-to-left application.
55
56 For a slider that allows the user to select a single value, see \l Slider.
57
58 \sa {Customizing RangeSlider}, {Input Controls},
59 {Focus Management in Qt Quick Controls}
60*/
61
62class QQuickRangeSliderNodePrivate : public QObjectPrivate
63{
64 Q_DECLARE_PUBLIC(QQuickRangeSliderNode)
65public:
66 QQuickRangeSliderNodePrivate(qreal value, QQuickRangeSlider *slider)
67 : value(value),
68 slider(slider)
69 {
70 }
71
72 bool isFirst() const;
73
74 void setPosition(qreal position, bool ignoreOtherPosition = false);
75 void updatePosition(bool ignoreOtherPosition = false);
76
77 void cancelHandle();
78 void executeHandle(bool complete = false);
79
80 static QQuickRangeSliderNodePrivate *get(QQuickRangeSliderNode *node);
81
82 qreal value = 0;
83 bool isPendingValue = false;
84 qreal pendingValue = 0;
85 qreal position = 0;
86 QQuickDeferredPointer<QQuickItem> handle;
87 QQuickRangeSlider *slider = nullptr;
88 bool pressed = false;
89 bool hovered = false;
90 int touchId = -1;
91};
92
93bool QQuickRangeSliderNodePrivate::isFirst() const
94{
95 return this == get(node: slider->first());
96}
97
98void QQuickRangeSliderNodePrivate::setPosition(qreal position, bool ignoreOtherPosition)
99{
100 Q_Q(QQuickRangeSliderNode);
101
102 const qreal min = isFirst() || ignoreOtherPosition ? 0.0 : qMax<qreal>(a: 0.0, b: slider->first()->position());
103 const qreal max = !isFirst() || ignoreOtherPosition ? 1.0 : qMin<qreal>(a: 1.0, b: slider->second()->position());
104 position = qBound(min, val: position, max);
105 if (!qFuzzyCompare(p1: this->position, p2: position)) {
106 this->position = position;
107 emit q->positionChanged();
108 emit q->visualPositionChanged();
109 }
110}
111
112void QQuickRangeSliderNodePrivate::updatePosition(bool ignoreOtherPosition)
113{
114 qreal pos = 0;
115 if (!qFuzzyCompare(p1: slider->from(), p2: slider->to()))
116 pos = (value - slider->from()) / (slider->to() - slider->from());
117 setPosition(position: pos, ignoreOtherPosition);
118}
119
120void QQuickRangeSliderNodePrivate::cancelHandle()
121{
122 Q_Q(QQuickRangeSliderNode);
123 quickCancelDeferred(object: q, property: handleName());
124}
125
126void QQuickRangeSliderNodePrivate::executeHandle(bool complete)
127{
128 Q_Q(QQuickRangeSliderNode);
129 if (handle.wasExecuted())
130 return;
131
132 if (!handle || complete)
133 quickBeginDeferred(object: q, property: handleName(), delegate&: handle);
134 if (complete)
135 quickCompleteDeferred(object: q, property: handleName(), delegate&: handle);
136}
137
138QQuickRangeSliderNodePrivate *QQuickRangeSliderNodePrivate::get(QQuickRangeSliderNode *node)
139{
140 return node->d_func();
141}
142
143QQuickRangeSliderNode::QQuickRangeSliderNode(qreal value, QQuickRangeSlider *slider)
144 : QObject(*(new QQuickRangeSliderNodePrivate(value, slider)), slider)
145{
146}
147
148QQuickRangeSliderNode::~QQuickRangeSliderNode()
149{
150}
151
152qreal QQuickRangeSliderNode::value() const
153{
154 Q_D(const QQuickRangeSliderNode);
155 return d->value;
156}
157
158void QQuickRangeSliderNode::setValue(qreal value)
159{
160 Q_D(QQuickRangeSliderNode);
161 if (!d->slider->isComponentComplete()) {
162 d->pendingValue = value;
163 d->isPendingValue = true;
164 return;
165 }
166
167 // First, restrict the first value to be within to and from.
168 const qreal smaller = qMin(a: d->slider->to(), b: d->slider->from());
169 const qreal larger = qMax(a: d->slider->to(), b: d->slider->from());
170 value = qBound(min: smaller, val: value, max: larger);
171
172 // Then, ensure that it doesn't go past the other value,
173 // a check that depends on whether or not the range is inverted.
174 const bool invertedRange = d->slider->from() > d->slider->to();
175 if (d->isFirst()) {
176 if (invertedRange) {
177 if (value < d->slider->second()->value())
178 value = d->slider->second()->value();
179 } else {
180 if (value > d->slider->second()->value())
181 value = d->slider->second()->value();
182 }
183 } else {
184 if (invertedRange) {
185 if (value > d->slider->first()->value())
186 value = d->slider->first()->value();
187 } else {
188 if (value < d->slider->first()->value())
189 value = d->slider->first()->value();
190 }
191 }
192
193 if (!qFuzzyCompare(p1: d->value, p2: value)) {
194 d->value = value;
195 d->updatePosition();
196 emit valueChanged();
197 }
198}
199
200qreal QQuickRangeSliderNode::position() const
201{
202 Q_D(const QQuickRangeSliderNode);
203 return d->position;
204}
205
206qreal QQuickRangeSliderNode::visualPosition() const
207{
208 Q_D(const QQuickRangeSliderNode);
209 if (d->slider->orientation() == Qt::Vertical || d->slider->isMirrored())
210 return 1.0 - d->position;
211 return d->position;
212}
213
214QQuickItem *QQuickRangeSliderNode::handle() const
215{
216 QQuickRangeSliderNodePrivate *d = const_cast<QQuickRangeSliderNodePrivate *>(d_func());
217 if (!d->handle)
218 d->executeHandle();
219 return d->handle;
220}
221
222void QQuickRangeSliderNode::setHandle(QQuickItem *handle)
223{
224 Q_D(QQuickRangeSliderNode);
225 if (d->handle == handle)
226 return;
227
228 if (!d->handle.isExecuting())
229 d->cancelHandle();
230
231 const qreal oldImplicitHandleWidth = implicitHandleWidth();
232 const qreal oldImplicitHandleHeight = implicitHandleHeight();
233
234 QQuickControlPrivate::get(control: d->slider)->removeImplicitSizeListener(item: d->handle);
235 QQuickControlPrivate::hideOldItem(item: d->handle);
236 d->handle = handle;
237
238 if (handle) {
239 if (!handle->parentItem())
240 handle->setParentItem(d->slider);
241
242 QQuickItem *firstHandle = QQuickRangeSliderNodePrivate::get(node: d->slider->first())->handle;
243 QQuickItem *secondHandle = QQuickRangeSliderNodePrivate::get(node: d->slider->second())->handle;
244 if (firstHandle && secondHandle) {
245 // The order of property assignments in QML is undefined,
246 // but we need the first handle to be before the second due
247 // to focus order constraints, so check for that here.
248 const QList<QQuickItem *> childItems = d->slider->childItems();
249 const int firstIndex = childItems.indexOf(t: firstHandle);
250 const int secondIndex = childItems.indexOf(t: secondHandle);
251 if (firstIndex != -1 && secondIndex != -1 && firstIndex > secondIndex) {
252 firstHandle->stackBefore(secondHandle);
253 // Ensure we have some way of knowing which handle is above
254 // the other when it comes to mouse presses, and also that
255 // they are rendered in the correct order.
256 secondHandle->setZ(secondHandle->z() + 1);
257 }
258 }
259
260 handle->setActiveFocusOnTab(true);
261 QQuickControlPrivate::get(control: d->slider)->addImplicitSizeListener(item: handle);
262 }
263
264 if (!qFuzzyCompare(p1: oldImplicitHandleWidth, p2: implicitHandleWidth()))
265 emit implicitHandleWidthChanged();
266 if (!qFuzzyCompare(p1: oldImplicitHandleHeight, p2: implicitHandleHeight()))
267 emit implicitHandleHeightChanged();
268 if (!d->handle.isExecuting())
269 emit handleChanged();
270}
271
272bool QQuickRangeSliderNode::isPressed() const
273{
274 Q_D(const QQuickRangeSliderNode);
275 return d->pressed;
276}
277
278void QQuickRangeSliderNode::setPressed(bool pressed)
279{
280 Q_D(QQuickRangeSliderNode);
281 if (d->pressed == pressed)
282 return;
283
284 d->pressed = pressed;
285 d->slider->setAccessibleProperty(propertyName: "pressed", value: pressed || d->slider->second()->isPressed());
286 emit pressedChanged();
287}
288
289bool QQuickRangeSliderNode::isHovered() const
290{
291 Q_D(const QQuickRangeSliderNode);
292 return d->hovered;
293}
294
295void QQuickRangeSliderNode::setHovered(bool hovered)
296{
297 Q_D(QQuickRangeSliderNode);
298 if (d->hovered == hovered)
299 return;
300
301 d->hovered = hovered;
302 emit hoveredChanged();
303}
304
305qreal QQuickRangeSliderNode::implicitHandleWidth() const
306{
307 Q_D(const QQuickRangeSliderNode);
308 if (!d->handle)
309 return 0;
310 return d->handle->implicitWidth();
311}
312
313qreal QQuickRangeSliderNode::implicitHandleHeight() const
314{
315 Q_D(const QQuickRangeSliderNode);
316 if (!d->handle)
317 return 0;
318 return d->handle->implicitHeight();
319}
320
321void QQuickRangeSliderNode::increase()
322{
323 Q_D(QQuickRangeSliderNode);
324 qreal step = qFuzzyIsNull(d: d->slider->stepSize()) ? 0.1 : d->slider->stepSize();
325 setValue(d->value + step);
326}
327
328void QQuickRangeSliderNode::decrease()
329{
330 Q_D(QQuickRangeSliderNode);
331 qreal step = qFuzzyIsNull(d: d->slider->stepSize()) ? 0.1 : d->slider->stepSize();
332 setValue(d->value - step);
333}
334
335static const qreal defaultFrom = 0.0;
336static const qreal defaultTo = 1.0;
337
338class QQuickRangeSliderPrivate : public QQuickControlPrivate
339{
340 Q_DECLARE_PUBLIC(QQuickRangeSlider)
341
342public:
343 QQuickRangeSliderNode *pressedNode(int touchId = -1) const;
344
345#if QT_CONFIG(quicktemplates2_multitouch)
346 bool acceptTouch(const QTouchEvent::TouchPoint &point) override;
347#endif
348 bool handlePress(const QPointF &point, ulong timestamp) override;
349 bool handleMove(const QPointF &point, ulong timestamp) override;
350 bool handleRelease(const QPointF &point, ulong timestamp) override;
351 void handleUngrab() override;
352
353 void updateHover(const QPointF &pos);
354
355 void itemImplicitWidthChanged(QQuickItem *item) override;
356 void itemImplicitHeightChanged(QQuickItem *item) override;
357
358 bool live = true;
359 qreal from = defaultFrom;
360 qreal to = defaultTo;
361 qreal stepSize = 0;
362 qreal touchDragThreshold = -1;
363 QQuickRangeSliderNode *first = nullptr;
364 QQuickRangeSliderNode *second = nullptr;
365 QPointF pressPoint;
366 Qt::Orientation orientation = Qt::Horizontal;
367 QQuickRangeSlider::SnapMode snapMode = QQuickRangeSlider::NoSnap;
368};
369
370static qreal valueAt(const QQuickRangeSlider *slider, qreal position)
371{
372 return slider->from() + (slider->to() - slider->from()) * position;
373}
374
375static qreal snapPosition(const QQuickRangeSlider *slider, qreal position)
376{
377 const qreal range = slider->to() - slider->from();
378 if (qFuzzyIsNull(d: range))
379 return position;
380
381 const qreal effectiveStep = slider->stepSize() / range;
382 if (qFuzzyIsNull(d: effectiveStep))
383 return position;
384
385 return qRound(d: position / effectiveStep) * effectiveStep;
386}
387
388static qreal positionAt(const QQuickRangeSlider *slider, QQuickItem *handle, const QPointF &point)
389{
390 if (slider->orientation() == Qt::Horizontal) {
391 const qreal hw = handle ? handle->width() : 0;
392 const qreal offset = hw / 2;
393 const qreal extent = slider->availableWidth() - hw;
394 if (!qFuzzyIsNull(d: extent)) {
395 if (slider->isMirrored())
396 return (slider->width() - point.x() - slider->rightPadding() - offset) / extent;
397 return (point.x() - slider->leftPadding() - offset) / extent;
398 }
399 } else {
400 const qreal hh = handle ? handle->height() : 0;
401 const qreal offset = hh / 2;
402 const qreal extent = slider->availableHeight() - hh;
403 if (!qFuzzyIsNull(d: extent))
404 return (slider->height() - point.y() - slider->bottomPadding() - offset) / extent;
405 }
406 return 0;
407}
408
409QQuickRangeSliderNode *QQuickRangeSliderPrivate::pressedNode(int touchId) const
410{
411 if (touchId == -1)
412 return first->isPressed() ? first : (second->isPressed() ? second : nullptr);
413 if (QQuickRangeSliderNodePrivate::get(node: first)->touchId == touchId)
414 return first;
415 if (QQuickRangeSliderNodePrivate::get(node: second)->touchId == touchId)
416 return second;
417 return nullptr;
418}
419
420#if QT_CONFIG(quicktemplates2_multitouch)
421bool QQuickRangeSliderPrivate::acceptTouch(const QTouchEvent::TouchPoint &point)
422{
423 int firstId = QQuickRangeSliderNodePrivate::get(node: first)->touchId;
424 int secondId = QQuickRangeSliderNodePrivate::get(node: second)->touchId;
425
426 if (((firstId == -1 || secondId == -1) && point.state() == QEventPoint::Pressed) || point.id() == firstId || point.id() == secondId) {
427 touchId = point.id();
428 return true;
429 }
430
431 return false;
432}
433#endif
434
435bool QQuickRangeSliderPrivate::handlePress(const QPointF &point, ulong timestamp)
436{
437 Q_Q(QQuickRangeSlider);
438 QQuickControlPrivate::handlePress(point, timestamp);
439 pressPoint = point;
440
441 QQuickItem *firstHandle = first->handle();
442 QQuickItem *secondHandle = second->handle();
443 const bool firstHit = firstHandle && !first->isPressed() && firstHandle->contains(point: q->mapToItem(item: firstHandle, point));
444 const bool secondHit = secondHandle && !second->isPressed() && secondHandle->contains(point: q->mapToItem(item: secondHandle, point));
445 QQuickRangeSliderNode *hitNode = nullptr;
446 QQuickRangeSliderNode *otherNode = nullptr;
447
448 if (firstHit && secondHit) {
449 // choose highest
450 hitNode = firstHandle->z() > secondHandle->z() ? first : second;
451 otherNode = firstHandle->z() > secondHandle->z() ? second : first;
452 } else if (firstHit) {
453 hitNode = first;
454 otherNode = second;
455 } else if (secondHit) {
456 hitNode = second;
457 otherNode = first;
458 } else {
459 // find the nearest
460 const qreal firstPos = positionAt(slider: q, handle: firstHandle, point);
461 const qreal secondPos = positionAt(slider: q, handle: secondHandle, point);
462 const qreal firstDistance = qAbs(t: firstPos - first->position());
463 const qreal secondDistance = qAbs(t: secondPos - second->position());
464
465 if (qFuzzyCompare(p1: firstDistance, p2: secondDistance)) {
466 // same distance => choose the one that can be moved towards the press position
467 const bool inverted = from > to;
468 if ((!inverted && firstPos < first->position()) || (inverted && firstPos > first->position())) {
469 hitNode = first;
470 otherNode = second;
471 } else {
472 hitNode = second;
473 otherNode = first;
474 }
475 } else if (firstDistance < secondDistance) {
476 hitNode = first;
477 otherNode = second;
478 } else {
479 hitNode = second;
480 otherNode = first;
481 }
482 }
483
484 if (hitNode) {
485 hitNode->setPressed(true);
486 if (QQuickItem *handle = hitNode->handle())
487 handle->setZ(1);
488 QQuickRangeSliderNodePrivate::get(node: hitNode)->touchId = touchId;
489 }
490 if (otherNode) {
491 if (QQuickItem *handle = otherNode->handle())
492 handle->setZ(0);
493 }
494 return true;
495}
496
497bool QQuickRangeSliderPrivate::handleMove(const QPointF &point, ulong timestamp)
498{
499 Q_Q(QQuickRangeSlider);
500 QQuickControlPrivate::handleMove(point, timestamp);
501 QQuickRangeSliderNode *pressedNode = QQuickRangeSliderPrivate::pressedNode(touchId);
502 if (pressedNode) {
503 const qreal oldPos = pressedNode->position();
504 qreal pos = positionAt(slider: q, handle: pressedNode->handle(), point);
505 if (snapMode == QQuickRangeSlider::SnapAlways)
506 pos = snapPosition(slider: q, position: pos);
507 if (live)
508 pressedNode->setValue(valueAt(slider: q, position: pos));
509 else
510 QQuickRangeSliderNodePrivate::get(node: pressedNode)->setPosition(position: pos);
511
512 if (!qFuzzyCompare(p1: pressedNode->position(), p2: oldPos))
513 emit pressedNode->moved();
514 }
515 return true;
516}
517
518bool QQuickRangeSliderPrivate::handleRelease(const QPointF &point, ulong timestamp)
519{
520 Q_Q(QQuickRangeSlider);
521 QQuickControlPrivate::handleRelease(point, timestamp);
522 pressPoint = QPointF();
523
524 QQuickRangeSliderNode *pressedNode = QQuickRangeSliderPrivate::pressedNode(touchId);
525 if (!pressedNode)
526 return true;
527 QQuickRangeSliderNodePrivate *pressedNodePrivate = QQuickRangeSliderNodePrivate::get(node: pressedNode);
528
529 const qreal oldPos = pressedNode->position();
530 qreal pos = positionAt(slider: q, handle: pressedNode->handle(), point);
531 if (snapMode != QQuickRangeSlider::NoSnap)
532 pos = snapPosition(slider: q, position: pos);
533 qreal val = valueAt(slider: q, position: pos);
534 if (!qFuzzyCompare(p1: val, p2: pressedNode->value()))
535 pressedNode->setValue(val);
536 else if (snapMode != QQuickRangeSlider::NoSnap)
537 pressedNodePrivate->setPosition(position: pos);
538 q->setKeepMouseGrab(false);
539 q->setKeepTouchGrab(false);
540
541 if (!qFuzzyCompare(p1: pressedNode->position(), p2: oldPos))
542 emit pressedNode->moved();
543
544 pressedNode->setPressed(false);
545 pressedNodePrivate->touchId = -1;
546 return true;
547}
548
549void QQuickRangeSliderPrivate::handleUngrab()
550{
551 QQuickControlPrivate::handleUngrab();
552 pressPoint = QPointF();
553 first->setPressed(false);
554 second->setPressed(false);
555 QQuickRangeSliderNodePrivate::get(node: first)->touchId = -1;
556 QQuickRangeSliderNodePrivate::get(node: second)->touchId = -1;
557}
558
559void QQuickRangeSliderPrivate::updateHover(const QPointF &pos)
560{
561 Q_Q(QQuickRangeSlider);
562 QQuickItem *firstHandle = first->handle();
563 QQuickItem *secondHandle = second->handle();
564 bool firstHandleHovered = firstHandle && firstHandle->isEnabled()
565 && firstHandle->contains(point: q->mapToItem(item: firstHandle, point: pos));
566 bool secondHandleHovered = secondHandle && secondHandle->isEnabled()
567 && secondHandle->contains(point: q->mapToItem(item: secondHandle, point: pos));
568
569 if (firstHandleHovered && secondHandleHovered) {
570 // Only hover the handle with the higher Z value.
571 const bool firstHandleHasHigherZ = firstHandle->z() > secondHandle->z();
572 firstHandleHovered = firstHandleHasHigherZ;
573 secondHandleHovered = !firstHandleHasHigherZ;
574 }
575 first->setHovered(firstHandleHovered);
576 second->setHovered(secondHandleHovered);
577}
578
579void QQuickRangeSliderPrivate::itemImplicitWidthChanged(QQuickItem *item)
580{
581 QQuickControlPrivate::itemImplicitWidthChanged(item);
582 if (item == first->handle())
583 emit first->implicitHandleWidthChanged();
584 else if (item == second->handle())
585 emit second->implicitHandleWidthChanged();
586}
587
588void QQuickRangeSliderPrivate::itemImplicitHeightChanged(QQuickItem *item)
589{
590 QQuickControlPrivate::itemImplicitHeightChanged(item);
591 if (item == first->handle())
592 emit first->implicitHandleHeightChanged();
593 else if (item == second->handle())
594 emit second->implicitHandleHeightChanged();
595}
596
597QQuickRangeSlider::QQuickRangeSlider(QQuickItem *parent)
598 : QQuickControl(*(new QQuickRangeSliderPrivate), parent)
599{
600 Q_D(QQuickRangeSlider);
601 d->first = new QQuickRangeSliderNode(0.0, this);
602 d->second = new QQuickRangeSliderNode(1.0, this);
603
604 setFlag(flag: QQuickItem::ItemIsFocusScope);
605 setAcceptedMouseButtons(Qt::LeftButton);
606#if QT_CONFIG(quicktemplates2_multitouch)
607 setAcceptTouchEvents(true);
608#endif
609#if QT_CONFIG(cursor)
610 setCursor(Qt::ArrowCursor);
611#endif
612}
613
614QQuickRangeSlider::~QQuickRangeSlider()
615{
616 Q_D(QQuickRangeSlider);
617 d->removeImplicitSizeListener(item: d->first->handle());
618 d->removeImplicitSizeListener(item: d->second->handle());
619}
620
621/*!
622 \qmlproperty real QtQuick.Controls::RangeSlider::from
623
624 This property holds the starting value for the range. The default value is \c 0.0.
625
626 \sa to, first.value, second.value
627*/
628qreal QQuickRangeSlider::from() const
629{
630 Q_D(const QQuickRangeSlider);
631 return d->from;
632}
633
634void QQuickRangeSlider::setFrom(qreal from)
635{
636 Q_D(QQuickRangeSlider);
637 if (qFuzzyCompare(p1: d->from, p2: from))
638 return;
639
640 d->from = from;
641 emit fromChanged();
642
643 if (isComponentComplete()) {
644 d->first->setValue(d->first->value());
645 d->second->setValue(d->second->value());
646 auto *firstPrivate = QQuickRangeSliderNodePrivate::get(node: d->first);
647 auto *secondPrivate = QQuickRangeSliderNodePrivate::get(node: d->second);
648 firstPrivate->updatePosition(ignoreOtherPosition: true);
649 secondPrivate->updatePosition();
650 }
651}
652
653/*!
654 \qmlproperty real QtQuick.Controls::RangeSlider::to
655
656 This property holds the end value for the range. The default value is \c 1.0.
657
658 \sa from, first.value, second.value
659*/
660qreal QQuickRangeSlider::to() const
661{
662 Q_D(const QQuickRangeSlider);
663 return d->to;
664}
665
666void QQuickRangeSlider::setTo(qreal to)
667{
668 Q_D(QQuickRangeSlider);
669 if (qFuzzyCompare(p1: d->to, p2: to))
670 return;
671
672 d->to = to;
673 emit toChanged();
674
675 if (isComponentComplete()) {
676 d->first->setValue(d->first->value());
677 d->second->setValue(d->second->value());
678 auto *firstPrivate = QQuickRangeSliderNodePrivate::get(node: d->first);
679 auto *secondPrivate = QQuickRangeSliderNodePrivate::get(node: d->second);
680 firstPrivate->updatePosition(ignoreOtherPosition: true);
681 secondPrivate->updatePosition();
682 }
683}
684
685/*!
686 \since QtQuick.Controls 2.5 (Qt 5.12)
687 \qmlproperty qreal QtQuick.Controls::RangeSlider::touchDragThreshold
688
689 This property holds the threshold (in logical pixels) at which a touch drag event will be initiated.
690 The mouse drag threshold won't be affected.
691 The default value is \c Qt.styleHints.startDragDistance.
692
693 \sa QStyleHints
694
695*/
696qreal QQuickRangeSlider::touchDragThreshold() const
697{
698 Q_D(const QQuickRangeSlider);
699 return d->touchDragThreshold;
700}
701
702void QQuickRangeSlider::setTouchDragThreshold(qreal touchDragThreshold)
703{
704 Q_D(QQuickRangeSlider);
705 if (d->touchDragThreshold == touchDragThreshold)
706 return;
707
708 d->touchDragThreshold = touchDragThreshold;
709 emit touchDragThresholdChanged();
710}
711
712void QQuickRangeSlider::resetTouchDragThreshold()
713{
714 setTouchDragThreshold(-1);
715}
716
717/*!
718 \since QtQuick.Controls 2.5 (Qt 5.12)
719 \qmlmethod real QtQuick.Controls::RangeSlider::valueAt(real position)
720
721 Returns the value for the given \a position.
722
723 \sa first.value, second.value, first.position, second.position, live
724*/
725qreal QQuickRangeSlider::valueAt(qreal position) const
726{
727 Q_D(const QQuickRangeSlider);
728 const qreal value = (d->to - d->from) * position;
729 if (qFuzzyIsNull(d: d->stepSize))
730 return d->from + value;
731 return d->from + qRound(d: value / d->stepSize) * d->stepSize;
732}
733
734/*!
735 \qmlproperty real QtQuick.Controls::RangeSlider::first.value
736 \qmlproperty real QtQuick.Controls::RangeSlider::first.position
737 \qmlproperty real QtQuick.Controls::RangeSlider::first.visualPosition
738 \qmlproperty Item QtQuick.Controls::RangeSlider::first.handle
739 \qmlproperty bool QtQuick.Controls::RangeSlider::first.pressed
740 \qmlproperty bool QtQuick.Controls::RangeSlider::first.hovered
741 \qmlproperty real QtQuick.Controls::RangeSlider::first.implicitHandleWidth
742 \qmlproperty real QtQuick.Controls::RangeSlider::first.implicitHandleHeight
743
744 \table
745 \header
746 \li Property
747 \li Description
748 \row
749 \li value
750 \li This property holds the value of the first handle in the range
751 \c from - \c to.
752
753 If \l from is greater than \l to, the value of the first handle
754 must be greater than the second, and vice versa.
755
756 The default value is \c 0.0.
757 \row
758 \li handle
759 \li This property holds the first handle item.
760 \row
761 \li visualPosition
762 \li This property holds the visual position of the first handle.
763
764 The position is expressed as a fraction of the control's size, in the range
765 \c {0.0 - 1.0}. When the control is \l {Control::mirrored}{mirrored}, the
766 value is equal to \c {1.0 - position}. This makes the value suitable for
767 visualizing the slider, taking right-to-left support into account.
768 \row
769 \li position
770 \li This property holds the logical position of the first handle.
771
772 The position is expressed as a fraction of the control's size, in the range
773 \c {0.0 - 1.0}. For visualizing a slider, the right-to-left aware
774 \l {first.visualPosition}{visualPosition} should be used instead.
775 \row
776 \li pressed
777 \li This property holds whether the first handle is pressed by either touch,
778 mouse, or keys.
779 \row
780 \li hovered
781 \li This property holds whether the first handle is hovered.
782 This property was introduced in QtQuick.Controls 2.1.
783 \row
784 \li implicitHandleWidth
785 \li This property holds the implicit width of the first handle.
786 This property was introduced in QtQuick.Controls 2.5.
787 \row
788 \li implicitHandleHeight
789 \li This property holds the implicit height of the first handle.
790 This property was introduced in QtQuick.Controls 2.5.
791 \endtable
792
793 \sa first.moved(), first.increase(), first.decrease()
794*/
795QQuickRangeSliderNode *QQuickRangeSlider::first() const
796{
797 Q_D(const QQuickRangeSlider);
798 return d->first;
799}
800
801/*!
802 \qmlsignal void QtQuick.Controls::RangeSlider::first.moved()
803 \qmlsignal void QtQuick.Controls::RangeSlider::second.moved()
804 \since QtQuick.Controls 2.5
805
806 This signal is emitted when either the first or second handle has been
807 interactively moved by the user by either touch, mouse, or keys.
808
809 \sa first, second
810*/
811
812/*!
813 \qmlproperty real QtQuick.Controls::RangeSlider::second.value
814 \qmlproperty real QtQuick.Controls::RangeSlider::second.position
815 \qmlproperty real QtQuick.Controls::RangeSlider::second.visualPosition
816 \qmlproperty Item QtQuick.Controls::RangeSlider::second.handle
817 \qmlproperty bool QtQuick.Controls::RangeSlider::second.pressed
818 \qmlproperty bool QtQuick.Controls::RangeSlider::second.hovered
819 \qmlproperty real QtQuick.Controls::RangeSlider::second.implicitHandleWidth
820 \qmlproperty real QtQuick.Controls::RangeSlider::second.implicitHandleHeight
821
822 \table
823 \header
824 \li Property
825 \li Description
826 \row
827 \li value
828 \li This property holds the value of the second handle in the range
829 \c from - \c to.
830
831 If \l from is greater than \l to, the value of the first handle
832 must be greater than the second, and vice versa.
833
834 The default value is \c 0.0.
835 \row
836 \li handle
837 \li This property holds the second handle item.
838 \row
839 \li visualPosition
840 \li This property holds the visual position of the second handle.
841
842 The position is expressed as a fraction of the control's size, in the range
843 \c {0.0 - 1.0}. When the control is \l {Control::mirrored}{mirrored}, the
844 value is equal to \c {1.0 - position}. This makes the value suitable for
845 visualizing the slider, taking right-to-left support into account.
846 \row
847 \li position
848 \li This property holds the logical position of the second handle.
849
850 The position is expressed as a fraction of the control's size, in the range
851 \c {0.0 - 1.0}. For visualizing a slider, the right-to-left aware
852 \l {second.visualPosition}{visualPosition} should be used instead.
853 \row
854 \li pressed
855 \li This property holds whether the second handle is pressed by either touch,
856 mouse, or keys.
857 \row
858 \li hovered
859 \li This property holds whether the second handle is hovered.
860 This property was introduced in QtQuick.Controls 2.1.
861 \row
862 \li implicitHandleWidth
863 \li This property holds the implicit width of the second handle.
864 This property was introduced in QtQuick.Controls 2.5.
865 \row
866 \li implicitHandleHeight
867 \li This property holds the implicit height of the second handle.
868 This property was introduced in QtQuick.Controls 2.5.
869 \endtable
870
871 \sa second.moved(), second.increase(), second.decrease()
872*/
873QQuickRangeSliderNode *QQuickRangeSlider::second() const
874{
875 Q_D(const QQuickRangeSlider);
876 return d->second;
877}
878
879/*!
880 \qmlproperty real QtQuick.Controls::RangeSlider::stepSize
881
882 This property holds the step size. The default value is \c 0.0.
883
884 \sa snapMode, first.increase(), first.decrease()
885*/
886qreal QQuickRangeSlider::stepSize() const
887{
888 Q_D(const QQuickRangeSlider);
889 return d->stepSize;
890}
891
892void QQuickRangeSlider::setStepSize(qreal step)
893{
894 Q_D(QQuickRangeSlider);
895 if (qFuzzyCompare(p1: d->stepSize, p2: step))
896 return;
897
898 d->stepSize = step;
899 emit stepSizeChanged();
900}
901
902/*!
903 \qmlproperty enumeration QtQuick.Controls::RangeSlider::snapMode
904
905 This property holds the snap mode.
906
907 The snap mode determines how the slider handles behave with
908 regards to the \l stepSize.
909
910 Possible values:
911 \value RangeSlider.NoSnap The slider does not snap (default).
912 \value RangeSlider.SnapAlways The slider snaps while the handle is dragged.
913 \value RangeSlider.SnapOnRelease The slider does not snap while being dragged, but only after the handle is released.
914
915 For visual explanations of the various modes, see the
916 \l {Slider::}{snapMode} documentation of \l Slider.
917
918 \sa stepSize
919*/
920QQuickRangeSlider::SnapMode QQuickRangeSlider::snapMode() const
921{
922 Q_D(const QQuickRangeSlider);
923 return d->snapMode;
924}
925
926void QQuickRangeSlider::setSnapMode(SnapMode mode)
927{
928 Q_D(QQuickRangeSlider);
929 if (d->snapMode == mode)
930 return;
931
932 d->snapMode = mode;
933 emit snapModeChanged();
934}
935
936/*!
937 \qmlproperty enumeration QtQuick.Controls::RangeSlider::orientation
938
939 This property holds the orientation.
940
941 Possible values:
942 \value Qt.Horizontal Horizontal (default)
943 \value Qt.Vertical Vertical
944
945 \sa horizontal, vertical
946*/
947Qt::Orientation QQuickRangeSlider::orientation() const
948{
949 Q_D(const QQuickRangeSlider);
950 return d->orientation;
951}
952
953void QQuickRangeSlider::setOrientation(Qt::Orientation orientation)
954{
955 Q_D(QQuickRangeSlider);
956 if (d->orientation == orientation)
957 return;
958
959 d->orientation = orientation;
960 emit orientationChanged();
961}
962
963/*!
964 \qmlmethod void QtQuick.Controls::RangeSlider::setValues(real firstValue, real secondValue)
965
966 Sets \l first.value and \l second.value with the given arguments.
967
968 If \l to is larger than \l from and \a firstValue is larger than
969 \a secondValue, firstValue will be clamped to secondValue.
970
971 If \l from is larger than \l to and secondValue is larger than
972 firstValue, secondValue will be clamped to firstValue.
973
974 This function may be necessary to set the first and second values
975 after the control has been completed, as there is a circular
976 dependency between firstValue and secondValue which can cause
977 assigned values to be clamped to each other.
978
979 \sa stepSize
980*/
981void QQuickRangeSlider::setValues(qreal firstValue, qreal secondValue)
982{
983 Q_D(QQuickRangeSlider);
984 // Restrict the values to be within to and from.
985 const qreal smaller = qMin(a: d->to, b: d->from);
986 const qreal larger = qMax(a: d->to, b: d->from);
987 firstValue = qBound(min: smaller, val: firstValue, max: larger);
988 secondValue = qBound(min: smaller, val: secondValue, max: larger);
989
990 if (d->from > d->to) {
991 // If the from and to values are reversed, the secondValue
992 // might be less than the first value, which is not allowed.
993 if (secondValue > firstValue)
994 secondValue = firstValue;
995 } else {
996 // Otherwise, clamp first to second if it's too large.
997 if (firstValue > secondValue)
998 firstValue = secondValue;
999 }
1000
1001 // Then set both values. If they didn't change, no change signal will be emitted.
1002 QQuickRangeSliderNodePrivate *firstPrivate = QQuickRangeSliderNodePrivate::get(node: d->first);
1003 if (firstValue != firstPrivate->value) {
1004 firstPrivate->value = firstValue;
1005 emit d->first->valueChanged();
1006 }
1007
1008 QQuickRangeSliderNodePrivate *secondPrivate = QQuickRangeSliderNodePrivate::get(node: d->second);
1009 if (secondValue != secondPrivate->value) {
1010 secondPrivate->value = secondValue;
1011 emit d->second->valueChanged();
1012 }
1013
1014 // After we've set both values, then we can update the positions.
1015 // If we don't do this last, the positions may be incorrect.
1016 firstPrivate->updatePosition(ignoreOtherPosition: true);
1017 secondPrivate->updatePosition();
1018}
1019
1020/*!
1021 \since QtQuick.Controls 2.2 (Qt 5.9)
1022 \qmlproperty bool QtQuick.Controls::RangeSlider::live
1023
1024 This property holds whether the slider provides live updates for the \l first.value
1025 and \l second.value properties while the respective handles are dragged.
1026
1027 The default value is \c true.
1028
1029 \sa first.value, second.value
1030*/
1031bool QQuickRangeSlider::live() const
1032{
1033 Q_D(const QQuickRangeSlider);
1034 return d->live;
1035}
1036
1037void QQuickRangeSlider::setLive(bool live)
1038{
1039 Q_D(QQuickRangeSlider);
1040 if (d->live == live)
1041 return;
1042
1043 d->live = live;
1044 emit liveChanged();
1045}
1046
1047/*!
1048 \since QtQuick.Controls 2.3 (Qt 5.10)
1049 \qmlproperty bool QtQuick.Controls::RangeSlider::horizontal
1050 \readonly
1051
1052 This property holds whether the slider is horizontal.
1053
1054 \sa orientation
1055*/
1056bool QQuickRangeSlider::isHorizontal() const
1057{
1058 Q_D(const QQuickRangeSlider);
1059 return d->orientation == Qt::Horizontal;
1060}
1061
1062/*!
1063 \since QtQuick.Controls 2.3 (Qt 5.10)
1064 \qmlproperty bool QtQuick.Controls::RangeSlider::vertical
1065 \readonly
1066
1067 This property holds whether the slider is vertical.
1068
1069 \sa orientation
1070*/
1071bool QQuickRangeSlider::isVertical() const
1072{
1073 Q_D(const QQuickRangeSlider);
1074 return d->orientation == Qt::Vertical;
1075}
1076
1077void QQuickRangeSlider::focusInEvent(QFocusEvent *event)
1078{
1079 Q_D(QQuickRangeSlider);
1080 QQuickControl::focusInEvent(event);
1081
1082 // The active focus ends up to RangeSlider when using forceActiveFocus()
1083 // or QML KeyNavigation. We must forward the focus to one of the handles,
1084 // because RangeSlider handles key events for the focused handle. If
1085 // neither handle has active focus, RangeSlider doesn't do anything.
1086 QQuickItem *handle = nextItemInFocusChain();
1087 // QQuickItem::nextItemInFocusChain() only works as desired with
1088 // Qt::TabFocusAllControls. otherwise pick the first handle
1089 if (!handle || handle == this)
1090 handle = d->first->handle();
1091 if (handle)
1092 handle->forceActiveFocus(reason: event->reason());
1093}
1094
1095void QQuickRangeSlider::keyPressEvent(QKeyEvent *event)
1096{
1097 Q_D(QQuickRangeSlider);
1098 QQuickControl::keyPressEvent(event);
1099
1100 QQuickRangeSliderNode *focusNode = d->first->handle()->hasActiveFocus()
1101 ? d->first : (d->second->handle()->hasActiveFocus() ? d->second : nullptr);
1102 if (!focusNode)
1103 return;
1104
1105 const qreal oldValue = focusNode->value();
1106 if (d->orientation == Qt::Horizontal) {
1107 if (event->key() == Qt::Key_Left) {
1108 focusNode->setPressed(true);
1109 if (isMirrored())
1110 focusNode->increase();
1111 else
1112 focusNode->decrease();
1113 event->accept();
1114 } else if (event->key() == Qt::Key_Right) {
1115 focusNode->setPressed(true);
1116 if (isMirrored())
1117 focusNode->decrease();
1118 else
1119 focusNode->increase();
1120 event->accept();
1121 }
1122 } else {
1123 if (event->key() == Qt::Key_Up) {
1124 focusNode->setPressed(true);
1125 focusNode->increase();
1126 event->accept();
1127 } else if (event->key() == Qt::Key_Down) {
1128 focusNode->setPressed(true);
1129 focusNode->decrease();
1130 event->accept();
1131 }
1132 }
1133 if (!qFuzzyCompare(p1: focusNode->value(), p2: oldValue))
1134 emit focusNode->moved();
1135}
1136
1137void QQuickRangeSlider::hoverEnterEvent(QHoverEvent *event)
1138{
1139 Q_D(QQuickRangeSlider);
1140 QQuickControl::hoverEnterEvent(event);
1141 d->updateHover(pos: event->position());
1142 event->ignore();
1143}
1144
1145void QQuickRangeSlider::hoverMoveEvent(QHoverEvent *event)
1146{
1147 Q_D(QQuickRangeSlider);
1148 QQuickControl::hoverMoveEvent(event);
1149 d->updateHover(pos: event->position());
1150 event->ignore();
1151}
1152
1153void QQuickRangeSlider::hoverLeaveEvent(QHoverEvent *event)
1154{
1155 Q_D(QQuickRangeSlider);
1156 QQuickControl::hoverLeaveEvent(event);
1157 d->first->setHovered(false);
1158 d->second->setHovered(false);
1159 event->ignore();
1160}
1161
1162void QQuickRangeSlider::keyReleaseEvent(QKeyEvent *event)
1163{
1164 Q_D(QQuickRangeSlider);
1165 QQuickControl::keyReleaseEvent(event);
1166 d->first->setPressed(false);
1167 d->second->setPressed(false);
1168}
1169
1170void QQuickRangeSlider::mousePressEvent(QMouseEvent *event)
1171{
1172 Q_D(QQuickRangeSlider);
1173 QQuickControl::mousePressEvent(event);
1174 d->handleMove(point: event->position(), timestamp: event->timestamp());
1175 setKeepMouseGrab(true);
1176}
1177
1178#if QT_CONFIG(quicktemplates2_multitouch)
1179void QQuickRangeSlider::touchEvent(QTouchEvent *event)
1180{
1181 Q_D(QQuickRangeSlider);
1182 switch (event->type()) {
1183 case QEvent::TouchUpdate:
1184 for (const QTouchEvent::TouchPoint &point : event->points()) {
1185 if (!d->acceptTouch(point))
1186 continue;
1187
1188 switch (point.state()) {
1189 case QEventPoint::Pressed:
1190 d->handlePress(point: point.position(), timestamp: event->timestamp());
1191 break;
1192 case QEventPoint::Updated:
1193 if (!keepTouchGrab()) {
1194 if (d->orientation == Qt::Horizontal)
1195 setKeepTouchGrab(QQuickWindowPrivate::dragOverThreshold(d: point.position().x() - point.pressPosition().x(), axis: Qt::XAxis, tp: &point, startDragThreshold: qRound(d: d->touchDragThreshold)));
1196 else
1197 setKeepTouchGrab(QQuickWindowPrivate::dragOverThreshold(d: point.position().y() - point.pressPosition().y(), axis: Qt::YAxis, tp: &point, startDragThreshold: qRound(d: d->touchDragThreshold)));
1198 }
1199 if (keepTouchGrab())
1200 d->handleMove(point: point.position(), timestamp: event->timestamp());
1201 break;
1202 case QEventPoint::Released:
1203 d->handleRelease(point: point.position(), timestamp: event->timestamp());
1204 break;
1205 default:
1206 break;
1207 }
1208 }
1209 break;
1210
1211 default:
1212 QQuickControl::touchEvent(event);
1213 break;
1214 }
1215}
1216#endif
1217
1218void QQuickRangeSlider::mirrorChange()
1219{
1220 Q_D(QQuickRangeSlider);
1221 QQuickControl::mirrorChange();
1222 emit d->first->visualPositionChanged();
1223 emit d->second->visualPositionChanged();
1224}
1225
1226void QQuickRangeSlider::classBegin()
1227{
1228 Q_D(QQuickRangeSlider);
1229 QQuickControl::classBegin();
1230
1231 QQmlContext *context = qmlContext(this);
1232 if (context) {
1233 QQmlEngine::setContextForObject(d->first, context);
1234 QQmlEngine::setContextForObject(d->second, context);
1235 }
1236}
1237
1238void QQuickRangeSlider::componentComplete()
1239{
1240 Q_D(QQuickRangeSlider);
1241 QQuickRangeSliderNodePrivate *firstPrivate = QQuickRangeSliderNodePrivate::get(node: d->first);
1242 QQuickRangeSliderNodePrivate *secondPrivate = QQuickRangeSliderNodePrivate::get(node: d->second);
1243 firstPrivate->executeHandle(complete: true);
1244 secondPrivate->executeHandle(complete: true);
1245
1246 QQuickControl::componentComplete();
1247
1248 if (firstPrivate->isPendingValue || secondPrivate->isPendingValue
1249 || !qFuzzyCompare(p1: d->from, p2: defaultFrom) || !qFuzzyCompare(p1: d->to, p2: defaultTo)) {
1250 // Properties were set while we were loading. To avoid clamping issues that occur when setting the
1251 // values of first and second overriding values set by the user, set them all at once at the end.
1252 // Another reason that we must set these values here is that the from and to values might have made the old range invalid.
1253 setValues(firstValue: firstPrivate->isPendingValue ? firstPrivate->pendingValue : firstPrivate->value,
1254 secondValue: secondPrivate->isPendingValue ? secondPrivate->pendingValue : secondPrivate->value);
1255
1256 firstPrivate->pendingValue = 0;
1257 firstPrivate->isPendingValue = false;
1258 secondPrivate->pendingValue = 0;
1259 secondPrivate->isPendingValue = false;
1260 } else {
1261 // If there was no pending data, we must still update the positions,
1262 // as first.setValue()/second.setValue() won't be called as part of default construction.
1263 // Don't need to ignore the second position when updating the first position here,
1264 // as our default values are guaranteed to be valid.
1265 firstPrivate->updatePosition();
1266 secondPrivate->updatePosition();
1267 }
1268}
1269
1270/*!
1271 \qmlmethod void QtQuick.Controls::RangeSlider::first.increase()
1272
1273 Increases the value of the handle by stepSize, or \c 0.1 if stepSize is not defined.
1274
1275 \sa first
1276*/
1277
1278/*!
1279 \qmlmethod void QtQuick.Controls::RangeSlider::first.decrease()
1280
1281 Decreases the value of the handle by stepSize, or \c 0.1 if stepSize is not defined.
1282
1283 \sa first
1284*/
1285
1286/*!
1287 \qmlmethod void QtQuick.Controls::RangeSlider::second.increase()
1288
1289 Increases the value of the handle by stepSize, or \c 0.1 if stepSize is not defined.
1290
1291 \sa second
1292*/
1293
1294/*!
1295 \qmlmethod void QtQuick.Controls::RangeSlider::second.decrease()
1296
1297 Decreases the value of the handle by stepSize, or \c 0.1 if stepSize is not defined.
1298
1299 \sa second
1300*/
1301
1302#if QT_CONFIG(accessibility)
1303QAccessible::Role QQuickRangeSlider::accessibleRole() const
1304{
1305 return QAccessible::Slider;
1306}
1307#endif
1308
1309QT_END_NAMESPACE
1310
1311#include "moc_qquickrangeslider_p.cpp"
1312

source code of qtdeclarative/src/quicktemplates/qquickrangeslider.cpp