visualboyadvance-m/src/wx/config/user-input.h

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_