1 /****************************************************************************
3 ** Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies).
4 ** All rights reserved.
5 ** Contact: Nokia Corporation (qt-info@nokia.com)
7 ** This file is part of the QtDeclarative module of the Qt Toolkit.
9 ** $QT_BEGIN_LICENSE:LGPL$
10 ** GNU Lesser General Public License Usage
11 ** This file may be used under the terms of the GNU Lesser General Public
12 ** License version 2.1 as published by the Free Software Foundation and
13 ** appearing in the file LICENSE.LGPL included in the packaging of this
14 ** file. Please review the following information to ensure the GNU Lesser
15 ** General Public License version 2.1 requirements will be met:
16 ** http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html.
18 ** In addition, as a special exception, Nokia gives you certain additional
19 ** rights. These rights are described in the Nokia Qt LGPL Exception
20 ** version 1.1, included in the file LGPL_EXCEPTION.txt in this package.
22 ** GNU General Public License Usage
23 ** Alternatively, this file may be used under the terms of the GNU General
24 ** Public License version 3.0 as published by the Free Software Foundation
25 ** and appearing in the file LICENSE.GPL included in the packaging of this
26 ** file. Please review the following information to ensure the GNU General
27 ** Public License version 3.0 requirements will be met:
28 ** http://www.gnu.org/copyleft/gpl.html.
31 ** Alternatively, this file may be used in accordance with the terms and
32 ** conditions contained in a signed written agreement between you and Nokia.
40 ****************************************************************************/
42 #include "qsgflickable_p.h"
43 #include "qsgflickable_p_p.h"
44 #include "qsgcanvas.h"
45 #include "qsgcanvas_p.h"
47 #include <QtDeclarative/qdeclarativeinfo.h>
48 #include <QtGui/qgraphicssceneevent.h>
49 #include <QtGui/qapplication.h>
50 #include "qplatformdefs.h"
54 // The maximum number of pixels a flick can overshoot
55 #ifndef QML_FLICK_OVERSHOOT
56 #define QML_FLICK_OVERSHOOT 200
59 // The number of samples to use in calculating the velocity of a flick
60 #ifndef QML_FLICK_SAMPLEBUFFER
61 #define QML_FLICK_SAMPLEBUFFER 3
64 // The number of samples to discard when calculating the flick velocity.
65 // Touch panels often produce inaccurate results as the finger is lifted.
66 #ifndef QML_FLICK_DISCARDSAMPLES
67 #define QML_FLICK_DISCARDSAMPLES 1
70 // The default maximum velocity of a flick.
71 #ifndef QML_FLICK_DEFAULTMAXVELOCITY
72 #define QML_FLICK_DEFAULTMAXVELOCITY 2500
75 // The default deceleration of a flick.
76 #ifndef QML_FLICK_DEFAULTDECELERATION
77 #define QML_FLICK_DEFAULTDECELERATION 1500
80 // How much faster to decelerate when overshooting
81 #ifndef QML_FLICK_OVERSHOOTFRICTION
82 #define QML_FLICK_OVERSHOOTFRICTION 8
85 // FlickThreshold determines how far the "mouse" must have moved
86 // before we perform a flick.
87 static const int FlickThreshold = 20;
89 // RetainGrabVelocity is the maxmimum instantaneous velocity that
90 // will ensure the Flickable retains the grab on consecutive flicks.
91 static const int RetainGrabVelocity = 15;
93 QSGFlickableVisibleArea::QSGFlickableVisibleArea(QSGFlickable *parent)
94 : QObject(parent), flickable(parent), m_xPosition(0.), m_widthRatio(0.)
95 , m_yPosition(0.), m_heightRatio(0.)
99 qreal QSGFlickableVisibleArea::widthRatio() const
104 qreal QSGFlickableVisibleArea::xPosition() const
109 qreal QSGFlickableVisibleArea::heightRatio() const
111 return m_heightRatio;
114 qreal QSGFlickableVisibleArea::yPosition() const
119 void QSGFlickableVisibleArea::updateVisible()
121 QSGFlickablePrivate *p = QSGFlickablePrivate::get(flickable);
123 bool changeX = false;
124 bool changeY = false;
125 bool changeWidth = false;
126 bool changeHeight = false;
129 const qreal viewheight = flickable->height();
130 const qreal maxyextent = -flickable->maxYExtent() + flickable->minYExtent();
131 qreal pagePos = (-p->vData.move.value() + flickable->minYExtent()) / (maxyextent + viewheight);
132 qreal pageSize = viewheight / (maxyextent + viewheight);
134 if (pageSize != m_heightRatio) {
135 m_heightRatio = pageSize;
138 if (pagePos != m_yPosition) {
139 m_yPosition = pagePos;
144 const qreal viewwidth = flickable->width();
145 const qreal maxxextent = -flickable->maxXExtent() + flickable->minXExtent();
146 pagePos = (-p->hData.move.value() + flickable->minXExtent()) / (maxxextent + viewwidth);
147 pageSize = viewwidth / (maxxextent + viewwidth);
149 if (pageSize != m_widthRatio) {
150 m_widthRatio = pageSize;
153 if (pagePos != m_xPosition) {
154 m_xPosition = pagePos;
159 emit xPositionChanged(m_xPosition);
161 emit yPositionChanged(m_yPosition);
163 emit widthRatioChanged(m_widthRatio);
165 emit heightRatioChanged(m_heightRatio);
169 QSGFlickablePrivate::QSGFlickablePrivate()
170 : contentItem(new QSGItem)
171 , hData(this, &QSGFlickablePrivate::setViewportX)
172 , vData(this, &QSGFlickablePrivate::setViewportY)
173 , flickingHorizontally(false), flickingVertically(false)
174 , hMoved(false), vMoved(false)
175 , movingHorizontally(false), movingVertically(false)
176 , stealMouse(false), pressed(false), interactive(true), calcVelocity(false)
177 , deceleration(QML_FLICK_DEFAULTDECELERATION)
178 , maxVelocity(QML_FLICK_DEFAULTMAXVELOCITY), reportedVelocitySmoothing(100)
179 , delayedPressEvent(0), delayedPressTarget(0), pressDelay(0), fixupDuration(400)
180 , fixupMode(Normal), vTime(0), visibleArea(0)
181 , flickableDirection(QSGFlickable::AutoFlickDirection)
182 , boundsBehavior(QSGFlickable::DragAndOvershootBounds)
186 void QSGFlickablePrivate::init()
189 QDeclarative_setParent_noEvent(contentItem, q);
190 contentItem->setParentItem(q);
191 static int timelineUpdatedIdx = -1;
192 static int timelineCompletedIdx = -1;
193 static int flickableTickedIdx = -1;
194 static int flickableMovementEndingIdx = -1;
195 if (timelineUpdatedIdx == -1) {
196 timelineUpdatedIdx = QDeclarativeTimeLine::staticMetaObject.indexOfSignal("updated()");
197 timelineCompletedIdx = QDeclarativeTimeLine::staticMetaObject.indexOfSignal("completed()");
198 flickableTickedIdx = QSGFlickable::staticMetaObject.indexOfSlot("ticked()");
199 flickableMovementEndingIdx = QSGFlickable::staticMetaObject.indexOfSlot("movementEnding()");
201 QMetaObject::connect(&timeline, timelineUpdatedIdx,
202 q, flickableTickedIdx, Qt::DirectConnection);
203 QMetaObject::connect(&timeline, timelineCompletedIdx,
204 q, flickableMovementEndingIdx, Qt::DirectConnection);
205 q->setAcceptedMouseButtons(Qt::LeftButton);
206 q->setFiltersChildMouseEvents(true);
207 QSGItemPrivate *viewportPrivate = QSGItemPrivate::get(contentItem);
208 viewportPrivate->addItemChangeListener(this, QSGItemPrivate::Geometry);
209 lastPosTime.invalidate();
213 Returns the amount to overshoot by given a velocity.
214 Will be roughly in range 0 - size/4
216 qreal QSGFlickablePrivate::overShootDistance(qreal size)
218 if (maxVelocity <= 0)
221 return qMin(qreal(QML_FLICK_OVERSHOOT), size/3);
224 void QSGFlickablePrivate::AxisData::addVelocitySample(qreal v, qreal maxVelocity)
228 else if (v < -maxVelocity)
230 velocityBuffer.append(v);
231 if (velocityBuffer.count() > QML_FLICK_SAMPLEBUFFER)
232 velocityBuffer.remove(0);
235 void QSGFlickablePrivate::AxisData::updateVelocity()
237 if (velocityBuffer.count() > QML_FLICK_DISCARDSAMPLES) {
239 int count = velocityBuffer.count()-QML_FLICK_DISCARDSAMPLES;
240 for (int i = 0; i < count; ++i) {
241 qreal v = velocityBuffer.at(i);
248 void QSGFlickablePrivate::itemGeometryChanged(QSGItem *item, const QRectF &newGeom, const QRectF &oldGeom)
251 if (item == contentItem) {
252 if (newGeom.x() != oldGeom.x())
253 emit q->contentXChanged();
254 if (newGeom.y() != oldGeom.y())
255 emit q->contentYChanged();
259 void QSGFlickablePrivate::flickX(qreal velocity)
262 flick(hData, q->minXExtent(), q->maxXExtent(), q->width(), fixupX_callback, velocity);
265 void QSGFlickablePrivate::flickY(qreal velocity)
268 flick(vData, q->minYExtent(), q->maxYExtent(), q->height(), fixupY_callback, velocity);
271 void QSGFlickablePrivate::flick(AxisData &data, qreal minExtent, qreal maxExtent, qreal,
272 QDeclarativeTimeLineCallback::Callback fixupCallback, qreal velocity)
275 qreal maxDistance = -1;
276 data.fixingUp = false;
277 // -ve velocity means list is moving up
279 maxDistance = qAbs(minExtent - data.move.value());
280 data.flickTarget = minExtent;
282 maxDistance = qAbs(maxExtent - data.move.value());
283 data.flickTarget = maxExtent;
285 if (maxDistance > 0) {
287 if (maxVelocity != -1 && maxVelocity < qAbs(v)) {
293 timeline.reset(data.move);
294 if (boundsBehavior == QSGFlickable::DragAndOvershootBounds)
295 timeline.accel(data.move, v, deceleration);
297 timeline.accel(data.move, v, deceleration, maxDistance);
298 timeline.callback(QDeclarativeTimeLineCallback(&data.move, fixupCallback, this));
299 if (!flickingHorizontally && q->xflick()) {
300 flickingHorizontally = true;
301 emit q->flickingChanged();
302 emit q->flickingHorizontallyChanged();
303 if (!flickingVertically)
304 emit q->flickStarted();
306 if (!flickingVertically && q->yflick()) {
307 flickingVertically = true;
308 emit q->flickingChanged();
309 emit q->flickingVerticallyChanged();
310 if (!flickingHorizontally)
311 emit q->flickStarted();
314 timeline.reset(data.move);
315 fixup(data, minExtent, maxExtent);
319 void QSGFlickablePrivate::fixupY_callback(void *data)
321 ((QSGFlickablePrivate *)data)->fixupY();
324 void QSGFlickablePrivate::fixupX_callback(void *data)
326 ((QSGFlickablePrivate *)data)->fixupX();
329 void QSGFlickablePrivate::fixupX()
332 fixup(hData, q->minXExtent(), q->maxXExtent());
335 void QSGFlickablePrivate::fixupY()
338 fixup(vData, q->minYExtent(), q->maxYExtent());
341 void QSGFlickablePrivate::fixup(AxisData &data, qreal minExtent, qreal maxExtent)
343 if (data.move.value() > minExtent || maxExtent > minExtent) {
344 timeline.reset(data.move);
345 if (data.move.value() != minExtent) {
348 timeline.set(data.move, minExtent);
351 // The target has changed. Don't start from the beginning; just complete the
352 // second half of the animation using the new extent.
353 timeline.move(data.move, minExtent, QEasingCurve(QEasingCurve::OutExpo), 3*fixupDuration/4);
354 data.fixingUp = true;
357 qreal dist = minExtent - data.move;
358 timeline.move(data.move, minExtent - dist/2, QEasingCurve(QEasingCurve::InQuad), fixupDuration/4);
359 timeline.move(data.move, minExtent, QEasingCurve(QEasingCurve::OutExpo), 3*fixupDuration/4);
360 data.fixingUp = true;
364 } else if (data.move.value() < maxExtent) {
365 timeline.reset(data.move);
368 timeline.set(data.move, maxExtent);
371 // The target has changed. Don't start from the beginning; just complete the
372 // second half of the animation using the new extent.
373 timeline.move(data.move, maxExtent, QEasingCurve(QEasingCurve::OutExpo), 3*fixupDuration/4);
374 data.fixingUp = true;
377 qreal dist = maxExtent - data.move;
378 timeline.move(data.move, maxExtent - dist/2, QEasingCurve(QEasingCurve::InQuad), fixupDuration/4);
379 timeline.move(data.move, maxExtent, QEasingCurve(QEasingCurve::OutExpo), 3*fixupDuration/4);
380 data.fixingUp = true;
384 data.inOvershoot = false;
386 vTime = timeline.time();
389 void QSGFlickablePrivate::updateBeginningEnd()
392 bool atBoundaryChange = false;
395 const int maxyextent = int(-q->maxYExtent());
396 const qreal ypos = -vData.move.value();
397 bool atBeginning = (ypos <= -q->minYExtent());
398 bool atEnd = (maxyextent <= ypos);
400 if (atBeginning != vData.atBeginning) {
401 vData.atBeginning = atBeginning;
402 atBoundaryChange = true;
404 if (atEnd != vData.atEnd) {
406 atBoundaryChange = true;
410 const int maxxextent = int(-q->maxXExtent());
411 const qreal xpos = -hData.move.value();
412 atBeginning = (xpos <= -q->minXExtent());
413 atEnd = (maxxextent <= xpos);
415 if (atBeginning != hData.atBeginning) {
416 hData.atBeginning = atBeginning;
417 atBoundaryChange = true;
419 if (atEnd != hData.atEnd) {
421 atBoundaryChange = true;
424 if (atBoundaryChange)
425 emit q->isAtBoundaryChanged();
428 visibleArea->updateVisible();
431 QSGFlickable::QSGFlickable(QSGItem *parent)
432 : QSGItem(*(new QSGFlickablePrivate), parent)
438 QSGFlickable::QSGFlickable(QSGFlickablePrivate &dd, QSGItem *parent)
439 : QSGItem(dd, parent)
445 QSGFlickable::~QSGFlickable()
449 qreal QSGFlickable::contentX() const
451 Q_D(const QSGFlickable);
452 return -d->contentItem->x();
455 void QSGFlickable::setContentX(qreal pos)
458 d->timeline.reset(d->hData.move);
459 d->vTime = d->timeline.time();
461 if (-pos != d->hData.move.value()) {
462 d->hData.move.setValue(-pos);
467 qreal QSGFlickable::contentY() const
469 Q_D(const QSGFlickable);
470 return -d->contentItem->y();
473 void QSGFlickable::setContentY(qreal pos)
476 d->timeline.reset(d->vData.move);
477 d->vTime = d->timeline.time();
479 if (-pos != d->vData.move.value()) {
480 d->vData.move.setValue(-pos);
485 bool QSGFlickable::isInteractive() const
487 Q_D(const QSGFlickable);
488 return d->interactive;
491 void QSGFlickable::setInteractive(bool interactive)
494 if (interactive != d->interactive) {
495 d->interactive = interactive;
496 if (!interactive && (d->flickingHorizontally || d->flickingVertically)) {
498 d->vTime = d->timeline.time();
499 d->flickingHorizontally = false;
500 d->flickingVertically = false;
501 emit flickingChanged();
502 emit flickingHorizontallyChanged();
503 emit flickingVerticallyChanged();
506 emit interactiveChanged();
510 qreal QSGFlickable::horizontalVelocity() const
512 Q_D(const QSGFlickable);
513 return d->hData.smoothVelocity.value();
516 qreal QSGFlickable::verticalVelocity() const
518 Q_D(const QSGFlickable);
519 return d->vData.smoothVelocity.value();
522 bool QSGFlickable::isAtXEnd() const
524 Q_D(const QSGFlickable);
525 return d->hData.atEnd;
528 bool QSGFlickable::isAtXBeginning() const
530 Q_D(const QSGFlickable);
531 return d->hData.atBeginning;
534 bool QSGFlickable::isAtYEnd() const
536 Q_D(const QSGFlickable);
537 return d->vData.atEnd;
540 bool QSGFlickable::isAtYBeginning() const
542 Q_D(const QSGFlickable);
543 return d->vData.atBeginning;
546 void QSGFlickable::ticked()
551 QSGItem *QSGFlickable::contentItem()
554 return d->contentItem;
557 QSGFlickableVisibleArea *QSGFlickable::visibleArea()
561 d->visibleArea = new QSGFlickableVisibleArea(this);
562 return d->visibleArea;
565 QSGFlickable::FlickableDirection QSGFlickable::flickableDirection() const
567 Q_D(const QSGFlickable);
568 return d->flickableDirection;
571 void QSGFlickable::setFlickableDirection(FlickableDirection direction)
574 if (direction != d->flickableDirection) {
575 d->flickableDirection = direction;
576 emit flickableDirectionChanged();
580 void QSGFlickablePrivate::handleMousePressEvent(QGraphicsSceneMouseEvent *event)
583 if (interactive && timeline.isActive()
584 && (qAbs(hData.smoothVelocity.value()) > RetainGrabVelocity
585 || qAbs(vData.smoothVelocity.value()) > RetainGrabVelocity)) {
586 stealMouse = true; // If we've been flicked then steal the click.
590 q->setKeepMouseGrab(stealMouse);
595 hData.dragMinBound = q->minXExtent();
596 vData.dragMinBound = q->minYExtent();
597 hData.dragMaxBound = q->maxXExtent();
598 vData.dragMaxBound = q->maxYExtent();
601 QSGItemPrivate::start(lastPosTime);
602 pressPos = event->pos();
603 hData.pressPos = hData.move.value();
604 vData.pressPos = vData.move.value();
605 flickingHorizontally = false;
606 flickingVertically = false;
607 QSGItemPrivate::start(pressTime);
608 QSGItemPrivate::start(velocityTime);
611 void QSGFlickablePrivate::handleMouseMoveEvent(QGraphicsSceneMouseEvent *event)
614 if (!interactive || !lastPosTime.isValid())
616 bool rejectY = false;
617 bool rejectX = false;
619 bool stealY = stealMouse;
620 bool stealX = stealMouse;
623 int dy = int(event->pos().y() - pressPos.y());
624 if (qAbs(dy) > QApplication::startDragDistance() || QSGItemPrivate::elapsed(pressTime) > 200) {
626 vData.dragStartOffset = dy;
627 qreal newY = dy + vData.pressPos - vData.dragStartOffset;
628 const qreal minY = vData.dragMinBound;
629 const qreal maxY = vData.dragMaxBound;
631 newY = minY + (newY - minY) / 2;
632 if (newY < maxY && maxY - minY <= 0)
633 newY = maxY + (newY - maxY) / 2;
634 if (boundsBehavior == QSGFlickable::StopAtBounds && (newY > minY || newY < maxY)) {
645 if (!rejectY && stealMouse) {
646 vData.move.setValue(qRound(newY));
649 if (qAbs(dy) > QApplication::startDragDistance())
655 int dx = int(event->pos().x() - pressPos.x());
656 if (qAbs(dx) > QApplication::startDragDistance() || QSGItemPrivate::elapsed(pressTime) > 200) {
658 hData.dragStartOffset = dx;
659 qreal newX = dx + hData.pressPos - hData.dragStartOffset;
660 const qreal minX = hData.dragMinBound;
661 const qreal maxX = hData.dragMaxBound;
663 newX = minX + (newX - minX) / 2;
664 if (newX < maxX && maxX - minX <= 0)
665 newX = maxX + (newX - maxX) / 2;
666 if (boundsBehavior == QSGFlickable::StopAtBounds && (newX > minX || newX < maxX)) {
677 if (!rejectX && stealMouse) {
678 hData.move.setValue(qRound(newX));
682 if (qAbs(dx) > QApplication::startDragDistance())
687 stealMouse = stealX || stealY;
689 q->setKeepMouseGrab(true);
692 vData.velocityBuffer.clear();
696 hData.velocityBuffer.clear();
700 if (hMoved || vMoved) {
701 q->movementStarting();
705 if (!lastPos.isNull()) {
706 qreal elapsed = qreal(QSGItemPrivate::elapsed(lastPosTime)) / 1000.;
709 QSGItemPrivate::restart(lastPosTime);
710 qreal dy = event->pos().y()-lastPos.y();
711 if (q->yflick() && !rejectY)
712 vData.addVelocitySample(dy/elapsed, maxVelocity);
713 qreal dx = event->pos().x()-lastPos.x();
714 if (q->xflick() && !rejectX)
715 hData.addVelocitySample(dx/elapsed, maxVelocity);
718 lastPos = event->pos();
721 void QSGFlickablePrivate::handleMouseReleaseEvent(QGraphicsSceneMouseEvent *event)
725 q->setKeepMouseGrab(false);
727 if (!lastPosTime.isValid())
730 // if we drag then pause before release we should not cause a flick.
731 if (QSGItemPrivate::elapsed(lastPosTime) < 100) {
732 vData.updateVelocity();
733 hData.updateVelocity();
735 hData.velocity = 0.0;
736 vData.velocity = 0.0;
739 vTime = timeline.time();
741 qreal velocity = vData.velocity;
742 if (vData.atBeginning || vData.atEnd)
744 if (qAbs(velocity) > MinimumFlickVelocity && qAbs(event->pos().y() - pressPos.y()) > FlickThreshold)
749 velocity = hData.velocity;
750 if (hData.atBeginning || hData.atEnd)
752 if (qAbs(velocity) > MinimumFlickVelocity && qAbs(event->pos().x() - pressPos.x()) > FlickThreshold)
757 if (!timeline.isActive())
761 void QSGFlickable::mousePressEvent(QGraphicsSceneMouseEvent *event)
764 if (d->interactive) {
766 d->handleMousePressEvent(event);
769 QSGItem::mousePressEvent(event);
773 void QSGFlickable::mouseMoveEvent(QGraphicsSceneMouseEvent *event)
776 if (d->interactive) {
777 d->handleMouseMoveEvent(event);
780 QSGItem::mouseMoveEvent(event);
784 void QSGFlickable::mouseReleaseEvent(QGraphicsSceneMouseEvent *event)
787 if (d->interactive) {
788 d->clearDelayedPress();
789 d->handleMouseReleaseEvent(event);
793 QSGItem::mouseReleaseEvent(event);
797 void QSGFlickable::wheelEvent(QWheelEvent *event)
800 if (!d->interactive) {
801 QSGItem::wheelEvent(event);
802 } else if (yflick() && event->orientation() == Qt::Vertical) {
804 if (event->delta() > 0 && contentY() > -minYExtent()) {
805 d->vData.velocity = qMax(event->delta()*2 - d->vData.smoothVelocity.value(), qreal(d->maxVelocity/4));
807 } else if (event->delta() < 0 && contentY() < -maxYExtent()) {
808 d->vData.velocity = qMin(event->delta()*2 - d->vData.smoothVelocity.value(), qreal(-d->maxVelocity/4));
812 d->flickingVertically = false;
813 d->flickY(d->vData.velocity);
814 if (d->flickingVertically) {
820 } else if (xflick() && event->orientation() == Qt::Horizontal) {
822 if (event->delta() > 0 && contentX() > -minXExtent()) {
823 d->hData.velocity = qMax(event->delta()*2 - d->hData.smoothVelocity.value(), qreal(d->maxVelocity/4));
825 } else if (event->delta() < 0 && contentX() < -maxXExtent()) {
826 d->hData.velocity = qMin(event->delta()*2 - d->hData.smoothVelocity.value(), qreal(-d->maxVelocity/4));
830 d->flickingHorizontally = false;
831 d->flickX(d->hData.velocity);
832 if (d->flickingHorizontally) {
839 QSGItem::wheelEvent(event);
843 bool QSGFlickablePrivate::isOutermostPressDelay() const
845 Q_Q(const QSGFlickable);
846 QSGItem *item = q->parentItem();
848 QSGFlickable *flick = qobject_cast<QSGFlickable*>(item);
849 if (flick && flick->pressDelay() > 0 && flick->isInteractive())
851 item = item->parentItem();
857 void QSGFlickablePrivate::captureDelayedPress(QGraphicsSceneMouseEvent *event)
860 if (!q->canvas() || pressDelay <= 0)
862 if (!isOutermostPressDelay())
864 delayedPressTarget = q->canvas()->mouseGrabberItem();
865 delayedPressEvent = new QGraphicsSceneMouseEvent(event->type());
866 delayedPressEvent->setAccepted(false);
867 for (int i = 0x1; i <= 0x10; i <<= 1) {
868 if (event->buttons() & i) {
869 Qt::MouseButton button = Qt::MouseButton(i);
870 delayedPressEvent->setButtonDownPos(button, event->buttonDownPos(button));
871 delayedPressEvent->setButtonDownScenePos(button, event->buttonDownScenePos(button));
872 delayedPressEvent->setButtonDownScreenPos(button, event->buttonDownScreenPos(button));
875 delayedPressEvent->setButtons(event->buttons());
876 delayedPressEvent->setButton(event->button());
877 delayedPressEvent->setPos(event->pos());
878 delayedPressEvent->setScenePos(event->scenePos());
879 delayedPressEvent->setScreenPos(event->screenPos());
880 delayedPressEvent->setLastPos(event->lastPos());
881 delayedPressEvent->setLastScenePos(event->lastScenePos());
882 delayedPressEvent->setLastScreenPos(event->lastScreenPos());
883 delayedPressEvent->setModifiers(event->modifiers());
884 delayedPressTimer.start(pressDelay, q);
887 void QSGFlickablePrivate::clearDelayedPress()
889 if (delayedPressEvent) {
890 delayedPressTimer.stop();
891 delete delayedPressEvent;
892 delayedPressEvent = 0;
896 void QSGFlickablePrivate::setViewportX(qreal x)
898 contentItem->setX(x);
901 void QSGFlickablePrivate::setViewportY(qreal y)
903 contentItem->setY(y);
906 void QSGFlickable::timerEvent(QTimerEvent *event)
909 if (event->timerId() == d->delayedPressTimer.timerId()) {
910 d->delayedPressTimer.stop();
911 if (d->delayedPressEvent) {
912 QSGItem *grabber = canvas() ? canvas()->mouseGrabberItem() : 0;
913 if (!grabber || grabber != this) {
914 // We replay the mouse press but the grabber we had might not be interessted by the event (e.g. overlay)
915 // so we reset the grabber
916 if (canvas()->mouseGrabberItem() == d->delayedPressTarget)
917 d->delayedPressTarget->ungrabMouse();
918 // Use the event handler that will take care of finding the proper item to propagate the event
919 QSGCanvasPrivate::get(canvas())->deliverMouseEvent(d->delayedPressEvent);
921 delete d->delayedPressEvent;
922 d->delayedPressEvent = 0;
927 qreal QSGFlickable::minYExtent() const
932 qreal QSGFlickable::minXExtent() const
938 qreal QSGFlickable::maxXExtent() const
940 return width() - vWidth();
943 qreal QSGFlickable::maxYExtent() const
945 return height() - vHeight();
948 void QSGFlickable::viewportMoved()
952 qreal prevX = d->lastFlickablePosition.x();
953 qreal prevY = d->lastFlickablePosition.y();
954 d->velocityTimeline.clear();
955 if (d->pressed || d->calcVelocity) {
956 int elapsed = QSGItemPrivate::restart(d->velocityTime);
958 qreal horizontalVelocity = (prevX - d->hData.move.value()) * 1000 / elapsed;
959 qreal verticalVelocity = (prevY - d->vData.move.value()) * 1000 / elapsed;
960 d->velocityTimeline.move(d->hData.smoothVelocity, horizontalVelocity, d->reportedVelocitySmoothing);
961 d->velocityTimeline.move(d->hData.smoothVelocity, 0, d->reportedVelocitySmoothing);
962 d->velocityTimeline.move(d->vData.smoothVelocity, verticalVelocity, d->reportedVelocitySmoothing);
963 d->velocityTimeline.move(d->vData.smoothVelocity, 0, d->reportedVelocitySmoothing);
966 if (d->timeline.time() > d->vTime) {
967 qreal horizontalVelocity = (prevX - d->hData.move.value()) * 1000 / (d->timeline.time() - d->vTime);
968 qreal verticalVelocity = (prevY - d->vData.move.value()) * 1000 / (d->timeline.time() - d->vTime);
969 d->hData.smoothVelocity.setValue(horizontalVelocity);
970 d->vData.smoothVelocity.setValue(verticalVelocity);
974 if (!d->vData.inOvershoot && !d->vData.fixingUp && d->flickingVertically
975 && (d->vData.move.value() > minYExtent() || d->vData.move.value() < maxYExtent())
976 && qAbs(d->vData.smoothVelocity.value()) > 100) {
977 // Increase deceleration if we've passed a bound
978 d->vData.inOvershoot = true;
979 qreal maxDistance = d->overShootDistance(height());
980 d->timeline.reset(d->vData.move);
981 d->timeline.accel(d->vData.move, -d->vData.smoothVelocity.value(), d->deceleration*QML_FLICK_OVERSHOOTFRICTION, maxDistance);
982 d->timeline.callback(QDeclarativeTimeLineCallback(&d->vData.move, d->fixupY_callback, d));
984 if (!d->hData.inOvershoot && !d->hData.fixingUp && d->flickingHorizontally
985 && (d->hData.move.value() > minXExtent() || d->hData.move.value() < maxXExtent())
986 && qAbs(d->hData.smoothVelocity.value()) > 100) {
987 // Increase deceleration if we've passed a bound
988 d->hData.inOvershoot = true;
989 qreal maxDistance = d->overShootDistance(width());
990 d->timeline.reset(d->hData.move);
991 d->timeline.accel(d->hData.move, -d->hData.smoothVelocity.value(), d->deceleration*QML_FLICK_OVERSHOOTFRICTION, maxDistance);
992 d->timeline.callback(QDeclarativeTimeLineCallback(&d->hData.move, d->fixupX_callback, d));
995 d->lastFlickablePosition = QPointF(d->hData.move.value(), d->vData.move.value());
997 d->vTime = d->timeline.time();
998 d->updateBeginningEnd();
1001 void QSGFlickable::geometryChanged(const QRectF &newGeometry,
1002 const QRectF &oldGeometry)
1005 QSGItem::geometryChanged(newGeometry, oldGeometry);
1007 bool changed = false;
1008 if (newGeometry.width() != oldGeometry.width()) {
1011 if (d->hData.viewSize < 0) {
1012 d->contentItem->setWidth(width());
1013 emit contentWidthChanged();
1015 // Make sure that we're entirely in view.
1016 if (!d->pressed && !d->movingHorizontally && !d->movingVertically) {
1017 d->fixupMode = QSGFlickablePrivate::Immediate;
1021 if (newGeometry.height() != oldGeometry.height()) {
1024 if (d->vData.viewSize < 0) {
1025 d->contentItem->setHeight(height());
1026 emit contentHeightChanged();
1028 // Make sure that we're entirely in view.
1029 if (!d->pressed && !d->movingHorizontally && !d->movingVertically) {
1030 d->fixupMode = QSGFlickablePrivate::Immediate;
1036 d->updateBeginningEnd();
1039 void QSGFlickable::cancelFlick()
1042 d->timeline.reset(d->hData.move);
1043 d->timeline.reset(d->vData.move);
1047 void QSGFlickablePrivate::data_append(QDeclarativeListProperty<QObject> *prop, QObject *o)
1049 QSGItem *i = qobject_cast<QSGItem *>(o);
1051 i->setParentItem(static_cast<QSGFlickablePrivate*>(prop->data)->contentItem);
1053 o->setParent(prop->object); // XXX todo - do we want this?
1057 int QSGFlickablePrivate::data_count(QDeclarativeListProperty<QObject> *)
1063 QObject *QSGFlickablePrivate::data_at(QDeclarativeListProperty<QObject> *, int)
1069 void QSGFlickablePrivate::data_clear(QDeclarativeListProperty<QObject> *)
1074 QDeclarativeListProperty<QObject> QSGFlickable::flickableData()
1077 return QDeclarativeListProperty<QObject>(this, (void *)d, QSGFlickablePrivate::data_append,
1078 QSGFlickablePrivate::data_count,
1079 QSGFlickablePrivate::data_at,
1080 QSGFlickablePrivate::data_clear);
1083 QDeclarativeListProperty<QSGItem> QSGFlickable::flickableChildren()
1086 return QSGItemPrivate::get(d->contentItem)->children();
1089 QSGFlickable::BoundsBehavior QSGFlickable::boundsBehavior() const
1091 Q_D(const QSGFlickable);
1092 return d->boundsBehavior;
1095 void QSGFlickable::setBoundsBehavior(BoundsBehavior b)
1098 if (b == d->boundsBehavior)
1100 d->boundsBehavior = b;
1101 emit boundsBehaviorChanged();
1104 qreal QSGFlickable::contentWidth() const
1106 Q_D(const QSGFlickable);
1107 return d->hData.viewSize;
1110 void QSGFlickable::setContentWidth(qreal w)
1113 if (d->hData.viewSize == w)
1115 d->hData.viewSize = w;
1117 d->contentItem->setWidth(width());
1119 d->contentItem->setWidth(w);
1120 // Make sure that we're entirely in view.
1121 if (!d->pressed && !d->movingHorizontally && !d->movingVertically) {
1122 d->fixupMode = QSGFlickablePrivate::Immediate;
1124 } else if (!d->pressed && d->hData.fixingUp) {
1125 d->fixupMode = QSGFlickablePrivate::ExtentChanged;
1128 emit contentWidthChanged();
1129 d->updateBeginningEnd();
1132 qreal QSGFlickable::contentHeight() const
1134 Q_D(const QSGFlickable);
1135 return d->vData.viewSize;
1138 void QSGFlickable::setContentHeight(qreal h)
1141 if (d->vData.viewSize == h)
1143 d->vData.viewSize = h;
1145 d->contentItem->setHeight(height());
1147 d->contentItem->setHeight(h);
1148 // Make sure that we're entirely in view.
1149 if (!d->pressed && !d->movingHorizontally && !d->movingVertically) {
1150 d->fixupMode = QSGFlickablePrivate::Immediate;
1152 } else if (!d->pressed && d->vData.fixingUp) {
1153 d->fixupMode = QSGFlickablePrivate::ExtentChanged;
1156 emit contentHeightChanged();
1157 d->updateBeginningEnd();
1160 void QSGFlickable::resizeContent(qreal w, qreal h, QPointF center)
1163 if (w != d->hData.viewSize) {
1164 qreal oldSize = d->hData.viewSize;
1165 d->hData.viewSize = w;
1166 d->contentItem->setWidth(w);
1167 emit contentWidthChanged();
1168 if (center.x() != 0) {
1169 qreal pos = center.x() * w / oldSize;
1170 setContentX(contentX() + pos - center.x());
1173 if (h != d->vData.viewSize) {
1174 qreal oldSize = d->vData.viewSize;
1175 d->vData.viewSize = h;
1176 d->contentItem->setHeight(h);
1177 emit contentHeightChanged();
1178 if (center.y() != 0) {
1179 qreal pos = center.y() * h / oldSize;
1180 setContentY(contentY() + pos - center.y());
1183 d->updateBeginningEnd();
1186 void QSGFlickable::returnToBounds()
1193 qreal QSGFlickable::vWidth() const
1195 Q_D(const QSGFlickable);
1196 if (d->hData.viewSize < 0)
1199 return d->hData.viewSize;
1202 qreal QSGFlickable::vHeight() const
1204 Q_D(const QSGFlickable);
1205 if (d->vData.viewSize < 0)
1208 return d->vData.viewSize;
1211 bool QSGFlickable::xflick() const
1213 Q_D(const QSGFlickable);
1214 if (d->flickableDirection == QSGFlickable::AutoFlickDirection)
1215 return vWidth() != width();
1216 return d->flickableDirection & QSGFlickable::HorizontalFlick;
1219 bool QSGFlickable::yflick() const
1221 Q_D(const QSGFlickable);
1222 if (d->flickableDirection == QSGFlickable::AutoFlickDirection)
1223 return vHeight() != height();
1224 return d->flickableDirection & QSGFlickable::VerticalFlick;
1227 void QSGFlickable::mouseUngrabEvent()
1231 // if our mouse grab has been removed (probably by another Flickable),
1234 d->stealMouse = false;
1235 setKeepMouseGrab(false);
1239 bool QSGFlickable::sendMouseEvent(QGraphicsSceneMouseEvent *event)
1242 QGraphicsSceneMouseEvent mouseEvent(event->type());
1243 QRectF myRect = mapRectToScene(QRectF(0, 0, width(), height()));
1245 QSGCanvas *c = canvas();
1246 QSGItem *grabber = c ? c->mouseGrabberItem() : 0;
1247 bool disabledItem = grabber && !grabber->isEnabled();
1248 bool stealThisEvent = d->stealMouse;
1249 if ((stealThisEvent || myRect.contains(event->scenePos().toPoint())) && (!grabber || !grabber->keepMouseGrab() || disabledItem)) {
1250 mouseEvent.setAccepted(false);
1251 for (int i = 0x1; i <= 0x10; i <<= 1) {
1252 if (event->buttons() & i) {
1253 Qt::MouseButton button = Qt::MouseButton(i);
1254 mouseEvent.setButtonDownPos(button, mapFromScene(event->buttonDownPos(button)));
1257 mouseEvent.setScenePos(event->scenePos());
1258 mouseEvent.setLastScenePos(event->lastScenePos());
1259 mouseEvent.setPos(mapFromScene(event->scenePos()));
1260 mouseEvent.setLastPos(mapFromScene(event->lastScenePos()));
1262 switch(mouseEvent.type()) {
1263 case QEvent::GraphicsSceneMouseMove:
1264 d->handleMouseMoveEvent(&mouseEvent);
1266 case QEvent::GraphicsSceneMousePress:
1267 if (d->pressed) // we are already pressed - this is a delayed replay
1270 d->handleMousePressEvent(&mouseEvent);
1271 d->captureDelayedPress(event);
1272 stealThisEvent = d->stealMouse; // Update stealThisEvent in case changed by function call above
1274 case QEvent::GraphicsSceneMouseRelease:
1275 if (d->delayedPressEvent) {
1276 // We replay the mouse press but the grabber we had might not be interessted by the event (e.g. overlay)
1277 // so we reset the grabber
1278 if (c->mouseGrabberItem() == d->delayedPressTarget)
1279 d->delayedPressTarget->ungrabMouse();
1280 //Use the event handler that will take care of finding the proper item to propagate the event
1281 QSGCanvasPrivate::get(canvas())->deliverMouseEvent(d->delayedPressEvent);
1282 d->clearDelayedPress();
1283 // We send the release
1284 canvas()->sendEvent(c->mouseGrabberItem(), event);
1285 // And the event has been consumed
1286 d->stealMouse = false;
1290 d->handleMouseReleaseEvent(&mouseEvent);
1295 grabber = qobject_cast<QSGItem*>(c->mouseGrabberItem());
1296 if ((grabber && stealThisEvent && !grabber->keepMouseGrab() && grabber != this) || disabledItem) {
1297 d->clearDelayedPress();
1301 return stealThisEvent || d->delayedPressEvent || disabledItem;
1302 } else if (d->lastPosTime.isValid()) {
1303 d->lastPosTime.invalidate();
1305 if (mouseEvent.type() == QEvent::GraphicsSceneMouseRelease) {
1306 d->clearDelayedPress();
1307 d->stealMouse = false;
1314 bool QSGFlickable::childMouseEventFilter(QSGItem *i, QEvent *e)
1317 if (!isVisible() || !d->interactive)
1318 return QSGItem::childMouseEventFilter(i, e);
1319 switch (e->type()) {
1320 case QEvent::GraphicsSceneMousePress:
1321 case QEvent::GraphicsSceneMouseMove:
1322 case QEvent::GraphicsSceneMouseRelease:
1323 return sendMouseEvent(static_cast<QGraphicsSceneMouseEvent *>(e));
1328 return QSGItem::childMouseEventFilter(i, e);
1331 qreal QSGFlickable::maximumFlickVelocity() const
1333 Q_D(const QSGFlickable);
1334 return d->maxVelocity;
1337 void QSGFlickable::setMaximumFlickVelocity(qreal v)
1340 if (v == d->maxVelocity)
1343 emit maximumFlickVelocityChanged();
1346 qreal QSGFlickable::flickDeceleration() const
1348 Q_D(const QSGFlickable);
1349 return d->deceleration;
1352 void QSGFlickable::setFlickDeceleration(qreal deceleration)
1355 if (deceleration == d->deceleration)
1357 d->deceleration = deceleration;
1358 emit flickDecelerationChanged();
1361 bool QSGFlickable::isFlicking() const
1363 Q_D(const QSGFlickable);
1364 return d->flickingHorizontally || d->flickingVertically;
1367 bool QSGFlickable::isFlickingHorizontally() const
1369 Q_D(const QSGFlickable);
1370 return d->flickingHorizontally;
1373 bool QSGFlickable::isFlickingVertically() const
1375 Q_D(const QSGFlickable);
1376 return d->flickingVertically;
1379 int QSGFlickable::pressDelay() const
1381 Q_D(const QSGFlickable);
1382 return d->pressDelay;
1385 void QSGFlickable::setPressDelay(int delay)
1388 if (d->pressDelay == delay)
1390 d->pressDelay = delay;
1391 emit pressDelayChanged();
1395 bool QSGFlickable::isMoving() const
1397 Q_D(const QSGFlickable);
1398 return d->movingHorizontally || d->movingVertically;
1401 bool QSGFlickable::isMovingHorizontally() const
1403 Q_D(const QSGFlickable);
1404 return d->movingHorizontally;
1407 bool QSGFlickable::isMovingVertically() const
1409 Q_D(const QSGFlickable);
1410 return d->movingVertically;
1413 void QSGFlickable::movementStarting()
1416 if (d->hMoved && !d->movingHorizontally) {
1417 d->movingHorizontally = true;
1418 emit movingChanged();
1419 emit movingHorizontallyChanged();
1420 if (!d->movingVertically)
1421 emit movementStarted();
1423 else if (d->vMoved && !d->movingVertically) {
1424 d->movingVertically = true;
1425 emit movingChanged();
1426 emit movingVerticallyChanged();
1427 if (!d->movingHorizontally)
1428 emit movementStarted();
1432 void QSGFlickable::movementEnding()
1437 d->hData.smoothVelocity.setValue(0);
1438 d->vData.smoothVelocity.setValue(0);
1441 void QSGFlickable::movementXEnding()
1444 if (d->flickingHorizontally) {
1445 d->flickingHorizontally = false;
1446 emit flickingChanged();
1447 emit flickingHorizontallyChanged();
1448 if (!d->flickingVertically)
1451 if (!d->pressed && !d->stealMouse) {
1452 if (d->movingHorizontally) {
1453 d->movingHorizontally = false;
1455 emit movingChanged();
1456 emit movingHorizontallyChanged();
1457 if (!d->movingVertically)
1458 emit movementEnded();
1461 d->hData.fixingUp = false;
1464 void QSGFlickable::movementYEnding()
1467 if (d->flickingVertically) {
1468 d->flickingVertically = false;
1469 emit flickingChanged();
1470 emit flickingVerticallyChanged();
1471 if (!d->flickingHorizontally)
1474 if (!d->pressed && !d->stealMouse) {
1475 if (d->movingVertically) {
1476 d->movingVertically = false;
1478 emit movingChanged();
1479 emit movingVerticallyChanged();
1480 if (!d->movingHorizontally)
1481 emit movementEnded();
1484 d->vData.fixingUp = false;
1487 void QSGFlickablePrivate::updateVelocity()
1490 emit q->horizontalVelocityChanged();
1491 emit q->verticalVelocityChanged();