| 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 | \nativetype 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 | |