257 lines
9.0 KiB
C++
257 lines
9.0 KiB
C++
#ifndef VBAM_WX_CONFIG_USER_INPUT_H_
|
|
#define VBAM_WX_CONFIG_USER_INPUT_H_
|
|
|
|
#include <cstdint>
|
|
#include <unordered_set>
|
|
|
|
#include <variant.hpp>
|
|
|
|
#include <wx/string.h>
|
|
|
|
#include "core/base/check.h"
|
|
|
|
namespace config {
|
|
|
|
// Abstract representation of a keyboard input. This class is used to represent
|
|
// a key press or release event. It is used in the configuration system to
|
|
// represent a key binding.
|
|
class KeyboardInput final {
|
|
public:
|
|
constexpr explicit KeyboardInput(wxKeyCode key, wxKeyModifier mod = wxMOD_NONE)
|
|
: key_(key), mod_(mod) {}
|
|
constexpr explicit KeyboardInput(char c, wxKeyModifier mod = wxMOD_NONE)
|
|
: key_(static_cast<wxKeyCode>(c)), mod_(mod) {}
|
|
|
|
~KeyboardInput() = default;
|
|
|
|
constexpr wxKeyCode key() const { return key_; }
|
|
constexpr wxKeyModifier mod() const { return mod_; }
|
|
|
|
wxString ToConfigString() const;
|
|
wxString ToLocalizedString() const;
|
|
|
|
bool operator==(const KeyboardInput& other) const {
|
|
return key_ == other.key_ && mod_ == other.mod_;
|
|
}
|
|
bool operator!=(const KeyboardInput& other) const { return !(*this == other); }
|
|
bool operator<(const KeyboardInput& other) const {
|
|
if (key_ == other.key_) {
|
|
return mod_ < other.mod_;
|
|
} else {
|
|
return key_ < other.key_;
|
|
}
|
|
}
|
|
bool operator<=(const KeyboardInput& other) const { return *this < other || *this == other; }
|
|
bool operator>(const KeyboardInput& other) const { return !(*this <= other); }
|
|
bool operator>=(const KeyboardInput& other) const { return !(*this < other); }
|
|
|
|
private:
|
|
const wxKeyCode key_;
|
|
const wxKeyModifier mod_;
|
|
};
|
|
|
|
// One of the possible joystick controls.
|
|
enum class JoyControl {
|
|
AxisPlus = 0,
|
|
AxisMinus,
|
|
Button,
|
|
HatNorth,
|
|
HatSouth,
|
|
HatWest,
|
|
HatEast,
|
|
Last = HatEast
|
|
};
|
|
|
|
// Abstraction for a single joystick. In the current implementation, this
|
|
// encapsulates an `sdl_index_`.
|
|
class JoyId final {
|
|
public:
|
|
static JoyId Invalid();
|
|
|
|
constexpr explicit JoyId(int sdl_index) : sdl_index_(sdl_index){};
|
|
~JoyId() = default;
|
|
|
|
wxString ToConfigString() const;
|
|
wxString ToLocalizedString() const;
|
|
|
|
constexpr bool operator==(const JoyId& other) const { return sdl_index_ == other.sdl_index_; }
|
|
constexpr bool operator!=(const JoyId& other) const { return sdl_index_ != other.sdl_index_; }
|
|
constexpr bool operator<(const JoyId& other) const { return sdl_index_ < other.sdl_index_; }
|
|
constexpr bool operator<=(const JoyId& other) const { return sdl_index_ <= other.sdl_index_; }
|
|
constexpr bool operator>(const JoyId& other) const { return sdl_index_ > other.sdl_index_; }
|
|
constexpr bool operator>=(const JoyId& other) const { return sdl_index_ >= other.sdl_index_; }
|
|
|
|
private:
|
|
JoyId() = delete;
|
|
|
|
const int sdl_index_;
|
|
|
|
friend struct std::hash<config::JoyId>;
|
|
};
|
|
|
|
// Abstraction for a joystick input. This class is used to represent a joystick
|
|
// control press or release event. It is used in the configuration system to
|
|
// represent a joystick binding.
|
|
class JoyInput final {
|
|
public:
|
|
constexpr JoyInput(JoyId joy, JoyControl control, uint8_t control_index)
|
|
: joy_(joy), control_(control), control_index_(control_index) {}
|
|
~JoyInput() = default;
|
|
|
|
constexpr JoyId joy() const { return joy_; }
|
|
constexpr JoyControl control() const { return control_; }
|
|
constexpr uint8_t control_index() const { return control_index_; }
|
|
|
|
wxString ToConfigString() const;
|
|
wxString ToLocalizedString() const;
|
|
|
|
constexpr bool operator==(const JoyInput& other) const {
|
|
return joy_ == other.joy_ && control_ == other.control_ &&
|
|
control_index_ == other.control_index_;
|
|
}
|
|
constexpr bool operator!=(const JoyInput& other) const { return !(*this == other); }
|
|
constexpr bool operator<(const JoyInput& other) const {
|
|
if (joy_ == other.joy_) {
|
|
if (control_ == other.control_) {
|
|
return control_index_ < other.control_index_;
|
|
} else {
|
|
return control_ < other.control_;
|
|
}
|
|
} else {
|
|
return joy_ < other.joy_;
|
|
}
|
|
}
|
|
constexpr bool operator<=(const JoyInput& other) const {
|
|
return *this < other || *this == other;
|
|
}
|
|
constexpr bool operator>(const JoyInput& other) const { return !(*this <= other); }
|
|
constexpr bool operator>=(const JoyInput& other) const { return !(*this < other); }
|
|
|
|
private:
|
|
const JoyId joy_;
|
|
const JoyControl control_;
|
|
const uint8_t control_index_;
|
|
};
|
|
|
|
// Abstraction for a user input, which can come from a keyboard or a joystick.
|
|
// TODO: Most of these methods should be constexpr but nonstd::variant is not.
|
|
class UserInput {
|
|
public:
|
|
// The device type for a user control.
|
|
enum class Device { Invalid = 0, Keyboard, Joystick, Last = Joystick };
|
|
|
|
// Constructor from a configuration string. Returns empty set on failure.
|
|
static std::unordered_set<UserInput> FromConfigString(const wxString& string);
|
|
|
|
// Converts a set of UserInput into a configuration string. This
|
|
// recomputes the configuration string every time and should not be used
|
|
// for comparison purposes.
|
|
// TODO: Replace std::unordered_set with std::span when the code base uses C++20.
|
|
static wxString SpanToConfigString(const std::unordered_set<UserInput>& user_inputs);
|
|
|
|
// Invalid UserInput, mainly used for comparison.
|
|
UserInput() : device_(Device::Invalid), input_(nonstd::monostate{}) {}
|
|
|
|
// Constructors for joystick and keyboard inputs. This object can be
|
|
// implicitly constructed from JoyInput and KeyboardInput.
|
|
UserInput(JoyInput joy_input) : device_(Device::Joystick), input_(joy_input) {}
|
|
UserInput(KeyboardInput keyboard_input)
|
|
: device_(Device::Keyboard), input_(keyboard_input) {}
|
|
|
|
Device device() const { return device_; }
|
|
|
|
const KeyboardInput& keyboard_input() const {
|
|
VBAM_CHECK(is_keyboard());
|
|
return nonstd::get<KeyboardInput>(input_);
|
|
};
|
|
|
|
const JoyInput& joy_input() const {
|
|
VBAM_CHECK(is_joystick());
|
|
return nonstd::get<JoyInput>(input_);
|
|
};
|
|
|
|
bool is_valid() const { return device_ != Device::Invalid; }
|
|
operator bool() const { return is_valid(); }
|
|
|
|
bool is_keyboard() const { return device_ == Device::Keyboard; }
|
|
bool is_joystick() const { return device_ == Device::Joystick; }
|
|
|
|
// Converts to a configuration string for saving.
|
|
wxString ToConfigString() const;
|
|
|
|
// Converts to a localized string for display.
|
|
wxString ToLocalizedString() const;
|
|
|
|
// Comparison operators.
|
|
bool operator==(const UserInput& other) const {
|
|
return device_ == other.device_ && input_ == other.input_;
|
|
}
|
|
bool operator!=(const UserInput& other) const { return !(*this == other); }
|
|
bool operator<(const UserInput& other) const {
|
|
if (device_ == other.device_) {
|
|
return input_ < other.input_;
|
|
} else {
|
|
return device_ < other.device_;
|
|
}
|
|
}
|
|
bool operator<=(const UserInput& other) const {
|
|
return *this < other || *this == other;
|
|
}
|
|
bool operator>(const UserInput& other) const { return !(*this <= other); }
|
|
bool operator>=(const UserInput& other) const { return !(*this < other); }
|
|
|
|
private:
|
|
const Device device_;
|
|
const nonstd::variant<nonstd::monostate, JoyInput, KeyboardInput> input_;
|
|
};
|
|
|
|
} // namespace config
|
|
|
|
// Specializations for hash functions for all of the above classes.
|
|
template <>
|
|
struct std::hash<config::JoyId> {
|
|
std::size_t operator()(const config::JoyId& joy_id) const noexcept {
|
|
return std::hash<int>{}(joy_id.sdl_index_);
|
|
}
|
|
};
|
|
|
|
template <>
|
|
struct std::hash<config::JoyInput> {
|
|
std::size_t operator()(const config::JoyInput& joy_input) const noexcept {
|
|
const std::size_t hash1 = std::hash<config::JoyId>{}(joy_input.joy());
|
|
const std::size_t hash2 = std::hash<int>{}(static_cast<int>(joy_input.control()));
|
|
const std::size_t hash3 = std::hash<int>{}(joy_input.control_index());
|
|
return hash1 ^ hash2 ^ hash3;
|
|
}
|
|
};
|
|
|
|
template <>
|
|
struct std::hash<config::KeyboardInput> {
|
|
std::size_t operator()(const config::KeyboardInput& keyboard_input) const noexcept {
|
|
const std::size_t hash1 = std::hash<int>{}(keyboard_input.key());
|
|
const std::size_t hash2 = std::hash<int>{}(keyboard_input.mod());
|
|
return hash1 ^ hash2;
|
|
}
|
|
};
|
|
|
|
template <>
|
|
struct std::hash<config::UserInput> {
|
|
std::size_t operator()(const config::UserInput& user_input) const noexcept {
|
|
const std::size_t device_hash = std::hash<int>{}(static_cast<int>(user_input.device()));
|
|
switch (user_input.device()) {
|
|
case config::UserInput::Device::Invalid:
|
|
return device_hash;
|
|
case config::UserInput::Device::Joystick:
|
|
return device_hash ^ std::hash<config::JoyInput>{}(user_input.joy_input());
|
|
case config::UserInput::Device::Keyboard:
|
|
return device_hash ^
|
|
std::hash<config::KeyboardInput>{}(user_input.keyboard_input());
|
|
}
|
|
|
|
VBAM_NOTREACHED();
|
|
return 0;
|
|
}
|
|
};
|
|
|
|
#endif // VBAM_WX_CONFIG_USER_INPUT_H_
|