Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

HelpComponent: Display different help icons depending on controller #808

Open
wants to merge 1 commit into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 4 additions & 1 deletion es-core/src/InputConfig.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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)
{
}

Expand Down
14 changes: 13 additions & 1 deletion es-core/src/InputConfig.h
Original file line number Diff line number Diff line change
Expand Up @@ -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:
Expand Down Expand Up @@ -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);
Expand All @@ -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);
Expand All @@ -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
54 changes: 50 additions & 4 deletions es-core/src/InputManager.cpp
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
#include "InputManager.h"

#include "resources/ResourceManager.h"
#include "utils/FileSystemUtil.h"
#include "CECInput.h"
#include "Log.h"
Expand Down Expand Up @@ -31,7 +32,7 @@ int SDL_USER_CECBUTTONUP = -1;

InputManager* InputManager::mInstance = NULL;

InputManager::InputManager() : mKeyboardInputConfig(NULL)
InputManager::InputManager() : mKeyboardInputConfig(NULL), mLastUsedKeyboardOrController(DEVICE_KEYBOARD)
{
}

Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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 << ").";
Expand All @@ -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)
Expand Down Expand Up @@ -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(); }
Expand All @@ -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)
Expand All @@ -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;
Expand Down
2 changes: 2 additions & 0 deletions es-core/src/InputManager.h
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@ class InputManager
std::map<SDL_JoystickID, InputConfig*> mInputConfigs;
InputConfig* mKeyboardInputConfig;
InputConfig* mCECInputConfig;
int mLastUsedKeyboardOrController;

std::map<SDL_JoystickID, int*> mPrevAxisValues;

Expand Down Expand Up @@ -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);
};
Expand Down
68 changes: 55 additions & 13 deletions es-core/src/components/HelpComponent.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -3,18 +3,18 @@
#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)

#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<std::string, const char*> 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" },
Expand All @@ -29,6 +29,20 @@ static const std::map<std::string, const char*> 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)
{
}
Expand Down Expand Up @@ -67,12 +81,15 @@ void HelpComponent::updateGrid()
std::vector< std::shared_ptr<ImageComponent> > icons;
std::vector< std::shared_ptr<TextComponent> > 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<ImageComponent>(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);
Expand Down Expand Up @@ -100,26 +117,51 @@ void HelpComponent::updateGrid()
mGrid->setOrigin(mStyle.origin);
}

std::shared_ptr<TextureResource> 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<TextureResource> 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<TextureResource> tex = TextureResource::get(pathLookup->second);
mIconCache[std::string(name)] = tex;
mIconCache[pathLookup->second] = tex;
return tex;
}

Expand Down
8 changes: 7 additions & 1 deletion es-core/src/components/HelpComponent.h
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,9 @@

#include "GuiComponent.h"
#include "HelpStyle.h"
#include "resources/TextureResource.h"

#include <string>

class ComponentGrid;
class ImageComponent;
Expand All @@ -22,8 +25,11 @@ class HelpComponent : public GuiComponent

void setStyle(const HelpStyle& style);

using IconPathMap = std::map<std::string /*name*/, std::string /*path*/>;

private:
std::shared_ptr<TextureResource> getIconTexture(const char* name);
const IconPathMap& getIconOverridesForInput(InputConfig* inputConfig);
std::shared_ptr<TextureResource> getIconTexture(const std::string& name, const IconPathMap& iconOverrides);
std::map< std::string, std::shared_ptr<TextureResource> > mIconCache;

std::shared_ptr<ComponentGrid> mGrid;
Expand Down