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