InputSourceTouch.cpp 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375
  1. /*
  2. * InputSourceTouch.cpp, part of VCMI engine
  3. *
  4. * Authors: listed in file AUTHORS in main folder
  5. *
  6. * License: GNU General Public License v2.0 or later
  7. * Full text of license available in license.txt file, in main folder
  8. *
  9. */
  10. #include "StdInc.h"
  11. #include "InputSourceTouch.h"
  12. #include "InputHandler.h"
  13. #include "../../lib/CConfigHandler.h"
  14. #include "../gui/CursorHandler.h"
  15. #include "../GameEngine.h"
  16. #include "../GameEngineUser.h"
  17. #include "../gui/EventDispatcher.h"
  18. #include "../gui/MouseButton.h"
  19. #include "../gui/WindowHandler.h"
  20. #include "../render/IScreenHandler.h"
  21. #if defined(VCMI_ANDROID)
  22. #include "../../lib/CAndroidVMHelper.h"
  23. #elif defined(VCMI_IOS)
  24. #include "../ios/utils.h"
  25. #endif
  26. #include <SDL_events.h>
  27. #include <SDL_hints.h>
  28. #include <SDL_timer.h>
  29. InputSourceTouch::InputSourceTouch()
  30. : lastTapTimeTicks(0), lastLeftClickTimeTicks(0), numTouchFingers(0)
  31. {
  32. params.useRelativeMode = settings["general"]["userRelativePointer"].Bool();
  33. params.relativeModeSpeedFactor = settings["general"]["relativePointerSpeedMultiplier"].Float();
  34. params.longTouchTimeMilliseconds = settings["general"]["longTouchTimeMilliseconds"].Float();
  35. params.hapticFeedbackEnabled = settings["general"]["hapticFeedback"].Bool();
  36. params.touchToleranceDistance = settings["input"]["touchToleranceDistance"].Float();
  37. if (params.useRelativeMode)
  38. state = TouchState::RELATIVE_MODE;
  39. else
  40. state = TouchState::IDLE;
  41. #ifdef VCMI_EMULATE_TOUCHSCREEN_WITH_MOUSE
  42. SDL_SetHint(SDL_HINT_MOUSE_TOUCH_EVENTS, "1");
  43. #else
  44. SDL_SetHint(SDL_HINT_MOUSE_TOUCH_EVENTS, "0");
  45. #endif
  46. SDL_SetHint(SDL_HINT_TOUCH_MOUSE_EVENTS, "0");
  47. }
  48. void InputSourceTouch::handleEventFingerMotion(const SDL_TouchFingerEvent & tfinger)
  49. {
  50. Point screenSize = ENGINE->screenDimensions();
  51. motionAccumulatedX[tfinger.fingerId] += tfinger.dx;
  52. motionAccumulatedY[tfinger.fingerId] += tfinger.dy;
  53. float motionThreshold = 1.0 / std::min(screenSize.x, screenSize.y);
  54. if(std::abs(motionAccumulatedX[tfinger.fingerId]) < motionThreshold && std::abs(motionAccumulatedY[tfinger.fingerId]) < motionThreshold)
  55. return;
  56. int scalingFactor = ENGINE->screenHandler().getScalingFactor();
  57. if (settings["video"]["cursor"].String() == "software" && state != TouchState::RELATIVE_MODE)
  58. {
  59. Point cursorPosition = Point(tfinger.x * screenSize.x, tfinger.y * screenSize.y) * scalingFactor;
  60. ENGINE->cursor().cursorMove(cursorPosition.x, cursorPosition.y);
  61. }
  62. switch(state)
  63. {
  64. case TouchState::RELATIVE_MODE:
  65. {
  66. Point moveDistance {
  67. static_cast<int>(screenSize.x * params.relativeModeSpeedFactor * motionAccumulatedX[tfinger.fingerId]),
  68. static_cast<int>(screenSize.y * params.relativeModeSpeedFactor * motionAccumulatedY[tfinger.fingerId])
  69. };
  70. ENGINE->input().moveCursorPosition(moveDistance);
  71. ENGINE->cursor().cursorMove(ENGINE->getCursorPosition().x * scalingFactor, ENGINE->getCursorPosition().y * scalingFactor);
  72. break;
  73. }
  74. case TouchState::IDLE:
  75. {
  76. // no-op, might happen in some edge cases, e.g. when fingerdown event was ignored
  77. break;
  78. }
  79. case TouchState::TAP_DOWN_SHORT:
  80. case TouchState::TAP_DOWN_LONG_AWAIT:
  81. {
  82. Point distance = convertTouchToMouse(tfinger) - lastTapPosition;
  83. if ( std::abs(distance.x) > params.panningSensitivityThreshold || std::abs(distance.y) > params.panningSensitivityThreshold)
  84. {
  85. state = state == TouchState::TAP_DOWN_SHORT ? TouchState::TAP_DOWN_PANNING : TouchState::TAP_DOWN_PANNING_POPUP;
  86. ENGINE->events().dispatchGesturePanningStarted(lastTapPosition);
  87. }
  88. break;
  89. }
  90. case TouchState::TAP_DOWN_PANNING:
  91. case TouchState::TAP_DOWN_PANNING_POPUP:
  92. {
  93. emitPanningEvent(tfinger);
  94. break;
  95. }
  96. case TouchState::TAP_DOWN_DOUBLE:
  97. {
  98. emitPinchEvent(tfinger);
  99. break;
  100. }
  101. case TouchState::TAP_DOWN_LONG:
  102. {
  103. // no-op
  104. break;
  105. }
  106. }
  107. if(std::abs(motionAccumulatedX[tfinger.fingerId]) >= motionThreshold)
  108. motionAccumulatedX[tfinger.fingerId] = 0;
  109. if(std::abs(motionAccumulatedY[tfinger.fingerId]) >= motionThreshold)
  110. motionAccumulatedY[tfinger.fingerId] = 0;
  111. }
  112. void InputSourceTouch::handleEventFingerDown(const SDL_TouchFingerEvent & tfinger)
  113. {
  114. numTouchFingers = SDL_GetNumTouchFingers(tfinger.touchId);
  115. // FIXME: better place to update potentially changed settings?
  116. params.longTouchTimeMilliseconds = settings["general"]["longTouchTimeMilliseconds"].Float();
  117. params.hapticFeedbackEnabled = settings["general"]["hapticFeedback"].Bool();
  118. lastTapTimeTicks = tfinger.timestamp;
  119. if (settings["video"]["cursor"].String() == "software" && state != TouchState::RELATIVE_MODE)
  120. {
  121. int scalingFactor = ENGINE->screenHandler().getScalingFactor();
  122. Point screenSize = ENGINE->screenDimensions();
  123. Point cursorPosition = Point(tfinger.x * screenSize.x, tfinger.y * screenSize.y) * scalingFactor;
  124. ENGINE->cursor().cursorMove(cursorPosition.x, cursorPosition.y);
  125. }
  126. switch(state)
  127. {
  128. case TouchState::RELATIVE_MODE:
  129. {
  130. if(tfinger.x > 0.5)
  131. {
  132. if (tfinger.y < 0.5)
  133. ENGINE->events().dispatchShowPopup(ENGINE->getCursorPosition(), params.touchToleranceDistance);
  134. else
  135. ENGINE->events().dispatchMouseLeftButtonPressed(ENGINE->getCursorPosition(), params.touchToleranceDistance);
  136. }
  137. break;
  138. }
  139. case TouchState::IDLE:
  140. {
  141. lastTapPosition = convertTouchToMouse(tfinger);
  142. ENGINE->input().setCursorPosition(lastTapPosition);
  143. state = TouchState::TAP_DOWN_SHORT;
  144. break;
  145. }
  146. case TouchState::TAP_DOWN_SHORT:
  147. {
  148. ENGINE->input().setCursorPosition(convertTouchToMouse(tfinger));
  149. ENGINE->events().dispatchGesturePanningStarted(lastTapPosition);
  150. state = TouchState::TAP_DOWN_DOUBLE;
  151. break;
  152. }
  153. case TouchState::TAP_DOWN_PANNING:
  154. {
  155. ENGINE->input().setCursorPosition(convertTouchToMouse(tfinger));
  156. state = TouchState::TAP_DOWN_DOUBLE;
  157. break;
  158. }
  159. case TouchState::TAP_DOWN_DOUBLE:
  160. {
  161. ENGINE->user().onGlobalLobbyInterfaceActivated();
  162. break;
  163. }
  164. case TouchState::TAP_DOWN_LONG_AWAIT:
  165. lastTapPosition = convertTouchToMouse(tfinger);
  166. break;
  167. case TouchState::TAP_DOWN_LONG:
  168. case TouchState::TAP_DOWN_PANNING_POPUP:
  169. {
  170. // no-op
  171. break;
  172. }
  173. }
  174. }
  175. void InputSourceTouch::handleEventFingerUp(const SDL_TouchFingerEvent & tfinger)
  176. {
  177. numTouchFingers = SDL_GetNumTouchFingers(tfinger.touchId);
  178. switch(state)
  179. {
  180. case TouchState::RELATIVE_MODE:
  181. {
  182. if(tfinger.x > 0.5)
  183. {
  184. if (tfinger.y < 0.5)
  185. ENGINE->events().dispatchClosePopup(ENGINE->getCursorPosition());
  186. else
  187. ENGINE->events().dispatchMouseLeftButtonReleased(ENGINE->getCursorPosition(), params.touchToleranceDistance);
  188. }
  189. break;
  190. }
  191. case TouchState::IDLE:
  192. {
  193. // no-op, might happen in some edge cases, e.g. when fingerdown event was ignored
  194. break;
  195. }
  196. case TouchState::TAP_DOWN_SHORT:
  197. {
  198. ENGINE->input().setCursorPosition(convertTouchToMouse(tfinger));
  199. if(tfinger.timestamp - lastLeftClickTimeTicks < params.doubleTouchTimeMilliseconds && (convertTouchToMouse(tfinger) - lastLeftClickPosition).length() < params.doubleTouchToleranceDistance)
  200. {
  201. ENGINE->events().dispatchMouseDoubleClick(convertTouchToMouse(tfinger), params.touchToleranceDistance);
  202. ENGINE->events().dispatchMouseLeftButtonReleased(convertTouchToMouse(tfinger), params.touchToleranceDistance);
  203. }
  204. else
  205. {
  206. ENGINE->events().dispatchMouseLeftButtonPressed(convertTouchToMouse(tfinger), params.touchToleranceDistance);
  207. ENGINE->events().dispatchMouseLeftButtonReleased(convertTouchToMouse(tfinger), params.touchToleranceDistance);
  208. lastLeftClickTimeTicks = tfinger.timestamp;
  209. lastLeftClickPosition = convertTouchToMouse(tfinger);
  210. }
  211. state = TouchState::IDLE;
  212. break;
  213. }
  214. case TouchState::TAP_DOWN_PANNING:
  215. case TouchState::TAP_DOWN_PANNING_POPUP:
  216. {
  217. ENGINE->events().dispatchGesturePanningEnded(lastTapPosition, convertTouchToMouse(tfinger));
  218. state = state == TouchState::TAP_DOWN_PANNING ? TouchState::IDLE : TouchState::TAP_DOWN_LONG_AWAIT;
  219. break;
  220. }
  221. case TouchState::TAP_DOWN_DOUBLE:
  222. {
  223. if (SDL_GetNumTouchFingers(tfinger.touchId) == 1)
  224. state = TouchState::TAP_DOWN_PANNING;
  225. if (SDL_GetNumTouchFingers(tfinger.touchId) == 0)
  226. {
  227. ENGINE->events().dispatchGesturePanningEnded(lastTapPosition, convertTouchToMouse(tfinger));
  228. state = TouchState::IDLE;
  229. }
  230. break;
  231. }
  232. case TouchState::TAP_DOWN_LONG:
  233. {
  234. if (SDL_GetNumTouchFingers(tfinger.touchId) == 0)
  235. {
  236. state = TouchState::TAP_DOWN_LONG_AWAIT;
  237. }
  238. break;
  239. }
  240. case TouchState::TAP_DOWN_LONG_AWAIT:
  241. {
  242. if (SDL_GetNumTouchFingers(tfinger.touchId) == 0)
  243. {
  244. ENGINE->input().setCursorPosition(convertTouchToMouse(tfinger));
  245. ENGINE->events().dispatchClosePopup(convertTouchToMouse(tfinger));
  246. state = TouchState::IDLE;
  247. }
  248. break;
  249. }
  250. }
  251. }
  252. void InputSourceTouch::handleUpdate()
  253. {
  254. if ( state == TouchState::TAP_DOWN_SHORT)
  255. {
  256. uint32_t currentTime = SDL_GetTicks();
  257. if (currentTime > lastTapTimeTicks + params.longTouchTimeMilliseconds)
  258. {
  259. ENGINE->events().dispatchShowPopup(ENGINE->getCursorPosition(), params.touchToleranceDistance);
  260. if (ENGINE->windows().isTopWindowPopup())
  261. {
  262. hapticFeedback();
  263. state = TouchState::TAP_DOWN_LONG;
  264. }
  265. }
  266. }
  267. }
  268. Point InputSourceTouch::convertTouchToMouse(const SDL_TouchFingerEvent & tfinger)
  269. {
  270. return convertTouchToMouse(tfinger.x, tfinger.y);
  271. }
  272. Point InputSourceTouch::convertTouchToMouse(float x, float y)
  273. {
  274. return Point(x * ENGINE->screenDimensions().x, y * ENGINE->screenDimensions().y);
  275. }
  276. bool InputSourceTouch::hasTouchInputDevice() const
  277. {
  278. return SDL_GetNumTouchDevices() > 0;
  279. }
  280. int InputSourceTouch::getNumTouchFingers() const
  281. {
  282. return numTouchFingers;
  283. }
  284. void InputSourceTouch::emitPanningEvent(const SDL_TouchFingerEvent & tfinger)
  285. {
  286. Point distance = convertTouchToMouse(-motionAccumulatedX[tfinger.fingerId], -motionAccumulatedY[tfinger.fingerId]);
  287. ENGINE->events().dispatchGesturePanning(lastTapPosition, convertTouchToMouse(tfinger), distance);
  288. }
  289. void InputSourceTouch::emitPinchEvent(const SDL_TouchFingerEvent & tfinger)
  290. {
  291. int fingers = SDL_GetNumTouchFingers(tfinger.touchId);
  292. if (fingers < 2)
  293. return;
  294. bool otherFingerFound = false;
  295. double otherX;
  296. double otherY;
  297. for (int i = 0; i < fingers; ++i)
  298. {
  299. SDL_Finger * finger = SDL_GetTouchFinger(tfinger.touchId, i);
  300. if (finger && finger->id != tfinger.fingerId)
  301. {
  302. otherX = finger->x * ENGINE->screenDimensions().x;
  303. otherY = finger->y * ENGINE->screenDimensions().y;
  304. otherFingerFound = true;
  305. break;
  306. }
  307. }
  308. if (!otherFingerFound)
  309. return; // should be impossible, but better to avoid weird edge cases
  310. float thisX = tfinger.x * ENGINE->screenDimensions().x;
  311. float thisY = tfinger.y * ENGINE->screenDimensions().y;
  312. float deltaX = motionAccumulatedX[tfinger.fingerId] * ENGINE->screenDimensions().x;
  313. float deltaY = motionAccumulatedY[tfinger.fingerId] * ENGINE->screenDimensions().y;
  314. float oldX = thisX - deltaX - otherX;
  315. float oldY = thisY - deltaY - otherY;
  316. float newX = thisX - otherX;
  317. float newY = thisY - otherY;
  318. double distanceOld = std::sqrt(oldX * oldX + oldY * oldY);
  319. double distanceNew = std::sqrt(newX * newX + newY * newY);
  320. if (distanceOld > params.pinchSensitivityThreshold)
  321. ENGINE->events().dispatchGesturePinch(lastTapPosition, distanceNew / distanceOld);
  322. }
  323. void InputSourceTouch::hapticFeedback() {
  324. if(params.hapticFeedbackEnabled) {
  325. #if defined(VCMI_ANDROID)
  326. CAndroidVMHelper vmHelper;
  327. vmHelper.callStaticVoidMethod(CAndroidVMHelper::NATIVE_METHODS_DEFAULT_CLASS, "hapticFeedback");
  328. #elif defined(VCMI_IOS)
  329. iOS_utils::hapticFeedback();
  330. #endif
  331. }
  332. }