1 | // Copyright (C) 2016 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 "qquickmultipointhandler_p.h" |
5 | #include "qquickmultipointhandler_p_p.h" |
6 | #include <private/qquickitem_p.h> |
7 | #include <QLineF> |
8 | #include <QMouseEvent> |
9 | #include <QDebug> |
10 | |
11 | QT_BEGIN_NAMESPACE |
12 | |
13 | /*! |
14 | \qmltype MultiPointHandler |
15 | \since 5.10 |
16 | \preliminary |
17 | \instantiates QQuickMultiPointHandler |
18 | \inherits PointerDeviceHandler |
19 | \inqmlmodule QtQuick |
20 | \brief Abstract handler for multi-point Pointer Events. |
21 | |
22 | An intermediate class (not registered as a QML type) |
23 | for any type of handler which requires and acts upon a specific number |
24 | of multiple touchpoints. |
25 | */ |
26 | QQuickMultiPointHandler::QQuickMultiPointHandler(QQuickItem *parent, int minimumPointCount, int maximumPointCount) |
27 | : QQuickPointerDeviceHandler(*(new QQuickMultiPointHandlerPrivate(minimumPointCount, maximumPointCount)), parent) |
28 | { |
29 | } |
30 | |
31 | bool QQuickMultiPointHandler::wantsPointerEvent(QPointerEvent *event) |
32 | { |
33 | Q_D(QQuickMultiPointHandler); |
34 | if (!QQuickPointerDeviceHandler::wantsPointerEvent(event)) |
35 | return false; |
36 | |
37 | if (event->type() == QEvent::Wheel) |
38 | return false; |
39 | |
40 | bool ret = false; |
41 | #if QT_CONFIG(gestures) |
42 | if (event->type() == QEvent::NativeGesture && event->point(i: 0).state() != QEventPoint::Released) |
43 | ret = true; |
44 | #endif |
45 | |
46 | // If points were pressed or released within parentItem, reset stored state |
47 | // and check eligible points again. This class of handlers is intended to |
48 | // handle a specific number of points, so a differing number of points will |
49 | // usually result in different behavior. But otherwise if the currentPoints |
50 | // are all still there in the event, we're good to go (do not reset |
51 | // currentPoints, because we don't want to lose the pressPosition, and do |
52 | // not want to reshuffle the order either). |
53 | const auto candidatePoints = eligiblePoints(event); |
54 | if (candidatePoints.size() != d->currentPoints.size()) { |
55 | d->currentPoints.clear(); |
56 | if (active()) { |
57 | setActive(false); |
58 | d->centroid.reset(); |
59 | emit centroidChanged(); |
60 | } |
61 | } else if (hasCurrentPoints(event)) { |
62 | return true; |
63 | } |
64 | |
65 | ret = ret || (candidatePoints.size() >= minimumPointCount() && candidatePoints.size() <= maximumPointCount()); |
66 | if (ret) { |
67 | const int c = candidatePoints.size(); |
68 | d->currentPoints.resize(size: c); |
69 | for (int i = 0; i < c; ++i) { |
70 | d->currentPoints[i].reset(event, point: candidatePoints[i]); |
71 | if (auto par = parentItem()) |
72 | d->currentPoints[i].localize(item: par); |
73 | } |
74 | } else { |
75 | d->currentPoints.clear(); |
76 | } |
77 | return ret; |
78 | } |
79 | |
80 | void QQuickMultiPointHandler::handlePointerEventImpl(QPointerEvent *event) |
81 | { |
82 | Q_D(QQuickMultiPointHandler); |
83 | QQuickPointerHandler::handlePointerEventImpl(event); |
84 | // event's points can be reordered since the previous event, which is why currentPoints |
85 | // is _not_ a shallow copy of the QQuickPointerTouchEvent::m_touchPoints vector. |
86 | // So we have to update our currentPoints instances based on the given event. |
87 | for (QQuickHandlerPoint &p : d->currentPoints) { |
88 | if (const QEventPoint *ep = event->pointById(id: p.id())) |
89 | p.reset(event, point: *ep); |
90 | } |
91 | QPointF sceneGrabPos = d->centroid.sceneGrabPosition(); |
92 | d->centroid.reset(points: d->currentPoints); |
93 | d->centroid.m_sceneGrabPosition = sceneGrabPos; // preserve as it was |
94 | emit centroidChanged(); |
95 | } |
96 | |
97 | void QQuickMultiPointHandler::onActiveChanged() |
98 | { |
99 | Q_D(QQuickMultiPointHandler); |
100 | if (active()) { |
101 | d->centroid.m_sceneGrabPosition = d->centroid.m_scenePosition; |
102 | } else { |
103 | // Don't call centroid.reset() here, because in a QML onActiveChanged |
104 | // callback, we'd like to see what the position _was_, what the velocity _was_, etc. |
105 | // (having them undefined is not useful) |
106 | // But pressedButtons and pressedModifiers are meant to be more real-time than those |
107 | // (which seems a bit inconsistent, from one side). |
108 | d->centroid.m_pressedButtons = Qt::NoButton; |
109 | d->centroid.m_pressedModifiers = Qt::NoModifier; |
110 | } |
111 | } |
112 | |
113 | void QQuickMultiPointHandler::onGrabChanged(QQuickPointerHandler *grabber, QPointingDevice::GrabTransition transition, QPointerEvent *event, QEventPoint &point) |
114 | { |
115 | Q_D(QQuickMultiPointHandler); |
116 | // If another handler or item takes over this set of points, assume it has |
117 | // decided that it's the better fit for them. Don't immediately re-grab |
118 | // at the next opportunity. This should help to avoid grab cycles |
119 | // (e.g. between DragHandler and PinchHandler). |
120 | if (transition == QPointingDevice::UngrabExclusive || transition == QPointingDevice::CancelGrabExclusive) |
121 | d->currentPoints.clear(); |
122 | if (grabber != this) |
123 | return; |
124 | switch (transition) { |
125 | case QPointingDevice::GrabExclusive: |
126 | for (auto &pt : d->currentPoints) |
127 | if (pt.id() == point.id()) { |
128 | pt.m_sceneGrabPosition = point.scenePosition(); |
129 | break; |
130 | } |
131 | QQuickPointerHandler::onGrabChanged(grabber, transition, event, point); |
132 | break; |
133 | case QPointingDevice::GrabPassive: |
134 | case QPointingDevice::UngrabPassive: |
135 | case QPointingDevice::UngrabExclusive: |
136 | case QPointingDevice::CancelGrabPassive: |
137 | case QPointingDevice::CancelGrabExclusive: |
138 | QQuickPointerHandler::onGrabChanged(grabber, transition, event, point); |
139 | break; |
140 | case QPointingDevice::OverrideGrabPassive: |
141 | return; // don't emit |
142 | } |
143 | } |
144 | |
145 | QVector<QEventPoint> QQuickMultiPointHandler::eligiblePoints(QPointerEvent *event) |
146 | { |
147 | QVector<QEventPoint> ret; |
148 | // If one or more points are newly pressed or released, all non-released points are candidates for this handler. |
149 | // In other cases however, check whether it would be OK to steal the grab if the handler chooses to do that. |
150 | bool stealingAllowed = event->isBeginEvent() || event->isEndEvent(); |
151 | for (int i = 0; i < event->pointCount(); ++i) { |
152 | auto &p = event->point(i); |
153 | if (QQuickDeliveryAgentPrivate::isMouseEvent(ev: event)) { |
154 | if (static_cast<QMouseEvent *>(event)->buttons() == Qt::NoButton) |
155 | continue; |
156 | } |
157 | if (!stealingAllowed) { |
158 | QObject *exclusiveGrabber = event->exclusiveGrabber(point: p); |
159 | if (exclusiveGrabber && exclusiveGrabber != this && !canGrab(event, point: p)) |
160 | continue; |
161 | } |
162 | if (p.state() != QEventPoint::Released && wantsEventPoint(event, point: p)) |
163 | ret << p; |
164 | } |
165 | return ret; |
166 | } |
167 | |
168 | /*! |
169 | \qmlproperty int MultiPointHandler::minimumPointCount |
170 | |
171 | The minimum number of touchpoints required to activate this handler. |
172 | |
173 | If a smaller number of touchpoints are in contact with the |
174 | \l {PointerHandler::parent}{parent}, they will be ignored. |
175 | |
176 | Any ignored points are eligible to activate other Input Handlers that |
177 | have different constraints, on the same Item or on other Items. |
178 | |
179 | The default value is 2. |
180 | */ |
181 | int QQuickMultiPointHandler::minimumPointCount() const |
182 | { |
183 | Q_D(const QQuickMultiPointHandler); |
184 | return d->minimumPointCount; |
185 | } |
186 | |
187 | void QQuickMultiPointHandler::setMinimumPointCount(int c) |
188 | { |
189 | Q_D(QQuickMultiPointHandler); |
190 | if (d->minimumPointCount == c) |
191 | return; |
192 | |
193 | d->minimumPointCount = c; |
194 | emit minimumPointCountChanged(); |
195 | if (d->maximumPointCount < 0) |
196 | emit maximumPointCountChanged(); |
197 | } |
198 | |
199 | /*! |
200 | \qmlproperty int MultiPointHandler::maximumPointCount |
201 | |
202 | The maximum number of touchpoints this handler can utilize. |
203 | |
204 | If a larger number of touchpoints are in contact with the |
205 | \l {PointerHandler::parent}{parent}, the required number of points will be |
206 | chosen in the order that they are pressed, and the remaining points will |
207 | be ignored. |
208 | |
209 | Any ignored points are eligible to activate other Input Handlers that |
210 | have different constraints, on the same Item or on other Items. |
211 | |
212 | The default value is the same as \l minimumPointCount. |
213 | */ |
214 | int QQuickMultiPointHandler::maximumPointCount() const |
215 | { |
216 | Q_D(const QQuickMultiPointHandler); |
217 | return d->maximumPointCount >= 0 ? d->maximumPointCount : d->minimumPointCount; |
218 | } |
219 | |
220 | void QQuickMultiPointHandler::setMaximumPointCount(int maximumPointCount) |
221 | { |
222 | Q_D(QQuickMultiPointHandler); |
223 | if (d->maximumPointCount == maximumPointCount) |
224 | return; |
225 | |
226 | d->maximumPointCount = maximumPointCount; |
227 | emit maximumPointCountChanged(); |
228 | } |
229 | |
230 | /*! |
231 | \readonly |
232 | \qmlproperty QtQuick::handlerPoint QtQuick::MultiPointHandler::centroid |
233 | |
234 | A point exactly in the middle of the currently-pressed touch points. |
235 | If only one point is pressed, it's the same as that point. |
236 | A handler that has a \l target will normally transform it relative to this point. |
237 | */ |
238 | const QQuickHandlerPoint &QQuickMultiPointHandler::centroid() const |
239 | { |
240 | Q_D(const QQuickMultiPointHandler); |
241 | return d->centroid; |
242 | } |
243 | |
244 | /*! |
245 | Returns a modifiable reference to the point that will be returned by the |
246 | \l centroid property. If you modify it, you are responsible to emit |
247 | \l centroidChanged. |
248 | */ |
249 | QQuickHandlerPoint &QQuickMultiPointHandler::mutableCentroid() |
250 | { |
251 | Q_D(QQuickMultiPointHandler); |
252 | return d->centroid; |
253 | } |
254 | |
255 | QVector<QQuickHandlerPoint> &QQuickMultiPointHandler::currentPoints() |
256 | { |
257 | Q_D(QQuickMultiPointHandler); |
258 | return d->currentPoints; |
259 | } |
260 | |
261 | bool QQuickMultiPointHandler::hasCurrentPoints(QPointerEvent *event) |
262 | { |
263 | Q_D(const QQuickMultiPointHandler); |
264 | if (event->pointCount() < d->currentPoints.size() || d->currentPoints.size() == 0) |
265 | return false; |
266 | // TODO optimize: either ensure the points are sorted, |
267 | // or use std::equal with a predicate |
268 | for (const QQuickHandlerPoint &p : std::as_const(t: d->currentPoints)) { |
269 | const QEventPoint *ep = event->pointById(id: p.id()); |
270 | if (!ep) |
271 | return false; |
272 | if (ep->state() == QEventPoint::Released) |
273 | return false; |
274 | } |
275 | return true; |
276 | } |
277 | |
278 | qreal QQuickMultiPointHandler::averageTouchPointDistance(const QPointF &ref) |
279 | { |
280 | Q_D(const QQuickMultiPointHandler); |
281 | qreal ret = 0; |
282 | if (Q_UNLIKELY(d->currentPoints.size() == 0)) |
283 | return ret; |
284 | for (const QQuickHandlerPoint &p : d->currentPoints) |
285 | ret += QVector2D(p.scenePosition() - ref).length(); |
286 | return ret / d->currentPoints.size(); |
287 | } |
288 | |
289 | qreal QQuickMultiPointHandler::averageStartingDistance(const QPointF &ref) |
290 | { |
291 | Q_D(const QQuickMultiPointHandler); |
292 | // TODO cache it in setActive()? |
293 | qreal ret = 0; |
294 | if (Q_UNLIKELY(d->currentPoints.size() == 0)) |
295 | return ret; |
296 | for (const QQuickHandlerPoint &p : d->currentPoints) |
297 | ret += QVector2D(p.sceneGrabPosition() - ref).length(); |
298 | return ret / d->currentPoints.size(); |
299 | } |
300 | |
301 | QVector<QQuickMultiPointHandler::PointData> QQuickMultiPointHandler::angles(const QPointF &ref) const |
302 | { |
303 | Q_D(const QQuickMultiPointHandler); |
304 | QVector<PointData> angles; |
305 | angles.reserve(size: d->currentPoints.size()); |
306 | for (const QQuickHandlerPoint &p : d->currentPoints) { |
307 | qreal angle = QLineF(ref, p.scenePosition()).angle(); |
308 | angles.append(t: PointData(p.id(), -angle)); // convert to clockwise, to be consistent with QQuickItem::rotation |
309 | } |
310 | return angles; |
311 | } |
312 | |
313 | qreal QQuickMultiPointHandler::averageAngleDelta(const QVector<PointData> &old, const QVector<PointData> &newAngles) |
314 | { |
315 | qreal avgAngleDelta = 0; |
316 | int numSamples = 0; |
317 | |
318 | auto oldBegin = old.constBegin(); |
319 | |
320 | for (PointData newData : newAngles) { |
321 | quint64 id = newData.id; |
322 | auto it = std::find_if(first: oldBegin, last: old.constEnd(), pred: [id] (PointData pd) { return pd.id == id; }); |
323 | qreal angleD = 0; |
324 | if (it != old.constEnd()) { |
325 | PointData oldData = *it; |
326 | // We might rotate from 359 degrees to 1 degree. However, this |
327 | // should be interpreted as a rotation of +2 degrees instead of |
328 | // -358 degrees. Therefore, we call remainder() to translate the angle |
329 | // to be in the range [-180, 180] (-350 to +10 etc) |
330 | angleD = remainder(x: newData.angle - oldData.angle, y: qreal(360)); |
331 | // optimization: narrow down the O(n^2) search to optimally O(n) |
332 | // if both vectors have the same points and they are in the same order |
333 | if (it == oldBegin) |
334 | ++oldBegin; |
335 | numSamples++; |
336 | } |
337 | avgAngleDelta += angleD; |
338 | } |
339 | if (numSamples > 1) |
340 | avgAngleDelta /= numSamples; |
341 | |
342 | return avgAngleDelta; |
343 | } |
344 | |
345 | void QQuickMultiPointHandler::acceptPoints(const QVector<QEventPoint> &points) |
346 | { |
347 | // "auto point" is a copy, but it's OK because |
348 | // setAccepted() changes QEventPointPrivate::accept via the shared d-pointer |
349 | for (auto point : points) |
350 | point.setAccepted(); |
351 | } |
352 | |
353 | bool QQuickMultiPointHandler::grabPoints(QPointerEvent *event, const QVector<QEventPoint> &points) |
354 | { |
355 | if (points.isEmpty()) |
356 | return false; |
357 | bool allowed = true; |
358 | for (auto &point : points) { |
359 | if (event->exclusiveGrabber(point) != this && !canGrab(event, point)) { |
360 | allowed = false; |
361 | break; |
362 | } |
363 | } |
364 | if (allowed) { |
365 | for (const auto &point : std::as_const(t: points)) |
366 | setExclusiveGrab(ev: event, point); |
367 | } |
368 | return allowed; |
369 | } |
370 | |
371 | void QQuickMultiPointHandler::moveTarget(QPointF pos) |
372 | { |
373 | Q_D(QQuickMultiPointHandler); |
374 | if (QQuickItem *t = target()) { |
375 | d->xMetaProperty().write(obj: t, value: pos.x()); |
376 | d->yMetaProperty().write(obj: t, value: pos.y()); |
377 | d->centroid.m_position = t->mapFromScene(point: d->centroid.m_scenePosition); |
378 | } else { |
379 | qWarning() << "moveTarget: target is null" ; |
380 | } |
381 | } |
382 | |
383 | QQuickMultiPointHandlerPrivate::QQuickMultiPointHandlerPrivate(int minPointCount, int maxPointCount) |
384 | : QQuickPointerDeviceHandlerPrivate() |
385 | , minimumPointCount(minPointCount) |
386 | , maximumPointCount(maxPointCount) |
387 | { |
388 | } |
389 | |
390 | QMetaProperty &QQuickMultiPointHandlerPrivate::xMetaProperty() const |
391 | { |
392 | Q_Q(const QQuickMultiPointHandler); |
393 | if (!xProperty.isValid() && q->target()) { |
394 | const QMetaObject *targetMeta = q->target()->metaObject(); |
395 | xProperty = targetMeta->property(index: targetMeta->indexOfProperty(name: "x" )); |
396 | } |
397 | return xProperty; |
398 | } |
399 | |
400 | QMetaProperty &QQuickMultiPointHandlerPrivate::yMetaProperty() const |
401 | { |
402 | Q_Q(const QQuickMultiPointHandler); |
403 | if (!yProperty.isValid() && q->target()) { |
404 | const QMetaObject *targetMeta = q->target()->metaObject(); |
405 | yProperty = targetMeta->property(index: targetMeta->indexOfProperty(name: "y" )); |
406 | } |
407 | return yProperty; |
408 | } |
409 | |
410 | QT_END_NAMESPACE |
411 | |
412 | #include "moc_qquickmultipointhandler_p.cpp" |
413 | |