/*
|
|
* Copyright (C) 2017 Christopher J. Howard
|
|
*
|
|
* This file is part of Antkeeper Source Code.
|
|
*
|
|
* Antkeeper Source Code is free software: you can redistribute it and/or modify
|
|
* it under the terms of the GNU General Public License as published by
|
|
* the Free Software Foundation, either version 3 of the License, or
|
|
* (at your option) any later version.
|
|
*
|
|
* Antkeeper Source Code is distributed in the hope that it will be useful,
|
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
* GNU General Public License for more details.
|
|
*
|
|
* You should have received a copy of the GNU General Public License
|
|
* along with Antkeeper Source Code. If not, see <http://www.gnu.org/licenses/>.
|
|
*/
|
|
|
|
#include "controls.hpp"
|
|
#include <sstream>
|
|
#include <fstream>
|
|
#include <vector>
|
|
|
|
Control::Control():
|
|
deadzone(0.1f),
|
|
currentValue(0.0f),
|
|
previousValue(0.0f)
|
|
{}
|
|
|
|
Control::~Control()
|
|
{}
|
|
|
|
void Control::setDeadzone(float value)
|
|
{
|
|
deadzone = value;
|
|
}
|
|
|
|
void Control::update()
|
|
{
|
|
if (!boundMouseWheelAxes.empty())
|
|
{
|
|
currentValue = 0.0f;
|
|
}
|
|
|
|
previousValue = currentValue;
|
|
}
|
|
|
|
bool Control::isTriggered() const
|
|
{
|
|
return currentValue > deadzone;
|
|
}
|
|
|
|
bool Control::wasTriggered() const
|
|
{
|
|
return previousValue > deadzone;
|
|
}
|
|
|
|
bool Control::isUnbound() const
|
|
{
|
|
return (boundKeys.empty() && boundMouseButtons.empty() && boundMouseWheelAxes.empty() && boundGamepadButtons.empty() && boundGamepadAxes.empty());
|
|
}
|
|
|
|
void Control::bindKey(Keyboard* keyboard, int scancode)
|
|
{
|
|
// Check if already observing this keyboard
|
|
bool observing = false;
|
|
for (auto it: boundKeys)
|
|
{
|
|
if (it.first == keyboard)
|
|
{
|
|
observing = true;
|
|
break;
|
|
}
|
|
}
|
|
|
|
if (!observing)
|
|
keyboard->addKeyObserver(static_cast<KeyObserver*>(this));
|
|
boundKeys.push_back(std::pair<Keyboard*, int>(keyboard, scancode));
|
|
}
|
|
|
|
void Control::bindMouseButton(Mouse* mouse, int button)
|
|
{
|
|
// Checking if already observing this mouse
|
|
bool observing = false;
|
|
for (auto it: boundMouseButtons)
|
|
{
|
|
if (it.first == mouse)
|
|
{
|
|
observing = true;
|
|
break;
|
|
}
|
|
}
|
|
|
|
if (!observing)
|
|
mouse->addMouseButtonObserver(static_cast<MouseButtonObserver*>(this));
|
|
boundMouseButtons.push_back(std::pair<Mouse*, int>(mouse, button));
|
|
}
|
|
|
|
void Control::bindMouseWheelAxis(Mouse* mouse, MouseWheelAxis axis)
|
|
{
|
|
// Checking if already observing this mouse
|
|
bool observing = false;
|
|
for (auto it: boundMouseWheelAxes)
|
|
{
|
|
if (it.first == mouse)
|
|
{
|
|
observing = true;
|
|
break;
|
|
}
|
|
}
|
|
|
|
if (!observing)
|
|
mouse->addMouseWheelObserver(static_cast<MouseWheelObserver*>(this));
|
|
boundMouseWheelAxes.push_back(std::pair<Mouse*, MouseWheelAxis>(mouse, axis));
|
|
}
|
|
|
|
void Control::bindGamepadButton(Gamepad* gamepad, int button)
|
|
{
|
|
bool observing = false;
|
|
for (auto it: boundGamepadButtons)
|
|
{
|
|
if (it.first == gamepad)
|
|
{
|
|
observing = true;
|
|
break;
|
|
}
|
|
}
|
|
|
|
if (!observing)
|
|
gamepad->addGamepadButtonObserver(static_cast<GamepadButtonObserver*>(this));
|
|
boundGamepadButtons.push_back(std::pair<Gamepad*, int>(gamepad, button));
|
|
}
|
|
|
|
void Control::bindGamepadAxis(Gamepad* gamepad, int axis, bool negative)
|
|
{
|
|
bool observing = false;
|
|
for (auto it: boundGamepadAxes)
|
|
{
|
|
if (std::get<0>(it) == gamepad)
|
|
{
|
|
observing = true;
|
|
break;
|
|
}
|
|
}
|
|
|
|
if (!observing)
|
|
gamepad->addGamepadAxisObserver(static_cast<GamepadAxisObserver*>(this));
|
|
boundGamepadAxes.push_back(std::make_tuple(gamepad, axis, negative));
|
|
}
|
|
|
|
void Control::bind(const InputEvent& event)
|
|
{
|
|
switch (event.type)
|
|
{
|
|
case InputEvent::Type::KEY:
|
|
bindKey(event.key.first, event.key.second);
|
|
break;
|
|
|
|
case InputEvent::Type::MOUSE_BUTTON:
|
|
bindMouseButton(event.mouseButton.first, event.mouseButton.second);
|
|
break;
|
|
|
|
case InputEvent::Type::MOUSE_WHEEL:
|
|
{
|
|
int x = std::get<1>(event.mouseWheel);
|
|
int y = std::get<2>(event.mouseWheel);
|
|
|
|
MouseWheelAxis axis;
|
|
if (x > 0)
|
|
axis = MouseWheelAxis::POSITIVE_X;
|
|
else if (x < 0)
|
|
axis = MouseWheelAxis::NEGATIVE_X;
|
|
else if (y > 0)
|
|
axis = MouseWheelAxis::POSITIVE_Y;
|
|
else if (y < 0)
|
|
axis = MouseWheelAxis::NEGATIVE_Y;
|
|
else
|
|
break;
|
|
|
|
bindMouseWheelAxis(std::get<0>(event.mouseWheel), axis);
|
|
break;
|
|
}
|
|
|
|
case InputEvent::Type::GAMEPAD_BUTTON:
|
|
bindGamepadButton(event.gamepadButton.first, event.gamepadButton.second);
|
|
break;
|
|
|
|
case InputEvent::Type::GAMEPAD_AXIS:
|
|
bindGamepadAxis(std::get<0>(event.gamepadAxis), std::get<1>(event.gamepadAxis), std::get<2>(event.gamepadAxis));
|
|
break;
|
|
|
|
default:
|
|
break;
|
|
}
|
|
}
|
|
|
|
void Control::unbind()
|
|
{
|
|
while (!boundKeys.empty())
|
|
{
|
|
// Remove the first bound key and stop observing its keyboard
|
|
Keyboard* keyboard = boundKeys.front().first;
|
|
keyboard->removeKeyObserver(static_cast<KeyObserver*>(this));
|
|
boundKeys.pop_front();
|
|
|
|
// Remove other bound keys which are associated with the keyboard
|
|
auto it = boundKeys.begin();
|
|
while (it != boundKeys.end())
|
|
{
|
|
if (it->first == keyboard)
|
|
boundKeys.erase(it++);
|
|
else
|
|
++it;
|
|
}
|
|
}
|
|
|
|
while (!boundMouseButtons.empty())
|
|
{
|
|
// Remove the first bound mouse button and stop observing its mouse
|
|
Mouse* mouse = boundMouseButtons.front().first;
|
|
mouse->removeMouseButtonObserver(static_cast<MouseButtonObserver*>(this));
|
|
boundMouseButtons.pop_front();
|
|
|
|
// Remove other bound mouse buttons which are associated with the mouse
|
|
auto it = boundMouseButtons.begin();
|
|
while (it != boundMouseButtons.end())
|
|
{
|
|
if (it->first == mouse)
|
|
boundMouseButtons.erase(it++);
|
|
else
|
|
++it;
|
|
}
|
|
}
|
|
|
|
while (!boundMouseWheelAxes.empty())
|
|
{
|
|
// Remove the first bound mouse button and stop observing its mouse
|
|
Mouse* mouse = boundMouseWheelAxes.front().first;
|
|
mouse->removeMouseWheelObserver(static_cast<MouseWheelObserver*>(this));
|
|
boundMouseWheelAxes.pop_front();
|
|
|
|
// Remove other bound mouse buttons which are associated with the mouse
|
|
auto it = boundMouseWheelAxes.begin();
|
|
while (it != boundMouseWheelAxes.end())
|
|
{
|
|
if (it->first == mouse)
|
|
boundMouseWheelAxes.erase(it++);
|
|
else
|
|
++it;
|
|
}
|
|
}
|
|
|
|
while (!boundGamepadButtons.empty())
|
|
{
|
|
// Remove the first bound gamepad button and stop observing its gamepad
|
|
Gamepad* gamepad = boundGamepadButtons.front().first;
|
|
gamepad->removeGamepadButtonObserver(static_cast<GamepadButtonObserver*>(this));
|
|
boundGamepadButtons.pop_front();
|
|
|
|
// Remove other bound gamepad buttons which are associated with the gamepad
|
|
auto it = boundGamepadButtons.begin();
|
|
while (it != boundGamepadButtons.end())
|
|
{
|
|
if (it->first == gamepad)
|
|
boundGamepadButtons.erase(it++);
|
|
else
|
|
++it;
|
|
}
|
|
}
|
|
|
|
while (!boundGamepadAxes.empty())
|
|
{
|
|
// Remove the first bound gamepad axis and stop observing its gamepad
|
|
Gamepad* gamepad = std::get<0>(boundGamepadAxes.front());
|
|
gamepad->removeGamepadAxisObserver(static_cast<GamepadAxisObserver*>(this));
|
|
boundGamepadAxes.pop_front();
|
|
|
|
// Remove other bound gamepad axes which are associated with the gamepad
|
|
auto it = boundGamepadAxes.begin();
|
|
while (it != boundGamepadAxes.end())
|
|
{
|
|
if (std::get<0>(*it) == gamepad)
|
|
boundGamepadAxes.erase(it++);
|
|
else
|
|
++it;
|
|
}
|
|
}
|
|
}
|
|
|
|
void Control::keyPressed(int scancode)
|
|
{
|
|
for (auto it: boundKeys)
|
|
{
|
|
if (it.second == scancode)
|
|
{
|
|
currentValue = 1.0f;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
void Control::keyReleased(int scancode)
|
|
{
|
|
for (auto it: boundKeys)
|
|
{
|
|
if (it.second == scancode)
|
|
{
|
|
currentValue = 0.0f;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
void Control::mouseButtonPressed(int button, int x, int y)
|
|
{
|
|
for (auto it: boundMouseButtons)
|
|
{
|
|
if (it.second == button)
|
|
{
|
|
currentValue = 1.0f;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
void Control::mouseButtonReleased(int button, int x, int y)
|
|
{
|
|
for (auto it: boundMouseButtons)
|
|
{
|
|
if (it.second == button)
|
|
{
|
|
currentValue = 0.0f;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
void Control::mouseWheelScrolled(int x, int y)
|
|
{
|
|
for (auto it: boundMouseWheelAxes)
|
|
{
|
|
if (it.second == MouseWheelAxis::POSITIVE_X && x > 0)
|
|
{
|
|
currentValue += x;
|
|
break;
|
|
}
|
|
else if (it.second == MouseWheelAxis::NEGATIVE_X && x < 0)
|
|
{
|
|
currentValue -= x;
|
|
break;
|
|
}
|
|
else if (it.second == MouseWheelAxis::POSITIVE_Y && y > 0)
|
|
{
|
|
currentValue += y;
|
|
break;
|
|
}
|
|
else if (it.second == MouseWheelAxis::NEGATIVE_Y && y < 0)
|
|
{
|
|
currentValue -= y;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
void Control::gamepadButtonPressed(int button)
|
|
{
|
|
for (auto it: boundGamepadButtons)
|
|
{
|
|
if (it.second == button)
|
|
{
|
|
currentValue = 1.0f;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
void Control::gamepadButtonReleased(int button)
|
|
{
|
|
for (auto it: boundGamepadButtons)
|
|
{
|
|
if (it.second == button)
|
|
{
|
|
currentValue = 0.0f;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
void Control::gamepadAxisMoved(int axis, bool negative, float value)
|
|
{
|
|
for (auto it: boundGamepadAxes)
|
|
{
|
|
if (std::get<1>(it) == axis && std::get<2>(it) == negative)
|
|
{
|
|
currentValue = value;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
const std::list<std::pair<Keyboard*, int>>* Control::getBoundKeys() const
|
|
{
|
|
return &boundKeys;
|
|
}
|
|
|
|
const std::list<std::pair<Mouse*, int>>* Control::getBoundMouseButtons() const
|
|
{
|
|
return &boundMouseButtons;
|
|
}
|
|
|
|
const std::list<std::pair<Mouse*, MouseWheelAxis>>* Control::getBoundMouseWheelAxes() const
|
|
{
|
|
return &boundMouseWheelAxes;
|
|
}
|
|
|
|
const std::list<std::pair<Gamepad*, int>>* Control::getBoundGamepadButtons() const
|
|
{
|
|
return &boundGamepadButtons;
|
|
}
|
|
|
|
const std::list<std::tuple<Gamepad*, int, bool>>* Control::getBoundGamepadAxes() const
|
|
{
|
|
return &boundGamepadAxes;
|
|
}
|
|
|
|
ControlProfile::ControlProfile(InputManager* inputManager):
|
|
inputManager(inputManager)
|
|
{}
|
|
|
|
void ControlProfile::registerControl(const std::string& name, Control* control)
|
|
{
|
|
controls[name] = control;
|
|
}
|
|
|
|
bool ControlProfile::save(const std::string& filename)
|
|
{
|
|
// Open control profile
|
|
std::ofstream file(filename.c_str());
|
|
if (!file.is_open())
|
|
{
|
|
std::cerr << std::string("Failed to open control profile \"") << filename << std::string("\"") << std::endl;
|
|
return false;
|
|
}
|
|
|
|
for (auto it = controls.begin(); it != controls.end(); ++it)
|
|
{
|
|
Control* control = it->second;
|
|
const std::list<std::pair<Keyboard*, int>>* boundKeys = control->getBoundKeys();
|
|
const std::list<std::pair<Mouse*, int>>* boundMouseButtons = control->getBoundMouseButtons();
|
|
const std::list<std::pair<Mouse*, MouseWheelAxis>>* boundMouseWheelAxes = control->getBoundMouseWheelAxes();
|
|
const std::list<std::pair<Gamepad*, int>>* boundGamepadButtons = control->getBoundGamepadButtons();
|
|
const std::list<std::tuple<Gamepad*, int, bool>>* boundGamepadAxes = control->getBoundGamepadAxes();
|
|
|
|
for (auto boundKey: *boundKeys)
|
|
{
|
|
int key = boundKey.second;
|
|
file << std::string("control\t") << it->first << std::string("\tkeyboard\tkey\t") << key << '\n';
|
|
}
|
|
|
|
for (auto boundMouseButton: *boundMouseButtons)
|
|
{
|
|
int button = boundMouseButton.second;
|
|
file << std::string("control\t") << it->first << std::string("\tmouse\tbutton\t") << button << '\n';
|
|
}
|
|
|
|
for (auto boundMouseWheelAxis: *boundMouseWheelAxes)
|
|
{
|
|
MouseWheelAxis axis = boundMouseWheelAxis.second;
|
|
|
|
file << std::string("control\t") << it->first << std::string("\tmouse\twheel\t");
|
|
if (axis == MouseWheelAxis::POSITIVE_X)
|
|
file << std::string("+x");
|
|
else if (axis == MouseWheelAxis::NEGATIVE_X)
|
|
file << std::string("-x");
|
|
else if (axis == MouseWheelAxis::POSITIVE_Y)
|
|
file << std::string("+y");
|
|
else if (axis == MouseWheelAxis::NEGATIVE_Y)
|
|
file << std::string("-y");
|
|
else
|
|
file << std::string("unknown");
|
|
file << '\n';
|
|
}
|
|
|
|
for (auto boundGamepadButton: *boundGamepadButtons)
|
|
{
|
|
const std::string& gamepadName = boundGamepadButton.first->getName();
|
|
int button = boundGamepadButton.second;
|
|
file << std::string("control\t") << it->first << std::string("\tgamepad\t") << gamepadName << std::string("\tbutton\t") << button << '\n';
|
|
}
|
|
|
|
for (auto boundGamepadAxis: *boundGamepadAxes)
|
|
{
|
|
const std::string& gamepadName = std::get<0>(boundGamepadAxis)->getName();
|
|
int axis = std::get<1>(boundGamepadAxis);
|
|
bool negative = std::get<2>(boundGamepadAxis);
|
|
|
|
std::stringstream axisstream;
|
|
if (negative)
|
|
axisstream << std::string("-");
|
|
else
|
|
axisstream << std::string("+");
|
|
axisstream << axis;
|
|
|
|
file << std::string("control\t") << it->first << std::string("\tgamepad\t") << gamepadName << std::string("\taxis\t") << axisstream.str() << '\n';
|
|
}
|
|
}
|
|
|
|
file.close();
|
|
|
|
return true;
|
|
}
|
|
|
|
bool ControlProfile::load(const std::string& filename)
|
|
{
|
|
// Open control profile
|
|
std::ifstream file(filename.c_str());
|
|
if (!file.is_open())
|
|
{
|
|
std::cerr << std::string("Failed to open control profile \"") << filename << std::string("\"") << std::endl;
|
|
return false;
|
|
}
|
|
|
|
// Read profile
|
|
std::string line;
|
|
while (file.good() && std::getline(file, line))
|
|
{
|
|
// Tokenize line (tab-delimeted)
|
|
std::vector<std::string> tokens;
|
|
std::string token;
|
|
std::istringstream linestream(line);
|
|
while (std::getline(linestream, token, '\t'))
|
|
tokens.push_back(token);
|
|
|
|
if (tokens.empty() || tokens[0][0] == '#')
|
|
continue;
|
|
|
|
if (tokens[0] == "control" && tokens.size() >= 5)
|
|
{
|
|
// Use control name to get control pointer
|
|
auto it = controls.find(tokens[1]);
|
|
if (it == controls.end())
|
|
{
|
|
std::cerr << std::string("Attempted to load unregistered control \"") << tokens[1] << std::string("\" from control profile \"") << filename << std::string("\"") << std::endl;
|
|
continue;
|
|
}
|
|
Control* control = it->second;
|
|
|
|
// Find input device
|
|
if (tokens[2] == "keyboard")
|
|
{
|
|
Keyboard* keyboard = inputManager->getKeyboards()->front();
|
|
|
|
if (tokens[3] == "key")
|
|
{
|
|
std::stringstream keystream(tokens[4]);
|
|
int scancode = -1;
|
|
keystream >> scancode;
|
|
|
|
control->bindKey(keyboard, scancode);
|
|
}
|
|
else
|
|
{
|
|
std::cerr << std::string("Invalid line \"") << line << std::string("\" in control profile \"") << filename << std::string("\"") << std::endl;
|
|
}
|
|
}
|
|
else if (tokens[2] == "mouse")
|
|
{
|
|
Mouse* mouse = inputManager->getMice()->front();
|
|
|
|
if (tokens[3] == "button")
|
|
{
|
|
std::stringstream buttonstream(tokens[4]);
|
|
int button = -1;
|
|
buttonstream >> button;
|
|
|
|
control->bindMouseButton(mouse, button);
|
|
}
|
|
else if (tokens[3] == "wheel")
|
|
{
|
|
MouseWheelAxis axis;
|
|
if (tokens[4] == "+x")
|
|
axis = MouseWheelAxis::POSITIVE_X;
|
|
else if (tokens[4] == "-x")
|
|
axis = MouseWheelAxis::NEGATIVE_X;
|
|
else if (tokens[4] == "+y")
|
|
axis = MouseWheelAxis::POSITIVE_Y;
|
|
else if (tokens[4] == "-y")
|
|
axis = MouseWheelAxis::NEGATIVE_Y;
|
|
else
|
|
{
|
|
std::cerr << std::string("Invalid line \"") << line << std::string("\" in control profile \"") << filename << std::string("\"") << std::endl;
|
|
continue;
|
|
}
|
|
|
|
control->bindMouseWheelAxis(mouse, axis);
|
|
}
|
|
else
|
|
{
|
|
std::cerr << std::string("Invalid line \"") << line << std::string("\" in control profile \"") << filename << std::string("\"") << std::endl;
|
|
continue;
|
|
}
|
|
}
|
|
else if (tokens[2] == "gamepad")
|
|
{
|
|
if (tokens.size() != 6)
|
|
{
|
|
std::cerr << std::string("Invalid line \"") << line << std::string("\" in control profile \"") << filename << std::string("\"") << std::endl;
|
|
continue;
|
|
}
|
|
|
|
Gamepad* gamepad = inputManager->getGamepad(tokens[3]);
|
|
if (!gamepad)
|
|
{
|
|
gamepad = new Gamepad(tokens[3]);
|
|
gamepad->setDisconnected(true);
|
|
inputManager->registerGamepad(gamepad);
|
|
}
|
|
|
|
if (tokens[4] == "button")
|
|
{
|
|
std::stringstream buttonstream(tokens[5]);
|
|
int button = -1;
|
|
buttonstream >> button;
|
|
|
|
control->bindGamepadButton(gamepad, button);
|
|
}
|
|
else if (tokens[4] == "axis")
|
|
{
|
|
bool negative = (tokens[5][0] == '-');
|
|
|
|
std::stringstream axisstream(tokens[5].substr(1, tokens[5].length() - 1));
|
|
int axis = -1;
|
|
axisstream >> axis;
|
|
|
|
control->bindGamepadAxis(gamepad, axis, negative);
|
|
}
|
|
else
|
|
{
|
|
std::cerr << std::string("Invalid line \"") << line << std::string("\" in control profile \"") << filename << std::string("\"") << std::endl;
|
|
continue;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
std::cerr << std::string("Unsupported input device \"") << tokens[3] << std::string("\" in control profile \"") << filename << std::string("\"") << std::endl;
|
|
continue;
|
|
}
|
|
}
|
|
}
|
|
|
|
file.close();
|
|
|
|
return true;
|
|
}
|
|
|
|
void ControlProfile::update()
|
|
{
|
|
for (auto it = controls.begin(); it != controls.end(); ++it)
|
|
{
|
|
it->second->update();
|
|
}
|
|
}
|
|
|