tb_scroller.cpp 13 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380
  1. // ================================================================================
  2. // == This file is a part of Turbo Badger. (C) 2011-2014, Emil Segerås ==
  3. // == See tb_core.h for more information. ==
  4. // ================================================================================
  5. #include "tb_scroller.h"
  6. #include "tb_widgets.h"
  7. #include "tb_system.h"
  8. #include <math.h>
  9. namespace tb {
  10. // == Misc constants ====================================================================
  11. #define PAN_TARGET_FPS 60
  12. #define PAN_MSG_DELAY_MS ((double)(1000.0 / PAN_TARGET_FPS))
  13. #define PAN_START_THRESHOLD_MS 50
  14. #define PAN_POWER_ACC_THRESHOLD_MS 600
  15. #define PAN_POWER_MULTIPLIER 1.3f
  16. #define SCROLL_DECAY 200.0f
  17. #define SF_GATE_THRESHOLD 0.01f
  18. // == TBScrollerFunction ================================================================
  19. // Lab: http://www.madtealab.com/?V=1&C=6&F=5&G=1&O=1&W=774&GW=720&GH=252&GX=13.389616776278201&GY=4.790704772336853&GS=0.13102127484993598&EH=189&a=3.6666666666666665&aMa=20&aN=OrgSpeed&bMa=3&bN=CurPos&c=8&cMa=60&cI=1&cN=FrameRate&d=16&dMa=16&dI=1&dN=numSimulatedSeconds&l=2.388888888888889&lMa=5&lN=Decay&m=0.1&mMa=0.1&mN=GateThreshold&f1=OrgSpeed+%2A+exp%28-x+%2F+Decay%29&f1N=Speed&f2=CurPos+%2B+OrgSpeed+%2A+%281-exp%28-x+%2F+Decay%29%29%2A+Decay&f2N=Pos&f3=marker%28x%2C+predictGatedPoint%29&f3N=GatePoint&f4=aToF%28simulatedPoints%2Cnearest%2C0%2CnumSimulatedSeconds%29%28x%29&f4N=Iterated&f5=OrgSpeed+%2A+x&f5N=Linear1&Expr=%0ApredictGatedPoint+%3D+-log%28GateThreshold+%2F+%28OrgSpeed%29%29+%2A+Decay%0A%0Avar+cur+%3D+OrgSpeed%0AsimulatedPoints+%3D+sample%28function%28%29+%7B%0A+++cur+%3D+cur+%2A+%281+-+0.05%29%3B%0A+++return+cur%0A+%7D%2C+%5BnumSimulatedSeconds+%2A+FrameRate%5D%29%3B%0A%0ApredictGatedPoint
  20. float TBScrollerFunction::GetDurationFromSpeed(float start_speed)
  21. {
  22. float abs_start_speed = ABS(start_speed);
  23. if (abs_start_speed <= SF_GATE_THRESHOLD)
  24. return 0;
  25. return -log(SF_GATE_THRESHOLD / abs_start_speed) * m_decay;
  26. }
  27. float TBScrollerFunction::GetSpeedFromDistance(float distance)
  28. {
  29. float speed = distance / m_decay;
  30. if (distance > SF_GATE_THRESHOLD)
  31. return speed + SF_GATE_THRESHOLD;
  32. else if (distance < -SF_GATE_THRESHOLD)
  33. return speed - SF_GATE_THRESHOLD;
  34. return speed;
  35. }
  36. float TBScrollerFunction::GetDistanceAtTime(float start_speed, float elapsed_time_ms)
  37. {
  38. assert(elapsed_time_ms >= 0);
  39. return start_speed * (1 - exp(-elapsed_time_ms / m_decay)) * m_decay;
  40. }
  41. int TBScrollerFunction::GetDistanceAtTimeInt(float start_speed, float elapsed_time_ms)
  42. {
  43. float distance = GetDistanceAtTime(start_speed, elapsed_time_ms);
  44. return (int)(distance < 0 ? distance - 0.5f : distance + 0.5f);
  45. }
  46. // == TBScroller ========================================================================
  47. TBScroller::TBScroller(TBWidget *target)
  48. : m_target(target)
  49. , m_snap_listener(nullptr)
  50. , m_func(SCROLL_DECAY)
  51. , m_previous_pan_dx(0)
  52. , m_previous_pan_dy(0)
  53. , m_scroll_start_ms(0)
  54. , m_scroll_duration_x_ms(0)
  55. , m_scroll_duration_y_ms(0)
  56. , m_pan_power_multiplier_x(1)
  57. , m_pan_power_multiplier_y(1)
  58. {
  59. Reset();
  60. }
  61. TBScroller::~TBScroller()
  62. {
  63. }
  64. void TBScroller::Reset()
  65. {
  66. m_is_started = false;
  67. m_pan_dx = m_pan_dy = 0;
  68. m_pan_time_ms = 0;
  69. m_pan_delta_time_ms = 0;
  70. m_scroll_start_speed_ppms_x = m_scroll_start_speed_ppms_y = 0;
  71. m_scroll_start_scroll_x = m_scroll_start_scroll_y = 0;
  72. // don't reset m_previous_pan_dx and m_previous_pan_dy here.
  73. // don't reset m_pan_power here. It's done on start since it's needed for next pan!
  74. m_expected_scroll_x = m_expected_scroll_y = 0;
  75. }
  76. void TBScroller::OnScrollBy(int dx, int dy, bool accumulative)
  77. {
  78. if (!IsStarted())
  79. Start();
  80. float ppms_x = m_func.GetSpeedFromDistance((float)dx);
  81. float ppms_y = m_func.GetSpeedFromDistance((float)dy);
  82. if (accumulative && IsScrolling())
  83. {
  84. TBWidget::ScrollInfo info = m_target->GetScrollInfo();
  85. // If new direction is the same as the current direction,
  86. // calculate the speed needed for the remaining part and
  87. // add that to the new scroll speed.
  88. if ((ppms_x < 0) == (m_scroll_start_speed_ppms_x < 0))
  89. {
  90. int distance_x = m_func.GetDistanceAtTimeInt(m_scroll_start_speed_ppms_x,
  91. m_func.GetDurationFromSpeed(m_scroll_start_speed_ppms_x));
  92. int distance_remaining_x = m_scroll_start_scroll_x + distance_x - info.x;
  93. distance_remaining_x += m_func.GetDistanceAtTimeInt(ppms_x, m_func.GetDurationFromSpeed(ppms_x));
  94. ppms_x = m_func.GetSpeedFromDistance((float)distance_remaining_x);
  95. }
  96. if ((ppms_y < 0) == (m_scroll_start_speed_ppms_y < 0))
  97. {
  98. int distance_y = m_func.GetDistanceAtTimeInt(m_scroll_start_speed_ppms_y,
  99. m_func.GetDurationFromSpeed(m_scroll_start_speed_ppms_y));
  100. int distance_remaining_y = m_scroll_start_scroll_y + distance_y - info.y;
  101. distance_remaining_y += m_func.GetDistanceAtTimeInt(ppms_y, m_func.GetDurationFromSpeed(ppms_y));
  102. ppms_y = m_func.GetSpeedFromDistance((float)distance_remaining_y);
  103. }
  104. }
  105. AdjustToSnappingAndScroll(ppms_x, ppms_y);
  106. }
  107. bool TBScroller::OnPan(int dx, int dy)
  108. {
  109. if (!IsStarted())
  110. Start();
  111. // Pan the target
  112. const int in_dx = dx, in_dy = dy;
  113. m_target->ScrollByRecursive(dx, dy);
  114. // Calculate the pan speed. Smooth it out with the
  115. // previous pan speed to reduce fluctuation a little.
  116. double now_ms = TBSystem::GetTimeMS();
  117. if (m_pan_time_ms)
  118. {
  119. if (m_pan_delta_time_ms)
  120. m_pan_delta_time_ms = (now_ms - m_pan_time_ms + m_pan_delta_time_ms) / 2.0f;
  121. else
  122. m_pan_delta_time_ms = now_ms - m_pan_time_ms;
  123. }
  124. m_pan_time_ms = now_ms;
  125. m_pan_dx = (m_pan_dx + in_dx) / 2.0f;
  126. m_pan_dy = (m_pan_dy + in_dy) / 2.0f;
  127. // If we change direction, reset the pan power multiplier in that axis.
  128. if (m_pan_dx != 0 && (m_previous_pan_dx < 0) != (m_pan_dx < 0))
  129. m_pan_power_multiplier_x = 1;
  130. if (m_pan_dy != 0 && (m_previous_pan_dy < 0) != (m_pan_dy < 0))
  131. m_pan_power_multiplier_y = 1;
  132. m_previous_pan_dx = m_pan_dx;
  133. m_previous_pan_dy = m_pan_dy;
  134. return in_dx != dx || in_dy != dy;
  135. }
  136. void TBScroller::OnPanReleased()
  137. {
  138. if (TBSystem::GetTimeMS() < m_pan_time_ms + PAN_START_THRESHOLD_MS)
  139. {
  140. // Don't start scroll if we have too little speed.
  141. // This will prevent us from scrolling accidently.
  142. float pan_start_distance_threshold_px = 2 * TBSystem::GetDPI() / 100.0f;
  143. if (ABS(m_pan_dx) < pan_start_distance_threshold_px && ABS(m_pan_dy) < pan_start_distance_threshold_px)
  144. {
  145. StopOrSnapScroll();
  146. return;
  147. }
  148. if (m_pan_delta_time_ms == 0)
  149. {
  150. StopOrSnapScroll();
  151. return;
  152. }
  153. float ppms_x = (float)m_pan_dx / (float)m_pan_delta_time_ms;
  154. float ppms_y = (float)m_pan_dy / (float)m_pan_delta_time_ms;
  155. ppms_x *= m_pan_power_multiplier_x;
  156. ppms_y *= m_pan_power_multiplier_y;
  157. AdjustToSnappingAndScroll(ppms_x, ppms_y);
  158. }
  159. else
  160. StopOrSnapScroll();
  161. }
  162. void TBScroller::Start()
  163. {
  164. if (IsStarted())
  165. return;
  166. m_is_started = true;
  167. double now_ms = TBSystem::GetTimeMS();
  168. if (now_ms < m_scroll_start_ms + PAN_POWER_ACC_THRESHOLD_MS)
  169. {
  170. m_pan_power_multiplier_x *= PAN_POWER_MULTIPLIER;
  171. m_pan_power_multiplier_y *= PAN_POWER_MULTIPLIER;
  172. }
  173. else
  174. {
  175. m_pan_power_multiplier_x = m_pan_power_multiplier_y = 1;
  176. }
  177. }
  178. void TBScroller::Stop()
  179. {
  180. DeleteAllMessages();
  181. Reset();
  182. }
  183. bool TBScroller::StopIfAlmostStill()
  184. {
  185. double now_ms = TBSystem::GetTimeMS();
  186. if (now_ms > m_scroll_start_ms + (double)m_scroll_duration_x_ms &&
  187. now_ms > m_scroll_start_ms + (double)m_scroll_duration_y_ms)
  188. {
  189. Stop();
  190. return true;
  191. }
  192. return false;
  193. }
  194. void TBScroller::StopOrSnapScroll()
  195. {
  196. AdjustToSnappingAndScroll(0, 0);
  197. if (!IsScrolling())
  198. Stop();
  199. }
  200. void TBScroller::AdjustToSnappingAndScroll(float ppms_x, float ppms_y)
  201. {
  202. if (m_snap_listener)
  203. {
  204. // Calculate the distance
  205. int distance_x = m_func.GetDistanceAtTimeInt(ppms_x, m_func.GetDurationFromSpeed(ppms_x));
  206. int distance_y = m_func.GetDistanceAtTimeInt(ppms_y, m_func.GetDurationFromSpeed(ppms_y));
  207. // Let the snap listener modify the distance
  208. TBWidget::ScrollInfo info = m_target->GetScrollInfo();
  209. int target_x = distance_x + info.x;
  210. int target_y = distance_y + info.y;
  211. m_snap_listener->OnScrollSnap(m_target, target_x, target_y);
  212. distance_x = target_x - info.x;
  213. distance_y = target_y - info.y;
  214. // Get the start speed from the new distance
  215. ppms_x = m_func.GetSpeedFromDistance((float)distance_x);
  216. ppms_y = m_func.GetSpeedFromDistance((float)distance_y);
  217. }
  218. Scroll(ppms_x, ppms_y);
  219. }
  220. void TBScroller::Scroll(float start_speed_ppms_x, float start_speed_ppms_y)
  221. {
  222. // Set start values
  223. m_scroll_start_ms = TBSystem::GetTimeMS();
  224. GetTargetScrollXY(m_scroll_start_scroll_x, m_scroll_start_scroll_y);
  225. m_scroll_start_speed_ppms_x = start_speed_ppms_x;
  226. m_scroll_start_speed_ppms_y = start_speed_ppms_y;
  227. // Calculate duration for the scroll (each axis independently)
  228. m_scroll_duration_x_ms = m_func.GetDurationFromSpeed(m_scroll_start_speed_ppms_x);
  229. m_scroll_duration_y_ms = m_func.GetDurationFromSpeed(m_scroll_start_speed_ppms_y);
  230. if (StopIfAlmostStill())
  231. return;
  232. // Post the pan message if we don't already have one
  233. if (!GetMessageByID(TBIDC("scroll")))
  234. {
  235. // Update expected translation
  236. GetTargetChildTranslation(m_expected_scroll_x, m_expected_scroll_y);
  237. PostMessageDelayed(TBIDC("scroll"), nullptr, (uint32)PAN_MSG_DELAY_MS);
  238. }
  239. }
  240. bool TBScroller::IsScrolling()
  241. {
  242. return GetMessageByID(TBIDC("scroll")) ? true : false;
  243. }
  244. void TBScroller::GetTargetChildTranslation(int &x, int &y) const
  245. {
  246. int root_x = 0, root_y = 0;
  247. int child_translation_x = 0, child_translation_y = 0;
  248. TBWidget *scroll_root = m_target->GetScrollRoot();
  249. scroll_root->ConvertToRoot(root_x, root_y);
  250. scroll_root->GetChildTranslation(child_translation_x, child_translation_y);
  251. x = root_x + child_translation_x;
  252. y = root_y + child_translation_y;
  253. }
  254. void TBScroller::GetTargetScrollXY(int &x, int &y) const
  255. {
  256. x = 0;
  257. y = 0;
  258. TBWidget *tmp = m_target->GetScrollRoot();
  259. while (tmp)
  260. {
  261. TBWidget::ScrollInfo info = tmp->GetScrollInfo();
  262. x += info.x;
  263. y += info.y;
  264. tmp = tmp->GetParent();
  265. }
  266. }
  267. void TBScroller::OnMessageReceived(TBMessage *msg)
  268. {
  269. if (msg->message == TBIDC("scroll"))
  270. {
  271. int actual_scroll_x = 0, actual_scroll_y = 0;
  272. GetTargetChildTranslation(actual_scroll_x, actual_scroll_y);
  273. if (actual_scroll_x != m_expected_scroll_x ||
  274. actual_scroll_y != m_expected_scroll_y)
  275. {
  276. // Something else has affected the target child translation.
  277. // This should abort the scroll.
  278. // This could happen f.ex if something shrunk the scroll limits,
  279. // some other action changed scroll position, or if another
  280. // scroller started operating on a sub child that when reacing
  281. // its scroll limit, started scrolling its chain of parents.
  282. Stop();
  283. return;
  284. }
  285. // Calculate the time elapsed from scroll start. Clip within the
  286. // duration for each axis.
  287. double now_ms = TBSystem::GetTimeMS();
  288. float elapsed_time_x = (float)(now_ms - m_scroll_start_ms);
  289. float elapsed_time_y = elapsed_time_x;
  290. elapsed_time_x = MIN(elapsed_time_x, m_scroll_duration_x_ms);
  291. elapsed_time_y = MIN(elapsed_time_y, m_scroll_duration_y_ms);
  292. // Get the new scroll position from the current distance in each axis.
  293. int scroll_x = m_func.GetDistanceAtTimeInt(m_scroll_start_speed_ppms_x, elapsed_time_x);
  294. int scroll_y = m_func.GetDistanceAtTimeInt(m_scroll_start_speed_ppms_y, elapsed_time_y);
  295. scroll_x += m_scroll_start_scroll_x;
  296. scroll_y += m_scroll_start_scroll_y;
  297. // Get the scroll delta and invoke ScrollByRecursive.
  298. int curr_scroll_x, curr_scroll_y;
  299. GetTargetScrollXY(curr_scroll_x, curr_scroll_y);
  300. const int dx = scroll_x - curr_scroll_x;
  301. const int dy = scroll_y - curr_scroll_y;
  302. int idx = dx, idy = dy;
  303. m_target->ScrollByRecursive(idx, idy);
  304. // Update expected translation
  305. GetTargetChildTranslation(m_expected_scroll_x, m_expected_scroll_y);
  306. if ((dx && actual_scroll_x == m_expected_scroll_x) &&
  307. (dy && actual_scroll_y == m_expected_scroll_y))
  308. {
  309. // We didn't get anywhere despite we tried,
  310. // so we're done (reached the end).
  311. Stop();
  312. return;
  313. }
  314. if (!StopIfAlmostStill())
  315. {
  316. double next_fire_time = msg->GetFireTime() + PAN_MSG_DELAY_MS;
  317. // avoid timer catch-up if program went sleeping for a while.
  318. next_fire_time = MAX(next_fire_time, now_ms);
  319. PostMessageOnTime(TBIDC("scroll"), nullptr, next_fire_time);
  320. }
  321. }
  322. }
  323. }; // namespace tb