ginebra2/Kinetics/KineticScroller.cpp
changeset 10 232fbd5a2dcb
equal deleted inserted replaced
6:1c3b8676e58c 10:232fbd5a2dcb
       
     1 /*
       
     2 * Copyright (c) 2010 Nokia Corporation and/or its subsidiary(-ies).
       
     3 * All rights reserved.
       
     4 *
       
     5 * This program is free software: you can redistribute it and/or modify
       
     6 * it under the terms of the GNU Lesser General Public License as published by
       
     7 * the Free Software Foundation, version 2.1 of the License.
       
     8 *
       
     9 * This program is distributed in the hope that it will be useful,
       
    10 * but WITHOUT ANY WARRANTY; without even the implied warranty of
       
    11 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
       
    12 * GNU Lesser General Public License for more details.
       
    13 *
       
    14 * You should have received a copy of the GNU Lesser General Public License
       
    15 * along with this program.  If not,
       
    16 * see "http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html/".
       
    17 *
       
    18 * Description:
       
    19 *
       
    20 */
       
    21 
       
    22 #include "KineticScroller.h"
       
    23 
       
    24 #include <QTimerEvent>
       
    25 
       
    26 namespace GVA {
       
    27 
       
    28 KineticScroller::KineticScroller(KineticScrollable* scrollableView)
       
    29     : m_scrollableView(scrollableView)
       
    30     , m_mode(KineticScroller::AutoMode)
       
    31     , m_state(KineticScrollable::Inactive)
       
    32     , m_overshootPolicy(KineticScroller::OvershootWhenScrollable)
       
    33     , m_motion(QPoint(0,0))
       
    34     , m_move(false)
       
    35     , m_bounceSteps(3)
       
    36     , m_maxOvershoot(150, 150)
       
    37     , m_vmaxOvershoot(130)
       
    38     , m_overshootDist(0, 0)
       
    39     , m_overshooting(0)
       
    40     , m_minVelocity(10)
       
    41     , m_maxVelocity(3500)
       
    42     , m_deceleration(0.85)
       
    43     , m_scrollsPerSecond(20)
       
    44     , m_idleTimerId(0)
       
    45     , m_scrollTimerId(0)
       
    46     , m_inactivityTimerId(0)
       
    47 {}
       
    48 
       
    49 KineticScroller::~KineticScroller()
       
    50 {}
       
    51 
       
    52 KineticScroller::OvershootPolicy KineticScroller::overshootPolicy() const
       
    53 {
       
    54     return m_overshootPolicy;
       
    55 }
       
    56 
       
    57 void KineticScroller::setOvershootPolicy(KineticScroller::OvershootPolicy policy)
       
    58 {
       
    59     m_overshootPolicy  = policy;
       
    60 }
       
    61 
       
    62 qreal KineticScroller::decelerationFactor() const
       
    63 {
       
    64     return m_deceleration;
       
    65 }
       
    66 
       
    67 void KineticScroller::setDecelerationFactor(qreal f)
       
    68 {
       
    69     m_deceleration = f;
       
    70 }
       
    71 
       
    72 int KineticScroller::scrollsPerSecond() const
       
    73 {
       
    74     return m_scrollsPerSecond;
       
    75 }
       
    76 
       
    77 void KineticScroller::setScrollsPerSecond(int sps)
       
    78 {
       
    79     m_scrollsPerSecond = qBound(1, sps, 100);
       
    80 }
       
    81 
       
    82 void KineticScroller::doPan(QPoint delta)
       
    83 {
       
    84     //stop inactivity timer 
       
    85     if (m_inactivityTimerId) {
       
    86         killTimer(m_inactivityTimerId);
       
    87         m_inactivityTimerId = 0;
       
    88     }
       
    89 
       
    90     //overshoot only if OvershootAlwaysOn is set
       
    91     QPoint maxPos = m_scrollableView->maximumScrollPosition();
       
    92     bool alwaysOvershoot = (overshootPolicy() == KineticScroller::OvershootAlwaysOn);
       
    93 
       
    94     if (!maxPos.x() && !alwaysOvershoot) {
       
    95         delta.setX(0);
       
    96     }
       
    97     if (!maxPos.y() && !alwaysOvershoot) {
       
    98         delta.setY(0);
       
    99     }
       
   100 
       
   101     if (m_scrollTimerId)
       
   102         m_motion += delta;
       
   103     else {
       
   104         m_mode = KineticScroller::PushMode;
       
   105         changeState(KineticScrollable::Pushing);
       
   106         // we do not delay the first event but the next ones
       
   107         setScrollPositionHelper(m_scrollableView->scrollPosition() - m_overshootDist - delta);
       
   108         m_motion = QPoint(0, 0);
       
   109         m_scrollTimerId = startTimer(1000 / KineticScroller::MotionEventsPerSecond);
       
   110     }
       
   111 }
       
   112 
       
   113 void KineticScroller::doFlick(QPointF velocity)
       
   114 {
       
   115     //stop inactivity timer 
       
   116     if (m_inactivityTimerId) {
       
   117         killTimer(m_inactivityTimerId);
       
   118         m_inactivityTimerId = 0;
       
   119     }
       
   120 
       
   121     m_motion = QPoint(0,0);
       
   122     m_mode = KineticScroller::AutoMode;
       
   123     changeState(KineticScrollable::AutoScrolling);
       
   124     m_velocity = velocity;
       
   125     m_idleTimerId = startTimer(1000 / m_scrollsPerSecond);
       
   126 }
       
   127 
       
   128 void KineticScroller::stop()
       
   129 {
       
   130     if (m_inactivityTimerId) {
       
   131         killTimer(m_inactivityTimerId);
       
   132         m_inactivityTimerId = 0;
       
   133     }
       
   134 
       
   135     if (m_scrollTimerId) {
       
   136         killTimer(m_scrollTimerId);
       
   137         m_scrollTimerId = 0;
       
   138         setScrollPositionHelper(m_scrollableView->scrollPosition() - m_overshootDist - m_motion);
       
   139     }
       
   140 
       
   141     if (m_idleTimerId) {
       
   142         killTimer(m_idleTimerId);
       
   143         m_idleTimerId = 0;
       
   144     }
       
   145     
       
   146     if (m_overshootDist.x()) {
       
   147         m_overshooting = m_bounceSteps; 
       
   148         m_velocity.setX(-m_overshootDist.x() * qreal(0.8));
       
   149     }
       
   150     if (m_overshootDist.y()) {
       
   151         m_overshooting = m_bounceSteps; 
       
   152         m_velocity.setY(-m_overshootDist.y() * qreal(0.8));
       
   153     }
       
   154 
       
   155     //if page is overshoot doFlick() to switch back
       
   156     if (!m_overshootDist.isNull()) {    
       
   157         doFlick(m_velocity);
       
   158         return;
       
   159     }
       
   160     
       
   161     m_motion = QPoint(0, 0);
       
   162     m_velocity = QPointF(0,0);
       
   163     m_mode = KineticScroller::AutoMode;
       
   164     changeState(KineticScrollable::Inactive);
       
   165 }
       
   166 
       
   167 void KineticScroller::changeState(KineticScrollable::State newState)
       
   168 {
       
   169     if (newState != m_state) {
       
   170         KineticScrollable::State oldState = m_state;
       
   171         m_state = newState;
       
   172         m_scrollableView->stateChanged(oldState, newState);
       
   173     }
       
   174 }
       
   175 
       
   176 void KineticScroller::reset()
       
   177 {
       
   178     changeState(KineticScrollable::Inactive);
       
   179 
       
   180     if (m_idleTimerId)
       
   181         killTimer(m_idleTimerId);
       
   182 
       
   183     m_idleTimerId = 0;
       
   184 
       
   185     if (m_scrollTimerId)
       
   186         killTimer(m_scrollTimerId);
       
   187 
       
   188     m_scrollTimerId = 0;
       
   189 
       
   190     if (m_inactivityTimerId)
       
   191         killTimer(m_inactivityTimerId);
       
   192     m_inactivityTimerId = 0;
       
   193 
       
   194     m_velocity = QPointF(0, 0);
       
   195     m_overshootDist = QPoint(0, 0);
       
   196     m_overshooting = 0;
       
   197 }
       
   198 
       
   199 void KineticScroller::setScrollPositionHelper(const QPoint &pos)
       
   200 {
       
   201     QPoint maxPos = m_scrollableView->maximumScrollPosition();
       
   202 
       
   203     QPoint clampedPos;
       
   204     clampedPos.setX(qBound(0, pos.x(), maxPos.x()));
       
   205     clampedPos.setY(qBound(0, pos.y(), maxPos.y()));
       
   206 
       
   207     bool alwaysOvershoot = (m_overshootPolicy == KineticScroller::OvershootAlwaysOn);
       
   208     int overshootX = (maxPos.x() || alwaysOvershoot) ? clampedPos.x() - pos.x() : 0;
       
   209     int overshootY = (maxPos.y() || alwaysOvershoot) ? clampedPos.y() - pos.y() : 0;
       
   210 
       
   211     m_overshootDist.setX(qBound(-m_maxOvershoot.x(), overshootX, m_maxOvershoot.x()));
       
   212     m_overshootDist.setY(qBound(-m_maxOvershoot.y(), overshootY, m_maxOvershoot.y()));
       
   213 
       
   214     m_scrollableView->setScrollPosition(clampedPos
       
   215                                         , m_overshootPolicy == KineticScroller::OvershootAlwaysOff
       
   216                                                                ? QPoint()
       
   217                                                                : m_overshootDist);
       
   218 }
       
   219 
       
   220 void KineticScroller::handleScrollTimer()
       
   221 {
       
   222     if (!m_motion.isNull())
       
   223         setScrollPositionHelper(m_scrollableView->scrollPosition() - m_overshootDist - m_motion);
       
   224 
       
   225     killTimer(m_scrollTimerId);
       
   226     m_scrollTimerId = 0;
       
   227 
       
   228     m_inactivityTimerId = startTimer(300);
       
   229 }
       
   230 
       
   231 void KineticScroller::handleIdleTimer()
       
   232 {
       
   233     if (m_mode == KineticScroller::PushMode && m_overshootDist.isNull()) {
       
   234         stop();
       
   235         return;
       
   236     }
       
   237  
       
   238     setScrollPositionHelper(m_scrollableView->scrollPosition() - m_overshootDist - m_velocity.toPoint());
       
   239 
       
   240     if (!m_overshootDist.isNull()) {
       
   241         m_overshooting++;
       
   242        
       
   243         /* When the overshoot has started we continue for
       
   244          * PROP_BOUNCE_STEPS more steps into the overshoot before we
       
   245          * reverse direction. The deceleration factor is calculated
       
   246          * based on the percentage distance from the first item with
       
   247          * each iteration, therefore always returning us to the
       
   248          * top/bottom most element
       
   249          */
       
   250         if (m_overshooting < m_bounceSteps) {
       
   251             m_velocity.setX( m_overshootDist.x() / m_maxOvershoot.x() * m_velocity.x() );
       
   252             m_velocity.setY( m_overshootDist.y() / m_maxOvershoot.y() * m_velocity.y() );
       
   253         } else {
       
   254             m_velocity.setX( -m_overshootDist.x() * 0.8 );
       
   255             m_velocity.setY( -m_overshootDist.y() * 0.8 );
       
   256 
       
   257             // ensure a minimum speed when scrolling back or else we might never return
       
   258             if (m_velocity.x() > -1.0 && m_velocity.x() < 0.0)
       
   259                 m_velocity.setX(-1.0);
       
   260             if (m_velocity.x() <  1.0 && m_velocity.x() > 0.0)
       
   261                 m_velocity.setX( 1.0);
       
   262             if (m_velocity.y() > -1.0 && m_velocity.y() < 0.0)
       
   263                 m_velocity.setY(-1.0);
       
   264             if (m_velocity.y() <  1.0 && m_velocity.y() > 0.0)
       
   265                 m_velocity.setY( 1.0);
       
   266         }
       
   267 
       
   268         m_velocity.setX( qBound((qreal)-m_vmaxOvershoot, m_velocity.x(), (qreal)m_vmaxOvershoot));
       
   269         m_velocity.setY( qBound((qreal)-m_vmaxOvershoot, m_velocity.y(), (qreal)m_vmaxOvershoot));
       
   270 
       
   271     } else if (m_state == KineticScrollable::AutoScrolling) {
       
   272         // Decelerate gradually when pointer is raised
       
   273         m_overshooting = 0;
       
   274 
       
   275         if (qAbs(m_velocity.x()) < qreal(0.8) * m_maxVelocity)
       
   276             m_velocity.rx() *= m_deceleration;
       
   277         if (qAbs(m_velocity.y()) < qreal(0.8) * m_maxVelocity)
       
   278             m_velocity.ry() *= m_deceleration;
       
   279 
       
   280         if ((qAbs(m_velocity.x()) < qreal(1.0)) && (qAbs(m_velocity.y()) < qreal(1.0))) 
       
   281             stop();
       
   282       
       
   283     } else if (m_mode == KineticScroller::AutoMode) 
       
   284           stop();
       
   285 }
       
   286 
       
   287 void KineticScroller::timerEvent(QTimerEvent *ev)
       
   288 {
       
   289     if (ev->timerId() == m_idleTimerId)
       
   290         handleIdleTimer();
       
   291     else if (ev->timerId() == m_scrollTimerId)
       
   292         handleScrollTimer();
       
   293     else if (ev->timerId() == m_inactivityTimerId) {
       
   294         //Do not move to inactive state if page is still moving  
       
   295         if (!m_idleTimerId && !m_scrollTimerId)
       
   296             stop();
       
   297     }
       
   298 }
       
   299 
       
   300 }//namespace GVA