PageRenderTime 39ms CodeModel.GetById 13ms app.highlight 22ms RepoModel.GetById 1ms app.codeStats 0ms

/xbmc/input/InertialScrollingHandler.cpp

http://github.com/xbmc/xbmc
C++ | 226 lines | 156 code | 30 blank | 40 comment | 37 complexity | 07a8cf50f8d30d0a7778508236339bc3 MD5 | raw file
  1/*
  2 *  Copyright (C) 2011-2018 Team Kodi
  3 *  This file is part of Kodi - https://kodi.tv
  4 *
  5 *  SPDX-License-Identifier: GPL-2.0-or-later
  6 *  See LICENSES/README.md for more information.
  7 */
  8
  9
 10#include "InertialScrollingHandler.h"
 11
 12#include "Application.h"
 13#include "ServiceBroker.h"
 14#include "guilib/GUIComponent.h"
 15#include "guilib/GUIWindowManager.h"
 16#include "input/Key.h"
 17#include "input/touch/generic/GenericTouchInputHandler.h"
 18#include "utils/TimeUtils.h"
 19#include "utils/log.h"
 20#include "windowing/WinSystem.h"
 21
 22#include <cmath>
 23#include <numeric>
 24
 25// time for reaching velocity 0 in secs
 26#define TIME_TO_ZERO_SPEED 1.0f
 27// minimum speed for doing inertial scroll is 100 pixels / s
 28#define MINIMUM_SPEED_FOR_INERTIA 200
 29// maximum speed for reducing time to zero
 30#define MAXIMUM_SPEED_FOR_REDUCTION 750
 31// maximum time between last movement and gesture end in ms to consider as moving
 32#define MAXIMUM_DELAY_FOR_INERTIA 200
 33
 34CInertialScrollingHandler::CInertialScrollingHandler()
 35: m_iLastGesturePoint(CPoint(0,0))
 36{
 37}
 38
 39unsigned int CInertialScrollingHandler::PanPoint::TimeElapsed() const
 40{
 41  return CTimeUtils::GetFrameTime() - time;
 42}
 43
 44bool CInertialScrollingHandler::CheckForInertialScrolling(const CAction* action)
 45{
 46  bool ret = false;//return value - false no inertial scrolling - true - inertial scrolling
 47
 48  if(CServiceBroker::GetWinSystem()->HasInertialGestures())
 49  {
 50    return ret;//no need for emulating inertial scrolling - windowing does support it natively.
 51  }
 52
 53  //reset screensaver during pan
 54  if( action->GetID() == ACTION_GESTURE_PAN )
 55  {
 56    g_application.ResetScreenSaver();
 57    if (!m_bScrolling)
 58    {
 59      m_panPoints.emplace_back(CTimeUtils::GetFrameTime(), CVector{action->GetAmount(4), action->GetAmount(5)});
 60    }
 61    return false;
 62  }
 63
 64  //mouse click aborts scrolling
 65  if( m_bScrolling && action->GetID() == ACTION_MOUSE_LEFT_CLICK )
 66  {
 67    ret = true;
 68    m_bAborting = true;//lets abort
 69  }
 70
 71  //trim saved pan points to time range that qualifies for inertial scrolling
 72  while (!m_panPoints.empty() && m_panPoints.front().TimeElapsed() > MAXIMUM_DELAY_FOR_INERTIA)
 73    m_panPoints.pop_front();
 74
 75  //on begin/tap stop all inertial scrolling
 76  if ( action->GetID() == ACTION_GESTURE_BEGIN )
 77  {
 78    //release any former exclusive mouse mode
 79    //for making switching between multiple lists
 80    //possible
 81    CGUIMessage message(GUI_MSG_EXCLUSIVE_MOUSE, 0, 0);
 82    CServiceBroker::GetGUI()->GetWindowManager().SendMessage(message);
 83    m_bScrolling = false;
 84    //wakeup screensaver on pan begin
 85    g_application.ResetScreenSaver();
 86    g_application.WakeUpScreenSaverAndDPMS();
 87  }
 88  else if(action->GetID() == ACTION_GESTURE_END && !m_panPoints.empty()) //do we need to animate inertial scrolling?
 89  {
 90    // Calculate velocity in the last MAXIMUM_DELAY_FOR_INERTIA milliseconds.
 91    // Do not use the velocity given by the ACTION_GESTURE_END data - it is calculated
 92    // for the whole duration of the touch and thus useless for inertia. The user
 93    // may scroll around for a few seconds and then only at the end flick in one
 94    // direction. Only the last flick should be relevant here.
 95    auto velocitySum = std::accumulate(m_panPoints.cbegin(), m_panPoints.cend(), CVector{}, [](CVector val, PanPoint const& p) {
 96      return val + p.velocity;
 97    });
 98    auto velocityX = velocitySum.x / m_panPoints.size();
 99    auto velocityY = velocitySum.y / m_panPoints.size();
100
101    m_timeToZero = TIME_TO_ZERO_SPEED;
102    auto velocityMax = std::max(std::abs(velocityX), std::abs(velocityY));
103#ifdef TARGET_DARWIN_OSX
104    float dpiScale = 1.0;
105#else
106    float dpiScale = CGenericTouchInputHandler::GetInstance().GetScreenDPI() / 160.0f;
107#endif
108    if (velocityMax > MINIMUM_SPEED_FOR_INERTIA * dpiScale)
109    {
110      if (velocityMax < MAXIMUM_SPEED_FOR_REDUCTION * dpiScale)
111        m_timeToZero = (m_timeToZero * velocityMax) / (MAXIMUM_SPEED_FOR_REDUCTION * dpiScale);
112
113      bool inertialRequested = false;
114      CGUIMessage message(GUI_MSG_GESTURE_NOTIFY, 0, 0, static_cast<int> (velocityX), static_cast<int> (velocityY));
115
116      //ask if the control wants inertial scrolling
117      if(CServiceBroker::GetGUI()->GetWindowManager().SendMessage(message))
118      {
119        int result = 0;
120        if (message.GetPointer())
121        {
122          int *p = static_cast<int*>(message.GetPointer());
123          message.SetPointer(nullptr);
124          result = *p;
125          delete p;
126        }
127        if( result == EVENT_RESULT_PAN_HORIZONTAL ||
128            result == EVENT_RESULT_PAN_VERTICAL)
129        {
130          inertialRequested = true;
131        }
132      }
133
134      if( inertialRequested )
135      {
136        m_iFlickVelocity.x = velocityX;//in pixels per sec
137        m_iFlickVelocity.y = velocityY;//in pixels per sec
138        m_iLastGesturePoint.x = action->GetAmount(2);//last gesture point x
139        m_iLastGesturePoint.y = action->GetAmount(3);//last gesture point y
140
141        //calc deacceleration for fullstop in TIME_TO_ZERO_SPEED secs
142        //v = a*t + v0 -> set v = 0 because we want to stop scrolling
143        //a = -v0 / t
144        m_inertialDeacceleration.x = -1 * m_iFlickVelocity.x / m_timeToZero;
145        m_inertialDeacceleration.y = -1 * m_iFlickVelocity.y / m_timeToZero;
146
147        m_inertialStartTime = CTimeUtils::GetFrameTime();//start time of inertial scrolling
148        ret = true;
149        m_bScrolling = true;//activate the inertial scrolling animation
150      }
151    }
152  }
153
154  if(action->GetID() == ACTION_GESTURE_BEGIN || action->GetID() == ACTION_GESTURE_END || action->GetID() == ACTION_GESTURE_ABORT)
155  {
156    m_panPoints.clear();
157  }
158
159  return ret;
160}
161
162bool CInertialScrollingHandler::ProcessInertialScroll(float frameTime)
163{
164  //do inertial scroll animation by sending gesture_pan
165  if( m_bScrolling)
166  {
167    float xMovement = 0.0;
168    float yMovement = 0.0;
169
170    //decrease based on negative acceleration
171    //calc the overall inertial scrolling time in secs
172    float absoluteInertialTime = (CTimeUtils::GetFrameTime() - m_inertialStartTime)/(float)1000;
173
174    //as long as we aren't over the overall inertial scroll time - do the deacceleration
175    if (absoluteInertialTime < m_timeToZero)
176    {
177      //v = s/t -> s = t * v
178      xMovement = frameTime * m_iFlickVelocity.x;
179      yMovement = frameTime * m_iFlickVelocity.y;
180
181      //save new gesture point
182      m_iLastGesturePoint.x += xMovement;
183      m_iLastGesturePoint.y += yMovement;
184
185      //fire the pan action
186      if (!g_application.OnAction(CAction(ACTION_GESTURE_PAN, 0, m_iLastGesturePoint.x,
187                                          m_iLastGesturePoint.y, xMovement, yMovement,
188                                          m_iFlickVelocity.x, m_iFlickVelocity.y)))
189      {
190        m_bAborting = true; // we are done
191      }
192
193      //calc new velocity based on deacceleration
194      //v = a*t + v0
195      m_iFlickVelocity.x = m_inertialDeacceleration.x * frameTime + m_iFlickVelocity.x;
196      m_iFlickVelocity.y = m_inertialDeacceleration.y * frameTime + m_iFlickVelocity.y;
197
198      //check if the signs are equal - which would mean we deaccelerated to long and reversed the direction
199      if( (m_inertialDeacceleration.x < 0) == (m_iFlickVelocity.x < 0) )
200      {
201        m_iFlickVelocity.x = 0;
202      }
203      if( (m_inertialDeacceleration.y < 0) == (m_iFlickVelocity.y < 0) )
204      {
205        m_iFlickVelocity.y = 0;
206      }
207    }
208    else//no movement -> done
209    {
210      m_bAborting = true;//we are done
211    }
212  }
213
214  //if we are done - or we where aborted
215  if( m_bAborting )
216  {
217    //fire gesture end action
218    g_application.OnAction(CAction(ACTION_GESTURE_END, 0, 0.0f, 0.0f, 0.0f, 0.0f));
219    m_bAborting = false;
220    m_bScrolling = false; //stop scrolling
221    m_iFlickVelocity.x = 0;
222    m_iFlickVelocity.y = 0;
223  }
224
225  return true;
226}