From 980a2c4ec6b207eabcf754c99caae258a9e79b13 Mon Sep 17 00:00:00 2001 From: Aloshi Date: Fri, 21 Mar 2014 20:12:57 -0500 Subject: [PATCH] InputManager mostly redone to handle rolling joystick changes instead of completely deinitializing/reinitializing itself every time a change is detected. Some other slight changes to better fit with SDL2's joystick improvements. Completely redid GuiDetectDevice and GuiInputConfig. Inching closer and closer to beta. --- src/InputConfig.cpp | 21 +-- src/InputConfig.h | 18 +- src/InputManager.cpp | 287 +++++++++++++++++-------------- src/InputManager.h | 30 ++-- src/components/ComponentList.cpp | 7 + src/components/ComponentList.h | 1 + src/components/HelpComponent.cpp | 2 +- src/guis/GuiDetectDevice.cpp | 153 ++++++++-------- src/guis/GuiDetectDevice.h | 27 ++- src/guis/GuiInputConfig.cpp | 224 ++++++++++++++---------- src/guis/GuiInputConfig.h | 29 +++- src/guis/GuiMenu.cpp | 8 +- src/main.cpp | 4 +- src/views/ViewController.cpp | 4 +- 14 files changed, 458 insertions(+), 357 deletions(-) diff --git a/src/InputConfig.cpp b/src/InputConfig.cpp index e7c4add6e..47d976ad9 100644 --- a/src/InputConfig.cpp +++ b/src/InputConfig.cpp @@ -49,9 +49,8 @@ std::string toLower(std::string str) } //end util functions -InputConfig::InputConfig(int deviceId, const std::string& deviceName) : mDeviceId(deviceId), mDeviceName(deviceName) +InputConfig::InputConfig(int deviceId, const std::string& deviceName, const std::string& deviceGUID) : mDeviceId(deviceId), mDeviceName(deviceName), mDeviceGUID(deviceGUID) { - mPlayerNum = -1; } void InputConfig::clear() @@ -59,6 +58,11 @@ void InputConfig::clear() mNameMap.clear(); } +bool InputConfig::isConfigured() +{ + return mNameMap.size() > 0; +} + void InputConfig::mapInput(const std::string& name, Input input) { mNameMap[toLower(name)] = input; @@ -126,11 +130,9 @@ std::vector InputConfig::getMappedTo(Input input) return maps; } -void InputConfig::loadFromXML(pugi::xml_node node, int playerNum) +void InputConfig::loadFromXML(pugi::xml_node node) { - this->clear(); - - setPlayerNum(playerNum); + clear(); for(pugi::xml_node input = node.child("input"); input; input = input.next_sibling("input")) { @@ -161,11 +163,14 @@ void InputConfig::writeToXML(pugi::xml_node parent) if(mDeviceId == DEVICE_KEYBOARD) { cfg.append_attribute("type") = "keyboard"; + cfg.append_attribute("deviceName") = "Keyboard"; }else{ cfg.append_attribute("type") = "joystick"; cfg.append_attribute("deviceName") = mDeviceName.c_str(); } + cfg.append_attribute("deviceGUID") = mDeviceGUID.c_str(); + typedef std::map::iterator it_type; for(it_type iterator = mNameMap.begin(); iterator != mNameMap.end(); iterator++) { @@ -179,7 +184,3 @@ void InputConfig::writeToXML(pugi::xml_node parent) input.append_attribute("value").set_value(iterator->second.value); } } - -void InputConfig::setPlayerNum(int num) { mPlayerNum = num; } -int InputConfig::getPlayerNum() { return mPlayerNum; } -int InputConfig::getDeviceId() { return mDeviceId; } diff --git a/src/InputConfig.h b/src/InputConfig.h index 01957d3cd..e26844acd 100644 --- a/src/InputConfig.h +++ b/src/InputConfig.h @@ -56,9 +56,6 @@ public: std::string string() { - if(!configured) - return ""; - std::stringstream stream; switch(type) { @@ -86,14 +83,14 @@ public: class InputConfig { public: - InputConfig(int deviceId, const std::string& deviceName); + InputConfig(int deviceId, const std::string& deviceName, const std::string& deviceGUID); void clear(); void mapInput(const std::string& name, Input input); - void setPlayerNum(int num); - int getPlayerNum(); - int getDeviceId(); + inline int getDeviceId() const { return mDeviceId; }; + inline const std::string& getDeviceName() { return mDeviceName; } + inline const std::string& getDeviceGUIDString() { return mDeviceGUID; } //Returns the input mapped to this name. Input getInputByName(const std::string& name); @@ -104,13 +101,16 @@ public: //Returns a list of names this input is mapped to. std::vector getMappedTo(Input input); - void loadFromXML(pugi::xml_node root, int playerNum); + void loadFromXML(pugi::xml_node root); void writeToXML(pugi::xml_node parent); + + bool isConfigured(); + private: std::map mNameMap; const int mDeviceId; const std::string mDeviceName; - int mPlayerNum; + const std::string mDeviceGUID; }; #endif diff --git a/src/InputManager.cpp b/src/InputManager.cpp index ee24ccc01..142778445 100644 --- a/src/InputManager.cpp +++ b/src/InputManager.cpp @@ -6,11 +6,23 @@ #include #include "platform.h" +#define KEYBOARD_GUID_STRING "-1" + +// SO HEY POTENTIAL POOR SAP WHO IS TRYING TO MAKE SENSE OF ALL THIS (by which I mean my future self) +// There are like four distinct IDs used for joysticks (crazy, right?) +// 1. Device index - this is the "lowest level" identifier, and is just the Nth joystick plugged in to the system (like /dev/js#). +// It can change even if the device is the same, and is only used to open joysticks (required to receive SDL events). +// 2. SDL_JoystickID - this is an ID for each joystick that is supposed to remain consistent between plugging and unplugging. +// ES doesn't care if it does, though. +// 3. "Device ID" - this is something I made up and is what InputConfig's getDeviceID() returns. +// This is actually just an SDL_JoystickID (also called instance ID), but -1 means "keyboard" instead of "error." +// 4. Joystick GUID - this is some squashed version of joystick vendor, version, and a bunch of other device-specific things. +// It should remain the same across runs of the program/system restarts/device reordering and is what I use to identify which joystick to load. + namespace fs = boost::filesystem; InputManager::InputManager(Window* window) : mWindow(window), - mKeyboardInputConfig(NULL), - mNumJoysticks(0), mNumPlayers(0) + mKeyboardInputConfig(NULL) { } @@ -25,54 +37,95 @@ void InputManager::init() deinit(); SDL_InitSubSystem(SDL_INIT_JOYSTICK); + SDL_JoystickEventState(SDL_ENABLE); - mNumJoysticks = SDL_NumJoysticks(); - - for(int i = 0; i < mNumJoysticks; i++) + // first, open all currently present joysticks + int numJoysticks = SDL_NumJoysticks(); + for(int i = 0; i < numJoysticks; i++) { - SDL_Joystick* joy = SDL_JoystickOpen(i); - SDL_JoystickID joyId = SDL_JoystickInstanceID(joy); - mJoysticks.push_back(joy); - mInputConfigs[joyId] = new InputConfig(i, SDL_JoystickName(joy)); - - int numAxes = SDL_JoystickNumAxes(joy); - mPrevAxisValues[joyId] = new int[numAxes]; - std::fill(mPrevAxisValues[joyId], mPrevAxisValues[joyId] + numAxes, 0); //initialize array to 0 + addJoystickByDeviceIndex(i); } - mKeyboardInputConfig = new InputConfig(DEVICE_KEYBOARD, "Keyboard"); + mKeyboardInputConfig = new InputConfig(DEVICE_KEYBOARD, "Keyboard", KEYBOARD_GUID_STRING); + loadInputConfig(mKeyboardInputConfig); +} - loadConfig(); +void InputManager::addJoystickByDeviceIndex(int id) +{ + assert(id >= 0 && id < SDL_NumJoysticks()); + + // open joystick & add to our list + SDL_Joystick* joy = SDL_JoystickOpen(id); + assert(joy); - SDL_JoystickEventState(SDL_ENABLE); + // add it to our list so we can close it again later + SDL_JoystickID joyId = SDL_JoystickInstanceID(joy); + mJoysticks[joyId] = joy; + + char guid[65]; + SDL_JoystickGetGUIDString(SDL_JoystickGetGUID(joy), guid, 65); + + // create the InputConfig + mInputConfigs[joyId] = new InputConfig(joyId, SDL_JoystickName(joy), guid); + if(!loadInputConfig(mInputConfigs[joyId])) + { + LOG(LogInfo) << "Added unconfigured joystick " << SDL_JoystickName(joy) << " (GUID: " << guid << ", instance ID: " << joyId << ", device index: " << id << ")."; + }else{ + LOG(LogInfo) << "Added known joystick " << SDL_JoystickName(joy) << " (instance ID: " << joyId << ", device index: " << id << ")"; + } + + // set up the prevAxisValues + int numAxes = SDL_JoystickNumAxes(joy); + mPrevAxisValues[joyId] = new int[numAxes]; + std::fill(mPrevAxisValues[joyId], mPrevAxisValues[joyId] + numAxes, 0); //initialize array to 0 +} + +void InputManager::removeJoystickByJoystickID(SDL_JoystickID joyId) +{ + assert(joyId != -1); + + // delete old prevAxisValues + auto axisIt = mPrevAxisValues.find(joyId); + delete[] axisIt->second; + mPrevAxisValues.erase(axisIt); + + // delete old InputConfig + auto it = mInputConfigs.find(joyId); + delete it->second; + mInputConfigs.erase(it); + + // close the joystick + auto joyIt = mJoysticks.find(joyId); + if(joyIt != mJoysticks.end()) + { + SDL_JoystickClose(joyIt->second); + mJoysticks.erase(joyIt); + }else{ + LOG(LogError) << "Could not find joystick to close (instance ID: " << joyId << ")"; + } } void InputManager::deinit() { - SDL_JoystickEventState(SDL_DISABLE); - if(!initialized()) return; for(auto iter = mJoysticks.begin(); iter != mJoysticks.end(); iter++) { - SDL_JoystickClose(*iter); + SDL_JoystickClose(iter->second); } - mJoysticks.clear(); for(auto iter = mInputConfigs.begin(); iter != mInputConfigs.end(); iter++) { delete iter->second; } - mInputConfigs.clear(); for(auto iter = mPrevAxisValues.begin(); iter != mPrevAxisValues.end(); iter++) { delete[] iter->second; } - mPrevAxisValues.clear(); if(mKeyboardInputConfig != NULL) @@ -81,11 +134,12 @@ void InputManager::deinit() mKeyboardInputConfig = NULL; } + SDL_JoystickEventState(SDL_DISABLE); SDL_QuitSubSystem(SDL_INIT_JOYSTICK); } -int InputManager::getNumJoysticks() { return mNumJoysticks; } -int InputManager::getButtonCountByDevice(int id) +int InputManager::getNumJoysticks() { return mJoysticks.size(); } +int InputManager::getButtonCountByDevice(SDL_JoystickID id) { if(id == DEVICE_KEYBOARD) return 120; //it's a lot, okay. @@ -93,10 +147,7 @@ int InputManager::getButtonCountByDevice(int id) return SDL_JoystickNumButtons(mJoysticks[id]); } -int InputManager::getNumPlayers() { return mNumPlayers; } -void InputManager::setNumPlayers(int num) { mNumPlayers = num; } - -InputConfig* InputManager::getInputConfigByDevice(SDL_JoystickID device) +InputConfig* InputManager::getInputConfigByDevice(int device) { if(device == DEVICE_KEYBOARD) return mKeyboardInputConfig; @@ -104,23 +155,6 @@ InputConfig* InputManager::getInputConfigByDevice(SDL_JoystickID device) return mInputConfigs[device]; } -InputConfig* InputManager::getInputConfigByPlayer(int player) -{ - if(mKeyboardInputConfig->getPlayerNum() == player) - return mKeyboardInputConfig; - - for(auto iter = mInputConfigs.begin(); iter != mInputConfigs.end(); iter++) - { - if(iter->second->getPlayerNum() == player) - { - return iter->second; - } - } - - LOG(LogError) << "Could not find input config for player number " << player << "!"; - return NULL; -} - bool InputManager::parseEvent(const SDL_Event& ev) { bool causedEvent = false; @@ -188,102 +222,51 @@ bool InputManager::parseEvent(const SDL_Event& ev) break; case SDL_JOYDEVICEADDED: - deinit(); - init(); + addJoystickByDeviceIndex(ev.jdevice.which); // ev.jdevice.which is a device index return true; + + case SDL_JOYDEVICEREMOVED: + removeJoystickByJoystickID(ev.jdevice.which); // ev.jdevice.which is an SDL_JoystickID (instance ID) + return false; } return false; } -void InputManager::loadConfig() +bool InputManager::loadInputConfig(InputConfig* config) { - if(!initialized()) - { - LOG(LogError) << "ERROR - cannot load InputManager config without being initialized!"; - } - std::string path = getConfigPath(); if(!fs::exists(path)) - return; - + return false; + pugi::xml_document doc; pugi::xml_parse_result res = doc.load_file(path.c_str()); if(!res) { - LOG(LogError) << "Error loading input config: " << res.description(); - return; - } - - mNumPlayers = 0; - - bool* configuredDevice = new bool[mNumJoysticks]; - std::fill(configuredDevice, configuredDevice + mNumJoysticks, false); - - for(auto iter = mInputConfigs.begin(); iter != mInputConfigs.end(); iter++) - { - iter->second->setPlayerNum(-1); + LOG(LogError) << "Error parsing input config: " << res.description(); + return false; } pugi::xml_node root = doc.child("inputList"); + if(!root) + return false; - bool loadedKeyboard = false; - for(pugi::xml_node node = root.child("inputConfig"); node; node = node.next_sibling("inputConfig")) - { - std::string type = node.attribute("type").as_string(); + pugi::xml_node configNode = root.find_child_by_attribute("inputConfig", "deviceGUID", getDeviceGUIDString(config->getDeviceId()).c_str()); + if(!configNode) + return false; - if(type == "keyboard") - { - getInputConfigByDevice(DEVICE_KEYBOARD)->loadFromXML(node, mNumPlayers); - loadedKeyboard = true; - mNumPlayers++; - }else if(type == "joystick") - { - bool found = false; - std::string devName = node.attribute("deviceName").as_string(); - for(int i = 0; i < mNumJoysticks; i++) - { - if(!configuredDevice[i] && SDL_JoystickName(mJoysticks[i]) == devName) - { - SDL_JoystickID joyId = SDL_JoystickInstanceID(mJoysticks[i]); - mInputConfigs[joyId]->loadFromXML(node, mNumPlayers); - mNumPlayers++; - found = true; - configuredDevice[i] = true; - break; - } - } - - if(!found) - { - LOG(LogWarning) << "Could not find unconfigured joystick named \"" << devName << "\"! Skipping it.\n"; - continue; - } - }else{ - LOG(LogWarning) << "Device type \"" << type << "\" unknown!\n"; - } - } - - delete[] configuredDevice; - - if(!loadedKeyboard) - { - LOG(LogInfo) << "No keyboard input found. Loading default keyboard config."; - loadDefaultConfig(); - } - - LOG(LogInfo) << "Loaded InputConfig data for " << getNumPlayers() << " devices."; + config->loadFromXML(configNode); + return true; } -//used in an "emergency" where no configs could be loaded from the inputmanager config file +//used in an "emergency" where no keyboard config could be loaded from the inputmanager config file //allows the user to select to reconfigure in menus if this happens without having to delete es_input.cfg manually -void InputManager::loadDefaultConfig() +void InputManager::loadDefaultKBConfig() { InputConfig* cfg = getInputConfigByDevice(DEVICE_KEYBOARD); - mNumPlayers++; - cfg->setPlayerNum(0); + cfg->clear(); cfg->mapInput("up", Input(DEVICE_KEYBOARD, TYPE_KEY, SDLK_UP, 1, true)); cfg->mapInput("down", Input(DEVICE_KEYBOARD, TYPE_KEY, SDLK_DOWN, 1, true)); cfg->mapInput("left", Input(DEVICE_KEYBOARD, TYPE_KEY, SDLK_LEFT, 1, true)); @@ -291,35 +274,45 @@ void InputManager::loadDefaultConfig() cfg->mapInput("a", Input(DEVICE_KEYBOARD, TYPE_KEY, SDLK_RETURN, 1, true)); cfg->mapInput("b", Input(DEVICE_KEYBOARD, TYPE_KEY, SDLK_ESCAPE, 1, true)); - cfg->mapInput("menu", Input(DEVICE_KEYBOARD, TYPE_KEY, SDLK_F1, 1, true)); + cfg->mapInput("start", Input(DEVICE_KEYBOARD, TYPE_KEY, SDLK_F1, 1, true)); cfg->mapInput("select", Input(DEVICE_KEYBOARD, TYPE_KEY, SDLK_F2, 1, true)); + cfg->mapInput("pageup", Input(DEVICE_KEYBOARD, TYPE_KEY, SDLK_RIGHTBRACKET, 1, true)); cfg->mapInput("pagedown", Input(DEVICE_KEYBOARD, TYPE_KEY, SDLK_LEFTBRACKET, 1, true)); - - cfg->mapInput("mastervolup", Input(DEVICE_KEYBOARD, TYPE_KEY, SDLK_PLUS, 1, true)); - cfg->mapInput("mastervoldown", Input(DEVICE_KEYBOARD, TYPE_KEY, SDLK_MINUS, 1, true)); } -void InputManager::writeConfig() +void InputManager::writeDeviceConfig(InputConfig* config) { - if(!initialized()) - { - LOG(LogError) << "ERROR - cannot write config without being initialized!"; - return; - } + assert(initialized()); std::string path = getConfigPath(); pugi::xml_document doc; - pugi::xml_node root = doc.append_child("inputList"); - - mKeyboardInputConfig->writeToXML(root); - for(int i = 0; i < mNumJoysticks; i++) + if(fs::exists(path)) { - mInputConfigs[i]->writeToXML(root); + // merge files + pugi::xml_parse_result result = doc.load_file(path.c_str()); + if(!result) + { + LOG(LogError) << "Error parsing input config: " << result.description(); + }else{ + // successfully loaded, delete the old entry if it exists + pugi::xml_node root = doc.child("inputList"); + if(root) + { + pugi::xml_node oldEntry = root.find_child_by_attribute("inputConfig", "deviceGUID", config->getDeviceGUIDString().c_str()); + if(oldEntry) + root.remove_child(oldEntry); + } + } } + pugi::xml_node root = doc.child("inputList"); + if(!root) + root = doc.append_child("inputList"); + + config->writeToXML(root); doc.save_file(path.c_str()); } @@ -334,3 +327,35 @@ bool InputManager::initialized() const { return mKeyboardInputConfig != NULL; } + +int InputManager::getNumConfiguredDevices() +{ + int num = 0; + for(auto it = mInputConfigs.begin(); it != mInputConfigs.end(); it++) + { + if(it->second->isConfigured()) + num++; + } + + if(mKeyboardInputConfig->isConfigured()) + num++; + + return num; +} + +std::string InputManager::getDeviceGUIDString(int deviceId) +{ + if(deviceId == DEVICE_KEYBOARD) + return KEYBOARD_GUID_STRING; + + auto it = mJoysticks.find(deviceId); + if(it == mJoysticks.end()) + { + LOG(LogError) << "getDeviceGUIDString - deviceId " << deviceId << " not found!"; + return "something went horribly wrong"; + } + + char guid[65]; + SDL_JoystickGetGUIDString(SDL_JoystickGetGUID(it->second), guid, 65); + return std::string(guid); +} diff --git a/src/InputManager.h b/src/InputManager.h index 9c7ff22a3..a3fc0101d 100644 --- a/src/InputManager.h +++ b/src/InputManager.h @@ -12,19 +12,14 @@ class Window; //you should only ever instantiate one of these, by the way class InputManager { +private: static const int DEADZONE = 23000; Window* mWindow; - //non-InputManager classes shouldn't use this, as you can easily miss the keyboard and don't have SDL_JoystickIDs - InputConfig* getInputConfigByDevice(SDL_JoystickID device); + void loadDefaultKBConfig(); - void loadDefaultConfig(); - - int mNumJoysticks; - int mNumPlayers; - - std::vector mJoysticks; + std::map mJoysticks; std::map mInputConfigs; InputConfig* mKeyboardInputConfig; @@ -32,26 +27,29 @@ class InputManager bool initialized() const; + void addJoystickByDeviceIndex(int id); + void removeJoystickByJoystickID(SDL_JoystickID id); + bool loadInputConfig(InputConfig* config); // returns true if successfully loaded, false if not (or didn't exist) + public: InputManager(Window* window); ~InputManager(); - void loadConfig(); - void writeConfig(); + void writeDeviceConfig(InputConfig* config); static std::string getConfigPath(); void init(); void deinit(); - void setNumPlayers(int num); - int getNumPlayers(); - int getNumJoysticks(); - int getButtonCountByDevice(int id); + int getButtonCountByDevice(int deviceId); + int getNumConfiguredDevices(); + + std::string getDeviceGUIDString(int deviceId); + + InputConfig* getInputConfigByDevice(int deviceId); bool parseEvent(const SDL_Event& ev); - - InputConfig* getInputConfigByPlayer(int player); }; #endif diff --git a/src/components/ComponentList.cpp b/src/components/ComponentList.cpp index 24e52d852..f3a9ecd0c 100644 --- a/src/components/ComponentList.cpp +++ b/src/components/ComponentList.cpp @@ -290,3 +290,10 @@ std::vector ComponentList::getHelpPrompts() return mEntries.at(mCursor).data.elements.back().component->getHelpPrompts(); } + +bool ComponentList::moveCursor(int amt) +{ + bool ret = listInput(amt); + listInput(0); + return ret; +} diff --git a/src/components/ComponentList.h b/src/components/ComponentList.h index 049e89776..fc33d6417 100644 --- a/src/components/ComponentList.h +++ b/src/components/ComponentList.h @@ -59,6 +59,7 @@ public: void onFocusGained() override; void onFocusLost() override; + bool moveCursor(int amt); inline int getCursorId() const { return mCursor; } float getTotalRowHeight() const; diff --git a/src/components/HelpComponent.cpp b/src/components/HelpComponent.cpp index 14925f64e..74a7ff1c8 100644 --- a/src/components/HelpComponent.cpp +++ b/src/components/HelpComponent.cpp @@ -12,7 +12,7 @@ static const std::map ICON_PATH_MAP = boost::assign::m ("up/down/left/right", ":/help/dpad_all.png") ("a", ":/help/a.png") ("b", ":/help/b.png") - ("menu", ":/help/start.png") + ("start", ":/help/start.png") ("select", ":/help/select.png"); HelpComponent::HelpComponent(Window* window) : GuiComponent(window) diff --git a/src/guis/GuiDetectDevice.cpp b/src/guis/GuiDetectDevice.cpp index 8bd59400c..53451f84b 100644 --- a/src/guis/GuiDetectDevice.cpp +++ b/src/guis/GuiDetectDevice.cpp @@ -3,104 +3,101 @@ #include "../Renderer.h" #include "../resources/Font.h" #include "GuiInputConfig.h" +#include "../components/TextComponent.h" #include #include #include +#include "../views/ViewController.h" -GuiDetectDevice::GuiDetectDevice(Window* window) : GuiComponent(window) +#define HOLD_TIME 1000 + +using namespace Eigen; + +GuiDetectDevice::GuiDetectDevice(Window* window, bool firstRun) : GuiComponent(window), + mBackground(window, ":/frame.png"), mGrid(window, Vector2i(1, 4)) { - //clear any player information from the InputManager - for(int i = 0; i < mWindow->getInputManager()->getNumPlayers(); i++) - { - InputConfig* cfg = mWindow->getInputManager()->getInputConfigByPlayer(i); - cfg->setPlayerNum(-1); - cfg->clear(); - } - mWindow->getInputManager()->setNumPlayers(0); + mHoldingConfig = NULL; + mHoldTime = 0; - mCurrentPlayer = 0; - mHoldingFinish = false; + addChild(&mBackground); + addChild(&mGrid); + + if(firstRun) + { + mDoneCallback = [window] { + window->getViewController()->goToStart(); + }; + } + + mTitle = std::make_shared(mWindow, firstRun ? "WELCOME TO EMULATIONSTATION" : "SELECT A DEVICE", + Font::get(FONT_SIZE_MEDIUM), 0x666666FF, TextComponent::ALIGN_CENTER); + mGrid.setEntry(mTitle, Vector2i(0, 0), false, true); + + std::string msg = (firstRun ? "First, we need to configure your input device!\n" : ""); + msg += "Hold a button on your input device to configure it.\n" + "Press F4 to quit at any time."; + mMsg = std::make_shared(mWindow, msg, Font::get(FONT_SIZE_SMALL), 0x777777FF, TextComponent::ALIGN_CENTER); + mGrid.setEntry(mMsg, Vector2i(0, 1), false, true); + + std::stringstream deviceInfo; + int numDevices = mWindow->getInputManager()->getNumJoysticks(); + + if(numDevices > 0) + deviceInfo << numDevices << " joystick" << (numDevices > 1 ? "s" : "") << " detected."; + else + deviceInfo << "No joysticks detected!"; + mDeviceInfo = std::make_shared(mWindow, deviceInfo.str(), Font::get(FONT_SIZE_SMALL), 0x888888FF, TextComponent::ALIGN_CENTER); + mGrid.setEntry(mDeviceInfo, Vector2i(0, 2), false, true); + + mDeviceHeld = std::make_shared(mWindow, "", Font::get(FONT_SIZE_MEDIUM), 0x777777FF, TextComponent::ALIGN_CENTER); + mGrid.setEntry(mDeviceHeld, Vector2i(0, 3), false, true); + + setSize(Renderer::getScreenWidth() * 0.6f, Renderer::getScreenHeight() * 0.5f); + setPosition((Renderer::getScreenWidth() - mSize.x()) / 2, (Renderer::getScreenHeight() - mSize.y()) / 2); +} + +void GuiDetectDevice::onSizeChanged() +{ + mBackground.fitTo(mSize, Vector3f::Zero(), Vector2f(-32, -32)); + + // grid + mGrid.setSize(mSize); + mGrid.setRowHeightPerc(0, mTitle->getFont()->getHeight() / mSize.y()); + mGrid.setRowHeightPerc(2, mDeviceInfo->getFont()->getHeight() / mSize.y()); + mGrid.setRowHeightPerc(3, mDeviceHeld->getFont()->getHeight() / mSize.y()); } bool GuiDetectDevice::input(InputConfig* config, Input input) { - if((input.type == TYPE_BUTTON || input.type == TYPE_KEY)) + if(input.type == TYPE_BUTTON || input.type == TYPE_KEY) { - if(config->getPlayerNum() != -1) + if(input.value && mHoldingConfig == NULL) { - if(config->getPlayerNum() == 0) - { - if(input.value) - { - mFinishTimer = 0; - mHoldingFinish = true; - }else{ - mHoldingFinish = false; - } - } - return true; - } - - if(!input.value) - return false; - - config->setPlayerNum(mCurrentPlayer); - mWindow->getInputManager()->setNumPlayers(mWindow->getInputManager()->getNumPlayers() + 1); //inc total number of players - mCurrentPlayer++; - - //mapped everything we possibly can? - if(mCurrentPlayer >= mWindow->getInputManager()->getNumJoysticks() + 1) //+1 for keyboard + // started holding + mHoldingConfig = config; + mHoldTime = HOLD_TIME; + mDeviceHeld->setText(config->getDeviceName()); + }else if(!input.value && mHoldingConfig == config) { - done(); + // cancel + mHoldingConfig = NULL; + mDeviceHeld->setText(""); } - - return true; } - return false; -} - -void GuiDetectDevice::done() -{ - mWindow->pushGui(new GuiInputConfig(mWindow, mWindow->getInputManager()->getInputConfigByPlayer(0))); - delete this; + return true; } void GuiDetectDevice::update(int deltaTime) { - if(mHoldingFinish) + if(mHoldingConfig) { - mFinishTimer += deltaTime; - - if(mFinishTimer > 1000) - done(); + mHoldTime -= deltaTime; + if(mHoldTime <= 0) + { + // picked one! + mWindow->pushGui(new GuiInputConfig(mWindow, mHoldingConfig, true, mDoneCallback)); + delete this; + } } } - -void GuiDetectDevice::render(const Eigen::Affine3f& parentTrans) -{ - Eigen::Affine3f trans = parentTrans * getTransform(); - Renderer::setMatrix(trans); - - std::shared_ptr font = Font::get(FONT_SIZE_MEDIUM); - - std::string playerString; - std::stringstream stream; - stream << (mCurrentPlayer + 1); - stream >> playerString; - - font->drawCenteredText("Press a button on the device for", 0, Renderer::getScreenHeight() / 3.0f, 0x000000FF); - font->drawCenteredText("PLAYER " + playerString, 0, (Renderer::getScreenHeight()*1.5f) / 3.0f, 0x333333FF); - - if(mWindow->getInputManager()->getNumPlayers() > 0) - { - font->drawCenteredText("(P1 - hold a button to finish)", 0, (Renderer::getScreenHeight()*2) / 3.0f, (mHoldingFinish ? 0x0000FFFF : 0x000066FF)); - } - - if(mWindow->getInputManager()->getNumJoysticks() == 0) - { - font->drawCenteredText("No joysticks detected!", 0, Renderer::getScreenHeight() - (font->getHeight()*2.0f)-10, 0xFF0000FF); - } - - font->drawCenteredText("Press F4 to quit.", 0, Renderer::getScreenHeight() - font->getHeight() - 2.0f , 0x000000FF); -} diff --git a/src/guis/GuiDetectDevice.h b/src/guis/GuiDetectDevice.h index b96cdc0b5..57ff8b4bd 100644 --- a/src/guis/GuiDetectDevice.h +++ b/src/guis/GuiDetectDevice.h @@ -1,20 +1,31 @@ #pragma once #include "../GuiComponent.h" +#include "../components/NinePatchComponent.h" +#include "../components/ComponentGrid.h" + +class TextComponent; class GuiDetectDevice : public GuiComponent { public: - GuiDetectDevice(Window* window); + GuiDetectDevice(Window* window, bool firstRun); - bool input(InputConfig* config, Input input); - void update(int deltaTime); - void render(const Eigen::Affine3f& parentTrans) override; + bool input(InputConfig* config, Input input) override; + void update(int deltaTime) override; + void onSizeChanged() override; private: - void done(); + InputConfig* mHoldingConfig; + int mHoldTime; - bool mHoldingFinish; - int mFinishTimer; - int mCurrentPlayer; + NinePatchComponent mBackground; + ComponentGrid mGrid; + + std::shared_ptr mTitle; + std::shared_ptr mMsg; + std::shared_ptr mDeviceInfo; + std::shared_ptr mDeviceHeld; + + std::function mDoneCallback; }; diff --git a/src/guis/GuiInputConfig.cpp b/src/guis/GuiInputConfig.cpp index 68103d049..509986c47 100644 --- a/src/guis/GuiInputConfig.cpp +++ b/src/guis/GuiInputConfig.cpp @@ -1,115 +1,157 @@ #include "GuiInputConfig.h" #include "../Window.h" -#include "../Renderer.h" -#include "../resources/Font.h" #include "../Log.h" -#include "../views/ViewController.h" +#include "../components/TextComponent.h" +#include "../components/ImageComponent.h" +#include "../components/MenuComponent.h" +#include "../components/ButtonComponent.h" +#include "../Util.h" static const int inputCount = 10; -static std::string inputName[inputCount] = { "Up", "Down", "Left", "Right", "A", "B", "Menu", "Select", "PageUp", "PageDown"}; -static std::string inputDispName[inputCount] = { "Up", "Down", "Left", "Right", "Accept", "Back", "Menu", "Jump to Letter", "Page Up", "Page Down"}; +static const char* inputName[inputCount] = { "Up", "Down", "Left", "Right", "A", "B", "Start", "Select", "PageUp", "PageDown"}; +static const char* inputDispName[inputCount] = { "Up", "Down", "Left", "Right", "A", "B", "Start", "Select", "Page Up", "Page Down"}; +static const char* inputIcon[inputCount] = { ":/help/dpad_up.png", ":/help/dpad_down.png", ":/help/dpad_left.png", ":/help/dpad_right.png", + ":/help/a.png", ":/help/b.png", ":/help/start.png", ":/help/select.png", ":/help/l.png", ":/help/r.png" }; //MasterVolUp and MasterVolDown are also hooked up, but do not appear on this screen. //If you want, you can manually add them to es_input.cfg. -GuiInputConfig::GuiInputConfig(Window* window, InputConfig* target) : GuiComponent(window), mTargetConfig(target), mCanSkip(false) +using namespace Eigen; + +GuiInputConfig::GuiInputConfig(Window* window, InputConfig* target, bool reconfigureAll, const std::function& okCallback) : GuiComponent(window), + mBackground(window, ":/frame.png"), mGrid(window, Vector2i(1, 5)), + mTargetConfig(target) { - mCurInputId = 0; LOG(LogInfo) << "Configuring device " << target->getDeviceId(); + + if(reconfigureAll) + target->clear(); + + mConfiguringAll = reconfigureAll; + mConfiguringRow = mConfiguringAll; + + addChild(&mBackground); + addChild(&mGrid); + + mTitle = std::make_shared(mWindow, "PLEASE CONFIGURE INPUT FOR", Font::get(FONT_SIZE_SMALL), 0x555555FF, TextComponent::ALIGN_CENTER); + mGrid.setEntry(mTitle, Vector2i(0, 0), false, true); + + mSubtitle1 = std::make_shared(mWindow, target->getDeviceName(), Font::get(FONT_SIZE_MEDIUM), 0x555555FF, TextComponent::ALIGN_CENTER); + mGrid.setEntry(mSubtitle1, Vector2i(0, 1), false, true); + + mSubtitle2 = std::make_shared(mWindow, "(HOLD ANY BUTTON TO SKIP BUT NOT YET)", Font::get(FONT_SIZE_SMALL), 0x999999FF, TextComponent::ALIGN_CENTER); + mGrid.setEntry(mSubtitle2, Vector2i(0, 2), false, true); + + mList = std::make_shared(mWindow); + mGrid.setEntry(mList, Vector2i(0, 3), true, true); + for(int i = 0; i < inputCount; i++) + { + ComponentListRow row; + + // icon + auto icon = std::make_shared(mWindow); + icon->setImage(inputIcon[i]); + icon->setResize(0, Font::get(FONT_SIZE_MEDIUM)->getHeight() * 0.8f); + row.addElement(icon, false); + + auto text = std::make_shared(mWindow, inputDispName[i], Font::get(FONT_SIZE_MEDIUM), 0x777777FF); + row.addElement(text, true); + + auto mapping = std::make_shared(mWindow, "-NOT DEFINED-", Font::get(FONT_SIZE_MEDIUM, FONT_PATH_LIGHT), 0x999999FF); + row.addElement(mapping, true); + + row.input_handler = [this, i, mapping](InputConfig* config, Input input) -> bool + { + if(!input.value) + return false; + + if(mConfiguringRow) + { + if(!process(config, input, i, mapping)) // button press invalid; try again + return true; + if(mConfiguringAll) + { + if(!mList->moveCursor(1)) // try to move to the next one + { + // at bottom of list + mConfiguringAll = false; + mConfiguringRow = false; + mGrid.moveCursor(Vector2i(0, 1)); + } + }else{ + mConfiguringRow = false; // we only wanted to configure one row + } + return true; + }else{ + // not configuring, start configuring when A is pressed + if(config->isMappedTo("a", input) && input.value) + { + mConfiguringRow = true; + return true; + } + return false; + } + + return false; + }; + mList->addRow(row); + } + + // buttons + std::vector< std::shared_ptr > buttons; + buttons.push_back(std::make_shared(mWindow, "OK", "ok", [this, okCallback] { + mWindow->getInputManager()->writeDeviceConfig(mTargetConfig); // save + if(okCallback) + okCallback(); + delete this; + })); + mButtonGrid = makeButtonGrid(mWindow, buttons); + mGrid.setEntry(mButtonGrid, Vector2i(0, 4), true, false); + + setSize(Renderer::getScreenWidth() * 0.6f, Renderer::getScreenHeight() * 0.7f); + setPosition((Renderer::getScreenWidth() - mSize.x()) / 2, (Renderer::getScreenHeight() - mSize.y()) / 2); } -void GuiInputConfig::update(int deltaTime) +void GuiInputConfig::onSizeChanged() { + mBackground.fitTo(mSize, Vector3f::Zero(), Vector2f(-32, -32)); + // update grid + mGrid.setSize(mSize); + + mGrid.setRowHeightPerc(0, mTitle->getFont()->getHeight() / mSize.y()); + mGrid.setRowHeightPerc(1, mSubtitle1->getFont()->getHeight() / mSize.y()); + mGrid.setRowHeightPerc(2, mSubtitle2->getFont()->getHeight() / mSize.y()); + mGrid.setRowHeightPerc(4, mButtonGrid->getSize().y() / mSize.y()); } -bool GuiInputConfig::input(InputConfig* config, Input input) +void GuiInputConfig::error(const std::string& msg) { - if(config != mTargetConfig || input.value == 0) + // TODO + LOG(LogWarning) << msg; +} + +bool GuiInputConfig::process(InputConfig* config, Input input, int inputId, const std::shared_ptr& text) +{ + // from some other input source + if(config != mTargetConfig) return false; - if(mCurInputId >= inputCount) + // if this input is mapped to something other than "nothing" or the current row, error + // (if it's the same as what it was before, allow it) + if(config->getMappedTo(input).size() > 0 && !config->isMappedTo(inputName[inputId], input)) { - //done - if(input.type == TYPE_BUTTON || input.type == TYPE_KEY) - { - if(mTargetConfig->getPlayerNum() < mWindow->getInputManager()->getNumPlayers() - 1) - { - mWindow->pushGui(new GuiInputConfig(mWindow, mWindow->getInputManager()->getInputConfigByPlayer(mTargetConfig->getPlayerNum() + 1))); - }else{ - mWindow->getInputManager()->writeConfig(); - mWindow->getViewController()->goToStart(); - } - delete this; - return true; - } - }else{ - if(mCanSkip && config->isMappedTo("a", input)) - { - mCurInputId++; - return true; - } - - if(config->getMappedTo(input).size() > 0) - { - mErrorMsg = "Already mapped!"; - return true; - } - - input.configured = true; - LOG(LogInfo) << " [" << input.string() << "] -> " << inputName[mCurInputId]; - - config->mapInput(inputName[mCurInputId], input); - mCurInputId++; - mErrorMsg = ""; - - //for buttons with not enough buttons, press A to skip - if(mCurInputId >= 7) - { - mCanSkip = true; - } - return true; + error("Already mapped!"); + return false; } - return false; -} - -void GuiInputConfig::render(const Eigen::Affine3f& parentTrans) -{ - Eigen::Affine3f trans = parentTrans * getTransform(); - Renderer::setMatrix(trans); - - std::shared_ptr font = Font::get(FONT_SIZE_MEDIUM); - - std::stringstream stream; - stream << "PLAYER " << mTargetConfig->getPlayerNum() + 1 << ", press..."; - font->drawText(stream.str(), Eigen::Vector2f(10, 10), 0x000000FF); - - int y = 14 + (int)font->getHeight(); - for(int i = 0; i < mCurInputId; i++) - { - font->drawText(inputDispName[i], Eigen::Vector2f(10, y), 0x00CC00FF); - y += (int)font->getHeight() + 5; - } - - if(mCurInputId >= inputCount) - { - font->drawCenteredText("Basic config done!", 0, Renderer::getScreenHeight() * 0.4f, 0x00CC00FF); - font->drawCenteredText("Press any button to continue.", 0, Renderer::getScreenHeight() * 0.4f + font->getHeight() + 4, 0x000000FF); - }else{ - font->drawText(inputDispName[mCurInputId], Eigen::Vector2f(10, y), 0x000000FF); - if(mCanSkip) - { - Eigen::Vector2f textSize = font->sizeText(inputDispName[mCurInputId]); - textSize[0] += 14; - - if(Renderer::getScreenWidth() / 2.5f > textSize.x()) - textSize[0] = Renderer::getScreenWidth() / 2.5f; - - font->drawText("press Accept to skip", Eigen::Vector2f(textSize.x(), y), 0x0000AAFF); - } - } - - if(!mErrorMsg.empty()) - font->drawCenteredText(mErrorMsg, 0, (float)Renderer::getScreenHeight() - font->getHeight() - 10, 0xFF0000FF); + text->setText(strToUpper(input.string())); + text->setColor(0x777777FF); + + input.configured = true; + config->mapInput(inputName[inputId], input); + + LOG(LogInfo) << " Mapping [" << input.string() << "] -> " << inputName[inputId]; + + return true; } diff --git a/src/guis/GuiInputConfig.h b/src/guis/GuiInputConfig.h index 3b0fb9bb0..be701b029 100644 --- a/src/guis/GuiInputConfig.h +++ b/src/guis/GuiInputConfig.h @@ -1,20 +1,33 @@ #pragma once #include "../GuiComponent.h" -#include +#include "../components/NinePatchComponent.h" +#include "../components/ComponentGrid.h" +#include "../components/ComponentList.h" + +class TextComponent; class GuiInputConfig : public GuiComponent { public: - GuiInputConfig(Window* window, InputConfig* target); + GuiInputConfig(Window* window, InputConfig* target, bool reconfigureAll, const std::function& okCallback); - bool input(InputConfig* config, Input input); - void update(int deltaTime); - void render(const Eigen::Affine3f& parentTrans) override; + void onSizeChanged() override; private: - std::string mErrorMsg; + void error(const std::string& msg); + bool process(InputConfig* config, Input input, int inputId, const std::shared_ptr& text); + + NinePatchComponent mBackground; + ComponentGrid mGrid; + + std::shared_ptr mTitle; + std::shared_ptr mSubtitle1; + std::shared_ptr mSubtitle2; + std::shared_ptr mList; + std::shared_ptr mButtonGrid; + InputConfig* mTargetConfig; - int mCurInputId; - bool mCanSkip; + bool mConfiguringRow; // next input captured by mList will be interpretted as a remap + bool mConfiguringAll; // move the cursor down after configuring a row and start configuring the next row until we reach the bottom }; diff --git a/src/guis/GuiMenu.cpp b/src/guis/GuiMenu.cpp index 5a7f1981e..a9a0b5c97 100644 --- a/src/guis/GuiMenu.cpp +++ b/src/guis/GuiMenu.cpp @@ -6,6 +6,7 @@ #include "GuiMsgBox.h" #include "GuiSettings.h" #include "GuiScraperStart.h" +#include "GuiDetectDevice.h" #include "../components/ButtonComponent.h" #include "../components/SwitchComponent.h" @@ -120,6 +121,11 @@ GuiMenu::GuiMenu(Window* window) : GuiComponent(window), mMenu(window, "MAIN MEN mWindow->pushGui(s); }); + addEntry("CONFIGURE INPUT", 0x777777FF, true, + [this] { + mWindow->pushGui(new GuiDetectDevice(mWindow, false)); + }); + addEntry("QUIT", 0x777777FF, true, [this] { auto s = new GuiSettings(mWindow, "QUIT"); @@ -191,7 +197,7 @@ void GuiMenu::addEntry(const char* name, unsigned int color, bool add_arrow, con bool GuiMenu::input(InputConfig* config, Input input) { - if((config->isMappedTo("b", input) || config->isMappedTo("menu", input)) && input.value != 0) + if((config->isMappedTo("b", input) || config->isMappedTo("start", input)) && input.value != 0) { delete this; return true; diff --git a/src/main.cpp b/src/main.cpp index 6b9479917..548f6dc19 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -175,11 +175,11 @@ int main(int argc, char* argv[]) window.getViewController()->preload(); //choose which GUI to open depending on if an input configuration already exists - if(fs::exists(InputManager::getConfigPath())) + if(fs::exists(InputManager::getConfigPath()) && window.getInputManager()->getNumConfiguredDevices() > 0) { window.getViewController()->goToStart(); }else{ - window.pushGui(new GuiDetectDevice(&window)); + window.pushGui(new GuiDetectDevice(&window, true)); } //generate joystick events since we're done loading diff --git a/src/views/ViewController.cpp b/src/views/ViewController.cpp index 9b75fd94c..c5f904964 100644 --- a/src/views/ViewController.cpp +++ b/src/views/ViewController.cpp @@ -161,7 +161,7 @@ bool ViewController::input(InputConfig* config, Input input) return true; // open menu - if(config->isMappedTo("menu", input) && input.value != 0) + if(config->isMappedTo("start", input) && input.value != 0) { // open menu mWindow->pushGui(new GuiMenu(mWindow)); @@ -255,7 +255,7 @@ std::vector ViewController::getHelpPrompts() return prompts; prompts = mCurrentView->getHelpPrompts(); - prompts.push_back(HelpPrompt("menu", "open menu")); + prompts.push_back(HelpPrompt("start", "open menu")); return prompts; }