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.
This commit is contained in:
Aloshi 2014-03-21 20:12:57 -05:00
parent 9a3b0af337
commit 980a2c4ec6
14 changed files with 458 additions and 357 deletions

View file

@ -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<std::string> 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<std::string, Input>::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; }

View file

@ -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<std::string> 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<std::string, Input> mNameMap;
const int mDeviceId;
const std::string mDeviceName;
int mPlayerNum;
const std::string mDeviceGUID;
};
#endif

View file

@ -6,11 +6,23 @@
#include <boost/filesystem.hpp>
#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);
}

View file

@ -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<SDL_Joystick*> mJoysticks;
std::map<SDL_JoystickID, SDL_Joystick*> mJoysticks;
std::map<SDL_JoystickID, InputConfig*> 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

View file

@ -290,3 +290,10 @@ std::vector<HelpPrompt> ComponentList::getHelpPrompts()
return mEntries.at(mCursor).data.elements.back().component->getHelpPrompts();
}
bool ComponentList::moveCursor(int amt)
{
bool ret = listInput(amt);
listInput(0);
return ret;
}

View file

@ -59,6 +59,7 @@ public:
void onFocusGained() override;
void onFocusLost() override;
bool moveCursor(int amt);
inline int getCursorId() const { return mCursor; }
float getTotalRowHeight() const;

View file

@ -12,7 +12,7 @@ static const std::map<std::string, const char*> 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)

View file

@ -3,104 +3,101 @@
#include "../Renderer.h"
#include "../resources/Font.h"
#include "GuiInputConfig.h"
#include "../components/TextComponent.h"
#include <iostream>
#include <string>
#include <sstream>
#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<TextComponent>(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<TextComponent>(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<TextComponent>(mWindow, deviceInfo.str(), Font::get(FONT_SIZE_SMALL), 0x888888FF, TextComponent::ALIGN_CENTER);
mGrid.setEntry(mDeviceInfo, Vector2i(0, 2), false, true);
mDeviceHeld = std::make_shared<TextComponent>(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 = 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);
}

View file

@ -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<TextComponent> mTitle;
std::shared_ptr<TextComponent> mMsg;
std::shared_ptr<TextComponent> mDeviceInfo;
std::shared_ptr<TextComponent> mDeviceHeld;
std::function<void()> mDoneCallback;
};

View file

@ -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<void()>& 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<TextComponent>(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<TextComponent>(mWindow, target->getDeviceName(), Font::get(FONT_SIZE_MEDIUM), 0x555555FF, TextComponent::ALIGN_CENTER);
mGrid.setEntry(mSubtitle1, Vector2i(0, 1), false, true);
mSubtitle2 = std::make_shared<TextComponent>(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<ComponentList>(mWindow);
mGrid.setEntry(mList, Vector2i(0, 3), true, true);
for(int i = 0; i < inputCount; i++)
{
ComponentListRow row;
// icon
auto icon = std::make_shared<ImageComponent>(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<TextComponent>(mWindow, inputDispName[i], Font::get(FONT_SIZE_MEDIUM), 0x777777FF);
row.addElement(text, true);
auto mapping = std::make_shared<TextComponent>(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<ButtonComponent> > buttons;
buttons.push_back(std::make_shared<ButtonComponent>(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<TextComponent>& 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 = 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;
}

View file

@ -1,20 +1,33 @@
#pragma once
#include "../GuiComponent.h"
#include <string>
#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<void()>& 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<TextComponent>& text);
NinePatchComponent mBackground;
ComponentGrid mGrid;
std::shared_ptr<TextComponent> mTitle;
std::shared_ptr<TextComponent> mSubtitle1;
std::shared_ptr<TextComponent> mSubtitle2;
std::shared_ptr<ComponentList> mList;
std::shared_ptr<ComponentGrid> 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
};

View file

@ -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;

View file

@ -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

View file

@ -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<HelpPrompt> ViewController::getHelpPrompts()
return prompts;
prompts = mCurrentView->getHelpPrompts();
prompts.push_back(HelpPrompt("menu", "open menu"));
prompts.push_back(HelpPrompt("start", "open menu"));
return prompts;
}