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
11QT_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*/
26QQuickMultiPointHandler::QQuickMultiPointHandler(QQuickItem *parent, int minimumPointCount, int maximumPointCount)
27 : QQuickPointerDeviceHandler(*(new QQuickMultiPointHandlerPrivate(minimumPointCount, maximumPointCount)), parent)
28{
29}
30
31bool 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
80void 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
97void 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
113void 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
145QVector<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*/
181int QQuickMultiPointHandler::minimumPointCount() const
182{
183 Q_D(const QQuickMultiPointHandler);
184 return d->minimumPointCount;
185}
186
187void 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*/
214int QQuickMultiPointHandler::maximumPointCount() const
215{
216 Q_D(const QQuickMultiPointHandler);
217 return d->maximumPointCount >= 0 ? d->maximumPointCount : d->minimumPointCount;
218}
219
220void 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*/
238const 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*/
249QQuickHandlerPoint &QQuickMultiPointHandler::mutableCentroid()
250{
251 Q_D(QQuickMultiPointHandler);
252 return d->centroid;
253}
254
255QVector<QQuickHandlerPoint> &QQuickMultiPointHandler::currentPoints()
256{
257 Q_D(QQuickMultiPointHandler);
258 return d->currentPoints;
259}
260
261bool 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
278qreal 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
289qreal 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
301QVector<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
313qreal 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
345void 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
353bool 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
371void 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
383QQuickMultiPointHandlerPrivate::QQuickMultiPointHandlerPrivate(int minPointCount, int maxPointCount)
384 : QQuickPointerDeviceHandlerPrivate()
385 , minimumPointCount(minPointCount)
386 , maximumPointCount(maxPointCount)
387{
388}
389
390QMetaProperty &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
400QMetaProperty &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
410QT_END_NAMESPACE
411
412#include "moc_qquickmultipointhandler_p.cpp"
413

source code of qtdeclarative/src/quick/handlers/qquickmultipointhandler.cpp