Explorar o código

Unified game controller input with keyboard/mouse

Ivan Savenko hai 1 ano
pai
achega
1dc27046ef

+ 1 - 3
client/CMakeLists.txt

@@ -35,7 +35,6 @@ set(client_SRCS
 	eventsSDL/InputSourceText.cpp
 	eventsSDL/InputSourceTouch.cpp
 	eventsSDL/InputSourceGameController.cpp
-	eventsSDL/GameControllerConfig.cpp
 
 	gui/CGuiHandler.cpp
 	gui/CIntObject.cpp
@@ -214,8 +213,7 @@ set(client_HEADERS
 	eventsSDL/InputSourceMouse.h
 	eventsSDL/InputSourceText.h
 	eventsSDL/InputSourceTouch.h
-    eventsSDL/InputSourceGameController.h
-	eventsSDL/GameControllerConfig.h
+	eventsSDL/InputSourceGameController.h
 
 	gui/CGuiHandler.h
 	gui/CIntObject.h

+ 0 - 212
client/eventsSDL/GameControllerConfig.cpp

@@ -1,212 +0,0 @@
-/*
-* GameControllerConfig.cpp, part of VCMI engine
-*
-* Authors: listed in file AUTHORS in main folder
-*
-* License: GNU General Public License v2.0 or later
-* Full text of license available in license.txt file, in main folder
-*
-*/
-#include <SDL.h>
-
-#include "StdInc.h"
-#include "GameControllerConfig.h"
-#include "../gui/CGuiHandler.h"
-#include "../gui/ShortcutHandler.h"
-
-
-GameControllerConfig::GameControllerConfig(): leftAxisType(AxisType::NONE), rightAxisType(AxisType::NONE)
-{
-	load();
-}
-
-void GameControllerConfig::load()
-{
-	const JsonNode config = JsonUtils::assembleFromFiles("config/shortcutsConfig");
-	for(auto const & entry : config["joystick"].Struct())
-	{
-		std::string configName = entry.first;
-		if(configName == "leftaxis")
-			leftAxisType = parseAxis(entry.first, entry.second);
-		else if (configName == "rightaxis")
-			rightAxisType = parseAxis(entry.first, entry.second);
-		else if (configName == "lefttrigger" || configName == "righttrigger")
-			parseTrigger(entry.first, entry.second);
-		else
-			parseButton(entry.first, entry.second);
-	}
-}
-
-AxisType GameControllerConfig::parseAxis(const std::string & key, const JsonNode & value)
-{
-	if(!value.isString())
-	{
-		logGlobal->error("The value of joystick config key %s should be a string!", key);
-		return AxisType::NONE;
-	}
-
-	std::string featureName = value.String();
-	if(featureName == "cursorMotion")
-		return AxisType::CURSOR_MOTION;
-	else if(featureName == "mapScroll")
-		return AxisType::MAP_SCROLL;
-	else if(featureName != "")
-		logGlobal->error("Unknown value %s of joystick config key %s!", featureName, key);
-	return AxisType::NONE;
-}
-
-void GameControllerConfig::parseTrigger(const std::string & key, const JsonNode & value)
-{
-	std::vector<std::string> operations = getOperations(key, value);
-	SDL_GameControllerAxis triggerAxis = key == "lefttrigger" ?
-											 SDL_CONTROLLER_AXIS_TRIGGERLEFT : SDL_CONTROLLER_AXIS_TRIGGERRIGHT;
-	std::vector<EShortcut> shortcuts;
-	for(const auto & operation : operations)
-	{
-		if(operation == "mouseLeftClick")
-		{
-			leftClickTriggerSet.insert(triggerAxis);
-		}
-		else if(operation == "mouseRightClick")
-		{
-			rightClickTriggerSet.insert(triggerAxis);
-		}
-		else
-		{
-			EShortcut shortcut = GH.shortcuts().findShortcut(operation);
-			if(shortcut == EShortcut::NONE)
-				logGlobal->error("Shortcut %s in joystick config key %s is invalid.", operation, key);
-			else
-				shortcuts.push_back(shortcut);
-		}
-	}
-
-	if(!shortcuts.empty())
-		triggerShortcutsMap.emplace(triggerAxis, std::move(shortcuts));
-}
-
-void GameControllerConfig::parseButton(const std::string & key, const JsonNode & value)
-{
-	std::vector<std::string> operations = getOperations(key, value);
-	SDL_GameControllerButton button = SDL_GameControllerGetButtonFromString(key.c_str());
-	if(button == SDL_CONTROLLER_BUTTON_INVALID)
-	{
-		logGlobal->error("Joystick config key %s is invalid.", key);
-		return;
-	}
-
-	std::vector<EShortcut> shortcuts;
-	for(const auto & operation : operations)
-	{
-		if(operation == "mouseLeftClick")
-		{
-			leftClickButtonSet.insert(button);
-		}
-		else if(operation == "mouseRightClick")
-		{
-			rightClickButtonSet.insert(button);
-		}
-		else
-		{
-			EShortcut shortcut = GH.shortcuts().findShortcut(operation);
-			if(shortcut == EShortcut::NONE)
-				logGlobal->error("Shortcut %s in joystick config key %s is invalid.", operation, key);
-			else
-				shortcuts.push_back(shortcut);
-		}
-	}
-
-	if(!shortcuts.empty())
-		buttonShortcutsMap.emplace(button, std::move(shortcuts));
-}
-
-const AxisType & GameControllerConfig::getLeftAxisType()
-{
-	return leftAxisType;
-}
-
-const AxisType & GameControllerConfig::getRightAxisType()
-{
-	return rightAxisType;
-}
-
-std::vector<std::string> GameControllerConfig::getOperations(const std::string & key, const JsonNode & value)
-{
-	std::vector<std::string> operations;
-	if(value.isString())
-	{
-		operations.push_back(value.String());
-	}
-	else if(value.isVector())
-	{
-		for(auto const & entryVector : value.Vector())
-		{
-			if(!entryVector.isString())
-				logGlobal->error("The vector of joystick config key %s can not contain non-string element.", key);
-			else
-				operations.push_back(entryVector.String());
-		}
-	}
-	else
-	{
-		logGlobal->error("The value of joystick config key %s should be string or string vector.", key);
-	}
-	return operations;
-}
-
-bool GameControllerConfig::isLeftClickButton(int buttonValue)
-{
-	SDL_GameControllerButton button = static_cast<SDL_GameControllerButton>(buttonValue);
-	return leftClickButtonSet.find(button) != leftClickButtonSet.end();
-}
-
-bool GameControllerConfig::isRightClickButton(int buttonValue)
-{
-	SDL_GameControllerButton button = static_cast<SDL_GameControllerButton>(buttonValue);
-	return rightClickButtonSet.find(button) != rightClickButtonSet.end();
-}
-
-bool GameControllerConfig::isShortcutsButton(int buttonValue)
-{
-	SDL_GameControllerButton button = static_cast<SDL_GameControllerButton>(buttonValue);
-	return buttonShortcutsMap.find(button) != buttonShortcutsMap.end();
-}
-
-const std::vector<EShortcut> & GameControllerConfig::getButtonShortcuts(int buttonValue)
-{
-	SDL_GameControllerButton button = static_cast<SDL_GameControllerButton>(buttonValue);
-	auto it = buttonShortcutsMap.find(button);
-	if(it != buttonShortcutsMap.end())
-		return it->second;
-	static std::vector<EShortcut> emptyVec;
-	return emptyVec;
-}
-
-bool GameControllerConfig::isLeftClickTrigger(int axisValue)
-{
-	SDL_GameControllerAxis axis = static_cast<SDL_GameControllerAxis>(axisValue);
-	return leftClickTriggerSet.find(axis) != leftClickTriggerSet.end();
-}
-
-bool GameControllerConfig::isRightClickTrigger(int axisValue)
-{
-	SDL_GameControllerAxis axis = static_cast<SDL_GameControllerAxis>(axisValue);
-	return rightClickTriggerSet.find(axis) != rightClickTriggerSet.end();
-}
-
-bool GameControllerConfig::isShortcutsTrigger(int axisValue)
-{
-	SDL_GameControllerAxis axis = static_cast<SDL_GameControllerAxis>(axisValue);
-	return triggerShortcutsMap.find(axis) != triggerShortcutsMap.end();
-}
-
-const std::vector<EShortcut> & GameControllerConfig::getTriggerShortcuts(int axisValue)
-{
-	SDL_GameControllerAxis axis = static_cast<SDL_GameControllerAxis>(axisValue);
-	auto it = triggerShortcutsMap.find(axis);
-	if(it != triggerShortcutsMap.end())
-		return it->second;
-	static std::vector<EShortcut> emptyVec;
-	return emptyVec;
-}
-

+ 0 - 59
client/eventsSDL/GameControllerConfig.h

@@ -1,59 +0,0 @@
-/*
-* GameControllerConfig.h, part of VCMI engine
-*
-* Authors: listed in file AUTHORS in main folder
-*
-* License: GNU General Public License v2.0 or later
-* Full text of license available in license.txt file, in main folder
-*
-*/
-
-#pragma once
-
-#include <SDL.h>
-
-#include "../gui/Shortcut.h"
-#include "../../lib/json/JsonUtils.h"
-
-enum AxisType
-{
-	CURSOR_MOTION,
-	MAP_SCROLL,
-	NONE
-};
-
-class GameControllerConfig {
-	using ButtonShortcutsMap = std::map<SDL_GameControllerButton, std::vector<EShortcut> >;
-	using TriggerShortcutsMap = std::map<SDL_GameControllerAxis, std::vector<EShortcut> >;
-	ButtonShortcutsMap buttonShortcutsMap;
-	TriggerShortcutsMap triggerShortcutsMap;
-	std::set<SDL_GameControllerButton> leftClickButtonSet;
-	std::set<SDL_GameControllerButton> rightClickButtonSet;
-	std::set<SDL_GameControllerAxis> leftClickTriggerSet;
-	std::set<SDL_GameControllerAxis> rightClickTriggerSet;
-	AxisType leftAxisType;
-	AxisType rightAxisType;
-
-	void load();
-	std::vector<std::string> getOperations(const std::string & key, const JsonNode & value);
-	AxisType parseAxis(const std::string & key, const JsonNode & value);
-	void parseTrigger(const std::string & key, const JsonNode & value);
-	void parseButton(const std::string & key, const JsonNode & value);
-
-public:
-	GameControllerConfig();
-	~GameControllerConfig() = default;
-
-	const AxisType & getLeftAxisType();
-	const AxisType & getRightAxisType();
-
-	bool isLeftClickButton(int buttonValue);
-	bool isRightClickButton(int buttonValue);
-	bool isShortcutsButton(int buttonValue);
-	const std::vector<EShortcut> & getButtonShortcuts(int buttonValue);
-
-	bool isLeftClickTrigger(int axisValue);
-	bool isRightClickTrigger(int axisValue);
-	bool isShortcutsTrigger(int axisValue);
-	const std::vector<EShortcut> & getTriggerShortcuts(int axisValue);
-};

+ 46 - 94
client/eventsSDL/InputSourceGameController.cpp

@@ -18,8 +18,6 @@
 #include "../gui/EventDispatcher.h"
 #include "../gui/ShortcutHandler.h"
 
-
-
 void InputSourceGameController::gameControllerDeleter(SDL_GameController * gameController)
 {
 	if(gameController)
@@ -135,76 +133,56 @@ int InputSourceGameController::getRealAxisValue(int value)
 	return (value - base) * AXIS_MAX_ZOOM / (AXIS_MAX_ZOOM - AXIS_DEAD_ZOOM);
 }
 
-void InputSourceGameController::dispatchTriggerShortcuts(const std::vector<EShortcut> & shortcutsVector, int axisValue)
-{
-	if(axisValue >= TRIGGER_PRESS_THRESHOLD)
-		GH.events().dispatchShortcutPressed(shortcutsVector);
-	else
-		GH.events().dispatchShortcutReleased(shortcutsVector);
-}
-
-void InputSourceGameController::dispatchTriggerLeftClick(int axisValue)
-{
-	const Point & position = GH.input().getCursorPosition();
-	if(axisValue >= TRIGGER_PRESS_THRESHOLD)
-		GH.events().dispatchMouseLeftButtonPressed(position, 0);
-	else
-		GH.events().dispatchMouseLeftButtonReleased(position, 0);
-}
-
-void InputSourceGameController::dispatchTriggerRightClick(int axisValue)
+void InputSourceGameController::dispatchAxisShortcuts(const std::vector<EShortcut> & shortcutsVector, SDL_GameControllerAxis axisID, int axisValue)
 {
-	const Point & position = GH.input().getCursorPosition();
 	if(axisValue >= TRIGGER_PRESS_THRESHOLD)
-		GH.events().dispatchShowPopup(position, 0);
+	{
+		if (!pressedAxes.count(axisID))
+		{
+			GH.events().dispatchShortcutPressed(shortcutsVector);
+			pressedAxes.insert(axisID);
+		}
+	}
 	else
-		GH.events().dispatchClosePopup(position);
+	{
+		if (pressedAxes.count(axisID))
+		{
+			GH.events().dispatchShortcutReleased(shortcutsVector);
+			pressedAxes.erase(axisID);
+		}
+	}
 }
 
 void InputSourceGameController::handleEventAxisMotion(const SDL_ControllerAxisEvent & axis)
 {
 	tryToConvertCursor();
-	if(axis.axis == SDL_CONTROLLER_AXIS_LEFTX)
-	{
-		if(config.getLeftAxisType() == AxisType::CURSOR_MOTION)
-			cursorAxisValueX = getRealAxisValue(axis.value);
-		else if(config.getLeftAxisType() == AxisType::MAP_SCROLL)
-			scrollAxisValueX = getRealAxisValue(axis.value);
-	}
-	else if(axis.axis == SDL_CONTROLLER_AXIS_LEFTY)
-	{
-		if(config.getLeftAxisType() == AxisType::CURSOR_MOTION)
-			cursorAxisValueY = getRealAxisValue(axis.value);
-		else if(config.getLeftAxisType() == AxisType::MAP_SCROLL)
-			scrollAxisValueY = getRealAxisValue(axis.value);
-	}
-	if(axis.axis == SDL_CONTROLLER_AXIS_RIGHTX)
-	{
-		if(config.getRightAxisType() == AxisType::CURSOR_MOTION)
-			cursorAxisValueX = getRealAxisValue(axis.value);
-		else if(config.getRightAxisType() == AxisType::MAP_SCROLL)
-			scrollAxisValueX = getRealAxisValue(axis.value);
-	}
-	else if(axis.axis == SDL_CONTROLLER_AXIS_RIGHTY)
-	{
-		if(config.getRightAxisType() == AxisType::CURSOR_MOTION)
-			cursorAxisValueY = getRealAxisValue(axis.value);
-		else if(config.getRightAxisType() == AxisType::MAP_SCROLL)
-			scrollAxisValueY = getRealAxisValue(axis.value);
-	}
-	else if(config.isLeftClickTrigger(axis.axis))
-	{
-		dispatchTriggerLeftClick(axis.value);
-	}
-	else if(config.isRightClickTrigger(axis.axis))
-	{
-		dispatchTriggerRightClick(axis.value);
-	}
-	else if(config.isShortcutsTrigger(axis.axis))
+
+	SDL_GameControllerAxis axisID = static_cast<SDL_GameControllerAxis>(axis.axis);
+	std::string axisName =  SDL_GameControllerGetStringForAxis(axisID);
+
+	auto axisActions = GH.shortcuts().translateJoystickAxis(axisName);
+	auto buttonActions = GH.shortcuts().translateJoystickButton(axisName);
+
+	for (auto const & action : axisActions)
 	{
-		const auto & shortcutsVector = config.getTriggerShortcuts(axis.axis);
-		dispatchTriggerShortcuts(shortcutsVector, axis.value);
+		switch (action)
+		{
+			case EShortcut::MOUSE_CURSOR_X:
+				cursorAxisValueX = getRealAxisValue(axis.value);
+				break;
+			case EShortcut::MOUSE_CURSOR_Y:
+				cursorAxisValueY = getRealAxisValue(axis.value);
+				break;
+			case EShortcut::MOUSE_SWIPE_X:
+				scrollAxisValueX = getRealAxisValue(axis.value);
+				break;
+			case EShortcut::MOUSE_SWIPE_Y:
+				scrollAxisValueY = getRealAxisValue(axis.value);
+				break;
+		}
 	}
+
+	dispatchAxisShortcuts(buttonActions, axisID, axis.value);
 }
 
 void InputSourceGameController::tryToConvertCursor()
@@ -222,42 +200,16 @@ void InputSourceGameController::tryToConvertCursor()
 
 void InputSourceGameController::handleEventButtonDown(const SDL_ControllerButtonEvent & button)
 {
-	const Point & position = GH.input().getCursorPosition();
-
-	if(config.isLeftClickButton(button.button))
-	{
-		GH.events().dispatchMouseLeftButtonPressed(position, 0);
-	}
-
-	if(config.isRightClickButton(button.button))
-	{
-		GH.events().dispatchShowPopup(position, 0);
-	}
-
-	if(config.isShortcutsButton(button.button))
-	{
-		const auto & shortcutsVector = config.getButtonShortcuts(button.button);
-		GH.events().dispatchShortcutPressed(shortcutsVector);
-	}
+	std::string buttonName = SDL_GameControllerGetStringForButton(static_cast<SDL_GameControllerButton>(button.button));
+	const auto & shortcutsVector = GH.shortcuts().translateJoystickButton(buttonName);
+	GH.events().dispatchShortcutPressed(shortcutsVector);
 }
 
 void InputSourceGameController::handleEventButtonUp(const SDL_ControllerButtonEvent & button)
 {
-	const Point & position = GH.input().getCursorPosition();
-
-	if(config.isLeftClickButton(button.button))
-	{
-		GH.events().dispatchMouseLeftButtonReleased(position, 0);
-	}
-	if(config.isRightClickButton(button.button))
-	{
-		GH.events().dispatchClosePopup(position);
-	}
-	if(config.isShortcutsButton(button.button))
-	{
-		const auto & shortcutsVector = config.getButtonShortcuts(button.button);
-		GH.events().dispatchShortcutReleased(shortcutsVector);
-	}
+	std::string buttonName = SDL_GameControllerGetStringForButton(static_cast<SDL_GameControllerButton>(button.button));
+	const auto & shortcutsVector = GH.shortcuts().translateJoystickButton(buttonName);
+	GH.events().dispatchShortcutReleased(shortcutsVector);
 }
 
 void InputSourceGameController::doCursorMove(int deltaX, int deltaY)

+ 16 - 12
client/eventsSDL/InputSourceGameController.h

@@ -10,19 +10,24 @@
 
 #pragma once
 
-#include <SDL.h>
+#include <SDL_events.h>
+#include <SDL_gamecontroller.h>
 
-#include "GameControllerConfig.h"
 #include "../gui/Shortcut.h"
 #include "../../lib/Point.h"
 
+constexpr int AXIS_DEAD_ZOOM = 6000;
+constexpr int AXIS_MAX_ZOOM = 32000;
+constexpr int AXIS_MOVE_SPEED = 500;
+constexpr int AXIS_CURSOR_MOVE_INTERVAL = 1000;
+constexpr int TRIGGER_PRESS_THRESHOLD = 8000;
 
-const int AXIS_DEAD_ZOOM = 6000;
-const int AXIS_MAX_ZOOM = 32000;
-const int AXIS_MOVE_SPEED = 500;
-const int AXIS_CURSOR_MOVE_INTERVAL = 1000;
-const int TRIGGER_PRESS_THRESHOLD = 8000;
-
+enum class AxisType
+{
+	CURSOR_MOTION,
+	MAP_SCROLL,
+	NONE
+};
 
 /// Class that handles game controller input from SDL events
 class InputSourceGameController
@@ -31,7 +36,8 @@ class InputSourceGameController
 	using GameControllerPtr = std::unique_ptr<SDL_GameController, decltype(&gameControllerDeleter)>;
 
 	std::map<int, GameControllerPtr> gameControllerMap;
-	GameControllerConfig config;
+	std::set<SDL_GameControllerAxis> pressedAxes;
+
 	long long lastCheckTime;
 	int cursorAxisValueX;
 	int cursorAxisValueY;
@@ -49,9 +55,7 @@ class InputSourceGameController
 	void openGameController(int index);
 	int getJoystickIndex(SDL_GameController * controller);
 	int getRealAxisValue(int value);
-	void dispatchTriggerShortcuts(const std::vector<EShortcut> & shortcutsVector, int axisValue);
-	void dispatchTriggerLeftClick(int axisValue);
-	void dispatchTriggerRightClick(int axisValue);
+	void dispatchAxisShortcuts(const std::vector<EShortcut> & shortcutsVector, SDL_GameControllerAxis axisID, int axisValue);
 	void tryToConvertCursor();
 	void doCursorMove(int deltaX, int deltaY);
 	int getMoveDis(float planDis);

+ 13 - 0
client/gui/EventDispatcher.cpp

@@ -15,6 +15,7 @@
 #include "CGuiHandler.h"
 #include "MouseButton.h"
 #include "WindowHandler.h"
+#include "gui/Shortcut.h"
 
 #include "../../lib/Rect.h"
 
@@ -74,6 +75,12 @@ void EventDispatcher::dispatchShortcutPressed(const std::vector<EShortcut> & sho
 {
 	bool keysCaptured = false;
 
+	if (vstd::contains(shortcutsVector, EShortcut::MOUSE_LEFT))
+		dispatchMouseLeftButtonPressed(GH.getCursorPosition(), 0);
+
+	if (vstd::contains(shortcutsVector, EShortcut::MOUSE_RIGHT))
+		dispatchShowPopup(GH.getCursorPosition(), 0);
+
 	for(auto & i : keyinterested)
 		for(EShortcut shortcut : shortcutsVector)
 			if(i->captureThisKey(shortcut))
@@ -97,6 +104,12 @@ void EventDispatcher::dispatchShortcutReleased(const std::vector<EShortcut> & sh
 {
 	bool keysCaptured = false;
 
+	if (vstd::contains(shortcutsVector, EShortcut::MOUSE_LEFT))
+		dispatchMouseLeftButtonReleased(GH.getCursorPosition(), 0);
+
+	if (vstd::contains(shortcutsVector, EShortcut::MOUSE_RIGHT))
+		dispatchClosePopup(GH.getCursorPosition());
+
 	for(auto & i : keyinterested)
 		for(EShortcut shortcut : shortcutsVector)
 			if(i->captureThisKey(shortcut))

+ 8 - 0
client/gui/Shortcut.h

@@ -13,6 +13,14 @@ enum class EShortcut
 {
 	NONE,
 
+	// preudo-shortcuts that trigger mouse events
+	MOUSE_LEFT,
+	MOUSE_RIGHT,
+	MOUSE_CURSOR_X,
+	MOUSE_CURSOR_Y,
+	MOUSE_SWIPE_X,
+	MOUSE_SWIPE_Y,
+
 	// Global hotkeys that are available in multiple dialogs
 	GLOBAL_ACCEPT,     // Return - Accept query
 	GLOBAL_CANCEL,     // Escape - Cancel query

+ 37 - 5
client/gui/ShortcutHandler.cpp

@@ -19,7 +19,16 @@ ShortcutHandler::ShortcutHandler()
 {
 	const JsonNode config = JsonUtils::assembleFromFiles("config/shortcutsConfig");
 
-	for (auto const & entry : config["keyboard"].Struct())
+	mappedKeyboardShortcuts = loadShortcuts(config["keyboard"]);
+	mappedJoystickShortcuts = loadShortcuts(config["joystickButtons"]);
+	mappedJoystickAxes = loadShortcuts(config["joystickAxes"]);
+}
+
+std::multimap<std::string, EShortcut> ShortcutHandler::loadShortcuts(const JsonNode & data) const
+{
+	std::multimap<std::string, EShortcut> result;
+
+	for (auto const & entry : data.Struct())
 	{
 		std::string shortcutName = entry.first;
 		EShortcut shortcutID = findShortcut(shortcutName);
@@ -32,20 +41,22 @@ ShortcutHandler::ShortcutHandler()
 
 		if (entry.second.isString())
 		{
-			mappedShortcuts.emplace(entry.second.String(), shortcutID);
+			result.emplace(entry.second.String(), shortcutID);
 		}
 
 		if (entry.second.isVector())
 		{
 			for (auto const & entryVector : entry.second.Vector())
-				mappedShortcuts.emplace(entryVector.String(), shortcutID);
+				result.emplace(entryVector.String(), shortcutID);
 		}
 	}
+
+	return result;
 }
 
-std::vector<EShortcut> ShortcutHandler::translateKeycode(const std::string & key) const
+std::vector<EShortcut> ShortcutHandler::translateShortcut(const std::multimap<std::string, EShortcut> & options, const std::string & key) const
 {
-	auto range = mappedShortcuts.equal_range(key);
+	auto range = options.equal_range(key);
 
 	// FIXME: some code expects calls to keyPressed / captureThisKey even without defined hotkeys
 	if (range.first == range.second)
@@ -59,9 +70,30 @@ std::vector<EShortcut> ShortcutHandler::translateKeycode(const std::string & key
 	return result;
 }
 
+std::vector<EShortcut> ShortcutHandler::translateKeycode(const std::string & key) const
+{
+	return translateShortcut(mappedKeyboardShortcuts, key);
+}
+
+std::vector<EShortcut> ShortcutHandler::translateJoystickButton(const std::string & key) const
+{
+	return translateShortcut(mappedJoystickShortcuts, key);
+}
+
+std::vector<EShortcut> ShortcutHandler::translateJoystickAxis(const std::string & key) const
+{
+	return translateShortcut(mappedJoystickAxes, key);
+}
+
 EShortcut ShortcutHandler::findShortcut(const std::string & identifier ) const
 {
 	static const std::map<std::string, EShortcut> shortcutNames = {
+		{"mouseClickLeft",           EShortcut::MOUSE_LEFT                },
+		{"mouseClickRight",          EShortcut::MOUSE_RIGHT               },
+		{"mouseCursorX",             EShortcut::MOUSE_CURSOR_X,           },
+		{"mouseCursorY",             EShortcut::MOUSE_CURSOR_Y,           },
+		{"mouseSwipeX",              EShortcut::MOUSE_SWIPE_X,            },
+		{"mouseSwipeY",              EShortcut::MOUSE_SWIPE_Y,            },
 		{"globalAccept",             EShortcut::GLOBAL_ACCEPT             },
 		{"globalCancel",             EShortcut::GLOBAL_CANCEL             },
 		{"globalReturn",             EShortcut::GLOBAL_RETURN             },

+ 15 - 1
client/gui/ShortcutHandler.h

@@ -12,15 +12,29 @@
 
 enum class EShortcut;
 
+VCMI_LIB_NAMESPACE_BEGIN
+class JsonNode;
+VCMI_LIB_NAMESPACE_END
+
 class ShortcutHandler
 {
-	std::multimap<std::string, EShortcut> mappedShortcuts;
+	std::multimap<std::string, EShortcut> mappedKeyboardShortcuts;
+	std::multimap<std::string, EShortcut> mappedJoystickShortcuts;
+	std::multimap<std::string, EShortcut> mappedJoystickAxes;
+
+	std::multimap<std::string, EShortcut> loadShortcuts(const JsonNode & data) const;
+	std::vector<EShortcut> translateShortcut(const std::multimap<std::string, EShortcut> & options, const std::string & key) const;
+
 public:
 	ShortcutHandler();
 
 	/// returns list of shortcuts assigned to provided SDL keycode
 	std::vector<EShortcut> translateKeycode(const std::string & key) const;
 
+	std::vector<EShortcut> translateJoystickButton(const std::string & key) const;
+
+	std::vector<EShortcut> translateJoystickAxis(const std::string & key) const;
+
 	/// attempts to find shortcut by its unique identifier. Returns EShortcut::NONE on failure
 	EShortcut findShortcut(const std::string & identifier ) const;
 };

+ 66 - 17
config/shortcutsConfig.json

@@ -110,6 +110,7 @@
 		"battleConsoleDown":        "Down",
 		"battleTacticsNext":        "Space",
 		"battleTacticsEnd":         [ "Return", "Keypad Enter"],
+		"battleToggleHeroesStats":  [],
 		"battleSelectAction":       "S",
 		"townOpenTavern":           "T",
 		"townSwapArmies":           "Space",
@@ -137,24 +138,72 @@
 		"heroCostume8":             "8",
 		"heroCostume9":             "9"
 	},
-	"joystick": {
-		"leftaxis": "cursorMotion",
-		"rightaxis": "mapScroll",
-		"a": ["globalAccept", "globalReturn", "lobbyBeginStandardGame", "lobbyBeginCampaign", "lobbyLoadGame", "lobbySaveGame", "adventureViewSelected", "adventureExitWorldView", "battleTacticsEnd"],
-		"b": ["globalCancel", "globalReturn", "adventureExitWorldView"],
-		"x": "mouseLeftClick",
-		"y": "mouseRightClick",
+	
+	"joystickAxes":
+	{
+		"mouseCursorX" : "leftx",
+		"mouseCursorY" : "lefty",
+		"mouseSwipeX" : "rightx",
+		"mouseSwipeY" : "righty"
+	},
+	
+	"joystickButtons": {
+		"globalAccept" : "a",
+		"globalCancel" : "b",
+		"globalReturn" : [ "a", "b" ],
+		
+		"lobbyBeginStandardGame" : "a",
+		"lobbyBeginCampaign" : "a",
+		"lobbyLoadGame" : "a",
+		"lobbySaveGame" : "a",
+		"adventureViewSelected" : "a",
+		"adventureExitWorldView" : [ "a", "b" ],
+		"battleTacticsEnd" : "a",
+		
+		"mouseClickLeft": "x",
+		"mouseClickRight": "y",
+
 		"leftshoulder": ["adventureNextHero", "battleDefend"],
 		"rightshoulder": ["adventureNextTown", "battleWait"],
-		"lefttrigger": ["adventureVisitObject", "battleTacticsNext", "battleUseCreatureSpell"],
-		"righttrigger": ["adventureCastSpell", "battleCastSpell"],
-		"back": ["gameEndTurn", "battleAutocombatEnd"],
-		"start": ["globalOptions", "adventureGameOptions"],
-		"dpup": ["moveUp", "adventureViewWorld", "recruitmentUpgrade", "recruitmentUpgradeAll", "battleConsoleUp", "recruitmentMax"],
-		"dpdown": ["moveDown", "adventureKingdomOverview", "battleConsoleDown","recruitmentMin"],
-		"dpleft": ["moveLeft", "adventureViewScenario"],
-		"dpright": ["moveRight", "adventureThievesGuild"],
-		"leftstick" : ["adventureToggleMapLevel", "battleToggleHeroesStats"],
-		"rightstick": ["adventureToggleGrid", "battleToggleQueue"]
+
+		"adventureNextTown" : "rightshoulder",
+		"battleWait" : "rightshoulder",
+
+		"adventureVisitObject" : "lefttrigger",
+		"battleTacticsNext" : "lefttrigger",
+		"battleUseCreatureSpell" : "lefttrigger",
+
+		"adventureCastSpell" : "righttrigger",
+		"battleCastSpell" : "righttrigger",
+		
+		"gameEndTurn" : "back",
+		"battleAutocombatEnd" : "back",
+		
+		"globalOptions" : "start",
+		"adventureGameOptions" : "start",
+		
+		"moveUp" : "dpup",
+		"adventureViewWorld" : "dpup",
+		"recruitmentUpgrade" : "dpup",
+		"recruitmentUpgradeAll" : "dpup",
+		"battleConsoleUp" : "dpup",
+		"recruitmentMax" : "dpup",
+		
+		"moveDown" : "dpdown",
+		"adventureKingdomOverview" : "dpdown",
+		"battleConsoleDown" : "dpdown",
+		"recruitmentMin" : "dpdown",
+		
+		"moveLeft" : "dpleft",
+		"adventureViewScenario" : "dpleft",
+
+		"moveRight" : "dpright",
+		"adventureThievesGuild" : "dpright",
+		
+		"adventureToggleMapLevel" : "leftstick",
+		"battleToggleHeroesStats" : "leftstick",
+		
+		"adventureToggleGrid" : "rightstick",
+		"battleToggleQueue" : "rightstick",
 	}
 }