304 lines
10 KiB
C++
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);
|
|
}
|
|
};
|