File indexing completed on 2024-04-21 03:49:38

0001 /*
0002     This file is part of the Ofi Labs X2 project.
0003 
0004     SPDX-FileCopyrightText: 2010 Ariya Hidayat <ariya.hidayat@gmail.com>
0005 
0006     SPDX-License-Identifier: BSD-3-Clause
0007 */
0008 
0009 #include "kineticmodel.h"
0010 
0011 #include <QTimer>
0012 #include <QElapsedTimer>
0013 
0014 static const int KineticModelDefaultUpdateInterval = 15; // ms
0015 
0016 class KineticModelPrivate
0017 {
0018 public:
0019     QTimer ticker;
0020 
0021     int duration;
0022     QPointF position;
0023     qreal heading;
0024     QPointF velocity;
0025     qreal velocityHeading;
0026     QPointF deacceleration;
0027     qreal deaccelerationHeading;
0028 
0029     QElapsedTimer timestamp;
0030     QPointF lastPosition;
0031     qreal lastHeading;
0032     bool changingPosition;
0033 
0034     KineticModelPrivate();
0035 };
0036 
0037 KineticModelPrivate::KineticModelPrivate()
0038     : duration(1403)
0039     , position(0, 0)
0040     , heading(0)
0041     , velocity(0, 0)
0042     , velocityHeading(0)
0043     , deacceleration(0, 0)
0044     , deaccelerationHeading(0)
0045     , lastPosition(0, 0)
0046     , lastHeading(0)
0047     , changingPosition(true)
0048 {
0049 
0050 }
0051 
0052 KineticModel::KineticModel(QObject *parent)
0053     : QObject(parent)
0054     , d_ptr(new KineticModelPrivate)
0055 {
0056     connect(&d_ptr->ticker, SIGNAL(timeout()), SLOT(update()));
0057     d_ptr->ticker.setInterval(KineticModelDefaultUpdateInterval);
0058 }
0059 
0060 KineticModel::~KineticModel()
0061 {
0062 
0063 }
0064 
0065 bool KineticModel::hasVelocity() const
0066 {
0067     return !d_ptr->velocity.isNull();
0068 }
0069 
0070 int KineticModel::duration() const
0071 {
0072     return d_ptr->duration;
0073 }
0074 
0075 void KineticModel::setDuration(int ms)
0076 {
0077     d_ptr->duration = ms;
0078 }
0079 
0080 QPointF KineticModel::position() const
0081 {
0082     return d_ptr->position;
0083 }
0084 
0085 void KineticModel::setPosition(const QPointF& position)
0086 {
0087     setPosition( position.x(), position.y() );
0088 }
0089 
0090 void KineticModel::setPosition(qreal posX, qreal posY)
0091 {
0092     d_ptr->position.setX( posX );
0093     d_ptr->position.setY( posY );
0094 
0095     int elapsed = d_ptr->timestamp.elapsed();
0096 
0097     // too fast gives less accuracy
0098     if (elapsed < d_ptr->ticker.interval() / 2) {
0099         return;
0100     }
0101 
0102     qreal delta = static_cast<qreal>( elapsed ) / 1000.0;
0103 
0104     QPointF lastSpeed = d_ptr->velocity;
0105     QPointF currentSpeed = ( d_ptr->position - d_ptr->lastPosition ) / delta;
0106     d_ptr->velocity = 0.2 * lastSpeed + 0.8 * currentSpeed;
0107     d_ptr->lastPosition = d_ptr->position;
0108 
0109     d_ptr->changingPosition = true;
0110     d_ptr->timestamp.start();
0111 }
0112 
0113 void KineticModel::setHeading(qreal heading)
0114 {
0115     d_ptr->heading = heading;
0116 
0117     int elapsed = d_ptr->timestamp.elapsed();
0118     qreal delta = static_cast<qreal>( elapsed ) / 1000.0;
0119 
0120     qreal lastSpeed = d_ptr->velocityHeading;
0121     qreal currentSpeed = delta ? ( d_ptr->heading - d_ptr->lastHeading ) / delta : 0;
0122     d_ptr->velocityHeading = 0.5 * lastSpeed + 0.2 * currentSpeed;
0123     d_ptr->lastHeading = d_ptr->heading;
0124 
0125     d_ptr->changingPosition = false;
0126     d_ptr->timestamp.start();
0127 }
0128 
0129 void KineticModel::jumpToPosition(const QPointF& position)
0130 {
0131     jumpToPosition( position.x(), position.y() );
0132 }
0133 
0134 void KineticModel::jumpToPosition(qreal posX, qreal posY)
0135 {
0136     d_ptr->position.setX( posX );
0137     d_ptr->position.setY( posY );
0138 }
0139 
0140 int KineticModel::updateInterval() const
0141 {
0142     return d_ptr->ticker.interval();
0143 }
0144 
0145 void KineticModel::setUpdateInterval(int ms)
0146 {
0147     d_ptr->ticker.setInterval(ms);
0148 }
0149 
0150 void KineticModel::stop()
0151 {
0152     Q_D(KineticModel);
0153 
0154     d->ticker.stop();
0155     d->timestamp.start();
0156     d->velocity = QPointF(0, 0);
0157     d->velocityHeading = 0;
0158 }
0159 
0160 void KineticModel::start()
0161 {
0162     Q_D(KineticModel);
0163 
0164     // prevent kinetic spinning if last mouse move is too long ago
0165     const int elapsed = d->timestamp.elapsed();
0166     if ( elapsed > 2 * d->ticker.interval() ) {
0167         d->ticker.stop();
0168         emit finished();
0169         return;
0170     }
0171 
0172     d->deacceleration = d->velocity * 1000 / ( 1 + d_ptr->duration );
0173     if (d->deacceleration.x() < 0) {
0174         d->deacceleration.setX( -d->deacceleration.x() );
0175     }
0176     if (d->deacceleration.y() < 0) {
0177         d->deacceleration.setY( -d->deacceleration.y() );
0178     }
0179 
0180     d->deaccelerationHeading = qAbs(d->velocityHeading) * 1000 / ( 1 + d_ptr->duration );
0181 
0182     if (!d->ticker.isActive())
0183         d->ticker.start();
0184 }
0185 
0186 void KineticModel::update()
0187 {
0188     Q_D(KineticModel);
0189 
0190     int elapsed = qMin( static_cast<int>(d->timestamp.elapsed()), 100 ); // limit to 100msec to reduce catapult effect (bug 294608)
0191     qreal delta = static_cast<qreal>(elapsed) / 1000.0;
0192 
0193     bool stop = false;
0194     if (d->changingPosition) {
0195         d->position += d->velocity * delta;
0196         QPointF vstep = d->deacceleration * delta;
0197 
0198         if (d->velocity.x() < vstep.x() && d->velocity.x() >= -vstep.x()) {
0199             d->velocity.setX( 0 );
0200         } else {
0201             if (d->velocity.x() > 0)
0202                 d->velocity.setX( d->velocity.x() - vstep.x() );
0203             else
0204                 d->velocity.setX( d->velocity.x() + vstep.x() );
0205         }
0206 
0207         if (d->velocity.y() < vstep.y() && d->velocity.y() >= -vstep.y()) {
0208             d->velocity.setY( 0 );
0209         } else {
0210             if (d->velocity.y() > 0)
0211                 d->velocity.setY( d->velocity.y() - vstep.y() );
0212             else
0213                 d->velocity.setY( d->velocity.y() + vstep.y() );
0214         }
0215 
0216         stop = d->velocity.isNull();
0217 
0218         emit positionChanged( d->position.x(), d->position.y() );
0219     } else {
0220         d->heading += d->velocityHeading * delta;
0221         qreal vstep = d->deaccelerationHeading * delta; // Always positive.
0222         if ((d->velocityHeading < vstep && d->velocityHeading >= -vstep) || !vstep) {
0223             d->velocityHeading = 0;
0224         } else {
0225             d->velocityHeading += d->velocityHeading > 0 ? -1 * vstep : vstep;
0226         }
0227 
0228         stop = !d->velocityHeading;
0229 
0230         emit headingChanged( d->heading );
0231     }
0232 
0233     if (stop) {
0234         emit finished();
0235         d->ticker.stop();
0236     }
0237 
0238     d->timestamp.start();
0239 }
0240 
0241 #include "moc_kineticmodel.cpp"
0242