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