Skip to content

Commit

Permalink
Flicking a pathview with large delegate spacing is inconsistent
Browse files Browse the repository at this point in the history
The deceleration is inconsistent and dragging slowly is jerky.
This was largely due to the poor resolution of the path points.
pointAt() now interpolates, and the dragging logic is more
accurate.  Also removed the rounding of item positioning so
that side-by-side items don't bounce around.

Task-number: QTBUG-24312
Change-Id: I956aff0b83c3c1211d5657159c3de1e4ef0b5171
Reviewed-by: Alan Alpert <alan.alpert@nokia.com>
  • Loading branch information
Martin Jones authored and Qt by Nokia committed Feb 21, 2012
1 parent a37f19f commit b672f19
Show file tree
Hide file tree
Showing 4 changed files with 94 additions and 24 deletions.
75 changes: 64 additions & 11 deletions src/quick/items/qquickpathview.cpp
Expand Up @@ -55,6 +55,17 @@
#include <QtCore/qmath.h>
#include <math.h>

// The number of samples to use in calculating the velocity of a flick
#ifndef QML_FLICK_SAMPLEBUFFER
#define QML_FLICK_SAMPLEBUFFER 3
#endif

// The number of samples to discard when calculating the flick velocity.
// Touch panels often produce inaccurate results as the finger is lifted.
#ifndef QML_FLICK_DISCARDSAMPLES
#define QML_FLICK_DISCARDSAMPLES 1
#endif

QT_BEGIN_NAMESPACE

inline qreal qmlMod(qreal x, qreal y)
Expand Down Expand Up @@ -377,8 +388,8 @@ void QQuickPathViewPrivate::updateItem(QQuickItem *item, qreal percent)
att->setValue(attr.toUtf8(), path->attributeAt(attr, percent));
}
QPointF pf = path->pointAt(percent);
item->setX(qRound(pf.x() - item->width()/2));
item->setY(qRound(pf.y() - item->height()/2));
item->setX(pf.x() - item->width()/2);
item->setY(pf.y() - item->height()/2);
}

void QQuickPathViewPrivate::regenerate()
Expand Down Expand Up @@ -1118,12 +1129,29 @@ void QQuickPathView::setPathItemCount(int i)

