/* * 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 . */ #include "controls.hpp" #include #include #include 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(this)); boundKeys.push_back(std::pair(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(this)); boundMouseButtons.push_back(std::pair(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(this)); boundMouseWheelAxes.push_back(std::pair(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(this)); boundGamepadButtons.push_back(std::pair(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(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(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(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(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(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(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>* Control::getBoundKeys() const { return &boundKeys; } const std::list>* Control::getBoundMouseButtons() const { return &boundMouseButtons; } const std::list>* Control::getBoundMouseWheelAxes() const { return &boundMouseWheelAxes; } const std::list>* Control::getBoundGamepadButtons() const { return &boundGamepadButtons; } const std::list>* 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>* boundKeys = control->getBoundKeys(); const std::list>* boundMouseButtons = control->getBoundMouseButtons(); const std::list>* boundMouseWheelAxes = control->getBoundMouseWheelAxes(); const std::list>* boundGamepadButtons = control->getBoundGamepadButtons(); const std::list>* 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 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(); } }