ginebra2/Kinetics/KineticScroller.cpp
changeset 9 b39122337a00
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/ginebra2/Kinetics/KineticScroller.cpp	Fri Aug 06 17:23:08 2010 -0400
@@ -0,0 +1,300 @@
+/*
+* Copyright (c) 2010 Nokia Corporation and/or its subsidiary(-ies).
+* All rights reserved.
+*
+* This program is free software: you can redistribute it and/or modify
+* it under the terms of the GNU Lesser General Public License as published by
+* the Free Software Foundation, version 2.1 of the License.
+*
+* This program is distributed in the hope that it will be useful,
+* but WITHOUT ANY WARRANTY; without even the implied warranty of
+* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+* GNU Lesser General Public License for more details.
+*
+* You should have received a copy of the GNU Lesser General Public License
+* along with this program.  If not,
+* see "http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html/".
+*
+* Description:
+*
+*/
+
+#include "KineticScroller.h"
+
+#include <QTimerEvent>
+
+namespace GVA {
+
+KineticScroller::KineticScroller(KineticScrollable* scrollableView)
+    : m_scrollableView(scrollableView)
+    , m_mode(KineticScroller::AutoMode)
+    , m_state(KineticScrollable::Inactive)
+    , m_overshootPolicy(KineticScroller::OvershootWhenScrollable)
+    , m_motion(QPoint(0,0))
+    , m_move(false)
+    , m_bounceSteps(3)
+    , m_maxOvershoot(150, 150)
+    , m_vmaxOvershoot(130)
+    , m_overshootDist(0, 0)
+    , m_overshooting(0)
+    , m_minVelocity(10)
+    , m_maxVelocity(3500)
+    , m_deceleration(0.85)
+    , m_scrollsPerSecond(20)
+    , m_idleTimerId(0)
+    , m_scrollTimerId(0)
+    , m_inactivityTimerId(0)
+{}
+
+KineticScroller::~KineticScroller()
+{}
+
+KineticScroller::OvershootPolicy KineticScroller::overshootPolicy() const
+{
+    return m_overshootPolicy;
+}
+
+void KineticScroller::setOvershootPolicy(KineticScroller::OvershootPolicy policy)
+{
+    m_overshootPolicy  = policy;
+}
+
+qreal KineticScroller::decelerationFactor() const
+{
+    return m_deceleration;
+}
+
+void KineticScroller::setDecelerationFactor(qreal f)
+{
+    m_deceleration = f;
+}
+
+int KineticScroller::scrollsPerSecond() const
+{
+    return m_scrollsPerSecond;
+}
+
+void KineticScroller::setScrollsPerSecond(int sps)
+{
+    m_scrollsPerSecond = qBound(1, sps, 100);
+}
+
+void KineticScroller::doPan(QPoint delta)
+{
+    //stop inactivity timer 
+    if (m_inactivityTimerId) {
+        killTimer(m_inactivityTimerId);
+        m_inactivityTimerId = 0;
+    }
+
+    //overshoot only if OvershootAlwaysOn is set
+    QPoint maxPos = m_scrollableView->maximumScrollPosition();
+    bool alwaysOvershoot = (overshootPolicy() == KineticScroller::OvershootAlwaysOn);
+
+    if (!maxPos.x() && !alwaysOvershoot) {
+        delta.setX(0);
+    }
+    if (!maxPos.y() && !alwaysOvershoot) {
+        delta.setY(0);
+    }
+
+    if (m_scrollTimerId)
+        m_motion += delta;
+    else {
+        m_mode = KineticScroller::PushMode;
+        changeState(KineticScrollable::Pushing);
+        // we do not delay the first event but the next ones
+        setScrollPositionHelper(m_scrollableView->scrollPosition() - m_overshootDist - delta);
+        m_motion = QPoint(0, 0);
+        m_scrollTimerId = startTimer(1000 / KineticScroller::MotionEventsPerSecond);
+    }
+}
+
+void KineticScroller::doFlick(QPointF velocity)
+{
+    //stop inactivity timer 
+    if (m_inactivityTimerId) {
+        killTimer(m_inactivityTimerId);
+        m_inactivityTimerId = 0;
+    }
+
+    m_motion = QPoint(0,0);
+    m_mode = KineticScroller::AutoMode;
+    changeState(KineticScrollable::AutoScrolling);
+    m_velocity = velocity;
+    m_idleTimerId = startTimer(1000 / m_scrollsPerSecond);
+}
+
+void KineticScroller::stop()
+{
+    if (m_inactivityTimerId) {
+        killTimer(m_inactivityTimerId);
+        m_inactivityTimerId = 0;
+    }
+
+    if (m_scrollTimerId) {
+        killTimer(m_scrollTimerId);
+        m_scrollTimerId = 0;
+        setScrollPositionHelper(m_scrollableView->scrollPosition() - m_overshootDist - m_motion);
+    }
+
+    if (m_idleTimerId) {
+        killTimer(m_idleTimerId);
+        m_idleTimerId = 0;
+    }
+    
+    if (m_overshootDist.x()) {
+        m_overshooting = m_bounceSteps; 
+        m_velocity.setX(-m_overshootDist.x() * qreal(0.8));
+    }
+    if (m_overshootDist.y()) {
+        m_overshooting = m_bounceSteps; 
+        m_velocity.setY(-m_overshootDist.y() * qreal(0.8));
+    }
+
+    //if page is overshoot doFlick() to switch back
+    if (!m_overshootDist.isNull()) {    
+        doFlick(m_velocity);
+        return;
+    }
+    
+    m_motion = QPoint(0, 0);
+    m_velocity = QPointF(0,0);
+    m_mode = KineticScroller::AutoMode;
+    changeState(KineticScrollable::Inactive);
+}
+
+void KineticScroller::changeState(KineticScrollable::State newState)
+{
+    if (newState != m_state) {
+        KineticScrollable::State oldState = m_state;
+        m_state = newState;
+        m_scrollableView->stateChanged(oldState, newState);
+    }
+}
+
+void KineticScroller::reset()
+{
+    changeState(KineticScrollable::Inactive);
+
+    if (m_idleTimerId)
+        killTimer(m_idleTimerId);
+
+    m_idleTimerId = 0;
+
+    if (m_scrollTimerId)
+        killTimer(m_scrollTimerId);
+
+    m_scrollTimerId = 0;
+
+    if (m_inactivityTimerId)
+        killTimer(m_inactivityTimerId);
+    m_inactivityTimerId = 0;
+
+    m_velocity = QPointF(0, 0);
+    m_overshootDist = QPoint(0, 0);
+    m_overshooting = 0;
+}
+
+void KineticScroller::setScrollPositionHelper(const QPoint &pos)
+{
+    QPoint maxPos = m_scrollableView->maximumScrollPosition();
+
+    QPoint clampedPos;
+    clampedPos.setX(qBound(0, pos.x(), maxPos.x()));
+    clampedPos.setY(qBound(0, pos.y(), maxPos.y()));
+
+    bool alwaysOvershoot = (m_overshootPolicy == KineticScroller::OvershootAlwaysOn);
+    int overshootX = (maxPos.x() || alwaysOvershoot) ? clampedPos.x() - pos.x() : 0;
+    int overshootY = (maxPos.y() || alwaysOvershoot) ? clampedPos.y() - pos.y() : 0;
+
+    m_overshootDist.setX(qBound(-m_maxOvershoot.x(), overshootX, m_maxOvershoot.x()));
+    m_overshootDist.setY(qBound(-m_maxOvershoot.y(), overshootY, m_maxOvershoot.y()));
+
+    m_scrollableView->setScrollPosition(clampedPos
+                                        , m_overshootPolicy == KineticScroller::OvershootAlwaysOff
+                                                               ? QPoint()
+                                                               : m_overshootDist);
+}
+
+void KineticScroller::handleScrollTimer()
+{
+    if (!m_motion.isNull())
+        setScrollPositionHelper(m_scrollableView->scrollPosition() - m_overshootDist - m_motion);
+
+    killTimer(m_scrollTimerId);
+    m_scrollTimerId = 0;
+
+    m_inactivityTimerId = startTimer(300);
+}
+
+void KineticScroller::handleIdleTimer()
+{
+    if (m_mode == KineticScroller::PushMode && m_overshootDist.isNull()) {
+        stop();
+        return;
+    }
+ 
+    setScrollPositionHelper(m_scrollableView->scrollPosition() - m_overshootDist - m_velocity.toPoint());
+
+    if (!m_overshootDist.isNull()) {
+        m_overshooting++;
+       
+        /* When the overshoot has started we continue for
+         * PROP_BOUNCE_STEPS more steps into the overshoot before we
+         * reverse direction. The deceleration factor is calculated
+         * based on the percentage distance from the first item with
+         * each iteration, therefore always returning us to the
+         * top/bottom most element
+         */
+        if (m_overshooting < m_bounceSteps) {
+            m_velocity.setX( m_overshootDist.x() / m_maxOvershoot.x() * m_velocity.x() );
+            m_velocity.setY( m_overshootDist.y() / m_maxOvershoot.y() * m_velocity.y() );
+        } else {
+            m_velocity.setX( -m_overshootDist.x() * 0.8 );
+            m_velocity.setY( -m_overshootDist.y() * 0.8 );
+
+            // ensure a minimum speed when scrolling back or else we might never return
+            if (m_velocity.x() > -1.0 && m_velocity.x() < 0.0)
+                m_velocity.setX(-1.0);
+            if (m_velocity.x() <  1.0 && m_velocity.x() > 0.0)
+                m_velocity.setX( 1.0);
+            if (m_velocity.y() > -1.0 && m_velocity.y() < 0.0)
+                m_velocity.setY(-1.0);
+            if (m_velocity.y() <  1.0 && m_velocity.y() > 0.0)
+                m_velocity.setY( 1.0);
+        }
+
+        m_velocity.setX( qBound((qreal)-m_vmaxOvershoot, m_velocity.x(), (qreal)m_vmaxOvershoot));
+        m_velocity.setY( qBound((qreal)-m_vmaxOvershoot, m_velocity.y(), (qreal)m_vmaxOvershoot));
+
+    } else if (m_state == KineticScrollable::AutoScrolling) {
+        // Decelerate gradually when pointer is raised
+        m_overshooting = 0;
+
+        if (qAbs(m_velocity.x()) < qreal(0.8) * m_maxVelocity)
+            m_velocity.rx() *= m_deceleration;
+        if (qAbs(m_velocity.y()) < qreal(0.8) * m_maxVelocity)
+            m_velocity.ry() *= m_deceleration;
+
+        if ((qAbs(m_velocity.x()) < qreal(1.0)) && (qAbs(m_velocity.y()) < qreal(1.0))) 
+            stop();
+      
+    } else if (m_mode == KineticScroller::AutoMode) 
+          stop();
+}
+
+void KineticScroller::timerEvent(QTimerEvent *ev)
+{
+    if (ev->timerId() == m_idleTimerId)
+        handleIdleTimer();
+    else if (ev->timerId() == m_scrollTimerId)
+        handleScrollTimer();
+    else if (ev->timerId() == m_inactivityTimerId) {
+        //Do not move to inactive state if page is still moving  
+        if (!m_idleTimerId && !m_scrollTimerId)
+            stop();
+    }
+}
+
+}//namespace GVA