From 819ecaf8b250f50f497a355c5e00eb8a2eeb7a2d Mon Sep 17 00:00:00 2001 From: Richard Eklycke Date: Sun, 21 Aug 2022 15:42:55 +0200 Subject: [PATCH] HelpComponent: Display different help icons depending on controller Before this commit, all buttons shown in the help component would use the SNES layout, which wouldn't match when having plugged in an Xbox or Sony Playstation controller. If there are different kinds of controllers plugged in, the input mapping shown will be of the last used or inserted. The controller identification is done via the SDL gamecontroller API. The intention here is to help solving #442, or at least most of it. --- es-core/src/InputConfig.cpp | 5 +- es-core/src/InputConfig.h | 14 ++++- es-core/src/InputManager.cpp | 54 +++++++++++++++++-- es-core/src/InputManager.h | 2 + es-core/src/components/HelpComponent.cpp | 68 +++++++++++++++++++----- es-core/src/components/HelpComponent.h | 8 ++- 6 files changed, 131 insertions(+), 20 deletions(-) diff --git a/es-core/src/InputConfig.cpp b/es-core/src/InputConfig.cpp index eb5e59cecd..1a092f08f1 100644 --- a/es-core/src/InputConfig.cpp +++ b/es-core/src/InputConfig.cpp @@ -50,7 +50,10 @@ std::string toLower(std::string str) } //end util functions -InputConfig::InputConfig(int deviceId, const std::string& deviceName, const std::string& deviceGUID) : mDeviceId(deviceId), mDeviceName(deviceName), mDeviceGUID(deviceGUID) +InputConfig::InputConfig(int deviceId, + const std::string& deviceName, + const std::string& deviceGUID, + InputButtonLayout buttonLayout) : mDeviceId(deviceId), mDeviceName(deviceName), mDeviceGUID(deviceGUID), mButtonLayout(buttonLayout) { } diff --git a/es-core/src/InputConfig.h b/es-core/src/InputConfig.h index 4969d8ff02..c1f1705952 100644 --- a/es-core/src/InputConfig.h +++ b/es-core/src/InputConfig.h @@ -24,6 +24,13 @@ enum InputType TYPE_COUNT }; +enum InputButtonLayout +{ + BUTTON_LAYOUT_DEFAULT, // In the style of an SNES controller + BUTTON_LAYOUT_PLAYSTATION, + BUTTON_LAYOUT_XBOX, +}; + struct Input { public: @@ -96,7 +103,10 @@ struct Input class InputConfig { public: - InputConfig(int deviceId, const std::string& deviceName, const std::string& deviceGUID); + InputConfig(int deviceId, + const std::string& deviceName, + const std::string& deviceGUID, + InputButtonLayout buttonLayout = BUTTON_LAYOUT_DEFAULT); void clear(); void mapInput(const std::string& name, Input input); @@ -105,6 +115,7 @@ class InputConfig inline int getDeviceId() const { return mDeviceId; }; inline const std::string& getDeviceName() { return mDeviceName; } inline const std::string& getDeviceGUIDString() { return mDeviceGUID; } + inline InputButtonLayout getButtonLayout() const { return mButtonLayout; } //Returns true if Input is mapped to this name, false otherwise. bool isMappedTo(const std::string& name, Input input); @@ -127,6 +138,7 @@ class InputConfig const int mDeviceId; const std::string mDeviceName; const std::string mDeviceGUID; + const InputButtonLayout mButtonLayout; }; #endif // ES_CORE_INPUT_CONFIG_H diff --git a/es-core/src/InputManager.cpp b/es-core/src/InputManager.cpp index 53e38f3373..04c32ea96f 100644 --- a/es-core/src/InputManager.cpp +++ b/es-core/src/InputManager.cpp @@ -1,5 +1,6 @@ #include "InputManager.h" +#include "resources/ResourceManager.h" #include "utils/FileSystemUtil.h" #include "CECInput.h" #include "Log.h" @@ -31,7 +32,7 @@ int SDL_USER_CECBUTTONUP = -1; InputManager* InputManager::mInstance = NULL; -InputManager::InputManager() : mKeyboardInputConfig(NULL) +InputManager::InputManager() : mKeyboardInputConfig(NULL), mLastUsedKeyboardOrController(DEVICE_KEYBOARD) { } @@ -62,7 +63,7 @@ void InputManager::init() SDL_SetHint(SDL_HINT_JOYSTICK_HIDAPI, "0"); #endif #endif - SDL_InitSubSystem(SDL_INIT_JOYSTICK); + SDL_InitSubSystem(SDL_INIT_GAMECONTROLLER); SDL_JoystickEventState(SDL_ENABLE); // first, open all currently present joysticks @@ -98,8 +99,36 @@ void InputManager::addJoystickByDeviceIndex(int id) char guid[65]; SDL_JoystickGetGUIDString(SDL_JoystickGetGUID(joy), guid, 65); + InputButtonLayout buttonLayout = BUTTON_LAYOUT_DEFAULT; + switch(SDL_GameControllerTypeForIndex(id)) + { + case SDL_CONTROLLER_TYPE_PS3: + case SDL_CONTROLLER_TYPE_PS4: + case SDL_CONTROLLER_TYPE_PS5: + buttonLayout = BUTTON_LAYOUT_PLAYSTATION; + break; + + case SDL_CONTROLLER_TYPE_XBOX360: + case SDL_CONTROLLER_TYPE_XBOXONE: + case SDL_CONTROLLER_TYPE_AMAZON_LUNA: + case SDL_CONTROLLER_TYPE_GOOGLE_STADIA: + case SDL_CONTROLLER_TYPE_NVIDIA_SHIELD: + buttonLayout = BUTTON_LAYOUT_XBOX; + break; + + case SDL_CONTROLLER_TYPE_NINTENDO_SWITCH_PRO: + case SDL_CONTROLLER_TYPE_NINTENDO_SWITCH_JOYCON_LEFT: + case SDL_CONTROLLER_TYPE_NINTENDO_SWITCH_JOYCON_RIGHT: + case SDL_CONTROLLER_TYPE_NINTENDO_SWITCH_JOYCON_PAIR: + break; + + case SDL_CONTROLLER_TYPE_UNKNOWN: + case SDL_CONTROLLER_TYPE_VIRTUAL: + break; + } + // create the InputConfig - mInputConfigs[joyId] = new InputConfig(joyId, SDL_JoystickName(joy), guid); + mInputConfigs[joyId] = new InputConfig(joyId, SDL_JoystickName(joy), guid, buttonLayout); if(!loadInputConfig(mInputConfigs[joyId])) { LOG(LogInfo) << "Added unconfigured joystick '" << SDL_JoystickName(joy) << "' (GUID: " << guid << ", instance ID: " << joyId << ", device index: " << id << ")."; @@ -111,6 +140,8 @@ void InputManager::addJoystickByDeviceIndex(int id) int numAxes = SDL_JoystickNumAxes(joy); mPrevAxisValues[joyId] = new int[numAxes]; std::fill(mPrevAxisValues[joyId], mPrevAxisValues[joyId] + numAxes, 0); //initialize array to 0 + + mLastUsedKeyboardOrController = joyId; } void InputManager::removeJoystickByJoystickID(SDL_JoystickID joyId) @@ -172,7 +203,7 @@ void InputManager::deinit() CECInput::deinit(); SDL_JoystickEventState(SDL_DISABLE); - SDL_QuitSubSystem(SDL_INIT_JOYSTICK); + SDL_QuitSubSystem(SDL_INIT_GAMECONTROLLER); } int InputManager::getNumJoysticks() { return (int)mJoysticks.size(); } @@ -198,6 +229,9 @@ int InputManager::getButtonCountByDevice(SDL_JoystickID id) InputConfig* InputManager::getInputConfigByDevice(int device) { + if(device != DEVICE_CEC) + mLastUsedKeyboardOrController = device; + if(device == DEVICE_KEYBOARD) return mKeyboardInputConfig; else if(device == DEVICE_CEC) @@ -206,6 +240,18 @@ InputConfig* InputManager::getInputConfigByDevice(int device) return mInputConfigs[device]; } +InputConfig* InputManager::getInputConfigForLastUsedDevice() const +{ + if(mLastUsedKeyboardOrController == DEVICE_KEYBOARD) + return mKeyboardInputConfig; + + const auto it = mInputConfigs.find(mLastUsedKeyboardOrController); + if(it != mInputConfigs.end()) + return it->second; + + return nullptr; // Could happen if last used controller was unplugged +} + bool InputManager::parseEvent(const SDL_Event& ev, Window* window) { bool causedEvent = false; diff --git a/es-core/src/InputManager.h b/es-core/src/InputManager.h index 388358d217..d7f85b50d4 100644 --- a/es-core/src/InputManager.h +++ b/es-core/src/InputManager.h @@ -26,6 +26,7 @@ class InputManager std::map mInputConfigs; InputConfig* mKeyboardInputConfig; InputConfig* mCECInputConfig; + int mLastUsedKeyboardOrController; std::map mPrevAxisValues; @@ -56,6 +57,7 @@ class InputManager std::string getDeviceGUIDString(int deviceId); InputConfig* getInputConfigByDevice(int deviceId); + InputConfig* getInputConfigForLastUsedDevice() const; bool parseEvent(const SDL_Event& ev, Window* window); }; diff --git a/es-core/src/components/HelpComponent.cpp b/es-core/src/components/HelpComponent.cpp index 1d7ef55291..82dfd56c39 100644 --- a/es-core/src/components/HelpComponent.cpp +++ b/es-core/src/components/HelpComponent.cpp @@ -3,10 +3,10 @@ #include "components/ComponentGrid.h" #include "components/ImageComponent.h" #include "components/TextComponent.h" -#include "resources/TextureResource.h" #include "utils/StringUtil.h" #include "Log.h" #include "Settings.h" +#include "InputManager.h" #define OFFSET_X 12 // move the entire thing right by this amount (px) #define OFFSET_Y 12 // move the entire thing up by this amount (px) @@ -14,7 +14,7 @@ #define ICON_TEXT_SPACING 8 // space between [icon] and [text] (px) #define ENTRY_SPACING 16 // space between [text] and next [icon] (px) -static const std::map ICON_PATH_MAP { +static const HelpComponent::IconPathMap DEFAULT_ICON_PATH_MAP { { "up/down", ":/help/dpad_updown.svg" }, { "left/right", ":/help/dpad_leftright.svg" }, { "up/down/left/right", ":/help/dpad_all.svg" }, @@ -29,6 +29,20 @@ static const std::map ICON_PATH_MAP { { "select", ":/help/button_select.svg" } }; +static const HelpComponent::IconPathMap NO_ICON_OVERRIDES {}; +static const HelpComponent::IconPathMap XBOX_ICON_OVERRIDES { + { "a", ":/help/button_b.svg" }, + { "b", ":/help/button_a.svg" }, + { "x", ":/help/button_y.svg" }, + { "y", ":/help/button_x.svg" }, +}; +static const HelpComponent::IconPathMap PLAYSTATION_ICON_OVERRIDES { + { "a", ":/help/button_circle.svg" }, + { "b", ":/help/button_cross.svg" }, + { "x", ":/help/button_triangle.svg" }, + { "y", ":/help/button_square.svg" }, +}; + HelpComponent::HelpComponent(Window* window) : GuiComponent(window) { } @@ -67,12 +81,15 @@ void HelpComponent::updateGrid() std::vector< std::shared_ptr > icons; std::vector< std::shared_ptr > labels; + const auto& iconOverrides = + getIconOverridesForInput(InputManager::getInstance()->getInputConfigForLastUsedDevice()); + float width = 0; const float height = Math::round(font->getLetterHeight() * 1.25f); for(auto it = mPrompts.cbegin(); it != mPrompts.cend(); it++) { auto icon = std::make_shared(mWindow); - icon->setImage(getIconTexture(it->first.c_str())); + icon->setImage(getIconTexture(it->first, iconOverrides)); icon->setColorShift(mStyle.iconColor); icon->setResize(0, height); icons.push_back(icon); @@ -100,26 +117,51 @@ void HelpComponent::updateGrid() mGrid->setOrigin(mStyle.origin); } -std::shared_ptr HelpComponent::getIconTexture(const char* name) +const HelpComponent::IconPathMap& HelpComponent::getIconOverridesForInput(InputConfig* inputConfig) { - auto it = mIconCache.find(name); - if(it != mIconCache.cend()) - return it->second; + if(!inputConfig) + return NO_ICON_OVERRIDES; - auto pathLookup = ICON_PATH_MAP.find(name); - if(pathLookup == ICON_PATH_MAP.cend()) + switch(inputConfig->getButtonLayout()) { - LOG(LogError) << "Unknown help icon \"" << name << "\"!"; - return nullptr; + case BUTTON_LAYOUT_PLAYSTATION: + return PLAYSTATION_ICON_OVERRIDES; + + case BUTTON_LAYOUT_XBOX: + return XBOX_ICON_OVERRIDES; + + case BUTTON_LAYOUT_DEFAULT: + break; } + + return NO_ICON_OVERRIDES; +} + +std::shared_ptr HelpComponent::getIconTexture(const std::string& name, const IconPathMap& iconOverrides) +{ + auto pathLookup = iconOverrides.find(name); + if(pathLookup == iconOverrides.cend()) + { + pathLookup = DEFAULT_ICON_PATH_MAP.find(name); + if(pathLookup == DEFAULT_ICON_PATH_MAP.cend()) + { + LOG(LogError) << "Unknown help icon \"" << name << "\"!"; + return nullptr; + } + } + + auto it = mIconCache.find(pathLookup->second); + if(it != mIconCache.cend()) + return it->second; + if(!ResourceManager::getInstance()->fileExists(pathLookup->second)) { - LOG(LogError) << "Help icon \"" << name << "\" - corresponding image file \"" << pathLookup->second << "\" misisng!"; + LOG(LogError) << "Help icon \"" << name << "\" - corresponding image file \"" << pathLookup->second << "\" missing!"; return nullptr; } std::shared_ptr tex = TextureResource::get(pathLookup->second); - mIconCache[std::string(name)] = tex; + mIconCache[pathLookup->second] = tex; return tex; } diff --git a/es-core/src/components/HelpComponent.h b/es-core/src/components/HelpComponent.h index e9c5d2e07d..4a65d1e214 100644 --- a/es-core/src/components/HelpComponent.h +++ b/es-core/src/components/HelpComponent.h @@ -4,6 +4,9 @@ #include "GuiComponent.h" #include "HelpStyle.h" +#include "resources/TextureResource.h" + +#include class ComponentGrid; class ImageComponent; @@ -22,8 +25,11 @@ class HelpComponent : public GuiComponent void setStyle(const HelpStyle& style); + using IconPathMap = std::map; + private: - std::shared_ptr getIconTexture(const char* name); + const IconPathMap& getIconOverridesForInput(InputConfig* inputConfig); + std::shared_ptr getIconTexture(const std::string& name, const IconPathMap& iconOverrides); std::map< std::string, std::shared_ptr > mIconCache; std::shared_ptr mGrid;