InputSourceGameController.cpp 9.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330
  1. /*
  2. * InputSourceGameController.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 "InputSourceGameController.h"
  12. #include "InputHandler.h"
  13. #include "../CGameInfo.h"
  14. #include "../gui/CGuiHandler.h"
  15. #include "../gui/CursorHandler.h"
  16. #include "../gui/EventDispatcher.h"
  17. #include "../gui/ShortcutHandler.h"
  18. #include "../render/IScreenHandler.h"
  19. #include "../../lib/CConfigHandler.h"
  20. void InputSourceGameController::gameControllerDeleter(SDL_GameController * gameController)
  21. {
  22. if(gameController)
  23. SDL_GameControllerClose(gameController);
  24. }
  25. InputSourceGameController::InputSourceGameController():
  26. cursorAxisValueX(0),
  27. cursorAxisValueY(0),
  28. cursorPlanDisX(0.0),
  29. cursorPlanDisY(0.0),
  30. scrollAxisMoved(false),
  31. scrollStart(Point(0,0)),
  32. scrollCurrent(Point(0,0)),
  33. scrollAxisValueX(0),
  34. scrollAxisValueY(0),
  35. scrollPlanDisX(0.0),
  36. scrollPlanDisY(0.0),
  37. configTriggerThreshold(settings["input"]["controllerTriggerThreshold"].Float()),
  38. configAxisDeadZone(settings["input"]["controllerAxisDeadZone"].Float()),
  39. configAxisFullZone(settings["input"]["controllerAxisFullZone"].Float()),
  40. configAxisSpeed(settings["input"]["controllerAxisSpeed"].Float()),
  41. configAxisScale(settings["input"]["controllerAxisScale"].Float())
  42. {
  43. tryOpenAllGameControllers();
  44. }
  45. void InputSourceGameController::tryOpenAllGameControllers()
  46. {
  47. for(int i = 0; i < SDL_NumJoysticks(); ++i)
  48. if(SDL_IsGameController(i))
  49. openGameController(i);
  50. else
  51. logGlobal->warn("Joystick %d is an unsupported game controller!", i);
  52. }
  53. void InputSourceGameController::openGameController(int index)
  54. {
  55. SDL_GameController * controller = SDL_GameControllerOpen(index);
  56. if(!controller)
  57. {
  58. logGlobal->error("Fail to open game controller %d!", index);
  59. return;
  60. }
  61. GameControllerPtr controllerPtr(controller, &gameControllerDeleter);
  62. // Need to save joystick index for event. Joystick index may not be equal to index sometimes.
  63. int joystickIndex = getJoystickIndex(controllerPtr.get());
  64. if(joystickIndex < 0)
  65. {
  66. logGlobal->error("Fail to get joystick index of game controller %d!", index);
  67. return;
  68. }
  69. if(gameControllerMap.find(joystickIndex) != gameControllerMap.end())
  70. {
  71. logGlobal->warn("Game controller with joystick index %d is already opened.", joystickIndex);
  72. return;
  73. }
  74. gameControllerMap.try_emplace(joystickIndex, std::move(controllerPtr));
  75. }
  76. int InputSourceGameController::getJoystickIndex(SDL_GameController * controller)
  77. {
  78. SDL_Joystick * joystick = SDL_GameControllerGetJoystick(controller);
  79. if(!joystick)
  80. return -1;
  81. SDL_JoystickID instanceID = SDL_JoystickInstanceID(joystick);
  82. if(instanceID < 0)
  83. return -1;
  84. return instanceID;
  85. }
  86. void InputSourceGameController::handleEventDeviceAdded(const SDL_ControllerDeviceEvent & device)
  87. {
  88. if(gameControllerMap.find(device.which) != gameControllerMap.end())
  89. {
  90. logGlobal->warn("Game controller %d is already opened.", device.which);
  91. return;
  92. }
  93. openGameController(device.which);
  94. }
  95. void InputSourceGameController::handleEventDeviceRemoved(const SDL_ControllerDeviceEvent & device)
  96. {
  97. if(gameControllerMap.find(device.which) == gameControllerMap.end())
  98. {
  99. logGlobal->warn("Game controller %d is not opened before.", device.which);
  100. return;
  101. }
  102. gameControllerMap.erase(device.which);
  103. }
  104. void InputSourceGameController::handleEventDeviceRemapped(const SDL_ControllerDeviceEvent & device)
  105. {
  106. if(gameControllerMap.find(device.which) == gameControllerMap.end())
  107. {
  108. logGlobal->warn("Game controller %d is not opened.", device.which);
  109. return;
  110. }
  111. gameControllerMap.erase(device.which);
  112. openGameController(device.which);
  113. }
  114. double InputSourceGameController::getRealAxisValue(int value) const
  115. {
  116. double ratio = static_cast<double>(value) / SDL_JOYSTICK_AXIS_MAX;
  117. double greenZone = configAxisFullZone - configAxisDeadZone;
  118. if (std::abs(ratio) < configAxisDeadZone)
  119. return 0;
  120. double scaledValue = (ratio - configAxisDeadZone) / greenZone;
  121. double clampedValue = std::clamp(scaledValue, -1.0, +1.0);
  122. return clampedValue;
  123. }
  124. void InputSourceGameController::dispatchAxisShortcuts(const std::vector<EShortcut> & shortcutsVector, SDL_GameControllerAxis axisID, int axisValue)
  125. {
  126. if(getRealAxisValue(axisValue) > configTriggerThreshold)
  127. {
  128. if(!pressedAxes.count(axisID))
  129. {
  130. GH.events().dispatchShortcutPressed(shortcutsVector);
  131. pressedAxes.insert(axisID);
  132. }
  133. }
  134. else
  135. {
  136. if(pressedAxes.count(axisID))
  137. {
  138. GH.events().dispatchShortcutReleased(shortcutsVector);
  139. pressedAxes.erase(axisID);
  140. }
  141. }
  142. }
  143. void InputSourceGameController::handleEventAxisMotion(const SDL_ControllerAxisEvent & axis)
  144. {
  145. tryToConvertCursor();
  146. SDL_GameControllerAxis axisID = static_cast<SDL_GameControllerAxis>(axis.axis);
  147. std::string axisName = SDL_GameControllerGetStringForAxis(axisID);
  148. auto axisActions = GH.shortcuts().translateJoystickAxis(axisName);
  149. auto buttonActions = GH.shortcuts().translateJoystickButton(axisName);
  150. for(const auto & action : axisActions)
  151. {
  152. switch(action)
  153. {
  154. case EShortcut::MOUSE_CURSOR_X:
  155. cursorAxisValueX = getRealAxisValue(axis.value);
  156. break;
  157. case EShortcut::MOUSE_CURSOR_Y:
  158. cursorAxisValueY = getRealAxisValue(axis.value);
  159. break;
  160. case EShortcut::MOUSE_SWIPE_X:
  161. scrollAxisValueX = getRealAxisValue(axis.value);
  162. break;
  163. case EShortcut::MOUSE_SWIPE_Y:
  164. scrollAxisValueY = getRealAxisValue(axis.value);
  165. break;
  166. }
  167. }
  168. dispatchAxisShortcuts(buttonActions, axisID, axis.value);
  169. }
  170. void InputSourceGameController::tryToConvertCursor()
  171. {
  172. assert(CCS);
  173. assert(CCS->curh);
  174. if(CCS->curh->getShowType() == Cursor::ShowType::HARDWARE)
  175. {
  176. int ScaleFactor = GH.screenHandler().getScalingFactor();
  177. const Point & cursorPosition = GH.getCursorPosition();
  178. CCS->curh->changeCursor(Cursor::ShowType::SOFTWARE);
  179. CCS->curh->cursorMove(cursorPosition.x * ScaleFactor, cursorPosition.y * ScaleFactor);
  180. GH.input().setCursorPosition(cursorPosition);
  181. }
  182. }
  183. void InputSourceGameController::handleEventButtonDown(const SDL_ControllerButtonEvent & button)
  184. {
  185. std::string buttonName = SDL_GameControllerGetStringForButton(static_cast<SDL_GameControllerButton>(button.button));
  186. const auto & shortcutsVector = GH.shortcuts().translateJoystickButton(buttonName);
  187. GH.events().dispatchShortcutPressed(shortcutsVector);
  188. }
  189. void InputSourceGameController::handleEventButtonUp(const SDL_ControllerButtonEvent & button)
  190. {
  191. std::string buttonName = SDL_GameControllerGetStringForButton(static_cast<SDL_GameControllerButton>(button.button));
  192. const auto & shortcutsVector = GH.shortcuts().translateJoystickButton(buttonName);
  193. GH.events().dispatchShortcutReleased(shortcutsVector);
  194. }
  195. void InputSourceGameController::doCursorMove(int deltaX, int deltaY)
  196. {
  197. if(deltaX == 0 && deltaY == 0)
  198. return;
  199. const Point & screenSize = GH.screenDimensions();
  200. const Point & cursorPosition = GH.getCursorPosition();
  201. int ScaleFactor = GH.screenHandler().getScalingFactor();
  202. int newX = std::min(std::max(cursorPosition.x + deltaX, 0), screenSize.x);
  203. int newY = std::min(std::max(cursorPosition.y + deltaY, 0), screenSize.y);
  204. Point targetPosition{newX, newY};
  205. GH.input().setCursorPosition(targetPosition);
  206. if(CCS && CCS->curh)
  207. CCS->curh->cursorMove(GH.getCursorPosition().x * ScaleFactor, GH.getCursorPosition().y * ScaleFactor);
  208. }
  209. int InputSourceGameController::getMoveDis(float planDis)
  210. {
  211. if(planDis >= 0)
  212. return std::floor(planDis);
  213. else
  214. return std::ceil(planDis);
  215. }
  216. void InputSourceGameController::handleUpdate()
  217. {
  218. std::chrono::steady_clock::time_point nowMs = std::chrono::steady_clock::now();
  219. if(lastCheckTime == std::chrono::steady_clock::time_point())
  220. {
  221. lastCheckTime = nowMs;
  222. return;
  223. }
  224. int32_t deltaTime = std::chrono::duration_cast<std::chrono::milliseconds>(nowMs - lastCheckTime).count();
  225. handleCursorUpdate(deltaTime);
  226. handleScrollUpdate(deltaTime);
  227. lastCheckTime = nowMs;
  228. }
  229. static double scaleAxis(double value, double power)
  230. {
  231. if (value > 0)
  232. return std::pow(value, power);
  233. else
  234. return -std::pow(-value, power);
  235. }
  236. void InputSourceGameController::handleCursorUpdate(int32_t deltaTimeMs)
  237. {
  238. float deltaTimeSeconds = static_cast<float>(deltaTimeMs) / 1000;
  239. if(vstd::isAlmostZero(cursorAxisValueX))
  240. cursorPlanDisX = 0;
  241. else
  242. cursorPlanDisX += deltaTimeSeconds * configAxisSpeed * scaleAxis(cursorAxisValueX, configAxisScale);
  243. if (vstd::isAlmostZero(cursorAxisValueY))
  244. cursorPlanDisY = 0;
  245. else
  246. cursorPlanDisY += deltaTimeSeconds * configAxisSpeed * scaleAxis(cursorAxisValueY, configAxisScale);
  247. int moveDisX = getMoveDis(cursorPlanDisX);
  248. int moveDisY = getMoveDis(cursorPlanDisY);
  249. cursorPlanDisX -= moveDisX;
  250. cursorPlanDisY -= moveDisY;
  251. doCursorMove(moveDisX, moveDisY);
  252. }
  253. void InputSourceGameController::handleScrollUpdate(int32_t deltaTimeMs)
  254. {
  255. if(!scrollAxisMoved && isScrollAxisReleased())
  256. {
  257. return;
  258. }
  259. else if(!scrollAxisMoved && !isScrollAxisReleased())
  260. {
  261. scrollAxisMoved = true;
  262. scrollCurrent = scrollStart = GH.input().getCursorPosition();
  263. GH.events().dispatchGesturePanningStarted(scrollStart);
  264. }
  265. else if(scrollAxisMoved && isScrollAxisReleased())
  266. {
  267. GH.events().dispatchGesturePanningEnded(scrollStart, scrollCurrent);
  268. scrollAxisMoved = false;
  269. scrollPlanDisX = scrollPlanDisY = 0;
  270. return;
  271. }
  272. float deltaTimeSeconds = static_cast<float>(deltaTimeMs) / 1000;
  273. scrollPlanDisX += deltaTimeSeconds * configAxisSpeed * scaleAxis(scrollAxisValueX, configAxisScale);
  274. scrollPlanDisY += deltaTimeSeconds * configAxisSpeed * scaleAxis(scrollAxisValueY, configAxisScale);
  275. int moveDisX = getMoveDis(scrollPlanDisX);
  276. int moveDisY = getMoveDis(scrollPlanDisY);
  277. if(moveDisX != 0 || moveDisY != 0)
  278. {
  279. scrollPlanDisX -= moveDisX;
  280. scrollPlanDisY -= moveDisY;
  281. scrollCurrent.x += moveDisX;
  282. scrollCurrent.y += moveDisY;
  283. Point distance(moveDisX, moveDisY);
  284. GH.events().dispatchGesturePanning(scrollStart, scrollCurrent, distance);
  285. }
  286. }
  287. bool InputSourceGameController::isScrollAxisReleased() const
  288. {
  289. return vstd::isAlmostZero(scrollAxisValueX) && vstd::isAlmostZero(scrollAxisValueY);
  290. }