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 "qquickswitchdelegate_p.h"
5
6#include "qquickitemdelegate_p_p.h"
7
8#include <algorithm>
9
10QT_BEGIN_NAMESPACE
11
12/*!
13 \qmltype SwitchDelegate
14 \inherits ItemDelegate
15//! \nativetype QQuickSwitchDelegate
16 \inqmlmodule QtQuick.Controls
17 \since 5.7
18 \ingroup qtquickcontrols-delegates
19 \brief Item delegate with a switch indicator that can be toggled on or off.
20
21 \image qtquickcontrols-switchdelegate.gif
22
23 SwitchDelegate presents an item delegate that can be toggled on (checked) or
24 off (unchecked). Switch delegates are typically used to select one or more
25 options from a set of options. For smaller sets of options, or for options
26 that need to be uniquely identifiable, consider using \l Switch instead.
27
28 SwitchDelegate inherits its API from \l ItemDelegate, which is inherited
29 from \l AbstractButton. For instance, you can set \l {AbstractButton::text}{text},
30 and react to \l {AbstractButton::clicked}{clicks} using the \l AbstractButton
31 API. The state of the switch delegate can be set with the
32 \l {AbstractButton::}{checked} property.
33
34 \code
35 ListView {
36 model: ["Option 1", "Option 2", "Option 3"]
37 delegate: SwitchDelegate {
38 text: modelData
39 }
40 }
41 \endcode
42
43 \sa {Customizing SwitchDelegate}, {Delegate Controls}
44*/
45
46class QQuickSwitchDelegatePrivate : public QQuickItemDelegatePrivate
47{
48 Q_DECLARE_PUBLIC(QQuickSwitchDelegate)
49
50public:
51 qreal positionAt(const QPointF &point) const;
52
53 bool canDrag(const QPointF &movePoint) const;
54 bool handleMove(const QPointF &point, ulong timestamp) override;
55 bool handleRelease(const QPointF &point, ulong timestamp) override;
56
57 QPalette defaultPalette() const override { return QQuickTheme::palette(scope: QQuickTheme::ListView); }
58
59 qreal position = 0;
60};
61
62qreal QQuickSwitchDelegatePrivate::positionAt(const QPointF &point) const
63{
64 Q_Q(const QQuickSwitchDelegate);
65 qreal pos = 0.0;
66 if (indicator)
67 pos = indicator->mapFromItem(item: q, point).x() / indicator->width();
68 if (q->isMirrored())
69 return 1.0 - pos;
70 return pos;
71}
72
73bool QQuickSwitchDelegatePrivate::canDrag(const QPointF &movePoint) const
74{
75 // don't start dragging the handle unless the initial press was at the indicator,
76 // or the drag has reached the indicator area. this prevents unnatural jumps when
77 // dragging far outside the indicator.
78 const qreal pressPos = positionAt(point: pressPoint);
79 const qreal movePos = positionAt(point: movePoint);
80 return (pressPos >= 0.0 && pressPos <= 1.0) || (movePos >= 0.0 && movePos <= 1.0);
81}
82
83bool QQuickSwitchDelegatePrivate::handleMove(const QPointF &point, ulong timestamp)
84{
85 Q_Q(QQuickSwitchDelegate);
86 QQuickItemDelegatePrivate::handleMove(point, timestamp);
87 if (q->keepMouseGrab() || q->keepTouchGrab())
88 q->setPosition(positionAt(point));
89 return true;
90}
91
92bool QQuickSwitchDelegatePrivate::handleRelease(const QPointF &point, ulong timestamp)
93{
94 Q_Q(QQuickSwitchDelegate);
95 QQuickItemDelegatePrivate::handleRelease(point, timestamp);
96 q->setKeepMouseGrab(false);
97 q->setKeepTouchGrab(false);
98 return true;
99}
100
101QQuickSwitchDelegate::QQuickSwitchDelegate(QQuickItem *parent)
102 : QQuickItemDelegate(*(new QQuickSwitchDelegatePrivate), parent)
103{
104 Q_D(QQuickSwitchDelegate);
105 d->keepPressed = true;
106 setCheckable(true);
107}
108
109/*!
110 \qmlproperty real QtQuick.Controls::SwitchDelegate::position
111 \readonly
112
113 \input includes/qquickswitch.qdocinc position
114*/
115qreal QQuickSwitchDelegate::position() const
116{
117 Q_D(const QQuickSwitchDelegate);
118 return d->position;
119}
120
121void QQuickSwitchDelegate::setPosition(qreal position)
122{
123 Q_D(QQuickSwitchDelegate);
124 position = std::clamp(val: position, lo: qreal(0.0), hi: qreal(1.0));
125 if (qFuzzyCompare(p1: d->position, p2: position))
126 return;
127
128 d->position = position;
129 emit positionChanged();
130 emit visualPositionChanged();
131}
132
133/*!
134 \qmlproperty real QtQuick.Controls::SwitchDelegate::visualPosition
135 \readonly
136
137 \input includes/qquickswitch.qdocinc visualPosition
138*/
139qreal QQuickSwitchDelegate::visualPosition() const
140{
141 Q_D(const QQuickSwitchDelegate);
142 if (isMirrored())
143 return 1.0 - d->position;
144 return d->position;
145}
146
147void QQuickSwitchDelegate::mouseMoveEvent(QMouseEvent *event)
148{
149 Q_D(QQuickSwitchDelegate);
150 if (!keepMouseGrab()) {
151 const QPointF movePoint = event->position();
152 if (d->canDrag(movePoint)) {
153 setKeepMouseGrab(QQuickDeliveryAgentPrivate::dragOverThreshold(d: movePoint.x() - d->pressPoint.x(),
154 axis: Qt::XAxis, event));
155 }
156 }
157 QQuickItemDelegate::mouseMoveEvent(event);
158}
159
160#if QT_CONFIG(quicktemplates2_multitouch)
161void QQuickSwitchDelegate::touchEvent(QTouchEvent *event)
162{
163 Q_D(QQuickSwitchDelegate);
164 if (!keepTouchGrab() && event->type() == QEvent::TouchUpdate) {
165 for (const QTouchEvent::TouchPoint &point : event->points()) {
166 if (point.id() != d->touchId || point.state() != QEventPoint::Updated)
167 continue;
168 if (d->canDrag(movePoint: point.position())) {
169 setKeepTouchGrab(QQuickDeliveryAgentPrivate::dragOverThreshold(d: point.position().x() - d->pressPoint.x(),
170 axis: Qt::XAxis, tp: point));
171 }
172 }
173 }
174 QQuickItemDelegate::touchEvent(event);
175}
176#endif
177
178QFont QQuickSwitchDelegate::defaultFont() const
179{
180 return QQuickTheme::font(scope: QQuickTheme::ListView);
181}
182
183void QQuickSwitchDelegate::mirrorChange()
184{
185 QQuickItemDelegate::mirrorChange();
186 emit visualPositionChanged();
187}
188
189void QQuickSwitchDelegate::nextCheckState()
190{
191 Q_D(QQuickSwitchDelegate);
192 if (keepMouseGrab() || keepTouchGrab()) {
193 d->toggle(value: d->position > 0.5);
194 // the checked state might not change => force a position update to
195 // avoid that the handle is left somewhere in the middle (QTBUG-57944)
196 setPosition(d->checked ? 1.0 : 0.0);
197 } else {
198 QQuickItemDelegate::nextCheckState();
199 }
200}
201
202void QQuickSwitchDelegate::buttonChange(ButtonChange change)
203{
204 Q_D(QQuickSwitchDelegate);
205 if (change == ButtonCheckedChange)
206 setPosition(d->checked ? 1.0 : 0.0);
207 else
208 QQuickAbstractButton::buttonChange(change);
209}
210
211QT_END_NAMESPACE
212
213#include "moc_qquickswitchdelegate_p.cpp"
214

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