flycast/core/input/mapping.h

304 lines
10 KiB
C++

/*
Copyright 2019 flyinghead
This file is part of reicast.
reicast 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 2 of the License, or
(at your option) any later version.
reicast 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 reicast. If not, see <https://www.gnu.org/licenses/>.
*/
#pragma once
#include "types.h"
#include "gamepad.h"
#include <map>
#include <set>
#include <memory>
#include <list>
#include <vector>
#include <limits>
namespace emucfg {
struct ConfigFile;
}
class InputMapping
{
public:
//! Uniquely identifies any input, be it button or axis
struct InputDef
{
//! Enumerates the type of input
enum class InputType : u8
{
BUTTON = 0x00, //!< Digital button type
AXIS_POS, //!< Axis, positive inclination
AXIS_NEG //!< Axis, negative inclination
};
//! Initialized and invalid value for code
static constexpr u32 INVALID_CODE = std::numeric_limits<u32>::max();
//! The unique code for the set type
u32 code = INVALID_CODE;
//! The type of input
InputType type = InputType::BUTTON;
//! Creates a 1:1 hash which uniquely identifies this input
u64 get_hash() const;
//! @return true iff this InputType is less than rhs
bool operator<(const InputDef& rhs) const;
//! @return true iff this InputType is equivalent to rhs
bool operator==(const InputDef& rhs) const;
//! @return true iff this InputType is not equivalent to rhs
bool operator!=(const InputDef& rhs) const;
//! @return true iff this InputType is a button
bool is_button() const;
//! @return true iff this InputType is an axis
bool is_axis() const;
//! @return true iff this input definition is valid
bool is_valid() const;
//! @return the suffix for this InputDef which signifies the type
const char* get_suffix() const;
//! @return the string equivalent to this InputDef
std::string to_str() const;
//! Converts a string to an InputDef
static InputDef from_str(const std::string& str);
//! Converts a button code to an InputDef
static InputDef from_button(u32 code);
//! Converts an axis code plus direction to an InputDef
static InputDef from_axis(u32 code, bool positive);
};
//! A group of inputs used to link to multiple keys to a function.
//! This class acts like a set with insertion order maintained.
class InputSet : public std::list<InputDef>
{
public:
InputSet();
explicit InputSet(std::initializer_list<value_type> l);
//! Insert new element to the back only if it doesn't already exist in the set
//! @param[in] val The value to insert at the back
//! @return true iff new item inserted
bool insert_back(const InputMapping::InputDef& val);
//! Insert new element to the back only if it doesn't already exist in the set
//! @param[in,out] val The value to insert at the back (move operation)
bool insert_back(InputMapping::InputDef&& val);
//! Removes all elements that match val
//! @param[in] val The value to remove
//! @return number of elements removed (normally 0 or 1)
std::size_t remove(const InputMapping::InputDef& val);
//! @return true iff this set contains the given val
bool contains(const InputMapping::InputDef& val) const;
//! @return true if this InputSet ends with the given rhs
bool ends_with(const InputSet& rhs, bool sequential) const;
private:
// Make push functionality private
using std::list<InputDef>::push_back;
using std::list<InputDef>::push_front;
//! Removes inverse axis value if val is an axis
void remove_inverse_axis(const InputMapping::InputDef& val);
};
//! Contains all settings for a button combination
struct ButtonCombo
{
//! The set of inputs that make up a button combination
InputSet inputs;
//! Set to true if the above input set must be pressed in the given sequence to activate the combo.
//! Set to false if all of the buttons may be pressed in any sequence to activate the combo.
bool sequential = true;
//! @return true iff this ButtonCombo is less than rhs
bool operator<(const ButtonCombo& rhs) const;
//! @return true iff this ButtonCombo is equivalent to rhs
bool operator==(const ButtonCombo& rhs) const;
//! @return true iff this ButtonCombo is not equivalent to rhs
bool operator!=(const ButtonCombo& rhs) const;
//! @return true iff this ButtonCombo is equal to or has an intersection with rhs
bool intersects(const ButtonCombo& rhs) const;
};
InputMapping() = default;
inline InputMapping(const InputMapping& other) {
name = other.name;
dead_zone = other.dead_zone;
saturation = other.saturation;
rumblePower = other.rumblePower;
for (int port = 0; port < 4; port++)
{
axes[port] = other.axes[port];
buttonMap[port] = other.buttonMap[port];
reverseButtonMap[port] = other.reverseButtonMap[port];
}
}
//! Number of controller ports there are
static constexpr const u32 NUM_PORTS = 4;
//! Version 4 adds button combos
static constexpr const int CURRENT_FILE_VERSION = 4;
//! Initialized and invalid value for code
static const u32 INVALID_CODE = InputDef::INVALID_CODE;
std::string name;
float dead_zone = 0.1f;
float saturation = 1.0f;
int rumblePower = 100;
int version = CURRENT_FILE_VERSION;
//! Resolves an ordered set of inputs into a key from a multi-input lookup
//! @param[in] port The port that the given inputs belong to [0,NUM_PORTS)
//! @param[in] inputSet The input set to look for
//! @return EMU_BTN_NONE if no key should be activated
//! @return the key that should be activated due to the given input set otherwise
DreamcastKey get_button_id(u32 port, const InputSet& inputSet) const;
//! Resolves currently active keys with a released input into keys that should be released
//! @param[in] port The port that the given inputs belong to [0,NUM_PORTS)
//! @param[in] activeKeys A list of active keys compiled from get_button_id()
//! @param[in] releasedInput The released input to check for
//! @return all keys that should be released due to the released input
std::list<DreamcastKey> get_button_released_ids(
u32 port,
const std::list<DreamcastKey>& activeKeys,
const InputDef& releasedInput) const;
//! Clears combo setting for a given key
//! @param[in] port The port [0,NUM_PORTS)
//! @param[in] id The key
void clear_button(u32 port, DreamcastKey id);
//! Sets DreamcastKey to a set of inputs
//! @param[in] port The port that the id should be mapped [0,NUM_PORTS)
//! @param[in] id The key to map
//! @param[in] combo Combination of inputs that should activate the key
//! @return true iff the combo has been set
bool set_button(u32 port, DreamcastKey id, const ButtonCombo& combo);
inline bool set_button(DreamcastKey id, const ButtonCombo& combo) {
return set_button(0, id, combo);
}
inline bool set_button(u32 port, DreamcastKey id, u32 buttonCode) {
return set_button(port, id, ButtonCombo{InputSet{InputDef{buttonCode, InputDef::InputType::BUTTON}}, false});
}
inline bool set_button(DreamcastKey id, u32 buttonCode) {
return set_button(0, id, buttonCode);
}
//! @param[in] port The port [0,NUM_PORTS)
//! @param[in] key The key
//! @return the ButtonCombo associated with the given key at the given port
ButtonCombo get_button_combo(u32 port, DreamcastKey key) const;
//! Get the button code if and only if the given key is mapped to a single code
//! @param[in] port The port [0,NUM_PORTS)
//! @param[in] key The key
//! @return the button code for the given key if found
//! @return InputDef::INVALID_CODE if multiple inputs are mapped to this key or none are mapped
u32 get_button_code(u32 port, DreamcastKey key) const;
inline DreamcastKey get_axis_id(u32 port, u32 code, bool pos)
{
auto it = axes[port].find(std::make_pair(code, pos));
if (it != axes[port].end())
return it->second;
else
return EMU_AXIS_NONE;
}
std::pair<u32, bool> get_axis_code(u32 port, DreamcastKey key);
void clear_axis(u32 port, DreamcastKey id);
void set_axis(u32 port, DreamcastKey id, u32 code, bool positive);
void set_axis(DreamcastKey id, u32 code, bool positive) { set_axis(0, id, code, positive); }
void load(FILE* fp);
bool save(const std::string& name);
void set_dirty();
bool is_dirty() const { return dirty; }
static std::shared_ptr<InputMapping> LoadMapping(const std::string& name);
static void SaveMapping(const std::string& name, const std::shared_ptr<InputMapping>& mapping);
static void DeleteMapping(const std::string& name);
void ClearMappings();
protected:
bool dirty = false;
private:
void loadv1(emucfg::ConfigFile& mf);
static std::vector<std::string> strSplit(const std::string str, char c, size_t maxsplit = 0);
std::map<std::pair<u32, bool>, DreamcastKey> axes[NUM_PORTS];
//! ButtonCombo -> DreamcastKey
std::map<ButtonCombo, DreamcastKey> buttonMap[NUM_PORTS];
//! DreamcastKey -> ButtonCombo
std::multimap<DreamcastKey, ButtonCombo> reverseButtonMap[NUM_PORTS];
static std::map<std::string, std::shared_ptr<InputMapping>> loaded_mappings;
};
class IdentityInputMapping : public InputMapping
{
public:
IdentityInputMapping() {
name = "Default";
dead_zone = 0.1f;
for (int i = 0; i < 32; i++)
set_button(0, (DreamcastKey)(1 << i), 1 << i);
set_button(0, EMU_BTN_FFORWARD, EMU_BTN_FFORWARD);
set_button(0, EMU_BTN_MENU, EMU_BTN_MENU);
set_button(0, EMU_BTN_ESCAPE, EMU_BTN_ESCAPE);
set_axis(0, DC_AXIS_LEFT, DC_AXIS_LEFT, true);
set_axis(0, DC_AXIS_RIGHT, DC_AXIS_RIGHT, true);
set_axis(0, DC_AXIS_UP, DC_AXIS_UP, true);
set_axis(0, DC_AXIS_DOWN, DC_AXIS_DOWN, true);
set_axis(0, DC_AXIS_LT, DC_AXIS_LT, true);
set_axis(0, DC_AXIS_RT, DC_AXIS_RT, true);
set_axis(0, DC_AXIS_LT2, DC_AXIS_LT2, true);
set_axis(0, DC_AXIS_RT2, DC_AXIS_RT2, true);
set_axis(0, DC_AXIS2_LEFT, DC_AXIS2_LEFT, true);
set_axis(0, DC_AXIS2_RIGHT, DC_AXIS2_RIGHT, true);
set_axis(0, DC_AXIS2_UP, DC_AXIS2_UP, true);
set_axis(0, DC_AXIS2_DOWN, DC_AXIS2_DOWN, true);
set_axis(0, DC_AXIS3_LEFT, DC_AXIS3_LEFT, true);
set_axis(0, DC_AXIS3_RIGHT, DC_AXIS3_RIGHT, true);
set_axis(0, DC_AXIS3_UP, DC_AXIS3_UP, true);
set_axis(0, DC_AXIS3_DOWN, DC_AXIS3_DOWN, true);
}
};