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

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