| 1 | // Copyright (C) 2020 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 "qquickflickable_p.h" |
| 5 | #include "qquickflickable_p_p.h" |
| 6 | #include "qquickflickablebehavior_p.h" |
| 7 | #include "qquickwindow.h" |
| 8 | #include "qquickwindow_p.h" |
| 9 | #include "qquickmousearea_p.h" |
| 10 | #if QT_CONFIG(quick_draganddrop) |
| 11 | #include "qquickdrag_p.h" |
| 12 | #endif |
| 13 | |
| 14 | #include <QtQuick/private/qquickpointerhandler_p.h> |
| 15 | #include <QtQuick/private/qquicktransition_p.h> |
| 16 | #include <private/qqmlglobal_p.h> |
| 17 | |
| 18 | #include <QtQml/qqmlinfo.h> |
| 19 | #include <QtGui/qevent.h> |
| 20 | #include <QtGui/qguiapplication.h> |
| 21 | #include <QtGui/private/qguiapplication_p.h> |
| 22 | #include <QtGui/private/qeventpoint_p.h> |
| 23 | #include <QtGui/qstylehints.h> |
| 24 | #include <QtGui/qaccessible.h> |
| 25 | #include <QtCore/qmath.h> |
| 26 | #include <qpa/qplatformtheme.h> |
| 27 | |
| 28 | #include <math.h> |
| 29 | #include <cmath> |
| 30 | |
| 31 | QT_BEGIN_NAMESPACE |
| 32 | |
| 33 | Q_STATIC_LOGGING_CATEGORY(lcFlickable, "qt.quick.flickable" ) |
| 34 | Q_STATIC_LOGGING_CATEGORY(lcFilter, "qt.quick.flickable.filter" ) |
| 35 | Q_STATIC_LOGGING_CATEGORY(lcReplay, "qt.quick.flickable.replay" ) |
| 36 | Q_STATIC_LOGGING_CATEGORY(lcWheel, "qt.quick.flickable.wheel" ) |
| 37 | Q_STATIC_LOGGING_CATEGORY(lcVel, "qt.quick.flickable.velocity" ) |
| 38 | |
| 39 | // RetainGrabVelocity is the maxmimum instantaneous velocity that |
| 40 | // will ensure the Flickable retains the grab on consecutive flicks. |
| 41 | static const int RetainGrabVelocity = 100; |
| 42 | |
| 43 | static qreal EaseOvershoot(qreal t) { |
| 44 | return qAtan(v: t); |
| 45 | } |
| 46 | |
| 47 | QQuickFlickableVisibleArea::QQuickFlickableVisibleArea(QQuickFlickable *parent) |
| 48 | : QObject(parent), flickable(parent), m_xPosition(0.), m_widthRatio(0.) |
| 49 | , m_yPosition(0.), m_heightRatio(0.) |
| 50 | { |
| 51 | } |
| 52 | |
| 53 | qreal QQuickFlickableVisibleArea::widthRatio() const |
| 54 | { |
| 55 | return m_widthRatio; |
| 56 | } |
| 57 | |
| 58 | qreal QQuickFlickableVisibleArea::xPosition() const |
| 59 | { |
| 60 | return m_xPosition; |
| 61 | } |
| 62 | |
| 63 | qreal QQuickFlickableVisibleArea::heightRatio() const |
| 64 | { |
| 65 | return m_heightRatio; |
| 66 | } |
| 67 | |
| 68 | qreal QQuickFlickableVisibleArea::yPosition() const |
| 69 | { |
| 70 | return m_yPosition; |
| 71 | } |
| 72 | |
| 73 | void QQuickFlickableVisibleArea::updateVisible() |
| 74 | { |
| 75 | QQuickFlickablePrivate *p = QQuickFlickablePrivate::get(o: flickable); |
| 76 | |
| 77 | bool changeX = false; |
| 78 | bool changeY = false; |
| 79 | bool changeWidth = false; |
| 80 | bool changeHeight = false; |
| 81 | |
| 82 | // Vertical |
| 83 | const qreal viewheight = flickable->height(); |
| 84 | const qreal maxyextent = -flickable->maxYExtent() + flickable->minYExtent(); |
| 85 | const qreal maxYBounds = maxyextent + viewheight; |
| 86 | qreal pagePos = 0; |
| 87 | qreal pageSize = 0; |
| 88 | if (!qFuzzyIsNull(d: maxYBounds)) { |
| 89 | qreal y = p->pixelAligned ? std::round(x: p->vData.move.value()) : p->vData.move.value(); |
| 90 | pagePos = (-y + flickable->minYExtent()) / maxYBounds; |
| 91 | pageSize = viewheight / maxYBounds; |
| 92 | } |
| 93 | |
| 94 | if (pageSize != m_heightRatio) { |
| 95 | m_heightRatio = pageSize; |
| 96 | changeHeight = true; |
| 97 | } |
| 98 | if (pagePos != m_yPosition) { |
| 99 | m_yPosition = pagePos; |
| 100 | changeY = true; |
| 101 | } |
| 102 | |
| 103 | // Horizontal |
| 104 | const qreal viewwidth = flickable->width(); |
| 105 | const qreal maxxextent = -flickable->maxXExtent() + flickable->minXExtent(); |
| 106 | const qreal maxXBounds = maxxextent + viewwidth; |
| 107 | if (!qFuzzyIsNull(d: maxXBounds)) { |
| 108 | qreal x = p->pixelAligned ? std::round(x: p->hData.move.value()) : p->hData.move.value(); |
| 109 | pagePos = (-x + flickable->minXExtent()) / maxXBounds; |
| 110 | pageSize = viewwidth / maxXBounds; |
| 111 | } else { |
| 112 | pagePos = 0; |
| 113 | pageSize = 0; |
| 114 | } |
| 115 | |
| 116 | if (pageSize != m_widthRatio) { |
| 117 | m_widthRatio = pageSize; |
| 118 | changeWidth = true; |
| 119 | } |
| 120 | if (pagePos != m_xPosition) { |
| 121 | m_xPosition = pagePos; |
| 122 | changeX = true; |
| 123 | } |
| 124 | |
| 125 | if (changeX) |
| 126 | emit xPositionChanged(xPosition: m_xPosition); |
| 127 | if (changeY) |
| 128 | emit yPositionChanged(yPosition: m_yPosition); |
| 129 | if (changeWidth) |
| 130 | emit widthRatioChanged(widthRatio: m_widthRatio); |
| 131 | if (changeHeight) |
| 132 | emit heightRatioChanged(heightRatio: m_heightRatio); |
| 133 | } |
| 134 | |
| 135 | |
| 136 | class QQuickFlickableReboundTransition : public QQuickTransitionManager |
| 137 | { |
| 138 | public: |
| 139 | QQuickFlickableReboundTransition(QQuickFlickable *f, const QString &name) |
| 140 | : flickable(f), axisData(nullptr), propName(name), active(false) |
| 141 | { |
| 142 | } |
| 143 | |
| 144 | ~QQuickFlickableReboundTransition() |
| 145 | { |
| 146 | flickable = nullptr; |
| 147 | } |
| 148 | |
| 149 | bool startTransition(QQuickFlickablePrivate::AxisData *data, qreal toPos) { |
| 150 | QQuickFlickablePrivate *fp = QQuickFlickablePrivate::get(o: flickable); |
| 151 | if (!fp->rebound || !fp->rebound->enabled()) |
| 152 | return false; |
| 153 | active = true; |
| 154 | axisData = data; |
| 155 | axisData->transitionTo = toPos; |
| 156 | axisData->transitionToSet = true; |
| 157 | |
| 158 | actions.clear(); |
| 159 | actions << QQuickStateAction(fp->contentItem, propName, toPos); |
| 160 | QQuickTransitionManager::transition(actions, transition: fp->rebound, defaultTarget: fp->contentItem); |
| 161 | return true; |
| 162 | } |
| 163 | |
| 164 | bool isActive() const { |
| 165 | return active; |
| 166 | } |
| 167 | |
| 168 | void stopTransition() { |
| 169 | if (!flickable || !isRunning()) |
| 170 | return; |
| 171 | QQuickFlickablePrivate *fp = QQuickFlickablePrivate::get(o: flickable); |
| 172 | if (axisData == &fp->hData) |
| 173 | axisData->move.setValue(-flickable->contentX()); |
| 174 | else |
| 175 | axisData->move.setValue(-flickable->contentY()); |
| 176 | active = false; |
| 177 | cancel(); |
| 178 | } |
| 179 | |
| 180 | protected: |
| 181 | void finished() override { |
| 182 | if (!flickable) |
| 183 | return; |
| 184 | axisData->move.setValue(axisData->transitionTo); |
| 185 | QQuickFlickablePrivate *fp = QQuickFlickablePrivate::get(o: flickable); |
| 186 | active = false; |
| 187 | |
| 188 | if (!fp->hData.transitionToBounds->isActive() |
| 189 | && !fp->vData.transitionToBounds->isActive()) { |
| 190 | flickable->movementEnding(); |
| 191 | } |
| 192 | } |
| 193 | |
| 194 | private: |
| 195 | QQuickStateOperation::ActionList actions; |
| 196 | QQuickFlickable *flickable; |
| 197 | QQuickFlickablePrivate::AxisData *axisData; |
| 198 | QString propName; |
| 199 | bool active; |
| 200 | }; |
| 201 | |
| 202 | QQuickFlickablePrivate::AxisData::~AxisData() |
| 203 | { |
| 204 | delete transitionToBounds; |
| 205 | } |
| 206 | |
| 207 | class QQuickFlickableContentItem : public QQuickItem |
| 208 | { |
| 209 | /*! |
| 210 | \internal |
| 211 | The flickable area inside the viewport can be bigger than the bounds of the |
| 212 | content item itself, if the flickable is using non-zero extents (as returned |
| 213 | by e.g minXExtent()). Since the default implementation in QQuickItem::contains() |
| 214 | only checks if the point is inside the bounds of the item, we need to override it |
| 215 | to check the extents as well. The easist way to do this is to simply check if the |
| 216 | point is inside the bounds of the flickable rather than the content item. |
| 217 | */ |
| 218 | bool contains(const QPointF &point) const override |
| 219 | { |
| 220 | const QQuickItem *flickable = parentItem(); |
| 221 | const QPointF posInFlickable = flickable->mapFromItem(item: this, point); |
| 222 | return flickable->contains(point: posInFlickable); |
| 223 | } |
| 224 | }; |
| 225 | |
| 226 | QQuickFlickablePrivate::QQuickFlickablePrivate() |
| 227 | : contentItem(new QQuickFlickableContentItem) |
| 228 | , hData(this, &QQuickFlickablePrivate::setViewportX) |
| 229 | , vData(this, &QQuickFlickablePrivate::setViewportY) |
| 230 | , hMoved(false), vMoved(false) |
| 231 | , stealMouse(false), pressed(false) |
| 232 | , scrollingPhase(false), interactive(true), calcVelocity(false) |
| 233 | , pixelAligned(false) |
| 234 | , syncDrag(false) |
| 235 | , acceptedButtons(Qt::LeftButton) |
| 236 | , lastPosTime(-1) |
| 237 | , lastPressTime(0) |
| 238 | , deceleration(QGuiApplicationPrivate::platformTheme()->themeHint(hint: QPlatformTheme::FlickDeceleration).toReal()) |
| 239 | , wheelDeceleration(15000) |
| 240 | , maxVelocity(QGuiApplicationPrivate::platformTheme()->themeHint(hint: QPlatformTheme::FlickMaximumVelocity).toReal()) |
| 241 | , delayedPressEvent(nullptr), pressDelay(0), fixupDuration(400) |
| 242 | , flickBoost(1.0), initialWheelFlickDistance(qApp->styleHints()->wheelScrollLines() * 24) |
| 243 | , fixupMode(Normal), vTime(0), visibleArea(nullptr) |
| 244 | , flickableDirection(QQuickFlickable::AutoFlickDirection) |
| 245 | , boundsBehavior(QQuickFlickable::DragAndOvershootBounds) |
| 246 | , boundsMovement(QQuickFlickable::FollowBoundsBehavior) |
| 247 | , rebound(nullptr) |
| 248 | { |
| 249 | const int wheelDecelerationEnv = qEnvironmentVariableIntValue(varName: "QT_QUICK_FLICKABLE_WHEEL_DECELERATION" ); |
| 250 | if (wheelDecelerationEnv > 0) |
| 251 | wheelDeceleration = wheelDecelerationEnv; |
| 252 | } |
| 253 | |
| 254 | void QQuickFlickablePrivate::init() |
| 255 | { |
| 256 | Q_Q(QQuickFlickable); |
| 257 | QQml_setParent_noEvent(object: contentItem, parent: q); |
| 258 | contentItem->setParentItem(q); |
| 259 | qmlobject_connect(&timeline, QQuickTimeLine, SIGNAL(completed()), |
| 260 | q, QQuickFlickable, SLOT(timelineCompleted())); |
| 261 | qmlobject_connect(&velocityTimeline, QQuickTimeLine, SIGNAL(completed()), |
| 262 | q, QQuickFlickable, SLOT(velocityTimelineCompleted())); |
| 263 | q->setAcceptedMouseButtons(acceptedButtons); |
| 264 | q->setAcceptTouchEvents(true); |
| 265 | q->setFiltersChildMouseEvents(true); |
| 266 | q->setFlag(flag: QQuickItem::ItemIsViewport); |
| 267 | QQuickItemPrivate *viewportPrivate = QQuickItemPrivate::get(item: contentItem); |
| 268 | viewportPrivate->addItemChangeListener(listener: this, types: QQuickItemPrivate::Geometry); |
| 269 | setSizePolicy(horizontalPolicy: QLayoutPolicy::Expanding, verticalPolicy: QLayoutPolicy::Expanding); |
| 270 | } |
| 271 | |
| 272 | /*! |
| 273 | \internal |
| 274 | Returns the distance to overshoot, given \a velocity. |
| 275 | Will be in range 0 - velocity / 3, but limited to a max of QML_FLICK_OVERSHOOT |
| 276 | */ |
| 277 | qreal QQuickFlickablePrivate::overShootDistance(qreal velocity) const |
| 278 | { |
| 279 | if (maxVelocity <= 0) |
| 280 | return 0; |
| 281 | |
| 282 | return qMin(a: qreal(QML_FLICK_OVERSHOOT), b: velocity / 3); |
| 283 | } |
| 284 | |
| 285 | void QQuickFlickablePrivate::AxisData::addVelocitySample(qreal v, qreal maxVelocity) |
| 286 | { |
| 287 | if (v > maxVelocity) |
| 288 | v = maxVelocity; |
| 289 | else if (v < -maxVelocity) |
| 290 | v = -maxVelocity; |
| 291 | velocityBuffer.append(v); |
| 292 | if (velocityBuffer.count() > QML_FLICK_SAMPLEBUFFER) |
| 293 | velocityBuffer.remove(idx: 0); |
| 294 | } |
| 295 | |
| 296 | void QQuickFlickablePrivate::AxisData::updateVelocity() |
| 297 | { |
| 298 | velocity = 0; |
| 299 | if (velocityBuffer.count() > QML_FLICK_DISCARDSAMPLES) { |
| 300 | int count = velocityBuffer.count()-QML_FLICK_DISCARDSAMPLES; |
| 301 | for (int i = 0; i < count; ++i) { |
| 302 | qreal v = velocityBuffer.at(idx: i); |
| 303 | velocity += v; |
| 304 | } |
| 305 | velocity /= count; |
| 306 | } |
| 307 | } |
| 308 | |
| 309 | void QQuickFlickablePrivate::itemGeometryChanged(QQuickItem *item, QQuickGeometryChange change, const QRectF &oldGeom) |
| 310 | { |
| 311 | Q_Q(QQuickFlickable); |
| 312 | if (item == contentItem) { |
| 313 | Qt::Orientations orient; |
| 314 | if (change.xChange()) |
| 315 | orient |= Qt::Horizontal; |
| 316 | if (change.yChange()) |
| 317 | orient |= Qt::Vertical; |
| 318 | if (orient) { |
| 319 | q->viewportMoved(orient); |
| 320 | const QPointF deltaMoved = item->position() - oldGeom.topLeft(); |
| 321 | if (hData.contentPositionChangedExternallyDuringDrag) |
| 322 | hData.pressPos += deltaMoved.x(); |
| 323 | if (vData.contentPositionChangedExternallyDuringDrag) |
| 324 | vData.pressPos += deltaMoved.y(); |
| 325 | } |
| 326 | if (orient & Qt::Horizontal) |
| 327 | emit q->contentXChanged(); |
| 328 | if (orient & Qt::Vertical) |
| 329 | emit q->contentYChanged(); |
| 330 | } |
| 331 | } |
| 332 | |
| 333 | bool QQuickFlickablePrivate::flickX(QEvent::Type eventType, qreal velocity) |
| 334 | { |
| 335 | Q_Q(QQuickFlickable); |
| 336 | return flick(data&: hData, minExtent: q->minXExtent(), maxExtent: q->maxXExtent(), vSize: q->width(), fixupCallback: fixupX_callback, eventType, velocity); |
| 337 | } |
| 338 | |
| 339 | bool QQuickFlickablePrivate::flickY(QEvent::Type eventType, qreal velocity) |
| 340 | { |
| 341 | Q_Q(QQuickFlickable); |
| 342 | return flick(data&: vData, minExtent: q->minYExtent(), maxExtent: q->maxYExtent(), vSize: q->height(), fixupCallback: fixupY_callback, eventType, velocity); |
| 343 | } |
| 344 | |
| 345 | bool QQuickFlickablePrivate::flick(AxisData &data, qreal minExtent, qreal maxExtent, qreal, |
| 346 | QQuickTimeLineCallback::Callback fixupCallback, |
| 347 | QEvent::Type eventType, qreal velocity) |
| 348 | { |
| 349 | Q_Q(QQuickFlickable); |
| 350 | qreal maxDistance = -1; |
| 351 | data.fixingUp = false; |
| 352 | // -ve velocity means list is moving up |
| 353 | if (velocity > 0) { |
| 354 | maxDistance = qAbs(t: minExtent - data.move.value()); |
| 355 | data.flickTarget = minExtent; |
| 356 | } else { |
| 357 | maxDistance = qAbs(t: maxExtent - data.move.value()); |
| 358 | data.flickTarget = maxExtent; |
| 359 | } |
| 360 | if (maxDistance > 0 || boundsBehavior & QQuickFlickable::OvershootBounds) { |
| 361 | qreal v = velocity; |
| 362 | if (maxVelocity != -1 && maxVelocity < qAbs(t: v)) { |
| 363 | if (v < 0) |
| 364 | v = -maxVelocity; |
| 365 | else |
| 366 | v = maxVelocity; |
| 367 | } |
| 368 | |
| 369 | qreal accel = eventType == QEvent::Wheel ? wheelDeceleration : deceleration; |
| 370 | qCDebug(lcFlickable) << "choosing deceleration" << accel << "for" << eventType; |
| 371 | // adjust accel so that we hit a full pixel |
| 372 | qreal v2 = v * v; |
| 373 | qreal dist = v2 / (accel * 2.0); |
| 374 | if (v > 0) |
| 375 | dist = -dist; |
| 376 | qreal target = std::round(x: data.move.value() - dist); |
| 377 | dist = -target + data.move.value(); |
| 378 | accel = v2 / (2.0f * qAbs(t: dist)); |
| 379 | |
| 380 | resetTimeline(data); |
| 381 | if (!data.inOvershoot) { |
| 382 | if (boundsBehavior & QQuickFlickable::OvershootBounds) |
| 383 | timeline.accel(data.move, velocity: v, accel); |
| 384 | else |
| 385 | timeline.accel(data.move, velocity: v, accel, maxDistance); |
| 386 | } |
| 387 | timeline.callback(QQuickTimeLineCallback(&data.move, fixupCallback, this)); |
| 388 | |
| 389 | if (&data == &hData) |
| 390 | return !hData.flicking && q->xflick(); |
| 391 | else if (&data == &vData) |
| 392 | return !vData.flicking && q->yflick(); |
| 393 | return false; |
| 394 | } else { |
| 395 | resetTimeline(data); |
| 396 | fixup(data, minExtent, maxExtent); |
| 397 | return false; |
| 398 | } |
| 399 | } |
| 400 | |
| 401 | void QQuickFlickablePrivate::fixupY_callback(void *data) |
| 402 | { |
| 403 | ((QQuickFlickablePrivate *)data)->fixupY(); |
| 404 | } |
| 405 | |
| 406 | void QQuickFlickablePrivate::fixupX_callback(void *data) |
| 407 | { |
| 408 | ((QQuickFlickablePrivate *)data)->fixupX(); |
| 409 | } |
| 410 | |
| 411 | void QQuickFlickablePrivate::fixupX() |
| 412 | { |
| 413 | Q_Q(QQuickFlickable); |
| 414 | if (!q->isComponentComplete()) |
| 415 | return; //Do not fixup from initialization values |
| 416 | fixup(data&: hData, minExtent: q->minXExtent(), maxExtent: q->maxXExtent()); |
| 417 | } |
| 418 | |
| 419 | void QQuickFlickablePrivate::fixupY() |
| 420 | { |
| 421 | Q_Q(QQuickFlickable); |
| 422 | if (!q->isComponentComplete()) |
| 423 | return; //Do not fixup from initialization values |
| 424 | fixup(data&: vData, minExtent: q->minYExtent(), maxExtent: q->maxYExtent()); |
| 425 | } |
| 426 | |
| 427 | /*! |
| 428 | \internal |
| 429 | |
| 430 | Adjusts the contentItem's position via the timeline. |
| 431 | This function is used by QQuickFlickablePrivate::fixup in order to |
| 432 | position the contentItem back into the viewport, in case flicking, |
| 433 | dragging or geometry adjustments moved it outside of bounds. |
| 434 | */ |
| 435 | void QQuickFlickablePrivate::adjustContentPos(AxisData &data, qreal toPos) |
| 436 | { |
| 437 | Q_Q(QQuickFlickable); |
| 438 | switch (fixupMode) { |
| 439 | case Immediate: |
| 440 | timeline.set(data.move, toPos); |
| 441 | break; |
| 442 | case ExtentChanged: |
| 443 | // The target has changed. Don't start from the beginning; just complete the |
| 444 | // second half of the animation using the new extent. |
| 445 | timeline.move(data.move, destination: toPos, QEasingCurve(QEasingCurve::OutExpo), time: 3*fixupDuration/4); |
| 446 | data.fixingUp = true; |
| 447 | break; |
| 448 | default: { |
| 449 | if (data.transitionToBounds && data.transitionToBounds->startTransition(data: &data, toPos)) { |
| 450 | q->movementStarting(); |
| 451 | data.fixingUp = true; |
| 452 | } else { |
| 453 | qreal dist = toPos - data.move; |
| 454 | timeline.move(data.move, destination: toPos - dist/2, QEasingCurve(QEasingCurve::InQuad), time: fixupDuration/4); |
| 455 | timeline.move(data.move, destination: toPos, QEasingCurve(QEasingCurve::OutExpo), time: 3*fixupDuration/4); |
| 456 | data.fixingUp = true; |
| 457 | } |
| 458 | } |
| 459 | } |
| 460 | } |
| 461 | |
| 462 | void QQuickFlickablePrivate::resetTimeline(AxisData &data) |
| 463 | { |
| 464 | timeline.reset(data.move); |
| 465 | if (data.transitionToBounds) |
| 466 | data.transitionToBounds->stopTransition(); |
| 467 | } |
| 468 | |
| 469 | void QQuickFlickablePrivate::clearTimeline() |
| 470 | { |
| 471 | timeline.clear(); |
| 472 | if (hData.transitionToBounds) |
| 473 | hData.transitionToBounds->stopTransition(); |
| 474 | if (vData.transitionToBounds) |
| 475 | vData.transitionToBounds->stopTransition(); |
| 476 | } |
| 477 | |
| 478 | /*! |
| 479 | \internal |
| 480 | |
| 481 | This function should be called after the contentItem has been moved, either programmatically, |
| 482 | or by the timeline (as a result of a flick). |
| 483 | It ensures that the contentItem will be moved back into bounds, |
| 484 | in case it was flicked outside of the visible area. |
| 485 | |
| 486 | The positional adjustment will usually be animated by the timeline, unless the fixupMode is set to Immediate. |
| 487 | */ |
| 488 | void QQuickFlickablePrivate::fixup(AxisData &data, qreal minExtent, qreal maxExtent) |
| 489 | { |
| 490 | if (data.move.value() >= minExtent || maxExtent > minExtent) { |
| 491 | resetTimeline(data); |
| 492 | if (data.move.value() != minExtent) { |
| 493 | adjustContentPos(data, toPos: minExtent); |
| 494 | } |
| 495 | } else if (data.move.value() <= maxExtent) { |
| 496 | resetTimeline(data); |
| 497 | adjustContentPos(data, toPos: maxExtent); |
| 498 | } else if (-std::round(x: -data.move.value()) != data.move.value()) { |
| 499 | // We could animate, but since it is less than 0.5 pixel it's probably not worthwhile. |
| 500 | resetTimeline(data); |
| 501 | qreal val = data.move.value(); |
| 502 | if (std::abs(x: std::round(x: val) - val) < 0.25) // round small differences |
| 503 | val = std::round(x: val); |
| 504 | else if (data.smoothVelocity.value() > 0) // continue direction of motion for larger |
| 505 | val = std::ceil(x: val); |
| 506 | else if (data.smoothVelocity.value() < 0) |
| 507 | val = std::floor(x: val); |
| 508 | else // otherwise round |
| 509 | val = std::round(x: val); |
| 510 | timeline.set(data.move, val); |
| 511 | } |
| 512 | data.inOvershoot = false; |
| 513 | fixupMode = Normal; |
| 514 | data.vTime = timeline.time(); |
| 515 | } |
| 516 | |
| 517 | static bool fuzzyLessThanOrEqualTo(qreal a, qreal b) |
| 518 | { |
| 519 | if (a == 0.0 || b == 0.0) { |
| 520 | // qFuzzyCompare is broken |
| 521 | a += 1.0; |
| 522 | b += 1.0; |
| 523 | } |
| 524 | return a <= b || qFuzzyCompare(p1: a, p2: b); |
| 525 | } |
| 526 | |
| 527 | /*! |
| 528 | \internal |
| 529 | |
| 530 | This function's main purpose is to update the atBeginning and atEnd flags |
| 531 | in hData and vData. It should be called when the contentItem has moved, |
| 532 | to ensure that hData and vData are up to date. |
| 533 | |
| 534 | The origin will also be updated, if AxisData::markExtentsDirty has been called |
| 535 | */ |
| 536 | void QQuickFlickablePrivate::updateBeginningEnd() |
| 537 | { |
| 538 | Q_Q(QQuickFlickable); |
| 539 | bool atXBeginningChange = false, atXEndChange = false; |
| 540 | bool atYBeginningChange = false, atYEndChange = false; |
| 541 | |
| 542 | // Vertical |
| 543 | const qreal maxyextent = -q->maxYExtent(); |
| 544 | const qreal minyextent = -q->minYExtent(); |
| 545 | const qreal ypos = pixelAligned ? -std::round(x: vData.move.value()) : -vData.move.value(); |
| 546 | bool atBeginning = fuzzyLessThanOrEqualTo(a: ypos, b: std::ceil(x: minyextent)); |
| 547 | bool atEnd = fuzzyLessThanOrEqualTo(a: std::floor(x: maxyextent), b: ypos); |
| 548 | |
| 549 | if (atBeginning != vData.atBeginning) { |
| 550 | vData.atBeginning = atBeginning; |
| 551 | atYBeginningChange = true; |
| 552 | if (!vData.moving && atBeginning) |
| 553 | vData.smoothVelocity.setValue(0); |
| 554 | } |
| 555 | if (atEnd != vData.atEnd) { |
| 556 | vData.atEnd = atEnd; |
| 557 | atYEndChange = true; |
| 558 | if (!vData.moving && atEnd) |
| 559 | vData.smoothVelocity.setValue(0); |
| 560 | } |
| 561 | |
| 562 | // Horizontal |
| 563 | const qreal maxxextent = -q->maxXExtent(); |
| 564 | const qreal minxextent = -q->minXExtent(); |
| 565 | const qreal xpos = pixelAligned ? -std::round(x: hData.move.value()) : -hData.move.value(); |
| 566 | atBeginning = fuzzyLessThanOrEqualTo(a: xpos, b: std::ceil(x: minxextent)); |
| 567 | atEnd = fuzzyLessThanOrEqualTo(a: std::floor(x: maxxextent), b: xpos); |
| 568 | |
| 569 | if (atBeginning != hData.atBeginning) { |
| 570 | hData.atBeginning = atBeginning; |
| 571 | atXBeginningChange = true; |
| 572 | if (!hData.moving && atBeginning) |
| 573 | hData.smoothVelocity.setValue(0); |
| 574 | } |
| 575 | if (atEnd != hData.atEnd) { |
| 576 | hData.atEnd = atEnd; |
| 577 | atXEndChange = true; |
| 578 | if (!hData.moving && atEnd) |
| 579 | hData.smoothVelocity.setValue(0); |
| 580 | } |
| 581 | |
| 582 | if (vData.extentsChanged) { |
| 583 | vData.extentsChanged = false; |
| 584 | qreal originY = q->originY(); |
| 585 | if (vData.origin != originY) { |
| 586 | vData.origin = originY; |
| 587 | emit q->originYChanged(); |
| 588 | } |
| 589 | } |
| 590 | |
| 591 | if (hData.extentsChanged) { |
| 592 | hData.extentsChanged = false; |
| 593 | qreal originX = q->originX(); |
| 594 | if (hData.origin != originX) { |
| 595 | hData.origin = originX; |
| 596 | emit q->originXChanged(); |
| 597 | } |
| 598 | } |
| 599 | |
| 600 | if (atXEndChange || atYEndChange || atXBeginningChange || atYBeginningChange) |
| 601 | emit q->isAtBoundaryChanged(); |
| 602 | if (atXEndChange) |
| 603 | emit q->atXEndChanged(); |
| 604 | if (atXBeginningChange) |
| 605 | emit q->atXBeginningChanged(); |
| 606 | if (atYEndChange) |
| 607 | emit q->atYEndChanged(); |
| 608 | if (atYBeginningChange) |
| 609 | emit q->atYBeginningChanged(); |
| 610 | |
| 611 | if (visibleArea) |
| 612 | visibleArea->updateVisible(); |
| 613 | } |
| 614 | |
| 615 | /*! |
| 616 | \qmlsignal QtQuick::Flickable::dragStarted() |
| 617 | |
| 618 | This signal is emitted when the view starts to be dragged due to user |
| 619 | interaction. |
| 620 | */ |
| 621 | |
| 622 | /*! |
| 623 | \qmlsignal QtQuick::Flickable::dragEnded() |
| 624 | |
| 625 | This signal is emitted when the user stops dragging the view. |
| 626 | |
| 627 | If the velocity of the drag is sufficient at the time the |
| 628 | touch/mouse button is released then a flick will start. |
| 629 | */ |
| 630 | |
| 631 | /*! |
| 632 | \qmltype Flickable |
| 633 | \nativetype QQuickFlickable |
| 634 | \inqmlmodule QtQuick |
| 635 | \ingroup qtquick-input |
| 636 | \ingroup qtquick-containers |
| 637 | |
| 638 | \brief Provides a surface that can be "flicked". |
| 639 | \inherits Item |
| 640 | |
| 641 | The Flickable item places its children on a surface that can be dragged |
| 642 | and flicked, causing the view onto the child items to scroll. This |
| 643 | behavior forms the basis of Items that are designed to show large numbers |
| 644 | of child items, such as \l ListView and \l GridView. |
| 645 | |
| 646 | In traditional user interfaces, views can be scrolled using standard |
| 647 | controls, such as scroll bars and arrow buttons. In some situations, it |
| 648 | is also possible to drag the view directly by pressing and holding a |
| 649 | mouse button while moving the cursor. In touch-based user interfaces, |
| 650 | this dragging action is often complemented with a flicking action, where |
| 651 | scrolling continues after the user has stopped touching the view. |
| 652 | |
| 653 | Flickable does not automatically clip its contents. If it is not used as |
| 654 | a full-screen item, you should consider setting the \l{Item::}{clip} property |
| 655 | to true. |
| 656 | |
| 657 | \section1 Example Usage |
| 658 | |
| 659 | \div {class="float-right"} |
| 660 | \inlineimage flickable.gif |
| 661 | \enddiv |
| 662 | |
| 663 | The following example shows a small view onto a large image in which the |
| 664 | user can drag or flick the image in order to view different parts of it. |
| 665 | |
| 666 | \snippet qml/flickable.qml document |
| 667 | |
| 668 | \clearfloat |
| 669 | |
| 670 | Items declared as children of a Flickable are automatically parented to the |
| 671 | Flickable's \l contentItem. This should be taken into account when |
| 672 | operating on the children of the Flickable; it is usually the children of |
| 673 | \c contentItem that are relevant. For example, the bound of Items added |
| 674 | to the Flickable will be available by \c contentItem.childrenRect |
| 675 | |
| 676 | \section1 Examples of contentX and contentY |
| 677 | |
| 678 | The following images demonstrate a flickable being flicked in various |
| 679 | directions and the resulting \l contentX and \l contentY values. |
| 680 | The blue square represents the flickable's content, and the black |
| 681 | border represents the bounds of the flickable. |
| 682 | |
| 683 | \table |
| 684 | \row |
| 685 | \li \image flickable-contentXY-resting.png |
| 686 | \li The \c contentX and \c contentY are both \c 0. |
| 687 | \row |
| 688 | \li \image flickable-contentXY-top-left.png |
| 689 | \li The \c contentX and the \c contentY are both \c 50. |
| 690 | \row |
| 691 | \li \image flickable-contentXY-top-right.png |
| 692 | \li The \c contentX is \c -50 and the \c contentY is \c 50. |
| 693 | \row |
| 694 | \li \image flickable-contentXY-bottom-right.png |
| 695 | \li The \c contentX and the \c contentY are both \c -50. |
| 696 | \row |
| 697 | \li \image flickable-contentXY-bottom-left.png |
| 698 | \li The \c contentX is \c 50 and the \c contentY is \c -50. |
| 699 | \endtable |
| 700 | |
| 701 | \section1 Limitations |
| 702 | |
| 703 | \note Due to an implementation detail, items placed inside a Flickable |
| 704 | cannot anchor to the Flickable. Instead, use \l {Item::}{parent}, which |
| 705 | refers to the Flickable's \l contentItem. The size of the content item is |
| 706 | determined by \l contentWidth and \l contentHeight. |
| 707 | */ |
| 708 | |
| 709 | /*! |
| 710 | \qmlsignal QtQuick::Flickable::movementStarted() |
| 711 | |
| 712 | This signal is emitted when the view begins moving due to user |
| 713 | interaction or a generated flick(). |
| 714 | */ |
| 715 | |
| 716 | /*! |
| 717 | \qmlsignal QtQuick::Flickable::movementEnded() |
| 718 | |
| 719 | This signal is emitted when the view stops moving due to user |
| 720 | interaction or a generated flick(). If a flick was active, this signal will |
| 721 | be emitted once the flick stops. If a flick was not |
| 722 | active, this signal will be emitted when the |
| 723 | user stops dragging - i.e. a mouse or touch release. |
| 724 | */ |
| 725 | |
| 726 | /*! |
| 727 | \qmlsignal QtQuick::Flickable::flickStarted() |
| 728 | |
| 729 | This signal is emitted when the view is flicked. A flick |
| 730 | starts from the point that the mouse or touch is released, |
| 731 | while still in motion. |
| 732 | */ |
| 733 | |
| 734 | /*! |
| 735 | \qmlsignal QtQuick::Flickable::flickEnded() |
| 736 | |
| 737 | This signal is emitted when the view stops moving after a flick |
| 738 | or a series of flicks. |
| 739 | */ |
| 740 | |
| 741 | /*! |
| 742 | \qmlpropertygroup QtQuick::Flickable::visibleArea |
| 743 | \qmlproperty real QtQuick::Flickable::visibleArea.xPosition |
| 744 | \qmlproperty real QtQuick::Flickable::visibleArea.widthRatio |
| 745 | \qmlproperty real QtQuick::Flickable::visibleArea.yPosition |
| 746 | \qmlproperty real QtQuick::Flickable::visibleArea.heightRatio |
| 747 | |
| 748 | These properties describe the position and size of the currently viewed area. |
| 749 | The size is defined as the percentage of the full view currently visible, |
| 750 | scaled to 0.0 - 1.0. The page position is usually in the range 0.0 (beginning) to |
| 751 | 1.0 minus size ratio (end), i.e. \c yPosition is in the range 0.0 to 1.0-\c heightRatio. |
| 752 | However, it is possible for the contents to be dragged outside of the normal |
| 753 | range, resulting in the page positions also being outside the normal range. |
| 754 | |
| 755 | These properties are typically used to draw a scrollbar. For example: |
| 756 | |
| 757 | \snippet qml/flickableScrollbar.qml 0 |
| 758 | \dots 8 |
| 759 | \snippet qml/flickableScrollbar.qml 1 |
| 760 | */ |
| 761 | QQuickFlickable::QQuickFlickable(QQuickItem *parent) |
| 762 | : QQuickItem(*(new QQuickFlickablePrivate), parent) |
| 763 | { |
| 764 | Q_D(QQuickFlickable); |
| 765 | d->init(); |
| 766 | } |
| 767 | |
| 768 | QQuickFlickable::QQuickFlickable(QQuickFlickablePrivate &dd, QQuickItem *parent) |
| 769 | : QQuickItem(dd, parent) |
| 770 | { |
| 771 | Q_D(QQuickFlickable); |
| 772 | d->init(); |
| 773 | } |
| 774 | |
| 775 | QQuickFlickable::~QQuickFlickable() |
| 776 | { |
| 777 | } |
| 778 | |
| 779 | /*! |
| 780 | \qmlproperty real QtQuick::Flickable::contentX |
| 781 | \qmlproperty real QtQuick::Flickable::contentY |
| 782 | |
| 783 | These properties hold the surface coordinate currently at the top-left |
| 784 | corner of the Flickable. For example, if you flick an image up 100 pixels, |
| 785 | \c contentY will increase by 100. |
| 786 | |
| 787 | \note If you flick back to the origin (the top-left corner), after the |
| 788 | rebound animation, \c contentX will settle to the same value as \c originX, |
| 789 | and \c contentY to \c originY. These are usually (0,0), however ListView |
| 790 | and GridView may have an arbitrary origin due to delegate size variation, |
| 791 | or item insertion/removal outside the visible region. So if you want to |
| 792 | implement something like a vertical scrollbar, one way is to use |
| 793 | \c {y: (contentY - originY) * (height / contentHeight)} |
| 794 | for the position; another way is to use the normalized values in |
| 795 | \l {QtQuick::Flickable::visibleArea}{visibleArea}. |
| 796 | |
| 797 | \sa {Examples of contentX and contentY}, originX, originY |
| 798 | */ |
| 799 | qreal QQuickFlickable::contentX() const |
| 800 | { |
| 801 | Q_D(const QQuickFlickable); |
| 802 | return -d->contentItem->x(); |
| 803 | } |
| 804 | |
| 805 | void QQuickFlickable::setContentX(qreal pos) |
| 806 | { |
| 807 | Q_D(QQuickFlickable); |
| 808 | d->hData.explicitValue = true; |
| 809 | d->resetTimeline(data&: d->hData); |
| 810 | d->hData.vTime = d->timeline.time(); |
| 811 | if (isMoving() || isFlicking()) |
| 812 | movementEnding(hMovementEnding: true, vMovementEnding: false); |
| 813 | if (!qFuzzyCompare(p1: -pos, p2: d->hData.move.value())) { |
| 814 | d->hData.contentPositionChangedExternallyDuringDrag = d->hData.dragging; |
| 815 | d->hData.move.setValue(-pos); |
| 816 | d->hData.contentPositionChangedExternallyDuringDrag = false; |
| 817 | } |
| 818 | } |
| 819 | |
| 820 | qreal QQuickFlickable::contentY() const |
| 821 | { |
| 822 | Q_D(const QQuickFlickable); |
| 823 | return -d->contentItem->y(); |
| 824 | } |
| 825 | |
| 826 | void QQuickFlickable::setContentY(qreal pos) |
| 827 | { |
| 828 | Q_D(QQuickFlickable); |
| 829 | d->vData.explicitValue = true; |
| 830 | d->resetTimeline(data&: d->vData); |
| 831 | d->vData.vTime = d->timeline.time(); |
| 832 | if (isMoving() || isFlicking()) |
| 833 | movementEnding(hMovementEnding: false, vMovementEnding: true); |
| 834 | if (!qFuzzyCompare(p1: -pos, p2: d->vData.move.value())) { |
| 835 | d->vData.contentPositionChangedExternallyDuringDrag = d->vData.dragging; |
| 836 | d->vData.move.setValue(-pos); |
| 837 | d->vData.contentPositionChangedExternallyDuringDrag = false; |
| 838 | } |
| 839 | } |
| 840 | |
| 841 | /*! |
| 842 | \qmlproperty bool QtQuick::Flickable::interactive |
| 843 | |
| 844 | This property describes whether the user can interact with the Flickable. |
| 845 | A user cannot drag or flick a Flickable that is not interactive. |
| 846 | |
| 847 | By default, this property is true. |
| 848 | |
| 849 | This property is useful for temporarily disabling flicking. This allows |
| 850 | special interaction with Flickable's children; for example, you might want |
| 851 | to freeze a flickable map while scrolling through a pop-up dialog that |
| 852 | is a child of the Flickable. |
| 853 | */ |
| 854 | bool QQuickFlickable::isInteractive() const |
| 855 | { |
| 856 | Q_D(const QQuickFlickable); |
| 857 | return d->interactive; |
| 858 | } |
| 859 | |
| 860 | void QQuickFlickable::setInteractive(bool interactive) |
| 861 | { |
| 862 | Q_D(QQuickFlickable); |
| 863 | if (interactive != d->interactive) { |
| 864 | d->interactive = interactive; |
| 865 | if (!interactive) { |
| 866 | d->cancelInteraction(); |
| 867 | } |
| 868 | emit interactiveChanged(); |
| 869 | } |
| 870 | } |
| 871 | |
| 872 | /*! |
| 873 | \qmlproperty real QtQuick::Flickable::horizontalVelocity |
| 874 | \qmlproperty real QtQuick::Flickable::verticalVelocity |
| 875 | |
| 876 | The instantaneous velocity of movement along the x and y axes, in pixels/sec. |
| 877 | |
| 878 | The reported velocity is smoothed to avoid erratic output. |
| 879 | |
| 880 | Note that for views with a large content size (more than 10 times the view size), |
| 881 | the velocity of the flick may exceed the velocity of the touch in the case |
| 882 | of multiple quick consecutive flicks. This allows the user to flick faster |
| 883 | through large content. |
| 884 | */ |
| 885 | qreal QQuickFlickable::horizontalVelocity() const |
| 886 | { |
| 887 | Q_D(const QQuickFlickable); |
| 888 | return d->hData.smoothVelocity.value(); |
| 889 | } |
| 890 | |
| 891 | qreal QQuickFlickable::verticalVelocity() const |
| 892 | { |
| 893 | Q_D(const QQuickFlickable); |
| 894 | return d->vData.smoothVelocity.value(); |
| 895 | } |
| 896 | |
| 897 | /*! |
| 898 | \qmlproperty bool QtQuick::Flickable::atXBeginning |
| 899 | \qmlproperty bool QtQuick::Flickable::atXEnd |
| 900 | \qmlproperty bool QtQuick::Flickable::atYBeginning |
| 901 | \qmlproperty bool QtQuick::Flickable::atYEnd |
| 902 | |
| 903 | These properties are true if the flickable view is positioned at the beginning, |
| 904 | or end respectively. |
| 905 | */ |
| 906 | bool QQuickFlickable::isAtXEnd() const |
| 907 | { |
| 908 | Q_D(const QQuickFlickable); |
| 909 | return d->hData.atEnd; |
| 910 | } |
| 911 | |
| 912 | bool QQuickFlickable::isAtXBeginning() const |
| 913 | { |
| 914 | Q_D(const QQuickFlickable); |
| 915 | return d->hData.atBeginning; |
| 916 | } |
| 917 | |
| 918 | bool QQuickFlickable::isAtYEnd() const |
| 919 | { |
| 920 | Q_D(const QQuickFlickable); |
| 921 | return d->vData.atEnd; |
| 922 | } |
| 923 | |
| 924 | bool QQuickFlickable::isAtYBeginning() const |
| 925 | { |
| 926 | Q_D(const QQuickFlickable); |
| 927 | return d->vData.atBeginning; |
| 928 | } |
| 929 | |
| 930 | /*! |
| 931 | \qmlproperty Item QtQuick::Flickable::contentItem |
| 932 | |
| 933 | The internal item that contains the Items to be moved in the Flickable. |
| 934 | |
| 935 | Items declared as children of a Flickable are automatically parented to the Flickable's contentItem. |
| 936 | |
| 937 | Items created dynamically need to be explicitly parented to the \e contentItem: |
| 938 | \code |
| 939 | Flickable { |
| 940 | id: myFlickable |
| 941 | function addItem(file) { |
| 942 | var component = Qt.createComponent(file) |
| 943 | component.createObject(myFlickable.contentItem); |
| 944 | } |
| 945 | } |
| 946 | \endcode |
| 947 | */ |
| 948 | QQuickItem *QQuickFlickable::contentItem() const |
| 949 | { |
| 950 | Q_D(const QQuickFlickable); |
| 951 | return d->contentItem; |
| 952 | } |
| 953 | |
| 954 | QQuickFlickableVisibleArea *QQuickFlickable::visibleArea() |
| 955 | { |
| 956 | Q_D(QQuickFlickable); |
| 957 | if (!d->visibleArea) { |
| 958 | d->visibleArea = new QQuickFlickableVisibleArea(this); |
| 959 | d->visibleArea->updateVisible(); // calculate initial ratios |
| 960 | } |
| 961 | return d->visibleArea; |
| 962 | } |
| 963 | |
| 964 | /*! |
| 965 | \qmlproperty enumeration QtQuick::Flickable::flickableDirection |
| 966 | |
| 967 | This property determines which directions the view can be flicked. |
| 968 | |
| 969 | \list |
| 970 | \li Flickable.AutoFlickDirection (default) - allows flicking vertically if the |
| 971 | \e contentHeight is not equal to the \e height of the Flickable. |
| 972 | Allows flicking horizontally if the \e contentWidth is not equal |
| 973 | to the \e width of the Flickable. |
| 974 | \li Flickable.AutoFlickIfNeeded - allows flicking vertically if the |
| 975 | \e contentHeight is greater than the \e height of the Flickable. |
| 976 | Allows flicking horizontally if the \e contentWidth is greater than |
| 977 | to the \e width of the Flickable. (since \c{QtQuick 2.7}) |
| 978 | \li Flickable.HorizontalFlick - allows flicking horizontally. |
| 979 | \li Flickable.VerticalFlick - allows flicking vertically. |
| 980 | \li Flickable.HorizontalAndVerticalFlick - allows flicking in both directions. |
| 981 | \endlist |
| 982 | */ |
| 983 | QQuickFlickable::FlickableDirection QQuickFlickable::flickableDirection() const |
| 984 | { |
| 985 | Q_D(const QQuickFlickable); |
| 986 | return d->flickableDirection; |
| 987 | } |
| 988 | |
| 989 | void QQuickFlickable::setFlickableDirection(FlickableDirection direction) |
| 990 | { |
| 991 | Q_D(QQuickFlickable); |
| 992 | if (direction != d->flickableDirection) { |
| 993 | d->flickableDirection = direction; |
| 994 | emit flickableDirectionChanged(); |
| 995 | } |
| 996 | } |
| 997 | |
| 998 | /*! |
| 999 | \qmlproperty bool QtQuick::Flickable::pixelAligned |
| 1000 | |
| 1001 | This property sets the alignment of \l contentX and \l contentY to |
| 1002 | pixels (\c true) or subpixels (\c false). |
| 1003 | |
| 1004 | Enable pixelAligned to optimize for still content or moving content with |
| 1005 | high constrast edges, such as one-pixel-wide lines, text or vector graphics. |
| 1006 | Disable pixelAligned when optimizing for animation quality. |
| 1007 | |
| 1008 | The default is \c false. |
| 1009 | */ |
| 1010 | bool QQuickFlickable::pixelAligned() const |
| 1011 | { |
| 1012 | Q_D(const QQuickFlickable); |
| 1013 | return d->pixelAligned; |
| 1014 | } |
| 1015 | |
| 1016 | void QQuickFlickable::setPixelAligned(bool align) |
| 1017 | { |
| 1018 | Q_D(QQuickFlickable); |
| 1019 | if (align != d->pixelAligned) { |
| 1020 | d->pixelAligned = align; |
| 1021 | emit pixelAlignedChanged(); |
| 1022 | } |
| 1023 | } |
| 1024 | |
| 1025 | /*! |
| 1026 | \qmlproperty bool QtQuick::Flickable::synchronousDrag |
| 1027 | \since 5.12 |
| 1028 | |
| 1029 | If this property is set to true, then when the mouse or touchpoint moves |
| 1030 | far enough to begin dragging the content, the content will jump, such that |
| 1031 | the content pixel which was under the cursor or touchpoint when pressed |
| 1032 | remains under that point. |
| 1033 | |
| 1034 | The default is \c false, which provides a smoother experience (no jump) |
| 1035 | at the cost that some of the drag distance is "lost" at the beginning. |
| 1036 | */ |
| 1037 | bool QQuickFlickable::synchronousDrag() const |
| 1038 | { |
| 1039 | Q_D(const QQuickFlickable); |
| 1040 | return d->syncDrag; |
| 1041 | } |
| 1042 | |
| 1043 | void QQuickFlickable::setSynchronousDrag(bool v) |
| 1044 | { |
| 1045 | Q_D(QQuickFlickable); |
| 1046 | if (v != d->syncDrag) { |
| 1047 | d->syncDrag = v; |
| 1048 | emit synchronousDragChanged(); |
| 1049 | } |
| 1050 | } |
| 1051 | |
| 1052 | /*! |
| 1053 | \qmlproperty flags QtQuick::Flickable::acceptedButtons |
| 1054 | \since 6.9 |
| 1055 | |
| 1056 | The mouse buttons that can be used to scroll this Flickable by dragging. |
| 1057 | |
| 1058 | By default, this property is set to \l {QtQuick::MouseEvent::button} {Qt.LeftButton}, |
| 1059 | which provides the same behavior as in previous Qt versions; but in most |
| 1060 | user interfaces, this behavior is unexpected. Users expect to flick only on |
| 1061 | a touchscreen, and to use the mouse wheel, touchpad gestures or a scroll |
| 1062 | bar with mouse or touchpad. Set it to \c Qt.NoButton to disable dragging. |
| 1063 | |
| 1064 | It can be set to an OR combination of mouse buttons, and will ignore events |
| 1065 | from other buttons. |
| 1066 | */ |
| 1067 | Qt::MouseButtons QQuickFlickable::acceptedButtons() const |
| 1068 | { |
| 1069 | Q_D(const QQuickFlickable); |
| 1070 | return d->acceptedButtons; |
| 1071 | } |
| 1072 | |
| 1073 | void QQuickFlickable::setAcceptedButtons(Qt::MouseButtons buttons) |
| 1074 | { |
| 1075 | Q_D(QQuickFlickable); |
| 1076 | if (d->acceptedButtons == buttons) |
| 1077 | return; |
| 1078 | |
| 1079 | d->acceptedButtons = buttons; |
| 1080 | setAcceptedMouseButtons(buttons); |
| 1081 | emit acceptedButtonsChanged(); |
| 1082 | } |
| 1083 | |
| 1084 | /*! \internal |
| 1085 | Take the velocity of the first point from the given \a event and transform |
| 1086 | it to the local coordinate system (taking scale and rotation into account). |
| 1087 | */ |
| 1088 | QVector2D QQuickFlickablePrivate::firstPointLocalVelocity(QPointerEvent *event) |
| 1089 | { |
| 1090 | QTransform transform = windowToItemTransform(); |
| 1091 | // rotate and scale the velocity vector from scene to local |
| 1092 | return QVector2D(transform.map(p: event->point(i: 0).velocity().toPointF()) - transform.map(p: QPointF())); |
| 1093 | } |
| 1094 | |
| 1095 | qint64 QQuickFlickablePrivate::computeCurrentTime(QInputEvent *event) const |
| 1096 | { |
| 1097 | if (0 != event->timestamp()) |
| 1098 | return event->timestamp(); |
| 1099 | if (!timer.isValid()) |
| 1100 | return 0LL; |
| 1101 | return timer.elapsed(); |
| 1102 | } |
| 1103 | |
| 1104 | void QQuickFlickablePrivate::handlePressEvent(QPointerEvent *event) |
| 1105 | { |
| 1106 | Q_Q(QQuickFlickable); |
| 1107 | timer.start(); |
| 1108 | if (interactive && timeline.isActive() |
| 1109 | && ((qAbs(t: hData.smoothVelocity.value()) > RetainGrabVelocity && !hData.fixingUp && !hData.inOvershoot) |
| 1110 | || (qAbs(t: vData.smoothVelocity.value()) > RetainGrabVelocity && !vData.fixingUp && !vData.inOvershoot))) { |
| 1111 | stealMouse = true; // If we've been flicked then steal the click. |
| 1112 | int flickTime = timeline.time(); |
| 1113 | if (flickTime > 600) { |
| 1114 | // too long between flicks - cancel boost |
| 1115 | hData.continuousFlickVelocity = 0; |
| 1116 | vData.continuousFlickVelocity = 0; |
| 1117 | flickBoost = 1.0; |
| 1118 | } else { |
| 1119 | hData.continuousFlickVelocity = -hData.smoothVelocity.value(); |
| 1120 | vData.continuousFlickVelocity = -vData.smoothVelocity.value(); |
| 1121 | if (flickTime > 300) // slower flicking - reduce boost |
| 1122 | flickBoost = qMax(a: 1.0, b: flickBoost - 0.5); |
| 1123 | } |
| 1124 | } else { |
| 1125 | stealMouse = false; |
| 1126 | hData.continuousFlickVelocity = 0; |
| 1127 | vData.continuousFlickVelocity = 0; |
| 1128 | flickBoost = 1.0; |
| 1129 | } |
| 1130 | q->setKeepMouseGrab(stealMouse); |
| 1131 | |
| 1132 | maybeBeginDrag(currentTimestamp: computeCurrentTime(event), pressPosn: event->points().first().position(), |
| 1133 | buttons: event->isSinglePointEvent() ? static_cast<QSinglePointEvent *>(event)->buttons() |
| 1134 | : Qt::NoButton); |
| 1135 | } |
| 1136 | |
| 1137 | void QQuickFlickablePrivate::maybeBeginDrag(qint64 currentTimestamp, const QPointF &pressPosn, Qt::MouseButtons buttons) |
| 1138 | { |
| 1139 | Q_Q(QQuickFlickable); |
| 1140 | clearDelayedPress(); |
| 1141 | // consider dragging only when buttons intersect acceptedButtons, or it's a touch event which has no button |
| 1142 | pressed = (buttons == Qt::NoButton) || (acceptedButtons != Qt::NoButton && (buttons & acceptedButtons) != 0); |
| 1143 | |
| 1144 | if (hData.transitionToBounds) |
| 1145 | hData.transitionToBounds->stopTransition(); |
| 1146 | if (vData.transitionToBounds) |
| 1147 | vData.transitionToBounds->stopTransition(); |
| 1148 | if (!hData.fixingUp) |
| 1149 | resetTimeline(data&: hData); |
| 1150 | if (!vData.fixingUp) |
| 1151 | resetTimeline(data&: vData); |
| 1152 | |
| 1153 | hData.reset(); |
| 1154 | vData.reset(); |
| 1155 | hData.dragMinBound = q->minXExtent() - hData.startMargin; |
| 1156 | vData.dragMinBound = q->minYExtent() - vData.startMargin; |
| 1157 | hData.dragMaxBound = q->maxXExtent() + hData.endMargin; |
| 1158 | vData.dragMaxBound = q->maxYExtent() + vData.endMargin; |
| 1159 | fixupMode = Normal; |
| 1160 | lastPos = QPointF(); |
| 1161 | pressPos = pressPosn; |
| 1162 | hData.pressPos = hData.move.value(); |
| 1163 | vData.pressPos = vData.move.value(); |
| 1164 | const bool wasFlicking = hData.flicking || vData.flicking; |
| 1165 | hData.flickingWhenDragBegan = hData.flicking; |
| 1166 | vData.flickingWhenDragBegan = vData.flicking; |
| 1167 | if (hData.flicking) { |
| 1168 | hData.flicking = false; |
| 1169 | emit q->flickingHorizontallyChanged(); |
| 1170 | } |
| 1171 | if (vData.flicking) { |
| 1172 | vData.flicking = false; |
| 1173 | emit q->flickingVerticallyChanged(); |
| 1174 | } |
| 1175 | if (wasFlicking) |
| 1176 | emit q->flickingChanged(); |
| 1177 | lastPosTime = lastPressTime = currentTimestamp; |
| 1178 | vData.velocityTime.start(); |
| 1179 | hData.velocityTime.start(); |
| 1180 | } |
| 1181 | |
| 1182 | void QQuickFlickablePrivate::drag(qint64 currentTimestamp, QEvent::Type eventType, const QPointF &localPos, |
| 1183 | const QVector2D &deltas, bool overThreshold, bool momentum, |
| 1184 | bool velocitySensitiveOverBounds, const QVector2D &velocity) |
| 1185 | { |
| 1186 | Q_Q(QQuickFlickable); |
| 1187 | bool rejectY = false; |
| 1188 | bool rejectX = false; |
| 1189 | |
| 1190 | bool keepY = q->yflick(); |
| 1191 | bool keepX = q->xflick(); |
| 1192 | |
| 1193 | bool stealY = false; |
| 1194 | bool stealX = false; |
| 1195 | if (eventType == QEvent::MouseMove) { |
| 1196 | stealX = stealY = stealMouse; |
| 1197 | } else if (eventType == QEvent::Wheel) { |
| 1198 | stealX = stealY = scrollingPhase; |
| 1199 | } |
| 1200 | |
| 1201 | bool prevHMoved = hMoved; |
| 1202 | bool prevVMoved = vMoved; |
| 1203 | |
| 1204 | qint64 elapsedSincePress = currentTimestamp - lastPressTime; |
| 1205 | qCDebug(lcFlickable).nospace() << currentTimestamp << ' ' << eventType << " drag @ " << localPos.x() << ',' << localPos.y() |
| 1206 | << " \u0394 " << deltas.x() << ',' << deltas.y() << " vel " << velocity.x() << ',' << velocity.y() |
| 1207 | << " thrsld? " << overThreshold << " momentum? " << momentum << " velSens? " << velocitySensitiveOverBounds |
| 1208 | << " sincePress " << elapsedSincePress; |
| 1209 | |
| 1210 | if (q->yflick()) { |
| 1211 | qreal dy = deltas.y(); |
| 1212 | if (overThreshold || elapsedSincePress > 200) { |
| 1213 | if (!vMoved && !vData.dragging) |
| 1214 | vData.dragStartOffset = dy; |
| 1215 | qreal newY = dy + vData.pressPos - (syncDrag ? 0 : vData.dragStartOffset); |
| 1216 | // Recalculate bounds in case margins have changed, but use the content |
| 1217 | // size estimate taken at the start of the drag in case the drag causes |
| 1218 | // the estimate to be altered |
| 1219 | const qreal minY = vData.dragMinBound + vData.startMargin; |
| 1220 | const qreal maxY = vData.dragMaxBound - vData.endMargin; |
| 1221 | if (!(boundsBehavior & QQuickFlickable::DragOverBounds)) { |
| 1222 | if (fuzzyLessThanOrEqualTo(a: newY, b: maxY)) { |
| 1223 | newY = maxY; |
| 1224 | rejectY = vData.pressPos == maxY && vData.move.value() == maxY && dy < 0; |
| 1225 | } |
| 1226 | if (fuzzyLessThanOrEqualTo(a: minY, b: newY)) { |
| 1227 | newY = minY; |
| 1228 | rejectY |= vData.pressPos == minY && vData.move.value() == minY && dy > 0; |
| 1229 | } |
| 1230 | } else { |
| 1231 | qreal vel = velocity.y() / QML_FLICK_OVERSHOOTFRICTION; |
| 1232 | if (vel > 0. && vel > vData.velocity) |
| 1233 | vData.velocity = qMin(a: velocity.y() / QML_FLICK_OVERSHOOTFRICTION, b: maxVelocity); |
| 1234 | else if (vel < 0. && vel < vData.velocity) |
| 1235 | vData.velocity = qMax(a: velocity.y() / QML_FLICK_OVERSHOOTFRICTION, b: -maxVelocity); |
| 1236 | if (newY > minY) { |
| 1237 | // Overshoot beyond the top. But don't wait for momentum phase to end before returning to bounds. |
| 1238 | if (momentum && vData.atBeginning) { |
| 1239 | if (!vData.inRebound) { |
| 1240 | vData.inRebound = true; |
| 1241 | q->returnToBounds(); |
| 1242 | } |
| 1243 | return; |
| 1244 | } |
| 1245 | if (velocitySensitiveOverBounds) { |
| 1246 | qreal overshoot = (newY - minY) * vData.velocity / maxVelocity / QML_FLICK_OVERSHOOTFRICTION; |
| 1247 | overshoot = QML_FLICK_OVERSHOOT * effectiveDevicePixelRatio() * EaseOvershoot(t: overshoot / QML_FLICK_OVERSHOOT / effectiveDevicePixelRatio()); |
| 1248 | newY = minY + overshoot; |
| 1249 | } else { |
| 1250 | newY = minY + (newY - minY) / 2; |
| 1251 | } |
| 1252 | } else if (newY < maxY && maxY - minY <= 0) { |
| 1253 | // Overshoot beyond the bottom. But don't wait for momentum phase to end before returning to bounds. |
| 1254 | if (momentum && vData.atEnd) { |
| 1255 | if (!vData.inRebound) { |
| 1256 | vData.inRebound = true; |
| 1257 | q->returnToBounds(); |
| 1258 | } |
| 1259 | return; |
| 1260 | } |
| 1261 | if (velocitySensitiveOverBounds) { |
| 1262 | qreal overshoot = (newY - maxY) * vData.velocity / maxVelocity / QML_FLICK_OVERSHOOTFRICTION; |
| 1263 | overshoot = QML_FLICK_OVERSHOOT * effectiveDevicePixelRatio() * EaseOvershoot(t: overshoot / QML_FLICK_OVERSHOOT / effectiveDevicePixelRatio()); |
| 1264 | newY = maxY - overshoot; |
| 1265 | } else { |
| 1266 | newY = maxY + (newY - maxY) / 2; |
| 1267 | } |
| 1268 | } |
| 1269 | } |
| 1270 | if (!rejectY && stealMouse && dy != vData.previousDragDelta) { |
| 1271 | clearTimeline(); |
| 1272 | vData.move.setValue(newY); |
| 1273 | vMoved = true; |
| 1274 | } |
| 1275 | if (!rejectY && overThreshold) |
| 1276 | stealY = true; |
| 1277 | |
| 1278 | if ((newY >= minY && vData.pressPos == minY && vData.move.value() == minY && dy > 0) |
| 1279 | || (newY <= maxY && vData.pressPos == maxY && vData.move.value() == maxY && dy < 0)) { |
| 1280 | keepY = false; |
| 1281 | } |
| 1282 | } |
| 1283 | vData.previousDragDelta = dy; |
| 1284 | } |
| 1285 | |
| 1286 | if (q->xflick()) { |
| 1287 | qreal dx = deltas.x(); |
| 1288 | if (overThreshold || elapsedSincePress > 200) { |
| 1289 | if (!hMoved && !hData.dragging) |
| 1290 | hData.dragStartOffset = dx; |
| 1291 | qreal newX = dx + hData.pressPos - (syncDrag ? 0 : hData.dragStartOffset); |
| 1292 | const qreal minX = hData.dragMinBound + hData.startMargin; |
| 1293 | const qreal maxX = hData.dragMaxBound - hData.endMargin; |
| 1294 | if (!(boundsBehavior & QQuickFlickable::DragOverBounds)) { |
| 1295 | if (fuzzyLessThanOrEqualTo(a: newX, b: maxX)) { |
| 1296 | newX = maxX; |
| 1297 | rejectX = hData.pressPos == maxX && hData.move.value() == maxX && dx < 0; |
| 1298 | } |
| 1299 | if (fuzzyLessThanOrEqualTo(a: minX, b: newX)) { |
| 1300 | newX = minX; |
| 1301 | rejectX |= hData.pressPos == minX && hData.move.value() == minX && dx > 0; |
| 1302 | } |
| 1303 | } else { |
| 1304 | qreal vel = velocity.x() / QML_FLICK_OVERSHOOTFRICTION; |
| 1305 | if (vel > 0. && vel > hData.velocity) |
| 1306 | hData.velocity = qMin(a: velocity.x() / QML_FLICK_OVERSHOOTFRICTION, b: maxVelocity); |
| 1307 | else if (vel < 0. && vel < hData.velocity) |
| 1308 | hData.velocity = qMax(a: velocity.x() / QML_FLICK_OVERSHOOTFRICTION, b: -maxVelocity); |
| 1309 | if (newX > minX) { |
| 1310 | // Overshoot beyond the left. But don't wait for momentum phase to end before returning to bounds. |
| 1311 | if (momentum && hData.atBeginning) { |
| 1312 | if (!hData.inRebound) { |
| 1313 | hData.inRebound = true; |
| 1314 | q->returnToBounds(); |
| 1315 | } |
| 1316 | return; |
| 1317 | } |
| 1318 | if (velocitySensitiveOverBounds) { |
| 1319 | qreal overshoot = (newX - minX) * hData.velocity / maxVelocity / QML_FLICK_OVERSHOOTFRICTION; |
| 1320 | overshoot = QML_FLICK_OVERSHOOT * effectiveDevicePixelRatio() * EaseOvershoot(t: overshoot / QML_FLICK_OVERSHOOT / effectiveDevicePixelRatio()); |
| 1321 | newX = minX + overshoot; |
| 1322 | } else { |
| 1323 | newX = minX + (newX - minX) / 2; |
| 1324 | } |
| 1325 | } else if (newX < maxX && maxX - minX <= 0) { |
| 1326 | // Overshoot beyond the right. But don't wait for momentum phase to end before returning to bounds. |
| 1327 | if (momentum && hData.atEnd) { |
| 1328 | if (!hData.inRebound) { |
| 1329 | hData.inRebound = true; |
| 1330 | q->returnToBounds(); |
| 1331 | } |
| 1332 | return; |
| 1333 | } |
| 1334 | if (velocitySensitiveOverBounds) { |
| 1335 | qreal overshoot = (newX - maxX) * hData.velocity / maxVelocity / QML_FLICK_OVERSHOOTFRICTION; |
| 1336 | overshoot = QML_FLICK_OVERSHOOT * effectiveDevicePixelRatio() * EaseOvershoot(t: overshoot / QML_FLICK_OVERSHOOT / effectiveDevicePixelRatio()); |
| 1337 | newX = maxX - overshoot; |
| 1338 | } else { |
| 1339 | newX = maxX + (newX - maxX) / 2; |
| 1340 | } |
| 1341 | } |
| 1342 | } |
| 1343 | if (!rejectX && stealMouse && dx != hData.previousDragDelta) { |
| 1344 | clearTimeline(); |
| 1345 | hData.move.setValue(newX); |
| 1346 | hMoved = true; |
| 1347 | } |
| 1348 | |
| 1349 | if (!rejectX && overThreshold) |
| 1350 | stealX = true; |
| 1351 | |
| 1352 | if ((newX >= minX && vData.pressPos == minX && vData.move.value() == minX && dx > 0) |
| 1353 | || (newX <= maxX && vData.pressPos == maxX && vData.move.value() == maxX && dx < 0)) { |
| 1354 | keepX = false; |
| 1355 | } |
| 1356 | } |
| 1357 | hData.previousDragDelta = dx; |
| 1358 | } |
| 1359 | |
| 1360 | stealMouse = stealX || stealY; |
| 1361 | if (stealMouse) { |
| 1362 | if ((stealX && keepX) || (stealY && keepY)) |
| 1363 | q->setKeepMouseGrab(true); |
| 1364 | clearDelayedPress(); |
| 1365 | } |
| 1366 | |
| 1367 | if (rejectY) { |
| 1368 | vData.velocityBuffer.clear(); |
| 1369 | vData.velocity = 0; |
| 1370 | } |
| 1371 | if (rejectX) { |
| 1372 | hData.velocityBuffer.clear(); |
| 1373 | hData.velocity = 0; |
| 1374 | } |
| 1375 | |
| 1376 | if (momentum && !hData.flicking && !vData.flicking) |
| 1377 | flickingStarted(flickingH: hData.velocity != 0, flickingV: vData.velocity != 0); |
| 1378 | draggingStarting(); |
| 1379 | |
| 1380 | if ((hMoved && !prevHMoved) || (vMoved && !prevVMoved)) |
| 1381 | q->movementStarting(); |
| 1382 | |
| 1383 | lastPosTime = currentTimestamp; |
| 1384 | if (q->yflick() && !rejectY) |
| 1385 | vData.addVelocitySample(v: velocity.y(), maxVelocity); |
| 1386 | if (q->xflick() && !rejectX) |
| 1387 | hData.addVelocitySample(v: velocity.x(), maxVelocity); |
| 1388 | lastPos = localPos; |
| 1389 | } |
| 1390 | |
| 1391 | void QQuickFlickablePrivate::handleMoveEvent(QPointerEvent *event) |
| 1392 | { |
| 1393 | Q_Q(QQuickFlickable); |
| 1394 | if (!interactive || lastPosTime == -1 || |
| 1395 | (event->isSinglePointEvent() && !buttonsAccepted(event: static_cast<QSinglePointEvent *>(event)))) |
| 1396 | return; |
| 1397 | |
| 1398 | qint64 currentTimestamp = computeCurrentTime(event); |
| 1399 | const auto &firstPoint = event->points().first(); |
| 1400 | const auto &pos = firstPoint.position(); |
| 1401 | const QVector2D deltas = QVector2D(pos - q->mapFromGlobal(point: firstPoint.globalPressPosition())); |
| 1402 | const QVector2D velocity = firstPointLocalVelocity(event); |
| 1403 | bool overThreshold = false; |
| 1404 | |
| 1405 | if (q->isMoving()) { |
| 1406 | /* |
| 1407 | Only the first drag should be used to determine if the Flickable should start moving, |
| 1408 | to the exclusion of some inner Control (such as Slider) or a child Flickable. |
| 1409 | If the user releases the mouse or finger and drags again, this Flickable is the only |
| 1410 | sensible recipient as long as it's still moving. |
| 1411 | We also only care about the drag threshold for the first drag. If it's already moving, |
| 1412 | every subsequent move event (however small) should move the content item immediately. |
| 1413 | */ |
| 1414 | overThreshold = true; |
| 1415 | } else if (event->pointCount() == 1) { |
| 1416 | if (q->yflick()) |
| 1417 | overThreshold |= QQuickDeliveryAgentPrivate::dragOverThreshold(d: deltas.y(), axis: Qt::YAxis, tp: firstPoint); |
| 1418 | if (q->xflick()) |
| 1419 | overThreshold |= QQuickDeliveryAgentPrivate::dragOverThreshold(d: deltas.x(), axis: Qt::XAxis, tp: firstPoint); |
| 1420 | } else { |
| 1421 | qCDebug(lcFilter) << q->objectName() << "ignoring multi-touch" << event; |
| 1422 | } |
| 1423 | |
| 1424 | drag(currentTimestamp, eventType: event->type(), localPos: pos, deltas, overThreshold, momentum: false, velocitySensitiveOverBounds: false, velocity); |
| 1425 | } |
| 1426 | |
| 1427 | void QQuickFlickablePrivate::handleReleaseEvent(QPointerEvent *event) |
| 1428 | { |
| 1429 | Q_Q(QQuickFlickable); |
| 1430 | stealMouse = false; |
| 1431 | q->setKeepMouseGrab(false); |
| 1432 | pressed = false; |
| 1433 | |
| 1434 | // if we drag then pause before release we should not cause a flick. |
| 1435 | qint64 elapsed = computeCurrentTime(event) - lastPosTime; |
| 1436 | |
| 1437 | vData.updateVelocity(); |
| 1438 | hData.updateVelocity(); |
| 1439 | |
| 1440 | draggingEnding(); |
| 1441 | |
| 1442 | if (lastPosTime == -1) |
| 1443 | return; |
| 1444 | |
| 1445 | hData.vTime = vData.vTime = timeline.time(); |
| 1446 | |
| 1447 | bool canBoost = false; |
| 1448 | const auto pos = event->points().first().position(); |
| 1449 | const auto pressPos = q->mapFromGlobal(point: event->points().first().globalPressPosition()); |
| 1450 | const QVector2D eventVelocity = firstPointLocalVelocity(event); |
| 1451 | qCDebug(lcVel) << event->deviceType() << event->type() << "velocity" << event->points().first().velocity() << "transformed to local" << eventVelocity; |
| 1452 | |
| 1453 | qreal vVelocity = 0; |
| 1454 | if (elapsed < 100 && vData.velocity != 0.) { |
| 1455 | vVelocity = (event->device()->capabilities().testFlag(flag: QInputDevice::Capability::Velocity) |
| 1456 | ? eventVelocity.y() : vData.velocity); |
| 1457 | } |
| 1458 | if ((vData.atBeginning && vVelocity > 0.) || (vData.atEnd && vVelocity < 0.)) { |
| 1459 | vVelocity /= 2; |
| 1460 | } else if (vData.continuousFlickVelocity != 0.0 |
| 1461 | && vData.viewSize/q->height() > QML_FLICK_MULTIFLICK_RATIO |
| 1462 | && ((vVelocity > 0) == (vData.continuousFlickVelocity > 0)) |
| 1463 | && qAbs(t: vVelocity) > QML_FLICK_MULTIFLICK_THRESHOLD) { |
| 1464 | // accelerate flick for large view flicked quickly |
| 1465 | canBoost = true; |
| 1466 | } |
| 1467 | |
| 1468 | qreal hVelocity = 0; |
| 1469 | if (elapsed < 100 && hData.velocity != 0.) { |
| 1470 | hVelocity = (event->device()->capabilities().testFlag(flag: QInputDevice::Capability::Velocity) |
| 1471 | ? eventVelocity.x() : hData.velocity); |
| 1472 | } |
| 1473 | if ((hData.atBeginning && hVelocity > 0.) || (hData.atEnd && hVelocity < 0.)) { |
| 1474 | hVelocity /= 2; |
| 1475 | } else if (hData.continuousFlickVelocity != 0.0 |
| 1476 | && hData.viewSize/q->width() > QML_FLICK_MULTIFLICK_RATIO |
| 1477 | && ((hVelocity > 0) == (hData.continuousFlickVelocity > 0)) |
| 1478 | && qAbs(t: hVelocity) > QML_FLICK_MULTIFLICK_THRESHOLD) { |
| 1479 | // accelerate flick for large view flicked quickly |
| 1480 | canBoost = true; |
| 1481 | } |
| 1482 | |
| 1483 | flickBoost = canBoost ? qBound(min: 1.0, val: flickBoost+0.25, QML_FLICK_MULTIFLICK_MAXBOOST) : 1.0; |
| 1484 | const int flickThreshold = QGuiApplicationPrivate::platformTheme()->themeHint(hint: QPlatformTheme::FlickStartDistance).toInt(); |
| 1485 | |
| 1486 | bool anyPointGrabbed = event->points().constEnd() != |
| 1487 | std::find_if(first: event->points().constBegin(),last: event->points().constEnd(), |
| 1488 | pred: [q, event](const QEventPoint &point) { return event->exclusiveGrabber(point) == q; }); |
| 1489 | |
| 1490 | bool flickedVertically = false; |
| 1491 | vVelocity *= flickBoost; |
| 1492 | const bool isVerticalFlickAllowed = anyPointGrabbed && |
| 1493 | q->yflick() && qAbs(t: vVelocity) > _q_MinimumFlickVelocity && |
| 1494 | qAbs(t: pos.y() - pressPos.y()) > flickThreshold; |
| 1495 | if (isVerticalFlickAllowed) { |
| 1496 | velocityTimeline.reset(vData.smoothVelocity); |
| 1497 | vData.smoothVelocity.setValue(-vVelocity); |
| 1498 | flickedVertically = flickY(eventType: event->type(), velocity: vVelocity); |
| 1499 | } |
| 1500 | |
| 1501 | bool flickedHorizontally = false; |
| 1502 | hVelocity *= flickBoost; |
| 1503 | const bool isHorizontalFlickAllowed = anyPointGrabbed && |
| 1504 | q->xflick() && qAbs(t: hVelocity) > _q_MinimumFlickVelocity && |
| 1505 | qAbs(t: pos.x() - pressPos.x()) > flickThreshold; |
| 1506 | if (isHorizontalFlickAllowed) { |
| 1507 | velocityTimeline.reset(hData.smoothVelocity); |
| 1508 | hData.smoothVelocity.setValue(-hVelocity); |
| 1509 | flickedHorizontally = flickX(eventType: event->type(), velocity: hVelocity); |
| 1510 | } |
| 1511 | |
| 1512 | if (!isVerticalFlickAllowed) |
| 1513 | fixupY(); |
| 1514 | |
| 1515 | if (!isHorizontalFlickAllowed) |
| 1516 | fixupX(); |
| 1517 | |
| 1518 | flickingStarted(flickingH: flickedHorizontally, flickingV: flickedVertically); |
| 1519 | if (!isViewMoving()) { |
| 1520 | q->movementEnding(); |
| 1521 | } else { |
| 1522 | if (flickedVertically) |
| 1523 | vMoved = true; |
| 1524 | if (flickedHorizontally) |
| 1525 | hMoved = true; |
| 1526 | q->movementStarting(); |
| 1527 | } |
| 1528 | } |
| 1529 | |
| 1530 | bool QQuickFlickablePrivate::buttonsAccepted(const QSinglePointEvent *event) |
| 1531 | { |
| 1532 | return !((event->button() & acceptedButtons) == 0 && (event->buttons() & acceptedButtons) == 0); |
| 1533 | } |
| 1534 | |
| 1535 | void QQuickFlickable::mousePressEvent(QMouseEvent *event) |
| 1536 | { |
| 1537 | Q_D(QQuickFlickable); |
| 1538 | if (d->interactive && !d->replayingPressEvent && d->buttonsAccepted(event) && d->wantsPointerEvent(event)) { |
| 1539 | if (!d->pressed) |
| 1540 | d->handlePressEvent(event); |
| 1541 | event->accept(); |
| 1542 | } else { |
| 1543 | QQuickItem::mousePressEvent(event); |
| 1544 | } |
| 1545 | } |
| 1546 | |
| 1547 | void QQuickFlickable::mouseMoveEvent(QMouseEvent *event) |
| 1548 | { |
| 1549 | Q_D(QQuickFlickable); |
| 1550 | if (d->interactive && d->buttonsAccepted(event) && d->wantsPointerEvent(event)) { |
| 1551 | d->handleMoveEvent(event); |
| 1552 | event->accept(); |
| 1553 | } else { |
| 1554 | QQuickItem::mouseMoveEvent(event); |
| 1555 | } |
| 1556 | } |
| 1557 | |
| 1558 | void QQuickFlickable::mouseReleaseEvent(QMouseEvent *event) |
| 1559 | { |
| 1560 | Q_D(QQuickFlickable); |
| 1561 | if (d->interactive && d->buttonsAccepted(event) && d->wantsPointerEvent(event)) { |
| 1562 | if (d->delayedPressEvent) { |
| 1563 | d->replayDelayedPress(); |
| 1564 | |
| 1565 | auto &firstPoint = event->point(i: 0); |
| 1566 | if (const auto *grabber = event->exclusiveGrabber(point: firstPoint); grabber && grabber->isQuickItemType()) { |
| 1567 | // Since we sent the delayed press to the window, we need to resend the release to the window too. |
| 1568 | // We're not copying or detaching, so restore the original event position afterwards. |
| 1569 | const auto oldPosition = firstPoint.position(); |
| 1570 | QMutableEventPoint::setPosition(p&: firstPoint, arg: event->scenePosition()); |
| 1571 | QCoreApplication::sendEvent(receiver: window(), event); |
| 1572 | QMutableEventPoint::setPosition(p&: firstPoint, arg: oldPosition); |
| 1573 | } |
| 1574 | |
| 1575 | // And the event has been consumed |
| 1576 | d->stealMouse = false; |
| 1577 | d->pressed = false; |
| 1578 | return; |
| 1579 | } |
| 1580 | |
| 1581 | d->handleReleaseEvent(event); |
| 1582 | event->accept(); |
| 1583 | } else { |
| 1584 | QQuickItem::mouseReleaseEvent(event); |
| 1585 | } |
| 1586 | } |
| 1587 | |
| 1588 | void QQuickFlickable::touchEvent(QTouchEvent *event) |
| 1589 | { |
| 1590 | Q_D(QQuickFlickable); |
| 1591 | |
| 1592 | if (event->type() == QEvent::TouchCancel) { |
| 1593 | if (d->interactive && d->wantsPointerEvent(event)) |
| 1594 | d->cancelInteraction(); |
| 1595 | else |
| 1596 | QQuickItem::touchEvent(event); |
| 1597 | return; |
| 1598 | } |
| 1599 | |
| 1600 | bool unhandled = false; |
| 1601 | const auto &firstPoint = event->points().first(); |
| 1602 | switch (firstPoint.state()) { |
| 1603 | case QEventPoint::State::Pressed: |
| 1604 | if (d->interactive && !d->replayingPressEvent && d->wantsPointerEvent(event)) { |
| 1605 | if (!d->pressed) |
| 1606 | d->handlePressEvent(event); |
| 1607 | event->accept(); |
| 1608 | } else { |
| 1609 | unhandled = true; |
| 1610 | } |
| 1611 | break; |
| 1612 | case QEventPoint::State::Updated: |
| 1613 | if (d->interactive && d->wantsPointerEvent(event)) { |
| 1614 | d->handleMoveEvent(event); |
| 1615 | event->accept(); |
| 1616 | } else { |
| 1617 | unhandled = true; |
| 1618 | } |
| 1619 | break; |
| 1620 | case QEventPoint::State::Released: |
| 1621 | if (d->interactive && d->wantsPointerEvent(event)) { |
| 1622 | if (d->delayedPressEvent) { |
| 1623 | d->replayDelayedPress(); |
| 1624 | |
| 1625 | const auto &firstPoint = event->point(i: 0); |
| 1626 | if (const auto *grabber = event->exclusiveGrabber(point: firstPoint); grabber && grabber->isQuickItemType()) { |
| 1627 | // Since we sent the delayed press to the window, we need to resend the release to the window too. |
| 1628 | QScopedPointer<QPointerEvent> localizedEvent( |
| 1629 | QQuickDeliveryAgentPrivate::clonePointerEvent(event, transformedLocalPos: firstPoint.scenePosition())); |
| 1630 | QCoreApplication::sendEvent(receiver: window(), event: localizedEvent.data()); |
| 1631 | } |
| 1632 | |
| 1633 | // And the event has been consumed |
| 1634 | d->stealMouse = false; |
| 1635 | d->pressed = false; |
| 1636 | return; |
| 1637 | } |
| 1638 | |
| 1639 | d->handleReleaseEvent(event); |
| 1640 | event->accept(); |
| 1641 | } else { |
| 1642 | unhandled = true; |
| 1643 | } |
| 1644 | break; |
| 1645 | case QEventPoint::State::Stationary: |
| 1646 | case QEventPoint::State::Unknown: |
| 1647 | break; |
| 1648 | } |
| 1649 | if (unhandled) |
| 1650 | QQuickItem::touchEvent(event); |
| 1651 | } |
| 1652 | |
| 1653 | #if QT_CONFIG(wheelevent) |
| 1654 | void QQuickFlickable::wheelEvent(QWheelEvent *event) |
| 1655 | { |
| 1656 | Q_D(QQuickFlickable); |
| 1657 | if (!d->interactive || !d->wantsPointerEvent(event)) { |
| 1658 | QQuickItem::wheelEvent(event); |
| 1659 | return; |
| 1660 | } |
| 1661 | qCDebug(lcWheel) << event->device() << event << event->source(); |
| 1662 | event->setAccepted(false); |
| 1663 | qint64 currentTimestamp = d->computeCurrentTime(event); |
| 1664 | switch (event->phase()) { |
| 1665 | case Qt::ScrollBegin: |
| 1666 | d->scrollingPhase = true; |
| 1667 | d->accumulatedWheelPixelDelta = QVector2D(); |
| 1668 | d->vData.velocity = 0; |
| 1669 | d->hData.velocity = 0; |
| 1670 | d->timer.start(); |
| 1671 | d->maybeBeginDrag(currentTimestamp, pressPosn: event->position()); |
| 1672 | d->lastPosTime = -1; |
| 1673 | break; |
| 1674 | case Qt::NoScrollPhase: // default phase with an ordinary wheel mouse |
| 1675 | case Qt::ScrollUpdate: |
| 1676 | if (d->scrollingPhase) |
| 1677 | d->pressed = true; |
| 1678 | break; |
| 1679 | case Qt::ScrollMomentum: |
| 1680 | d->pressed = false; |
| 1681 | d->scrollingPhase = false; |
| 1682 | d->draggingEnding(); |
| 1683 | if (isMoving()) |
| 1684 | event->accept(); |
| 1685 | d->lastPosTime = -1; |
| 1686 | break; |
| 1687 | case Qt::ScrollEnd: |
| 1688 | d->pressed = false; |
| 1689 | d->scrollingPhase = false; |
| 1690 | d->draggingEnding(); |
| 1691 | returnToBounds(); |
| 1692 | d->lastPosTime = -1; |
| 1693 | d->stealMouse = false; |
| 1694 | if (!d->velocityTimeline.isActive() && !d->timeline.isActive()) |
| 1695 | movementEnding(hMovementEnding: true, vMovementEnding: true); |
| 1696 | return; |
| 1697 | } |
| 1698 | |
| 1699 | qreal elapsed = qreal(currentTimestamp - d->lastPosTime) / qreal(1000); |
| 1700 | if (elapsed <= 0) { |
| 1701 | d->lastPosTime = currentTimestamp; |
| 1702 | qCDebug(lcWheel) << "insufficient elapsed time: can't calculate velocity" << elapsed; |
| 1703 | return; |
| 1704 | } |
| 1705 | |
| 1706 | if (event->source() == Qt::MouseEventNotSynthesized || event->pixelDelta().isNull() || event->phase() == Qt::NoScrollPhase) { |
| 1707 | // no pixel delta (physical mouse wheel, or "dumb" touchpad), so use angleDelta |
| 1708 | int xDelta = event->angleDelta().x(); |
| 1709 | int yDelta = event->angleDelta().y(); |
| 1710 | |
| 1711 | if (d->wheelDeceleration > _q_MaximumWheelDeceleration) { |
| 1712 | const qreal wheelScroll = -qApp->styleHints()->wheelScrollLines() * 24; |
| 1713 | // If wheelDeceleration is very large, i.e. the user or the platform does not want to have any mouse wheel |
| 1714 | // acceleration behavior, we want to move a distance proportional to QStyleHints::wheelScrollLines() |
| 1715 | if (yflick() && yDelta != 0) { |
| 1716 | d->moveReason = QQuickFlickablePrivate::Mouse; // ItemViews will set fixupMode to Immediate in fixup() without this. |
| 1717 | d->vMoved = true; |
| 1718 | qreal scrollPixel = (-yDelta / 120.0 * wheelScroll); |
| 1719 | bool acceptEvent = true; // Set to false if event should propagate to parent |
| 1720 | if (scrollPixel > 0) { // Forward direction (away from user) |
| 1721 | if (d->vData.move.value() >= minYExtent()) { |
| 1722 | d->vMoved = false; |
| 1723 | acceptEvent = false; |
| 1724 | } |
| 1725 | } else { // Backward direction (towards user) |
| 1726 | if (d->vData.move.value() <= maxYExtent()) { |
| 1727 | d->vMoved = false; |
| 1728 | acceptEvent = false; |
| 1729 | } |
| 1730 | } |
| 1731 | if (d->vMoved) { |
| 1732 | if (d->boundsBehavior == QQuickFlickable::StopAtBounds) { |
| 1733 | const qreal estContentPos = scrollPixel + d->vData.move.value(); |
| 1734 | if (scrollPixel > 0) { // Forward direction (away from user) |
| 1735 | if (estContentPos > minYExtent()) { |
| 1736 | scrollPixel = minYExtent() - d->vData.move.value(); |
| 1737 | acceptEvent = false; |
| 1738 | } |
| 1739 | } else { // Backward direction (towards user) |
| 1740 | if (estContentPos < maxYExtent()) { |
| 1741 | scrollPixel = maxYExtent() - d->vData.move.value(); |
| 1742 | acceptEvent = false; |
| 1743 | } |
| 1744 | } |
| 1745 | } |
| 1746 | d->resetTimeline(data&: d->vData); |
| 1747 | movementStarting(); |
| 1748 | d->timeline.moveBy(d->vData.move, change: scrollPixel, QEasingCurve(QEasingCurve::OutExpo), time: 3*d->fixupDuration/4); |
| 1749 | d->vData.fixingUp = true; |
| 1750 | d->timeline.callback(QQuickTimeLineCallback(&d->vData.move, QQuickFlickablePrivate::fixupY_callback, d)); |
| 1751 | } |
| 1752 | if (acceptEvent) |
| 1753 | event->accept(); |
| 1754 | } |
| 1755 | if (xflick() && xDelta != 0) { |
| 1756 | d->moveReason = QQuickFlickablePrivate::Mouse; // ItemViews will set fixupMode to Immediate in fixup() without this. |
| 1757 | d->hMoved = true; |
| 1758 | qreal scrollPixel = (-xDelta / 120.0 * wheelScroll); |
| 1759 | bool acceptEvent = true; // Set to false if event should propagate to parent |
| 1760 | if (scrollPixel > 0) { // Forward direction (away from user) |
| 1761 | if (d->hData.move.value() >= minXExtent()) { |
| 1762 | d->hMoved = false; |
| 1763 | acceptEvent = false; |
| 1764 | } |
| 1765 | } else { // Backward direction (towards user) |
| 1766 | if (d->hData.move.value() <= maxXExtent()) { |
| 1767 | d->hMoved = false; |
| 1768 | acceptEvent = false; |
| 1769 | } |
| 1770 | } |
| 1771 | if (d->hMoved) { |
| 1772 | if (d->boundsBehavior == QQuickFlickable::StopAtBounds) { |
| 1773 | const qreal estContentPos = scrollPixel + d->hData.move.value(); |
| 1774 | if (scrollPixel > 0) { // Forward direction (away from user) |
| 1775 | if (estContentPos > minXExtent()) { |
| 1776 | scrollPixel = minXExtent() - d->hData.move.value(); |
| 1777 | acceptEvent = false; |
| 1778 | } |
| 1779 | } else { // Backward direction (towards user) |
| 1780 | if (estContentPos < maxXExtent()) { |
| 1781 | scrollPixel = maxXExtent() - d->hData.move.value(); |
| 1782 | acceptEvent = false; |
| 1783 | } |
| 1784 | } |
| 1785 | } |
| 1786 | d->resetTimeline(data&: d->hData); |
| 1787 | movementStarting(); |
| 1788 | d->timeline.moveBy(d->hData.move, change: scrollPixel, QEasingCurve(QEasingCurve::OutExpo), time: 3*d->fixupDuration/4); |
| 1789 | d->hData.fixingUp = true; |
| 1790 | d->timeline.callback(QQuickTimeLineCallback(&d->hData.move, QQuickFlickablePrivate::fixupX_callback, d)); |
| 1791 | } |
| 1792 | if (acceptEvent) |
| 1793 | event->accept(); |
| 1794 | } |
| 1795 | } else { |
| 1796 | // wheelDeceleration is set to some reasonable value: the user or the platform wants to have |
| 1797 | // the classic Qt Quick mouse wheel acceleration behavior. |
| 1798 | // For a single "clicky" wheel event (angleDelta +/- 120), |
| 1799 | // we want flick() to end up moving a distance proportional to QStyleHints::wheelScrollLines(). |
| 1800 | // The decel algo from there is |
| 1801 | // qreal dist = v2 / (accel * 2.0); |
| 1802 | // i.e. initialWheelFlickDistance = (120 / dt)^2 / (deceleration * 2) |
| 1803 | // now solve for dt: |
| 1804 | // dt = 120 / sqrt(deceleration * 2 * initialWheelFlickDistance) |
| 1805 | if (!isMoving()) |
| 1806 | elapsed = 120 / qSqrt(v: d->wheelDeceleration * 2 * d->initialWheelFlickDistance); |
| 1807 | if (yflick() && yDelta != 0) { |
| 1808 | qreal instVelocity = yDelta / elapsed; |
| 1809 | // if the direction has changed, start over with filtering, to allow instant movement in the opposite direction |
| 1810 | if ((instVelocity < 0 && d->vData.velocity > 0) || (instVelocity > 0 && d->vData.velocity < 0)) |
| 1811 | d->vData.velocityBuffer.clear(); |
| 1812 | d->vData.addVelocitySample(v: instVelocity, maxVelocity: d->maxVelocity); |
| 1813 | d->vData.updateVelocity(); |
| 1814 | if ((yDelta > 0 && contentY() > -minYExtent()) || (yDelta < 0 && contentY() < -maxYExtent())) { |
| 1815 | const bool newFlick = d->flickY(eventType: event->type(), velocity: d->vData.velocity); |
| 1816 | if (newFlick && (d->vData.atBeginning != (yDelta > 0) || d->vData.atEnd != (yDelta < 0))) { |
| 1817 | d->flickingStarted(flickingH: false, flickingV: true); |
| 1818 | d->vMoved = true; |
| 1819 | movementStarting(); |
| 1820 | } |
| 1821 | event->accept(); |
| 1822 | } |
| 1823 | } |
| 1824 | if (xflick() && xDelta != 0) { |
| 1825 | qreal instVelocity = xDelta / elapsed; |
| 1826 | // if the direction has changed, start over with filtering, to allow instant movement in the opposite direction |
| 1827 | if ((instVelocity < 0 && d->hData.velocity > 0) || (instVelocity > 0 && d->hData.velocity < 0)) |
| 1828 | d->hData.velocityBuffer.clear(); |
| 1829 | d->hData.addVelocitySample(v: instVelocity, maxVelocity: d->maxVelocity); |
| 1830 | d->hData.updateVelocity(); |
| 1831 | if ((xDelta > 0 && contentX() > -minXExtent()) || (xDelta < 0 && contentX() < -maxXExtent())) { |
| 1832 | const bool newFlick = d->flickX(eventType: event->type(), velocity: d->hData.velocity); |
| 1833 | if (newFlick && (d->hData.atBeginning != (xDelta > 0) || d->hData.atEnd != (xDelta < 0))) { |
| 1834 | d->flickingStarted(flickingH: true, flickingV: false); |
| 1835 | d->hMoved = true; |
| 1836 | movementStarting(); |
| 1837 | } |
| 1838 | event->accept(); |
| 1839 | } |
| 1840 | } |
| 1841 | } |
| 1842 | } else { |
| 1843 | // use pixelDelta (probably from a trackpad): this is where we want to be on most platforms eventually |
| 1844 | int xDelta = event->pixelDelta().x(); |
| 1845 | int yDelta = event->pixelDelta().y(); |
| 1846 | |
| 1847 | QVector2D velocity(xDelta / elapsed, yDelta / elapsed); |
| 1848 | d->accumulatedWheelPixelDelta += QVector2D(event->pixelDelta()); |
| 1849 | // Try to drag if 1) we already are dragging or flicking, or |
| 1850 | // 2) the flickable is free to flick both directions, or |
| 1851 | // 3) the movement so far has been mostly horizontal AND it's free to flick horizontally, or |
| 1852 | // 4) the movement so far has been mostly vertical AND it's free to flick vertically. |
| 1853 | // Otherwise, wait until the next event. Wheel events with pixel deltas tend to come frequently. |
| 1854 | if (isMoving() || isFlicking() || (yflick() && xflick()) |
| 1855 | || (xflick() && qAbs(t: d->accumulatedWheelPixelDelta.x()) > qAbs(t: d->accumulatedWheelPixelDelta.y() * 2)) |
| 1856 | || (yflick() && qAbs(t: d->accumulatedWheelPixelDelta.y()) > qAbs(t: d->accumulatedWheelPixelDelta.x() * 2))) { |
| 1857 | d->drag(currentTimestamp, eventType: event->type(), localPos: event->position(), deltas: d->accumulatedWheelPixelDelta, |
| 1858 | overThreshold: true, momentum: !d->scrollingPhase, velocitySensitiveOverBounds: true, velocity); |
| 1859 | d->updateBeginningEnd(); |
| 1860 | if ((xflick() && !isAtXBeginning() && !isAtXEnd()) || (yflick() && !isAtYBeginning() && !isAtYEnd())) |
| 1861 | event->accept(); |
| 1862 | } else { |
| 1863 | qCDebug(lcWheel) << "not dragging: accumulated deltas" << d->accumulatedWheelPixelDelta << |
| 1864 | "moving?" << isMoving() << "can flick horizontally?" << xflick() << "vertically?" << yflick(); |
| 1865 | } |
| 1866 | } |
| 1867 | d->lastPosTime = currentTimestamp; |
| 1868 | |
| 1869 | if (!event->isAccepted()) |
| 1870 | QQuickItem::wheelEvent(event); |
| 1871 | } |
| 1872 | #endif |
| 1873 | |
| 1874 | bool QQuickFlickablePrivate::isInnermostPressDelay(QQuickItem *i) const |
| 1875 | { |
| 1876 | Q_Q(const QQuickFlickable); |
| 1877 | QQuickItem *item = i; |
| 1878 | while (item) { |
| 1879 | QQuickFlickable *flick = qobject_cast<QQuickFlickable*>(object: item); |
| 1880 | if (flick && flick->pressDelay() > 0 && flick->isInteractive()) { |
| 1881 | // Found the innermost flickable with press delay - is it me? |
| 1882 | return (flick == q); |
| 1883 | } |
| 1884 | item = item->parentItem(); |
| 1885 | } |
| 1886 | return false; |
| 1887 | } |
| 1888 | |
| 1889 | void QQuickFlickablePrivate::captureDelayedPress(QQuickItem *item, QPointerEvent *event) |
| 1890 | { |
| 1891 | Q_Q(QQuickFlickable); |
| 1892 | if (!q->window() || pressDelay <= 0) |
| 1893 | return; |
| 1894 | |
| 1895 | // Only the innermost flickable should handle the delayed press; this allows |
| 1896 | // flickables up the parent chain to all see the events in their filter functions |
| 1897 | if (!isInnermostPressDelay(i: item)) |
| 1898 | return; |
| 1899 | |
| 1900 | delayedPressEvent = QQuickDeliveryAgentPrivate::clonePointerEvent(event); |
| 1901 | delayedPressEvent->setAccepted(false); |
| 1902 | delayedPressTimer.start(msec: pressDelay, obj: q); |
| 1903 | qCDebug(lcReplay) << "begin press delay" << pressDelay << "ms with" << delayedPressEvent; |
| 1904 | } |
| 1905 | |
| 1906 | void QQuickFlickablePrivate::clearDelayedPress() |
| 1907 | { |
| 1908 | if (delayedPressEvent) { |
| 1909 | delayedPressTimer.stop(); |
| 1910 | qCDebug(lcReplay) << "clear delayed press" << delayedPressEvent; |
| 1911 | delete delayedPressEvent; |
| 1912 | delayedPressEvent = nullptr; |
| 1913 | } |
| 1914 | } |
| 1915 | |
| 1916 | void QQuickFlickablePrivate::replayDelayedPress() |
| 1917 | { |
| 1918 | Q_Q(QQuickFlickable); |
| 1919 | if (delayedPressEvent) { |
| 1920 | // Losing the grab will clear the delayed press event; take control of it here |
| 1921 | QScopedPointer<QPointerEvent> event(delayedPressEvent); |
| 1922 | delayedPressEvent = nullptr; |
| 1923 | delayedPressTimer.stop(); |
| 1924 | |
| 1925 | // If we have the grab, release before delivering the event |
| 1926 | if (QQuickWindow *window = q->window()) { |
| 1927 | auto da = deliveryAgentPrivate(); |
| 1928 | da->allowChildEventFiltering = false; // don't allow re-filtering during replay |
| 1929 | replayingPressEvent = true; |
| 1930 | auto &firstPoint = event->point(i: 0); |
| 1931 | // At first glance, it's weird for delayedPressEvent to already have a grabber; |
| 1932 | // but on press, filterMouseEvent() took the exclusive grab, and that's stored |
| 1933 | // in the device-specific EventPointData instance in QPointingDevicePrivate::activePoints, |
| 1934 | // not in the event itself. If this Flickable is still the grabber of that point on that device, |
| 1935 | // that's the reason; but now it doesn't need that grab anymore. |
| 1936 | if (event->exclusiveGrabber(point: firstPoint) == q) |
| 1937 | event->setExclusiveGrabber(point: firstPoint, exclusiveGrabber: nullptr); |
| 1938 | |
| 1939 | qCDebug(lcReplay) << "replaying" << event.data(); |
| 1940 | // Put scenePosition into position, for the sake of QQuickWindowPrivate::translateTouchEvent() |
| 1941 | // TODO remove this if we remove QQuickWindowPrivate::translateTouchEvent() |
| 1942 | QMutableEventPoint::setPosition(p&: firstPoint, arg: firstPoint.scenePosition()); |
| 1943 | // Send it through like a fresh press event, and let QQuickWindow |
| 1944 | // (more specifically, QQuickWindowPrivate::deliverPressOrReleaseEvent) |
| 1945 | // find the item or handler that should receive it, as usual. |
| 1946 | QCoreApplication::sendEvent(receiver: window, event: event.data()); |
| 1947 | qCDebug(lcReplay) << "replay done" ; |
| 1948 | |
| 1949 | // We're done with replay, go back to normal delivery behavior |
| 1950 | replayingPressEvent = false; |
| 1951 | da->allowChildEventFiltering = true; |
| 1952 | } |
| 1953 | } |
| 1954 | } |
| 1955 | |
| 1956 | //XXX pixelAligned ignores the global position of the Flickable, i.e. assumes Flickable itself is pixel aligned. |
| 1957 | |
| 1958 | /*! |
| 1959 | \internal |
| 1960 | |
| 1961 | This function is called from the timeline, |
| 1962 | when advancement in the timeline is modifying the hData.move value. |
| 1963 | The \a x argument is the newly updated value in hData.move. |
| 1964 | The purpose of the function is to update the x position of the contentItem. |
| 1965 | */ |
| 1966 | void QQuickFlickablePrivate::setViewportX(qreal x) |
| 1967 | { |
| 1968 | Q_Q(QQuickFlickable); |
| 1969 | qreal effectiveX = pixelAligned ? -std::round(x: -x) : x; |
| 1970 | |
| 1971 | const qreal maxX = q->maxXExtent(); |
| 1972 | const qreal minX = q->minXExtent(); |
| 1973 | |
| 1974 | if (boundsMovement == int(QQuickFlickable::StopAtBounds)) |
| 1975 | effectiveX = qBound(min: maxX, val: effectiveX, max: minX); |
| 1976 | |
| 1977 | contentItem->setX(effectiveX); |
| 1978 | if (contentItem->x() != effectiveX) |
| 1979 | return; // reentered |
| 1980 | |
| 1981 | qreal overshoot = 0.0; |
| 1982 | if (x <= maxX) |
| 1983 | overshoot = maxX - x; |
| 1984 | else if (x >= minX) |
| 1985 | overshoot = minX - x; |
| 1986 | |
| 1987 | if (overshoot != hData.overshoot) { |
| 1988 | hData.overshoot = overshoot; |
| 1989 | emit q->horizontalOvershootChanged(); |
| 1990 | } |
| 1991 | } |
| 1992 | |
| 1993 | /*! |
| 1994 | \internal |
| 1995 | |
| 1996 | This function is called from the timeline, |
| 1997 | when advancement in the timeline is modifying the vData.move value. |
| 1998 | The \a y argument is the newly updated value in vData.move. |
| 1999 | The purpose of the function is to update the y position of the contentItem. |
| 2000 | */ |
| 2001 | void QQuickFlickablePrivate::setViewportY(qreal y) |
| 2002 | { |
| 2003 | Q_Q(QQuickFlickable); |
| 2004 | qreal effectiveY = pixelAligned ? -std::round(x: -y) : y; |
| 2005 | |
| 2006 | const qreal maxY = q->maxYExtent(); |
| 2007 | const qreal minY = q->minYExtent(); |
| 2008 | |
| 2009 | if (boundsMovement == int(QQuickFlickable::StopAtBounds)) |
| 2010 | effectiveY = qBound(min: maxY, val: effectiveY, max: minY); |
| 2011 | |
| 2012 | contentItem->setY(effectiveY); |
| 2013 | if (contentItem->y() != effectiveY) |
| 2014 | return; // reentered |
| 2015 | |
| 2016 | qreal overshoot = 0.0; |
| 2017 | if (y <= maxY) |
| 2018 | overshoot = maxY - y; |
| 2019 | else if (y >= minY) |
| 2020 | overshoot = minY - y; |
| 2021 | |
| 2022 | if (overshoot != vData.overshoot) { |
| 2023 | vData.overshoot = overshoot; |
| 2024 | emit q->verticalOvershootChanged(); |
| 2025 | } |
| 2026 | } |
| 2027 | |
| 2028 | void QQuickFlickable::timerEvent(QTimerEvent *event) |
| 2029 | { |
| 2030 | Q_D(QQuickFlickable); |
| 2031 | if (event->timerId() == d->delayedPressTimer.timerId()) { |
| 2032 | d->delayedPressTimer.stop(); |
| 2033 | if (d->delayedPressEvent) { |
| 2034 | d->replayDelayedPress(); |
| 2035 | } |
| 2036 | } |
| 2037 | } |
| 2038 | |
| 2039 | qreal QQuickFlickable::minYExtent() const |
| 2040 | { |
| 2041 | Q_D(const QQuickFlickable); |
| 2042 | return d->vData.startMargin; |
| 2043 | } |
| 2044 | |
| 2045 | qreal QQuickFlickable::minXExtent() const |
| 2046 | { |
| 2047 | Q_D(const QQuickFlickable); |
| 2048 | return d->hData.startMargin; |
| 2049 | } |
| 2050 | |
| 2051 | /* returns -ve */ |
| 2052 | qreal QQuickFlickable::maxXExtent() const |
| 2053 | { |
| 2054 | Q_D(const QQuickFlickable); |
| 2055 | return qMin<qreal>(a: minXExtent(), b: width() - vWidth() - d->hData.endMargin); |
| 2056 | } |
| 2057 | /* returns -ve */ |
| 2058 | qreal QQuickFlickable::maxYExtent() const |
| 2059 | { |
| 2060 | Q_D(const QQuickFlickable); |
| 2061 | return qMin<qreal>(a: minYExtent(), b: height() - vHeight() - d->vData.endMargin); |
| 2062 | } |
| 2063 | |
| 2064 | void QQuickFlickable::componentComplete() |
| 2065 | { |
| 2066 | Q_D(QQuickFlickable); |
| 2067 | QQuickItem::componentComplete(); |
| 2068 | if (!d->hData.explicitValue && d->hData.startMargin != 0.) |
| 2069 | setContentX(-minXExtent()); |
| 2070 | if (!d->vData.explicitValue && d->vData.startMargin != 0.) |
| 2071 | setContentY(-minYExtent()); |
| 2072 | if (lcWheel().isDebugEnabled() || lcVel().isDebugEnabled()) { |
| 2073 | d->timeline.setObjectName(QLatin1String("timeline for Flickable " ) + objectName()); |
| 2074 | d->velocityTimeline.setObjectName(QLatin1String("velocity timeline for Flickable " ) + objectName()); |
| 2075 | } |
| 2076 | } |
| 2077 | |
| 2078 | void QQuickFlickable::viewportMoved(Qt::Orientations orient) |
| 2079 | { |
| 2080 | Q_D(QQuickFlickable); |
| 2081 | if (orient & Qt::Vertical) |
| 2082 | d->viewportAxisMoved(data&: d->vData, minExtent: minYExtent(), maxExtent: maxYExtent(), fixupCallback: d->fixupY_callback); |
| 2083 | if (orient & Qt::Horizontal) |
| 2084 | d->viewportAxisMoved(data&: d->hData, minExtent: minXExtent(), maxExtent: maxXExtent(), fixupCallback: d->fixupX_callback); |
| 2085 | d->updateBeginningEnd(); |
| 2086 | } |
| 2087 | |
| 2088 | void QQuickFlickablePrivate::viewportAxisMoved(AxisData &data, qreal minExtent, qreal maxExtent, |
| 2089 | QQuickTimeLineCallback::Callback fixupCallback) |
| 2090 | { |
| 2091 | if (!scrollingPhase && (pressed || calcVelocity)) { |
| 2092 | int elapsed = data.velocityTime.restart(); |
| 2093 | if (elapsed > 0) { |
| 2094 | qreal velocity = (data.lastPos - data.move.value()) * 1000 / elapsed; |
| 2095 | if (qAbs(t: velocity) > 0) { |
| 2096 | velocityTimeline.reset(data.smoothVelocity); |
| 2097 | velocityTimeline.set(data.smoothVelocity, velocity); |
| 2098 | qCDebug(lcVel) << "touchpad scroll phase: velocity" << velocity; |
| 2099 | } |
| 2100 | } |
| 2101 | } else { |
| 2102 | if (timeline.time() > data.vTime) { |
| 2103 | velocityTimeline.reset(data.smoothVelocity); |
| 2104 | int dt = timeline.time() - data.vTime; |
| 2105 | if (dt > 2) { |
| 2106 | qreal velocity = (data.lastPos - data.move.value()) * 1000 / dt; |
| 2107 | if (!qFuzzyCompare(p1: data.smoothVelocity.value(), p2: velocity)) |
| 2108 | qCDebug(lcVel) << "velocity" << data.smoothVelocity.value() << "->" << velocity |
| 2109 | << "computed as (" << data.lastPos << "-" << data.move.value() << ") * 1000 / (" |
| 2110 | << timeline.time() << "-" << data.vTime << ")" ; |
| 2111 | data.smoothVelocity.setValue(velocity); |
| 2112 | } |
| 2113 | } |
| 2114 | } |
| 2115 | |
| 2116 | if (!data.inOvershoot && !data.fixingUp && data.flicking |
| 2117 | && (data.move.value() > minExtent || data.move.value() < maxExtent) |
| 2118 | && qAbs(t: data.smoothVelocity.value()) > 10) { |
| 2119 | // Increase deceleration if we've passed a bound |
| 2120 | qreal overBound = data.move.value() > minExtent |
| 2121 | ? data.move.value() - minExtent |
| 2122 | : maxExtent - data.move.value(); |
| 2123 | data.inOvershoot = true; |
| 2124 | qreal maxDistance = overShootDistance(velocity: qAbs(t: data.smoothVelocity.value())) - overBound; |
| 2125 | resetTimeline(data); |
| 2126 | if (maxDistance > 0) |
| 2127 | timeline.accel(data.move, velocity: -data.smoothVelocity.value(), accel: deceleration*QML_FLICK_OVERSHOOTFRICTION, maxDistance); |
| 2128 | timeline.callback(QQuickTimeLineCallback(&data.move, fixupCallback, this)); |
| 2129 | } |
| 2130 | |
| 2131 | data.lastPos = data.move.value(); |
| 2132 | data.vTime = timeline.time(); |
| 2133 | } |
| 2134 | |
| 2135 | void QQuickFlickable::geometryChange(const QRectF &newGeometry, const QRectF &oldGeometry) |
| 2136 | { |
| 2137 | Q_D(QQuickFlickable); |
| 2138 | QQuickItem::geometryChange(newGeometry, oldGeometry); |
| 2139 | |
| 2140 | bool changed = false; |
| 2141 | if (newGeometry.width() != oldGeometry.width()) { |
| 2142 | changed = true; // we must update visualArea.widthRatio |
| 2143 | if (d->hData.viewSize < 0) |
| 2144 | d->contentItem->setWidth(width() - d->hData.startMargin - d->hData.endMargin); |
| 2145 | // Make sure that we're entirely in view. |
| 2146 | if (!d->pressed && !d->hData.moving && !d->vData.moving) { |
| 2147 | d->fixupMode = QQuickFlickablePrivate::Immediate; |
| 2148 | d->fixupX(); |
| 2149 | } |
| 2150 | } |
| 2151 | if (newGeometry.height() != oldGeometry.height()) { |
| 2152 | changed = true; // we must update visualArea.heightRatio |
| 2153 | if (d->vData.viewSize < 0) |
| 2154 | d->contentItem->setHeight(height() - d->vData.startMargin - d->vData.endMargin); |
| 2155 | // Make sure that we're entirely in view. |
| 2156 | if (!d->pressed && !d->hData.moving && !d->vData.moving) { |
| 2157 | d->fixupMode = QQuickFlickablePrivate::Immediate; |
| 2158 | d->fixupY(); |
| 2159 | } |
| 2160 | } |
| 2161 | |
| 2162 | if (changed) |
| 2163 | d->updateBeginningEnd(); |
| 2164 | } |
| 2165 | |
| 2166 | /*! |
| 2167 | \qmlmethod QtQuick::Flickable::flick(qreal xVelocity, qreal yVelocity) |
| 2168 | |
| 2169 | Flicks the content with \a xVelocity horizontally and \a yVelocity vertically in pixels/sec. |
| 2170 | |
| 2171 | Calling this method will update the corresponding moving and flicking properties and signals, |
| 2172 | just like a real touchscreen flick. |
| 2173 | */ |
| 2174 | |
| 2175 | void QQuickFlickable::flick(qreal xVelocity, qreal yVelocity) |
| 2176 | { |
| 2177 | Q_D(QQuickFlickable); |
| 2178 | d->hData.reset(); |
| 2179 | d->vData.reset(); |
| 2180 | d->hData.velocity = xVelocity; |
| 2181 | d->vData.velocity = yVelocity; |
| 2182 | d->hData.vTime = d->vData.vTime = d->timeline.time(); |
| 2183 | |
| 2184 | const bool flickedX = xflick() && !qFuzzyIsNull(d: xVelocity) && d->flickX(eventType: QEvent::TouchUpdate, velocity: xVelocity); |
| 2185 | const bool flickedY = yflick() && !qFuzzyIsNull(d: yVelocity) && d->flickY(eventType: QEvent::TouchUpdate, velocity: yVelocity); |
| 2186 | |
| 2187 | if (flickedX) |
| 2188 | d->hMoved = true; |
| 2189 | if (flickedY) |
| 2190 | d->vMoved = true; |
| 2191 | movementStarting(); |
| 2192 | d->flickingStarted(flickingH: flickedX, flickingV: flickedY); |
| 2193 | } |
| 2194 | |
| 2195 | void QQuickFlickablePrivate::flickingStarted(bool flickingH, bool flickingV) |
| 2196 | { |
| 2197 | Q_Q(QQuickFlickable); |
| 2198 | if (!flickingH && !flickingV) |
| 2199 | return; |
| 2200 | |
| 2201 | bool wasFlicking = hData.flicking || vData.flicking; |
| 2202 | if (flickingH && !hData.flicking) { |
| 2203 | hData.flicking = true; |
| 2204 | emit q->flickingHorizontallyChanged(); |
| 2205 | } |
| 2206 | if (flickingV && !vData.flicking) { |
| 2207 | vData.flicking = true; |
| 2208 | emit q->flickingVerticallyChanged(); |
| 2209 | } |
| 2210 | if (!wasFlicking && (hData.flicking || vData.flicking)) { |
| 2211 | emit q->flickingChanged(); |
| 2212 | emit q->flickStarted(); |
| 2213 | } |
| 2214 | } |
| 2215 | |
| 2216 | /*! |
| 2217 | \qmlmethod QtQuick::Flickable::cancelFlick() |
| 2218 | |
| 2219 | Cancels the current flick animation. |
| 2220 | */ |
| 2221 | |
| 2222 | void QQuickFlickable::cancelFlick() |
| 2223 | { |
| 2224 | Q_D(QQuickFlickable); |
| 2225 | d->resetTimeline(data&: d->hData); |
| 2226 | d->resetTimeline(data&: d->vData); |
| 2227 | movementEnding(); |
| 2228 | } |
| 2229 | |
| 2230 | void QQuickFlickablePrivate::data_append(QQmlListProperty<QObject> *prop, QObject *o) |
| 2231 | { |
| 2232 | if (!prop || !prop->data) |
| 2233 | return; |
| 2234 | |
| 2235 | if (QQuickItem *i = qmlobject_cast<QQuickItem *>(object: o)) { |
| 2236 | i->setParentItem(static_cast<QQuickFlickablePrivate*>(prop->data)->contentItem); |
| 2237 | } else if (QQuickPointerHandler *pointerHandler = qmlobject_cast<QQuickPointerHandler *>(object: o)) { |
| 2238 | static_cast<QQuickFlickablePrivate*>(prop->data)->addPointerHandler(h: pointerHandler); |
| 2239 | } else { |
| 2240 | o->setParent(prop->object); // XXX todo - do we want this? |
| 2241 | } |
| 2242 | } |
| 2243 | |
| 2244 | qsizetype QQuickFlickablePrivate::data_count(QQmlListProperty<QObject> *) |
| 2245 | { |
| 2246 | // XXX todo |
| 2247 | return 0; |
| 2248 | } |
| 2249 | |
| 2250 | QObject *QQuickFlickablePrivate::data_at(QQmlListProperty<QObject> *, qsizetype) |
| 2251 | { |
| 2252 | // XXX todo |
| 2253 | return nullptr; |
| 2254 | } |
| 2255 | |
| 2256 | void QQuickFlickablePrivate::data_clear(QQmlListProperty<QObject> *) |
| 2257 | { |
| 2258 | // XXX todo |
| 2259 | } |
| 2260 | |
| 2261 | QQmlListProperty<QObject> QQuickFlickable::flickableData() |
| 2262 | { |
| 2263 | Q_D(QQuickFlickable); |
| 2264 | return QQmlListProperty<QObject>(this, (void *)d, QQuickFlickablePrivate::data_append, |
| 2265 | QQuickFlickablePrivate::data_count, |
| 2266 | QQuickFlickablePrivate::data_at, |
| 2267 | QQuickFlickablePrivate::data_clear); |
| 2268 | } |
| 2269 | |
| 2270 | QQmlListProperty<QQuickItem> QQuickFlickable::flickableChildren() |
| 2271 | { |
| 2272 | Q_D(QQuickFlickable); |
| 2273 | return QQuickItemPrivate::get(item: d->contentItem)->children(); |
| 2274 | } |
| 2275 | |
| 2276 | /*! |
| 2277 | \qmlproperty enumeration QtQuick::Flickable::boundsBehavior |
| 2278 | This property holds whether the surface may be dragged |
| 2279 | beyond the Flickable's boundaries, or overshoot the |
| 2280 | Flickable's boundaries when flicked. |
| 2281 | |
| 2282 | When the \l boundsMovement is \c Flickable.FollowBoundsBehavior, a value |
| 2283 | other than \c Flickable.StopAtBounds will give a feeling that the edges of |
| 2284 | the view are soft, rather than a hard physical boundary. |
| 2285 | |
| 2286 | The \c boundsBehavior can be one of: |
| 2287 | |
| 2288 | \list |
| 2289 | \li Flickable.StopAtBounds - the contents can not be dragged beyond the boundary |
| 2290 | of the flickable, and flicks will not overshoot. |
| 2291 | \li Flickable.DragOverBounds - the contents can be dragged beyond the boundary |
| 2292 | of the Flickable, but flicks will not overshoot. |
| 2293 | \li Flickable.OvershootBounds - the contents can overshoot the boundary when flicked, |
| 2294 | but the content cannot be dragged beyond the boundary of the flickable. (since \c{QtQuick 2.5}) |
| 2295 | \li Flickable.DragAndOvershootBounds (default) - the contents can be dragged |
| 2296 | beyond the boundary of the Flickable, and can overshoot the |
| 2297 | boundary when flicked. |
| 2298 | \endlist |
| 2299 | |
| 2300 | \sa horizontalOvershoot, verticalOvershoot, boundsMovement |
| 2301 | */ |
| 2302 | QQuickFlickable::BoundsBehavior QQuickFlickable::boundsBehavior() const |
| 2303 | { |
| 2304 | Q_D(const QQuickFlickable); |
| 2305 | return d->boundsBehavior; |
| 2306 | } |
| 2307 | |
| 2308 | void QQuickFlickable::setBoundsBehavior(BoundsBehavior b) |
| 2309 | { |
| 2310 | Q_D(QQuickFlickable); |
| 2311 | if (b == d->boundsBehavior) |
| 2312 | return; |
| 2313 | d->boundsBehavior = b; |
| 2314 | emit boundsBehaviorChanged(); |
| 2315 | } |
| 2316 | |
| 2317 | /*! |
| 2318 | \qmlproperty Transition QtQuick::Flickable::rebound |
| 2319 | |
| 2320 | This holds the transition to be applied to the content view when |
| 2321 | it snaps back to the bounds of the flickable. The transition is |
| 2322 | triggered when the view is flicked or dragged past the edge of the |
| 2323 | content area, or when returnToBounds() is called. |
| 2324 | |
| 2325 | \qml |
| 2326 | import QtQuick 2.0 |
| 2327 | |
| 2328 | Flickable { |
| 2329 | width: 150; height: 150 |
| 2330 | contentWidth: 300; contentHeight: 300 |
| 2331 | |
| 2332 | rebound: Transition { |
| 2333 | NumberAnimation { |
| 2334 | properties: "x,y" |
| 2335 | duration: 1000 |
| 2336 | easing.type: Easing.OutBounce |
| 2337 | } |
| 2338 | } |
| 2339 | |
| 2340 | Rectangle { |
| 2341 | width: 300; height: 300 |
| 2342 | gradient: Gradient { |
| 2343 | GradientStop { position: 0.0; color: "lightsteelblue" } |
| 2344 | GradientStop { position: 1.0; color: "blue" } |
| 2345 | } |
| 2346 | } |
| 2347 | } |
| 2348 | \endqml |
| 2349 | |
| 2350 | When the above view is flicked beyond its bounds, it will return to its |
| 2351 | bounds using the transition specified: |
| 2352 | |
| 2353 | \image flickable-rebound.gif |
| 2354 | |
| 2355 | If this property is not set, a default animation is applied. |
| 2356 | */ |
| 2357 | QQuickTransition *QQuickFlickable::rebound() const |
| 2358 | { |
| 2359 | Q_D(const QQuickFlickable); |
| 2360 | return d->rebound; |
| 2361 | } |
| 2362 | |
| 2363 | void QQuickFlickable::setRebound(QQuickTransition *transition) |
| 2364 | { |
| 2365 | Q_D(QQuickFlickable); |
| 2366 | if (transition) { |
| 2367 | if (!d->hData.transitionToBounds) |
| 2368 | d->hData.transitionToBounds = new QQuickFlickableReboundTransition(this, QLatin1String("x" )); |
| 2369 | if (!d->vData.transitionToBounds) |
| 2370 | d->vData.transitionToBounds = new QQuickFlickableReboundTransition(this, QLatin1String("y" )); |
| 2371 | } |
| 2372 | if (d->rebound != transition) { |
| 2373 | d->rebound = transition; |
| 2374 | emit reboundChanged(); |
| 2375 | } |
| 2376 | } |
| 2377 | |
| 2378 | /*! |
| 2379 | \qmlproperty real QtQuick::Flickable::contentWidth |
| 2380 | \qmlproperty real QtQuick::Flickable::contentHeight |
| 2381 | |
| 2382 | The dimensions of the content (the surface controlled by Flickable). |
| 2383 | This should typically be set to the combined size of the items placed in the |
| 2384 | Flickable. |
| 2385 | |
| 2386 | The following snippet shows how these properties are used to display |
| 2387 | an image that is larger than the Flickable item itself: |
| 2388 | |
| 2389 | \snippet qml/flickable.qml document |
| 2390 | |
| 2391 | In some cases, the content dimensions can be automatically set |
| 2392 | based on the \l {Item::childrenRect.width}{childrenRect.width} |
| 2393 | and \l {Item::childrenRect.height}{childrenRect.height} properties |
| 2394 | of the \l contentItem. For example, the previous snippet could be rewritten with: |
| 2395 | |
| 2396 | \code |
| 2397 | contentWidth: contentItem.childrenRect.width; contentHeight: contentItem.childrenRect.height |
| 2398 | \endcode |
| 2399 | |
| 2400 | Though this assumes that the origin of the childrenRect is 0,0. |
| 2401 | */ |
| 2402 | qreal QQuickFlickable::contentWidth() const |
| 2403 | { |
| 2404 | Q_D(const QQuickFlickable); |
| 2405 | return d->hData.viewSize; |
| 2406 | } |
| 2407 | |
| 2408 | void QQuickFlickable::setContentWidth(qreal w) |
| 2409 | { |
| 2410 | Q_D(QQuickFlickable); |
| 2411 | if (d->hData.viewSize == w) |
| 2412 | return; |
| 2413 | d->hData.viewSize = w; |
| 2414 | if (w < 0) |
| 2415 | d->contentItem->setWidth(width() - d->hData.startMargin - d->hData.endMargin); |
| 2416 | else |
| 2417 | d->contentItem->setWidth(w); |
| 2418 | d->hData.markExtentsDirty(); |
| 2419 | // Make sure that we're entirely in view. |
| 2420 | if (!d->pressed && !d->hData.moving && !d->vData.moving) { |
| 2421 | d->fixupMode = QQuickFlickablePrivate::Immediate; |
| 2422 | d->fixupX(); |
| 2423 | } else if (!d->pressed && d->hData.fixingUp) { |
| 2424 | d->fixupMode = QQuickFlickablePrivate::ExtentChanged; |
| 2425 | d->fixupX(); |
| 2426 | } |
| 2427 | emit contentWidthChanged(); |
| 2428 | d->updateBeginningEnd(); |
| 2429 | } |
| 2430 | |
| 2431 | qreal QQuickFlickable::contentHeight() const |
| 2432 | { |
| 2433 | Q_D(const QQuickFlickable); |
| 2434 | return d->vData.viewSize; |
| 2435 | } |
| 2436 | |
| 2437 | void QQuickFlickable::setContentHeight(qreal h) |
| 2438 | { |
| 2439 | Q_D(QQuickFlickable); |
| 2440 | if (d->vData.viewSize == h) |
| 2441 | return; |
| 2442 | d->vData.viewSize = h; |
| 2443 | if (h < 0) |
| 2444 | d->contentItem->setHeight(height() - d->vData.startMargin - d->vData.endMargin); |
| 2445 | else |
| 2446 | d->contentItem->setHeight(h); |
| 2447 | d->vData.markExtentsDirty(); |
| 2448 | // Make sure that we're entirely in view. |
| 2449 | if (!d->pressed && !d->hData.moving && !d->vData.moving) { |
| 2450 | d->fixupMode = QQuickFlickablePrivate::Immediate; |
| 2451 | d->fixupY(); |
| 2452 | } else if (!d->pressed && d->vData.fixingUp) { |
| 2453 | d->fixupMode = QQuickFlickablePrivate::ExtentChanged; |
| 2454 | d->fixupY(); |
| 2455 | } |
| 2456 | emit contentHeightChanged(); |
| 2457 | d->updateBeginningEnd(); |
| 2458 | } |
| 2459 | |
| 2460 | /*! |
| 2461 | \qmlproperty real QtQuick::Flickable::topMargin |
| 2462 | \qmlproperty real QtQuick::Flickable::leftMargin |
| 2463 | \qmlproperty real QtQuick::Flickable::bottomMargin |
| 2464 | \qmlproperty real QtQuick::Flickable::rightMargin |
| 2465 | |
| 2466 | These properties hold the margins around the content. This space is reserved |
| 2467 | in addition to the contentWidth and contentHeight. |
| 2468 | */ |
| 2469 | |
| 2470 | |
| 2471 | qreal QQuickFlickable::topMargin() const |
| 2472 | { |
| 2473 | Q_D(const QQuickFlickable); |
| 2474 | return d->vData.startMargin; |
| 2475 | } |
| 2476 | |
| 2477 | void QQuickFlickable::setTopMargin(qreal m) |
| 2478 | { |
| 2479 | Q_D(QQuickFlickable); |
| 2480 | if (d->vData.startMargin == m) |
| 2481 | return; |
| 2482 | d->vData.startMargin = m; |
| 2483 | d->vData.markExtentsDirty(); |
| 2484 | if (!d->pressed && !d->hData.moving && !d->vData.moving) { |
| 2485 | // FIXME: We're not consistently updating the contentY, see QTBUG-131478 |
| 2486 | d->fixupMode = QQuickFlickablePrivate::Immediate; |
| 2487 | d->fixupY(); |
| 2488 | } |
| 2489 | emit topMarginChanged(); |
| 2490 | d->updateBeginningEnd(); |
| 2491 | } |
| 2492 | |
| 2493 | qreal QQuickFlickable::bottomMargin() const |
| 2494 | { |
| 2495 | Q_D(const QQuickFlickable); |
| 2496 | return d->vData.endMargin; |
| 2497 | } |
| 2498 | |
| 2499 | void QQuickFlickable::setBottomMargin(qreal m) |
| 2500 | { |
| 2501 | Q_D(QQuickFlickable); |
| 2502 | if (d->vData.endMargin == m) |
| 2503 | return; |
| 2504 | d->vData.endMargin = m; |
| 2505 | d->vData.markExtentsDirty(); |
| 2506 | if (!d->pressed && !d->hData.moving && !d->vData.moving) { |
| 2507 | // FIXME: We're not consistently updating the contentY, see QTBUG-131478 |
| 2508 | d->fixupMode = QQuickFlickablePrivate::Immediate; |
| 2509 | d->fixupY(); |
| 2510 | } |
| 2511 | emit bottomMarginChanged(); |
| 2512 | d->updateBeginningEnd(); |
| 2513 | } |
| 2514 | |
| 2515 | qreal QQuickFlickable::leftMargin() const |
| 2516 | { |
| 2517 | Q_D(const QQuickFlickable); |
| 2518 | return d->hData.startMargin; |
| 2519 | } |
| 2520 | |
| 2521 | void QQuickFlickable::setLeftMargin(qreal m) |
| 2522 | { |
| 2523 | Q_D(QQuickFlickable); |
| 2524 | if (d->hData.startMargin == m) |
| 2525 | return; |
| 2526 | d->hData.startMargin = m; |
| 2527 | d->hData.markExtentsDirty(); |
| 2528 | if (!d->pressed && !d->hData.moving && !d->vData.moving) { |
| 2529 | // FIXME: We're not consistently updating the contentX, see QTBUG-131478 |
| 2530 | d->fixupMode = QQuickFlickablePrivate::Immediate; |
| 2531 | d->fixupX(); |
| 2532 | } |
| 2533 | emit leftMarginChanged(); |
| 2534 | d->updateBeginningEnd(); |
| 2535 | } |
| 2536 | |
| 2537 | qreal QQuickFlickable::rightMargin() const |
| 2538 | { |
| 2539 | Q_D(const QQuickFlickable); |
| 2540 | return d->hData.endMargin; |
| 2541 | } |
| 2542 | |
| 2543 | void QQuickFlickable::setRightMargin(qreal m) |
| 2544 | { |
| 2545 | Q_D(QQuickFlickable); |
| 2546 | if (d->hData.endMargin == m) |
| 2547 | return; |
| 2548 | d->hData.endMargin = m; |
| 2549 | d->hData.markExtentsDirty(); |
| 2550 | if (!d->pressed && !d->hData.moving && !d->vData.moving) { |
| 2551 | // FIXME: We're not consistently updating the contentX, see QTBUG-131478 |
| 2552 | d->fixupMode = QQuickFlickablePrivate::Immediate; |
| 2553 | d->fixupX(); |
| 2554 | } |
| 2555 | emit rightMarginChanged(); |
| 2556 | d->updateBeginningEnd(); |
| 2557 | } |
| 2558 | |
| 2559 | /*! |
| 2560 | \qmlproperty real QtQuick::Flickable::originX |
| 2561 | \qmlproperty real QtQuick::Flickable::originY |
| 2562 | |
| 2563 | These properties hold the origin of the content. This value always refers |
| 2564 | to the top-left position of the content regardless of layout direction. |
| 2565 | |
| 2566 | This is usually (0,0), however ListView and GridView may have an arbitrary |
| 2567 | origin due to delegate size variation, or item insertion/removal outside |
| 2568 | the visible region. |
| 2569 | |
| 2570 | \sa contentX, contentY |
| 2571 | */ |
| 2572 | |
| 2573 | qreal QQuickFlickable::originY() const |
| 2574 | { |
| 2575 | Q_D(const QQuickFlickable); |
| 2576 | return -minYExtent() + d->vData.startMargin; |
| 2577 | } |
| 2578 | |
| 2579 | qreal QQuickFlickable::originX() const |
| 2580 | { |
| 2581 | Q_D(const QQuickFlickable); |
| 2582 | return -minXExtent() + d->hData.startMargin; |
| 2583 | } |
| 2584 | |
| 2585 | |
| 2586 | /*! |
| 2587 | \qmlmethod QtQuick::Flickable::resizeContent(real width, real height, QPointF center) |
| 2588 | |
| 2589 | Resizes the content to \a width x \a height about \a center. |
| 2590 | |
| 2591 | This does not scale the contents of the Flickable - it only resizes the \l contentWidth |
| 2592 | and \l contentHeight. |
| 2593 | |
| 2594 | Resizing the content may result in the content being positioned outside |
| 2595 | the bounds of the Flickable. Calling \l returnToBounds() will |
| 2596 | move the content back within legal bounds. |
| 2597 | */ |
| 2598 | void QQuickFlickable::resizeContent(qreal w, qreal h, QPointF center) |
| 2599 | { |
| 2600 | Q_D(QQuickFlickable); |
| 2601 | const qreal oldHSize = d->hData.viewSize; |
| 2602 | const qreal oldVSize = d->vData.viewSize; |
| 2603 | const bool needToUpdateWidth = w != oldHSize; |
| 2604 | const bool needToUpdateHeight = h != oldVSize; |
| 2605 | d->hData.viewSize = w; |
| 2606 | d->vData.viewSize = h; |
| 2607 | d->contentItem->setSize(QSizeF(w, h)); |
| 2608 | if (needToUpdateWidth) |
| 2609 | emit contentWidthChanged(); |
| 2610 | if (needToUpdateHeight) |
| 2611 | emit contentHeightChanged(); |
| 2612 | |
| 2613 | if (center.x() != 0) { |
| 2614 | qreal pos = center.x() * w / oldHSize; |
| 2615 | setContentX(contentX() + pos - center.x()); |
| 2616 | } |
| 2617 | if (center.y() != 0) { |
| 2618 | qreal pos = center.y() * h / oldVSize; |
| 2619 | setContentY(contentY() + pos - center.y()); |
| 2620 | } |
| 2621 | d->updateBeginningEnd(); |
| 2622 | } |
| 2623 | |
| 2624 | /*! |
| 2625 | \qmlmethod QtQuick::Flickable::returnToBounds() |
| 2626 | |
| 2627 | Ensures the content is within legal bounds. |
| 2628 | |
| 2629 | This may be called to ensure that the content is within legal bounds |
| 2630 | after manually positioning the content. |
| 2631 | */ |
| 2632 | void QQuickFlickable::returnToBounds() |
| 2633 | { |
| 2634 | Q_D(QQuickFlickable); |
| 2635 | d->fixupX(); |
| 2636 | d->fixupY(); |
| 2637 | } |
| 2638 | |
| 2639 | qreal QQuickFlickable::vWidth() const |
| 2640 | { |
| 2641 | Q_D(const QQuickFlickable); |
| 2642 | if (d->hData.viewSize < 0) |
| 2643 | return width(); |
| 2644 | else |
| 2645 | return d->hData.viewSize; |
| 2646 | } |
| 2647 | |
| 2648 | qreal QQuickFlickable::vHeight() const |
| 2649 | { |
| 2650 | Q_D(const QQuickFlickable); |
| 2651 | if (d->vData.viewSize < 0) |
| 2652 | return height(); |
| 2653 | else |
| 2654 | return d->vData.viewSize; |
| 2655 | } |
| 2656 | |
| 2657 | /*! |
| 2658 | \internal |
| 2659 | |
| 2660 | The setFlickableDirection function can be used to set constraints on which axis the contentItem can be flicked along. |
| 2661 | |
| 2662 | \return true if the flickable is allowed to flick in the horizontal direction, otherwise returns false |
| 2663 | */ |
| 2664 | bool QQuickFlickable::xflick() const |
| 2665 | { |
| 2666 | Q_D(const QQuickFlickable); |
| 2667 | const int contentWidthWithMargins = d->contentItem->width() + d->hData.startMargin + d->hData.endMargin; |
| 2668 | if ((d->flickableDirection & QQuickFlickable::AutoFlickIfNeeded) && (contentWidthWithMargins > width())) |
| 2669 | return true; |
| 2670 | if (d->flickableDirection == QQuickFlickable::AutoFlickDirection) |
| 2671 | return std::floor(x: qAbs(t: contentWidthWithMargins - width())); |
| 2672 | return d->flickableDirection & QQuickFlickable::HorizontalFlick; |
| 2673 | } |
| 2674 | |
| 2675 | /*! |
| 2676 | \internal |
| 2677 | |
| 2678 | The setFlickableDirection function can be used to set constraints on which axis the contentItem can be flicked along. |
| 2679 | |
| 2680 | \return true if the flickable is allowed to flick in the vertical direction, otherwise returns false. |
| 2681 | */ |
| 2682 | bool QQuickFlickable::yflick() const |
| 2683 | { |
| 2684 | Q_D(const QQuickFlickable); |
| 2685 | const int contentHeightWithMargins = d->contentItem->height() + d->vData.startMargin + d->vData.endMargin; |
| 2686 | if ((d->flickableDirection & QQuickFlickable::AutoFlickIfNeeded) && (contentHeightWithMargins > height())) |
| 2687 | return true; |
| 2688 | if (d->flickableDirection == QQuickFlickable::AutoFlickDirection) |
| 2689 | return std::floor(x: qAbs(t: contentHeightWithMargins - height())); |
| 2690 | return d->flickableDirection & QQuickFlickable::VerticalFlick; |
| 2691 | } |
| 2692 | |
| 2693 | void QQuickFlickable::mouseUngrabEvent() |
| 2694 | { |
| 2695 | Q_D(QQuickFlickable); |
| 2696 | // if our mouse grab has been removed (probably by another Flickable), |
| 2697 | // fix our state |
| 2698 | if (!d->replayingPressEvent) |
| 2699 | d->cancelInteraction(); |
| 2700 | } |
| 2701 | |
| 2702 | void QQuickFlickablePrivate::cancelInteraction() |
| 2703 | { |
| 2704 | Q_Q(QQuickFlickable); |
| 2705 | if (pressed) { |
| 2706 | clearDelayedPress(); |
| 2707 | pressed = false; |
| 2708 | draggingEnding(); |
| 2709 | stealMouse = false; |
| 2710 | q->setKeepMouseGrab(false); |
| 2711 | fixupX(); |
| 2712 | fixupY(); |
| 2713 | if (!isViewMoving()) |
| 2714 | q->movementEnding(); |
| 2715 | } |
| 2716 | } |
| 2717 | |
| 2718 | void QQuickFlickablePrivate::addPointerHandler(QQuickPointerHandler *h) |
| 2719 | { |
| 2720 | Q_Q(const QQuickFlickable); |
| 2721 | qCDebug(lcHandlerParent) << "reparenting handler" << h << "to contentItem of" << q; |
| 2722 | h->setParent(contentItem); |
| 2723 | QQuickItemPrivate::get(item: contentItem)->addPointerHandler(h); |
| 2724 | } |
| 2725 | |
| 2726 | /*! \internal |
| 2727 | QQuickFlickable::filterPointerEvent filters pointer events intercepted on the way |
| 2728 | to the child \a receiver, and potentially steals the exclusive grab. |
| 2729 | |
| 2730 | This is how flickable takes over the handling of events from child items. |
| 2731 | |
| 2732 | Returns true if the event will be stolen and should <em>not</em> be delivered to the \a receiver. |
| 2733 | */ |
| 2734 | bool QQuickFlickable::filterPointerEvent(QQuickItem *receiver, QPointerEvent *event) |
| 2735 | { |
| 2736 | Q_D(QQuickFlickable); |
| 2737 | const bool isTouch = QQuickDeliveryAgentPrivate::isTouchEvent(ev: event); |
| 2738 | const bool isMouse = QQuickDeliveryAgentPrivate::isMouseEvent(ev: event); |
| 2739 | if (isMouse || QQuickDeliveryAgentPrivate::isTabletEvent(ev: event)) { |
| 2740 | if (!d->buttonsAccepted(event: static_cast<QSinglePointEvent *>(event))) |
| 2741 | return QQuickItem::childMouseEventFilter(receiver, event); |
| 2742 | } else if (!isTouch) { |
| 2743 | return false; // don't filter hover events or wheel events, for example |
| 2744 | } |
| 2745 | Q_ASSERT_X(receiver != this, "" , "Flickable received a filter event for itself" ); |
| 2746 | // If a touch event contains a new press point, don't steal right away: watch the movements for a while |
| 2747 | if (isTouch && static_cast<QTouchEvent *>(event)->touchPointStates().testFlag(flag: QEventPoint::State::Pressed)) |
| 2748 | d->stealMouse = false; |
| 2749 | // If multiple touchpoints are within bounds, don't grab: it's probably meant for multi-touch interaction in some child |
| 2750 | if (event->pointCount() > 1) { |
| 2751 | qCDebug(lcFilter) << objectName() << "ignoring multi-touch" << event << "for" << receiver; |
| 2752 | d->stealMouse = false; |
| 2753 | return false; |
| 2754 | } else { |
| 2755 | qCDebug(lcFilter) << objectName() << "filtering" << event << "for" << receiver; |
| 2756 | } |
| 2757 | |
| 2758 | const auto &firstPoint = event->points().first(); |
| 2759 | |
| 2760 | if (event->pointCount() == 1 && event->exclusiveGrabber(point: firstPoint) == this) { |
| 2761 | // We have an exclusive grab (since we're e.g dragging), but at the same time, we have |
| 2762 | // a child with a passive grab (which is why this filter is being called). And because |
| 2763 | // of that, we end up getting the same pointer events twice; First in our own event |
| 2764 | // handlers (because of the grab), then once more in here, since we filter the child. |
| 2765 | // To avoid processing the event twice (e.g avoid calling handleReleaseEvent once more |
| 2766 | // from below), we mark the event as filtered, and simply return. |
| 2767 | event->setAccepted(true); |
| 2768 | return true; |
| 2769 | } |
| 2770 | |
| 2771 | QPointF localPos = mapFromScene(point: firstPoint.scenePosition()); |
| 2772 | bool receiverDisabled = receiver && !receiver->isEnabled(); |
| 2773 | bool stealThisEvent = d->stealMouse; |
| 2774 | bool receiverKeepsGrab = receiver && (receiver->keepMouseGrab() || receiver->keepTouchGrab()); |
| 2775 | bool receiverRelinquishGrab = false; |
| 2776 | |
| 2777 | // Special case for MouseArea, try to guess what it does with the event |
| 2778 | if (auto *mouseArea = qmlobject_cast<QQuickMouseArea *>(object: receiver)) { |
| 2779 | bool preventStealing = mouseArea->preventStealing(); |
| 2780 | #if QT_CONFIG(quick_draganddrop) |
| 2781 | if (mouseArea->drag() && mouseArea->drag()->target()) |
| 2782 | preventStealing = true; |
| 2783 | #endif |
| 2784 | if (!preventStealing && receiverKeepsGrab) { |
| 2785 | receiverRelinquishGrab = !receiverDisabled || (isMouse |
| 2786 | && firstPoint.state() == QEventPoint::State::Pressed |
| 2787 | && (receiver->acceptedMouseButtons() & static_cast<QMouseEvent *>(event)->button())); |
| 2788 | if (receiverRelinquishGrab) |
| 2789 | receiverKeepsGrab = false; |
| 2790 | } |
| 2791 | } |
| 2792 | |
| 2793 | if ((stealThisEvent || contains(point: localPos)) && (!receiver || !receiverKeepsGrab || receiverDisabled)) { |
| 2794 | QScopedPointer<QPointerEvent> localizedEvent(QQuickDeliveryAgentPrivate::clonePointerEvent(event, transformedLocalPos: localPos)); |
| 2795 | localizedEvent->setAccepted(false); |
| 2796 | switch (firstPoint.state()) { |
| 2797 | case QEventPoint::State::Updated: |
| 2798 | d->handleMoveEvent(event: localizedEvent.data()); |
| 2799 | break; |
| 2800 | case QEventPoint::State::Pressed: |
| 2801 | d->handlePressEvent(event: localizedEvent.data()); |
| 2802 | d->captureDelayedPress(item: receiver, event); |
| 2803 | // never grab the pointing device on press during filtering: do it later, during a move |
| 2804 | d->stealMouse = false; |
| 2805 | stealThisEvent = false; |
| 2806 | break; |
| 2807 | case QEventPoint::State::Released: |
| 2808 | d->handleReleaseEvent(event: localizedEvent.data()); |
| 2809 | stealThisEvent = d->stealMouse; |
| 2810 | break; |
| 2811 | case QEventPoint::State::Stationary: |
| 2812 | case QEventPoint::State::Unknown: |
| 2813 | break; |
| 2814 | } |
| 2815 | if ((receiver && stealThisEvent && !receiverKeepsGrab && receiver != this) || receiverDisabled) { |
| 2816 | d->clearDelayedPress(); |
| 2817 | event->setExclusiveGrabber(point: firstPoint, exclusiveGrabber: this); |
| 2818 | } else if (d->delayedPressEvent) { |
| 2819 | event->setExclusiveGrabber(point: firstPoint, exclusiveGrabber: this); |
| 2820 | } |
| 2821 | /* |
| 2822 | Note that d->stealMouse can be false before the call to d->handleMoveEvent(), but true |
| 2823 | afterwards. That means we detected a drag. But even so, we deliberately don't filter |
| 2824 | the move event that cause this to happen, since the user might actually be dragging on |
| 2825 | a child item, such as a Slider, in which case the child should get a chance to detect |
| 2826 | the drag instead, and take the grab. Only if we receive another move event after this, |
| 2827 | without the child grabbing the mouse in-between, do we initiate a flick. |
| 2828 | An exception is if the user is already in a flicking session started from an earlier |
| 2829 | drag, meaning that the content item is still moving (isMoving() == true). In that |
| 2830 | case, we see any subsequent drags as being a continuation of the flicking. |
| 2831 | Then we filter all events straight away, to avoid triggering any taps or drags in a child. |
| 2832 | */ |
| 2833 | if (isMoving() || (!receiverRelinquishGrab && (stealThisEvent || d->delayedPressEvent || receiverDisabled))) { |
| 2834 | // Filter the event |
| 2835 | event->setAccepted(true); |
| 2836 | return true; |
| 2837 | } |
| 2838 | |
| 2839 | // Don't filter the event |
| 2840 | return false; |
| 2841 | } else if (d->lastPosTime != -1) { |
| 2842 | d->lastPosTime = -1; |
| 2843 | returnToBounds(); |
| 2844 | } |
| 2845 | if (firstPoint.state() == QEventPoint::State::Released || (receiverKeepsGrab && !receiverDisabled)) { |
| 2846 | // mouse released, or another item has claimed the grab |
| 2847 | d->lastPosTime = -1; |
| 2848 | d->clearDelayedPress(); |
| 2849 | d->stealMouse = false; |
| 2850 | d->pressed = false; |
| 2851 | } |
| 2852 | return false; |
| 2853 | } |
| 2854 | |
| 2855 | /*! \internal |
| 2856 | Despite the name, this function filters all pointer events on their way to any child within. |
| 2857 | Returns true if the event will be stolen and should <em>not</em> be delivered to the \a receiver. |
| 2858 | */ |
| 2859 | bool QQuickFlickable::childMouseEventFilter(QQuickItem *i, QEvent *e) |
| 2860 | { |
| 2861 | Q_D(QQuickFlickable); |
| 2862 | QPointerEvent *pointerEvent = e->isPointerEvent() ? static_cast<QPointerEvent *>(e) : nullptr; |
| 2863 | |
| 2864 | auto wantsPointerEvent_helper = [this, d, i, pointerEvent]() { |
| 2865 | Q_ASSERT(pointerEvent); |
| 2866 | QQuickDeliveryAgentPrivate::localizePointerEvent(ev: pointerEvent, dest: this); |
| 2867 | const bool wants = d->wantsPointerEvent(pointerEvent); |
| 2868 | // re-localize event back to \a i before returning |
| 2869 | QQuickDeliveryAgentPrivate::localizePointerEvent(ev: pointerEvent, dest: i); |
| 2870 | return wants; |
| 2871 | }; |
| 2872 | |
| 2873 | if (!isVisible() || !isEnabled() || !isInteractive() || |
| 2874 | (pointerEvent && !wantsPointerEvent_helper())) { |
| 2875 | d->cancelInteraction(); |
| 2876 | return QQuickItem::childMouseEventFilter(i, e); |
| 2877 | } |
| 2878 | |
| 2879 | if (e->type() == QEvent::UngrabMouse) { |
| 2880 | Q_ASSERT(e->isSinglePointEvent()); |
| 2881 | auto spe = static_cast<QSinglePointEvent *>(e); |
| 2882 | const QObject *grabber = spe->exclusiveGrabber(point: spe->points().first()); |
| 2883 | qCDebug(lcFilter) << "filtering UngrabMouse" << spe->points().first() << "for" << i << "grabber is" << grabber; |
| 2884 | if (grabber != this) |
| 2885 | mouseUngrabEvent(); // A child has been ungrabbed |
| 2886 | } else if (pointerEvent) { |
| 2887 | return filterPointerEvent(receiver: i, event: pointerEvent); |
| 2888 | } |
| 2889 | |
| 2890 | return QQuickItem::childMouseEventFilter(i, e); |
| 2891 | } |
| 2892 | |
| 2893 | /*! |
| 2894 | \qmlproperty real QtQuick::Flickable::maximumFlickVelocity |
| 2895 | This property holds the maximum velocity that the user can flick the view in pixels/second. |
| 2896 | |
| 2897 | The default value is platform dependent. |
| 2898 | */ |
| 2899 | qreal QQuickFlickable::maximumFlickVelocity() const |
| 2900 | { |
| 2901 | Q_D(const QQuickFlickable); |
| 2902 | return d->maxVelocity; |
| 2903 | } |
| 2904 | |
| 2905 | void QQuickFlickable::setMaximumFlickVelocity(qreal v) |
| 2906 | { |
| 2907 | Q_D(QQuickFlickable); |
| 2908 | if (v == d->maxVelocity) |
| 2909 | return; |
| 2910 | d->maxVelocity = v; |
| 2911 | emit maximumFlickVelocityChanged(); |
| 2912 | } |
| 2913 | |
| 2914 | /*! |
| 2915 | \qmlproperty real QtQuick::Flickable::flickDeceleration |
| 2916 | This property holds the rate at which a flick will decelerate: |
| 2917 | the higher the number, the faster it slows down when the user stops |
| 2918 | flicking via touch. For example 0.0001 is nearly |
| 2919 | "frictionless", and 10000 feels quite "sticky". |
| 2920 | |
| 2921 | The default value is platform dependent. Values of zero or less are not allowed. |
| 2922 | */ |
| 2923 | qreal QQuickFlickable::flickDeceleration() const |
| 2924 | { |
| 2925 | Q_D(const QQuickFlickable); |
| 2926 | return d->deceleration; |
| 2927 | } |
| 2928 | |
| 2929 | void QQuickFlickable::setFlickDeceleration(qreal deceleration) |
| 2930 | { |
| 2931 | Q_D(QQuickFlickable); |
| 2932 | if (deceleration == d->deceleration) |
| 2933 | return; |
| 2934 | d->deceleration = qMax(a: 0.001, b: deceleration); |
| 2935 | emit flickDecelerationChanged(); |
| 2936 | } |
| 2937 | |
| 2938 | bool QQuickFlickable::isFlicking() const |
| 2939 | { |
| 2940 | Q_D(const QQuickFlickable); |
| 2941 | return d->hData.flicking || d->vData.flicking; |
| 2942 | } |
| 2943 | |
| 2944 | /*! |
| 2945 | \qmlproperty bool QtQuick::Flickable::flicking |
| 2946 | \qmlproperty bool QtQuick::Flickable::flickingHorizontally |
| 2947 | \qmlproperty bool QtQuick::Flickable::flickingVertically |
| 2948 | |
| 2949 | These properties describe whether the view is currently moving horizontally, |
| 2950 | vertically or in either direction, due to the user flicking the view. |
| 2951 | */ |
| 2952 | bool QQuickFlickable::isFlickingHorizontally() const |
| 2953 | { |
| 2954 | Q_D(const QQuickFlickable); |
| 2955 | return d->hData.flicking; |
| 2956 | } |
| 2957 | |
| 2958 | bool QQuickFlickable::isFlickingVertically() const |
| 2959 | { |
| 2960 | Q_D(const QQuickFlickable); |
| 2961 | return d->vData.flicking; |
| 2962 | } |
| 2963 | |
| 2964 | /*! |
| 2965 | \qmlproperty bool QtQuick::Flickable::dragging |
| 2966 | \qmlproperty bool QtQuick::Flickable::draggingHorizontally |
| 2967 | \qmlproperty bool QtQuick::Flickable::draggingVertically |
| 2968 | |
| 2969 | These properties describe whether the view is currently moving horizontally, |
| 2970 | vertically or in either direction, due to the user dragging the view. |
| 2971 | */ |
| 2972 | bool QQuickFlickable::isDragging() const |
| 2973 | { |
| 2974 | Q_D(const QQuickFlickable); |
| 2975 | return d->hData.dragging || d->vData.dragging; |
| 2976 | } |
| 2977 | |
| 2978 | bool QQuickFlickable::isDraggingHorizontally() const |
| 2979 | { |
| 2980 | Q_D(const QQuickFlickable); |
| 2981 | return d->hData.dragging; |
| 2982 | } |
| 2983 | |
| 2984 | bool QQuickFlickable::isDraggingVertically() const |
| 2985 | { |
| 2986 | Q_D(const QQuickFlickable); |
| 2987 | return d->vData.dragging; |
| 2988 | } |
| 2989 | |
| 2990 | void QQuickFlickablePrivate::draggingStarting() |
| 2991 | { |
| 2992 | Q_Q(QQuickFlickable); |
| 2993 | bool wasDragging = hData.dragging || vData.dragging; |
| 2994 | if (hMoved && !hData.dragging) { |
| 2995 | hData.dragging = true; |
| 2996 | emit q->draggingHorizontallyChanged(); |
| 2997 | } |
| 2998 | if (vMoved && !vData.dragging) { |
| 2999 | vData.dragging = true; |
| 3000 | emit q->draggingVerticallyChanged(); |
| 3001 | } |
| 3002 | if (!wasDragging && (hData.dragging || vData.dragging)) { |
| 3003 | emit q->draggingChanged(); |
| 3004 | emit q->dragStarted(); |
| 3005 | } |
| 3006 | } |
| 3007 | |
| 3008 | void QQuickFlickablePrivate::draggingEnding() |
| 3009 | { |
| 3010 | Q_Q(QQuickFlickable); |
| 3011 | const bool wasDragging = hData.dragging || vData.dragging; |
| 3012 | if (hData.dragging) { |
| 3013 | hData.dragging = false; |
| 3014 | emit q->draggingHorizontallyChanged(); |
| 3015 | } |
| 3016 | if (vData.dragging) { |
| 3017 | vData.dragging = false; |
| 3018 | emit q->draggingVerticallyChanged(); |
| 3019 | } |
| 3020 | if (wasDragging) { |
| 3021 | if (!hData.dragging && !vData.dragging) { |
| 3022 | emit q->draggingChanged(); |
| 3023 | emit q->dragEnded(); |
| 3024 | } |
| 3025 | hData.inRebound = false; |
| 3026 | vData.inRebound = false; |
| 3027 | } |
| 3028 | } |
| 3029 | |
| 3030 | bool QQuickFlickablePrivate::isViewMoving() const |
| 3031 | { |
| 3032 | if (timeline.isActive() |
| 3033 | || (hData.transitionToBounds && hData.transitionToBounds->isActive()) |
| 3034 | || (vData.transitionToBounds && vData.transitionToBounds->isActive()) ) { |
| 3035 | return true; |
| 3036 | } |
| 3037 | return false; |
| 3038 | } |
| 3039 | |
| 3040 | /*! |
| 3041 | \qmlproperty int QtQuick::Flickable::pressDelay |
| 3042 | |
| 3043 | This property holds the time to delay (ms) delivering a press to |
| 3044 | children of the Flickable. This can be useful where reacting |
| 3045 | to a press before a flicking action has undesirable effects. |
| 3046 | |
| 3047 | If the flickable is dragged/flicked before the delay times out |
| 3048 | the press event will not be delivered. If the button is released |
| 3049 | within the timeout, both the press and release will be delivered. |
| 3050 | |
| 3051 | Note that for nested Flickables with pressDelay set, the pressDelay of |
| 3052 | outer Flickables is overridden by the innermost Flickable. If the drag |
| 3053 | exceeds the platform drag threshold, the press event will be delivered |
| 3054 | regardless of this property. |
| 3055 | |
| 3056 | \sa QStyleHints |
| 3057 | */ |
| 3058 | int QQuickFlickable::pressDelay() const |
| 3059 | { |
| 3060 | Q_D(const QQuickFlickable); |
| 3061 | return d->pressDelay; |
| 3062 | } |
| 3063 | |
| 3064 | void QQuickFlickable::setPressDelay(int delay) |
| 3065 | { |
| 3066 | Q_D(QQuickFlickable); |
| 3067 | if (d->pressDelay == delay) |
| 3068 | return; |
| 3069 | d->pressDelay = delay; |
| 3070 | emit pressDelayChanged(); |
| 3071 | } |
| 3072 | |
| 3073 | /*! |
| 3074 | \qmlproperty bool QtQuick::Flickable::moving |
| 3075 | \qmlproperty bool QtQuick::Flickable::movingHorizontally |
| 3076 | \qmlproperty bool QtQuick::Flickable::movingVertically |
| 3077 | |
| 3078 | These properties describe whether the view is currently moving horizontally, |
| 3079 | vertically or in either direction, due to the user either dragging or |
| 3080 | flicking the view. |
| 3081 | */ |
| 3082 | |
| 3083 | bool QQuickFlickable::isMoving() const |
| 3084 | { |
| 3085 | Q_D(const QQuickFlickable); |
| 3086 | return d->hData.moving || d->vData.moving; |
| 3087 | } |
| 3088 | |
| 3089 | bool QQuickFlickable::isMovingHorizontally() const |
| 3090 | { |
| 3091 | Q_D(const QQuickFlickable); |
| 3092 | return d->hData.moving; |
| 3093 | } |
| 3094 | |
| 3095 | bool QQuickFlickable::isMovingVertically() const |
| 3096 | { |
| 3097 | Q_D(const QQuickFlickable); |
| 3098 | return d->vData.moving; |
| 3099 | } |
| 3100 | |
| 3101 | void QQuickFlickable::velocityTimelineCompleted() |
| 3102 | { |
| 3103 | Q_D(QQuickFlickable); |
| 3104 | if ( (d->hData.transitionToBounds && d->hData.transitionToBounds->isActive()) |
| 3105 | || (d->vData.transitionToBounds && d->vData.transitionToBounds->isActive()) ) { |
| 3106 | return; |
| 3107 | } |
| 3108 | // With subclasses such as GridView, velocityTimeline.completed is emitted repeatedly: |
| 3109 | // for example setting currentIndex results in a visual "flick" which the user |
| 3110 | // didn't initiate directly. We don't want to end movement repeatedly, and in |
| 3111 | // that case movementEnding will happen after the sequence of movements ends. |
| 3112 | if (d->vData.flicking) |
| 3113 | movementEnding(); |
| 3114 | d->updateBeginningEnd(); |
| 3115 | } |
| 3116 | |
| 3117 | void QQuickFlickable::timelineCompleted() |
| 3118 | { |
| 3119 | Q_D(QQuickFlickable); |
| 3120 | if ( (d->hData.transitionToBounds && d->hData.transitionToBounds->isActive()) |
| 3121 | || (d->vData.transitionToBounds && d->vData.transitionToBounds->isActive()) ) { |
| 3122 | return; |
| 3123 | } |
| 3124 | movementEnding(); |
| 3125 | d->updateBeginningEnd(); |
| 3126 | } |
| 3127 | |
| 3128 | void QQuickFlickable::movementStarting() |
| 3129 | { |
| 3130 | Q_D(QQuickFlickable); |
| 3131 | bool wasMoving = d->hData.moving || d->vData.moving; |
| 3132 | if (d->hMoved && !d->hData.moving) { |
| 3133 | d->hData.moving = true; |
| 3134 | emit movingHorizontallyChanged(); |
| 3135 | } |
| 3136 | if (d->vMoved && !d->vData.moving) { |
| 3137 | d->vData.moving = true; |
| 3138 | emit movingVerticallyChanged(); |
| 3139 | } |
| 3140 | |
| 3141 | if (!wasMoving && (d->hData.moving || d->vData.moving)) { |
| 3142 | emit movingChanged(); |
| 3143 | emit movementStarted(); |
| 3144 | #if QT_CONFIG(accessibility) |
| 3145 | if (QAccessible::isActive()) { |
| 3146 | QAccessibleEvent ev(this, QAccessible::ScrollingStart); |
| 3147 | QAccessible::updateAccessibility(event: &ev); |
| 3148 | } |
| 3149 | #endif |
| 3150 | } |
| 3151 | } |
| 3152 | |
| 3153 | void QQuickFlickable::movementEnding() |
| 3154 | { |
| 3155 | movementEnding(hMovementEnding: true, vMovementEnding: true); |
| 3156 | } |
| 3157 | |
| 3158 | void QQuickFlickable::movementEnding(bool hMovementEnding, bool vMovementEnding) |
| 3159 | { |
| 3160 | Q_D(QQuickFlickable); |
| 3161 | |
| 3162 | // emit flicking signals |
| 3163 | const bool wasFlicking = d->hData.flicking || d->vData.flicking; |
| 3164 | if (hMovementEnding && d->hData.flicking) { |
| 3165 | d->hData.flicking = false; |
| 3166 | emit flickingHorizontallyChanged(); |
| 3167 | } |
| 3168 | if (vMovementEnding && d->vData.flicking) { |
| 3169 | d->vData.flicking = false; |
| 3170 | emit flickingVerticallyChanged(); |
| 3171 | } |
| 3172 | if (wasFlicking && (!d->hData.flicking || !d->vData.flicking)) { |
| 3173 | emit flickingChanged(); |
| 3174 | emit flickEnded(); |
| 3175 | } else if (d->hData.flickingWhenDragBegan || d->vData.flickingWhenDragBegan) { |
| 3176 | d->hData.flickingWhenDragBegan = !hMovementEnding; |
| 3177 | d->vData.flickingWhenDragBegan = !vMovementEnding; |
| 3178 | emit flickEnded(); |
| 3179 | } |
| 3180 | |
| 3181 | // emit moving signals |
| 3182 | bool wasMoving = isMoving(); |
| 3183 | if (hMovementEnding && d->hData.moving |
| 3184 | && (!d->pressed && !d->stealMouse)) { |
| 3185 | d->hData.moving = false; |
| 3186 | d->hMoved = false; |
| 3187 | emit movingHorizontallyChanged(); |
| 3188 | } |
| 3189 | if (vMovementEnding && d->vData.moving |
| 3190 | && (!d->pressed && !d->stealMouse)) { |
| 3191 | d->vData.moving = false; |
| 3192 | d->vMoved = false; |
| 3193 | emit movingVerticallyChanged(); |
| 3194 | } |
| 3195 | if (wasMoving && !isMoving()) { |
| 3196 | emit movingChanged(); |
| 3197 | emit movementEnded(); |
| 3198 | #if QT_CONFIG(accessibility) |
| 3199 | if (QAccessible::isActive()) { |
| 3200 | QAccessibleEvent ev(this, QAccessible::ScrollingEnd); |
| 3201 | QAccessible::updateAccessibility(event: &ev); |
| 3202 | } |
| 3203 | #endif |
| 3204 | } |
| 3205 | |
| 3206 | if (hMovementEnding) { |
| 3207 | d->hData.fixingUp = false; |
| 3208 | d->hData.smoothVelocity.setValue(0); |
| 3209 | d->hData.previousDragDelta = 0.0; |
| 3210 | } |
| 3211 | if (vMovementEnding) { |
| 3212 | d->vData.fixingUp = false; |
| 3213 | d->vData.smoothVelocity.setValue(0); |
| 3214 | d->vData.previousDragDelta = 0.0; |
| 3215 | } |
| 3216 | } |
| 3217 | |
| 3218 | void QQuickFlickablePrivate::updateVelocity() |
| 3219 | { |
| 3220 | Q_Q(QQuickFlickable); |
| 3221 | emit q->horizontalVelocityChanged(); |
| 3222 | emit q->verticalVelocityChanged(); |
| 3223 | } |
| 3224 | |
| 3225 | /*! |
| 3226 | \qmlproperty real QtQuick::Flickable::horizontalOvershoot |
| 3227 | \since 5.9 |
| 3228 | |
| 3229 | This property holds the horizontal overshoot, that is, the horizontal distance by |
| 3230 | which the contents has been dragged or flicked past the bounds of the flickable. |
| 3231 | The value is negative when the content is dragged or flicked beyond the beginning, |
| 3232 | and positive when beyond the end; \c 0.0 otherwise. |
| 3233 | |
| 3234 | Whether the values are reported for dragging and/or flicking is determined by |
| 3235 | \l boundsBehavior. The overshoot distance is reported even when \l boundsMovement |
| 3236 | is \c Flickable.StopAtBounds. |
| 3237 | |
| 3238 | \sa verticalOvershoot, boundsBehavior, boundsMovement |
| 3239 | */ |
| 3240 | qreal QQuickFlickable::horizontalOvershoot() const |
| 3241 | { |
| 3242 | Q_D(const QQuickFlickable); |
| 3243 | return d->hData.overshoot; |
| 3244 | } |
| 3245 | |
| 3246 | /*! |
| 3247 | \qmlproperty real QtQuick::Flickable::verticalOvershoot |
| 3248 | \since 5.9 |
| 3249 | |
| 3250 | This property holds the vertical overshoot, that is, the vertical distance by |
| 3251 | which the contents has been dragged or flicked past the bounds of the flickable. |
| 3252 | The value is negative when the content is dragged or flicked beyond the beginning, |
| 3253 | and positive when beyond the end; \c 0.0 otherwise. |
| 3254 | |
| 3255 | Whether the values are reported for dragging and/or flicking is determined by |
| 3256 | \l boundsBehavior. The overshoot distance is reported even when \l boundsMovement |
| 3257 | is \c Flickable.StopAtBounds. |
| 3258 | |
| 3259 | \sa horizontalOvershoot, boundsBehavior, boundsMovement |
| 3260 | */ |
| 3261 | qreal QQuickFlickable::verticalOvershoot() const |
| 3262 | { |
| 3263 | Q_D(const QQuickFlickable); |
| 3264 | return d->vData.overshoot; |
| 3265 | } |
| 3266 | |
| 3267 | /*! |
| 3268 | \qmlproperty enumeration QtQuick::Flickable::boundsMovement |
| 3269 | \since 5.10 |
| 3270 | |
| 3271 | This property holds whether the flickable will give a feeling that the edges of the |
| 3272 | view are soft, rather than a hard physical boundary. |
| 3273 | |
| 3274 | The \c boundsMovement can be one of: |
| 3275 | |
| 3276 | \list |
| 3277 | \li Flickable.StopAtBounds - this allows implementing custom edge effects where the |
| 3278 | contents do not follow drags or flicks beyond the bounds of the flickable. The values |
| 3279 | of \l horizontalOvershoot and \l verticalOvershoot can be utilized to implement custom |
| 3280 | edge effects. |
| 3281 | \li Flickable.FollowBoundsBehavior (default) - whether the contents follow drags or |
| 3282 | flicks beyond the bounds of the flickable is determined by \l boundsBehavior. |
| 3283 | \endlist |
| 3284 | |
| 3285 | The following example keeps the contents within bounds and instead applies a flip |
| 3286 | effect when flicked over horizontal bounds: |
| 3287 | \code |
| 3288 | Flickable { |
| 3289 | id: flickable |
| 3290 | boundsMovement: Flickable.StopAtBounds |
| 3291 | boundsBehavior: Flickable.DragAndOvershootBounds |
| 3292 | transform: Rotation { |
| 3293 | axis { x: 0; y: 1; z: 0 } |
| 3294 | origin.x: flickable.width / 2 |
| 3295 | origin.y: flickable.height / 2 |
| 3296 | angle: Math.min(30, Math.max(-30, flickable.horizontalOvershoot)) |
| 3297 | } |
| 3298 | } |
| 3299 | \endcode |
| 3300 | |
| 3301 | The following example keeps the contents within bounds and instead applies an opacity |
| 3302 | effect when dragged over vertical bounds: |
| 3303 | \code |
| 3304 | Flickable { |
| 3305 | boundsMovement: Flickable.StopAtBounds |
| 3306 | boundsBehavior: Flickable.DragOverBounds |
| 3307 | opacity: Math.max(0.5, 1.0 - Math.abs(verticalOvershoot) / height) |
| 3308 | } |
| 3309 | \endcode |
| 3310 | |
| 3311 | \sa boundsBehavior, verticalOvershoot, horizontalOvershoot |
| 3312 | */ |
| 3313 | QQuickFlickable::BoundsMovement QQuickFlickable::boundsMovement() const |
| 3314 | { |
| 3315 | Q_D(const QQuickFlickable); |
| 3316 | return d->boundsMovement; |
| 3317 | } |
| 3318 | |
| 3319 | void QQuickFlickable::setBoundsMovement(BoundsMovement movement) |
| 3320 | { |
| 3321 | Q_D(QQuickFlickable); |
| 3322 | if (d->boundsMovement == movement) |
| 3323 | return; |
| 3324 | |
| 3325 | d->boundsMovement = movement; |
| 3326 | emit boundsMovementChanged(); |
| 3327 | } |
| 3328 | |
| 3329 | QT_END_NAMESPACE |
| 3330 | |
| 3331 | #include "moc_qquickflickable_p_p.cpp" |
| 3332 | |
| 3333 | #include "moc_qquickflickable_p.cpp" |
| 3334 | |