QPointF QQuickPathViewPrivate::pointNear(const QPointF &point, qreal *nearPercent) const
{
//XXX maybe do recursively at increasing resolution.
qreal samples = qMin(path->path().length()/5, qreal(500.0));
qreal res = path->path().length()/samples;

qreal mindist = 1e10; // big number
QPointF nearPoint = path->pointAt(0);
qreal nearPc = 0;
for (qreal i=1; i < 1000; i++) {
QPointF pt = path->pointAt(i/1000.0);

// get rough pos
for (qreal i=1; i < samples; i++) {
QPointF pt = path->pointAt(i/samples);
QPointF diff = pt - point;
qreal dist = diff.x()*diff.x() + diff.y()*diff.y();
if (dist < mindist) {
nearPoint = pt;
nearPc = i;
mindist = dist;
}
}

// now refine
qreal approxPc = nearPc;
for (qreal i = approxPc-1.0; i < approxPc+1.0; i += 1/(2*res)) {
QPointF pt = path->pointAt(i/samples);
QPointF diff = pt - point;
qreal dist = diff.x()*diff.x() + diff.y()*diff.y();
if (dist < mindist) {
Expand All @@ -1134,11 +1162,32 @@ QPointF QQuickPathViewPrivate::pointNear(const QPointF &point, qreal *nearPercen
}

if (nearPercent)
*nearPercent = nearPc / 1000.0;
*nearPercent = nearPc / samples;

return nearPoint;
}

void QQuickPathViewPrivate::addVelocitySample(qreal v)
{
velocityBuffer.append(v);
if (velocityBuffer.count() > QML_FLICK_SAMPLEBUFFER)
velocityBuffer.remove(0);
}

qreal QQuickPathViewPrivate::calcVelocity() const
{
qreal velocity = 0;
if (velocityBuffer.count() > QML_FLICK_DISCARDSAMPLES) {
int count = velocityBuffer.count()-QML_FLICK_DISCARDSAMPLES;
for (int i = 0; i < count; ++i) {
qreal v = velocityBuffer.at(i);
velocity += v;
}
velocity /= count;
}
return velocity;
}

void QQuickPathView::mousePressEvent(QMouseEvent *event)
{
Q_D(QQuickPathView);
Expand All @@ -1155,6 +1204,7 @@ void QQuickPathViewPrivate::handleMousePressEvent(QMouseEvent *event)
Q_Q(QQuickPathView);
if (!interactive || !items.count())
return;
velocityBuffer.clear();
QPointF scenePoint = q->mapToScene(event->localPos());
int idx = 0;
for (; idx < items.count(); ++idx) {
Expand Down Expand Up @@ -1227,6 +1277,7 @@ void QQuickPathViewPrivate::handleMouseMoveEvent(QMouseEvent *event)
lastElapsed = QQuickItemPrivate::restart(lastPosTime);
lastDist = diff;
startPc = newPc;
addVelocitySample(diff / (qreal(lastElapsed) / 1000.));
}
if (!moving) {
moving = true;
Expand Down Expand Up @@ -1256,18 +1307,18 @@ void QQuickPathViewPrivate::handleMouseReleaseEvent(QMouseEvent *)
if (!interactive || !lastPosTime.isValid())
return;

qreal elapsed = qreal(lastElapsed + QQuickItemPrivate::elapsed(lastPosTime)) / 1000.;
qreal velocity = elapsed > 0. ? lastDist / elapsed : 0;
if (model && modelCount && qAbs(velocity) > 1.) {
qreal velocity = calcVelocity();
if (model && modelCount && qAbs(velocity) > 0.5) {
qreal count = pathItems == -1 ? modelCount : pathItems;
if (qAbs(velocity) > count * 2) // limit velocity
velocity = (velocity > 0 ? count : -count) * 2;
// Calculate the distance to be travelled
qreal v2 = velocity*velocity;
qreal accel = deceleration/10;
// + 0.25 to encourage moving at least one item in the flick direction
qreal dist = qMin(qreal(modelCount-1), qreal(v2 / (accel * 2.0) + 0.25));
qreal dist = 0;
if (haveHighlightRange && highlightRangeMode == QQuickPathView::StrictlyEnforceRange) {
// + 0.25 to encourage moving at least one item in the flick direction
dist = qMin(qreal(modelCount-1), qreal(v2 / (accel * 2.0) + 0.25));
// round to nearest item.
if (velocity > 0.)
dist = qRound(dist + offset) - offset;
Expand All @@ -1280,6 +1331,8 @@ void QQuickPathViewPrivate::handleMouseReleaseEvent(QMouseEvent *)
} else {
accel = v2 / (2.0f * qAbs(dist));
}
} else {
dist = qMin(qreal(modelCount-1), qreal(v2 / (accel * 2.0)));
}
offsetAdj = 0.0;
moveOffset.setValue(offset);
Expand Down
3 changes: 3 additions & 0 deletions src/quick/items/qquickpathview_p_p.h
Expand Up @@ -140,6 +140,8 @@ class QQuickPathViewPrivate : public QQuickItemPrivate, public QQuickItemChangeL
void updateItem(QQuickItem *, qreal);
void snapToCurrent();
QPointF pointNear(const QPointF &point, qreal *nearPercent=0) const;
void addVelocitySample(qreal v);
qreal calcVelocity() const;

QDeclarativePath *path;
int currentIndex;
Expand Down Expand Up @@ -191,6 +193,7 @@ class QQuickPathViewPrivate : public QQuickItemPrivate, public QQuickItemChangeL
QQuickPathView::HighlightRangeMode highlightRangeMode;
int highlightMoveDuration;
int modelCount;
QPODVector<qreal,10> velocityBuffer;
};

QT_END_NAMESPACE
Expand Down
31 changes: 25 additions & 6 deletions src/quick/util/qdeclarativepath.cpp
Expand Up @@ -654,12 +654,31 @@ QPointF QDeclarativePath::pointAt(qreal p) const
if (d->_pointCache.isEmpty())
return QPointF();
}
int idx = qRound(p*d->_pointCache.size());
if (idx >= d->_pointCache.size())
idx = d->_pointCache.size() - 1;
else if (idx < 0)
idx = 0;
return d->_pointCache.at(idx);

const int pointCacheSize = d->_pointCache.size();
qreal idxf = p*pointCacheSize;
int idx1 = qFloor(idxf);
qreal delta = idxf - idx1;
if (idx1 >= pointCacheSize)
idx1 = pointCacheSize - 1;
else if (idx1 < 0)
idx1 = 0;

if (delta == 0.0)
return d->_pointCache.at(idx1);

// interpolate between the two points.
int idx2 = qCeil(idxf);
if (idx2 >= pointCacheSize)
idx2 = pointCacheSize - 1;
else if (idx2 < 0)
idx2 = 0;

QPointF p1 = d->_pointCache.at(idx1);
QPointF p2 = d->_pointCache.at(idx2);
QPointF pos = p1 * (1.0-delta) + p2 * delta;

return pos;
}

qreal QDeclarativePath::attributeAt(const QString &name, qreal percent) const
Expand Down
9 changes: 2 additions & 7 deletions tests/auto/qtquick2/qquickpathview/tst_qquickpathview.cpp
Expand Up @@ -718,7 +718,7 @@ void tst_QQuickPathView::pathMoved()
for (int i=0; i<model.count(); i++) {
QQuickRectangle *curItem = findItem<QQuickRectangle>(pathview, "wrapper", i);
QPointF itemPos(path->pointAt(0.25 + i*0.25));
QCOMPARE(curItem->pos() + offset, QPointF(qRound(itemPos.x()), qRound(itemPos.y())));
QCOMPARE(curItem->pos() + offset, QPointF(itemPos.x(), itemPos.y()));
}

pathview->setOffset(0.0);
Expand Down Expand Up @@ -1279,8 +1279,6 @@ void tst_QQuickPathView::changePreferredHighlight()
QDeclarativePath *path = qobject_cast<QDeclarativePath*>(pathview->path());
QVERIFY(path);
QPointF start = path->pointAt(0.5);
start.setX(qRound(start.x()));
start.setY(qRound(start.y()));
QPointF offset;//Center of item is at point, but pos is from corner
offset.setX(firstItem->width()/2);
offset.setY(firstItem->height()/2);
Expand All @@ -1289,8 +1287,6 @@ void tst_QQuickPathView::changePreferredHighlight()
pathview->setPreferredHighlightBegin(0.8);
pathview->setPreferredHighlightEnd(0.8);
start = path->pointAt(0.8);
start.setX(qRound(start.x()));
start.setY(qRound(start.y()));
QTRY_COMPARE(firstItem->pos() + offset, start);
QCOMPARE(pathview->currentIndex(), 0);

Expand Down Expand Up @@ -1345,7 +1341,6 @@ void tst_QQuickPathView::currentOffsetOnInsertion()
QVERIFY(path);

QPointF start = path->pointAt(0.5);
start = QPointF(qRound(start.x()), qRound(start.y()));
QPointF offset;//Center of item is at point, but pos is from corner
offset.setX(item->width()/2);
offset.setY(item->height()/2);
Expand Down Expand Up @@ -1442,7 +1437,7 @@ void tst_QQuickPathView::asynchronous()
for (int i=0; i<5; i++) {
QQuickItem *curItem = findItem<QQuickItem>(pathview, "wrapper", i);
QPointF itemPos(path->pointAt(0.2 + i*0.2));
QCOMPARE(curItem->pos() + offset, QPointF(qRound(itemPos.x()), qRound(itemPos.y())));
QCOMPARE(curItem->pos() + offset, itemPos);
}

delete canvas;
Expand Down

0 comments on commit b672f19

Please sign in to comment.