pioneer/src/Input.h

254 lines
8.3 KiB
C++

// Copyright © 2008-2021 Pioneer Developers. See AUTHORS.txt for details
// Licensed under the terms of the GPL v3. See licenses/GPL-3.txt
#ifndef INPUT_H
#define INPUT_H
#include "InputBindings.h"
#include "utils.h"
#include <algorithm>
#include <array>
class IniConfig;
// Macro to simplify registering input bindings in the codebase
// TODO: evaluate if registering key bindings via lua / json file works better
#define REGISTER_INPUT_BINDING(name) \
namespace name##Input \
{ \
void Register(Input::Manager *input); \
bool name##Registered = Input::AddBindingRegistrar(&Register); \
} \
void name##Input::Register(Input::Manager *input)
namespace Input {
class Manager;
// The Page->Group->Binding system serves as a thin veneer for the UI to make
// sane reasonings about how to structure the Options dialog.
struct BindingGroup {
enum EntryType : uint8_t {
ENTRY_ACTION,
ENTRY_AXIS
};
std::map<std::string, EntryType> bindings;
};
struct BindingPage {
BindingGroup *GetBindingGroup(std::string id) { return &groups[id]; }
std::map<std::string, BindingGroup> groups;
};
struct InputFrame {
using Axis = InputBindings::Axis;
using Action = InputBindings::Action;
InputFrame(Input::Manager *man, bool modal = false) :
manager(man),
modal(modal)
{}
std::vector<Action *> actions;
std::vector<Axis *> axes;
// Must set this to a valid Input::Manager instance before using AddAction / AddAxis
Manager *manager = nullptr;
bool active = false;
bool modal = false;
// Call this at startup to register all the bindings associated with the frame.
virtual void RegisterBindings(){};
// Called when the frame is added to the stack.
sigc::signal<void, InputFrame *> onFrameAdded;
// Called when the frame is removed from the stack.
sigc::signal<void, InputFrame *> onFrameRemoved;
Action *AddAction(std::string id);
Axis *AddAxis(std::string id);
};
struct JoystickInfo {
struct Axis {
float value = 0.0;
float deadzone = 0.0;
float curve = 1.0;
bool zeroToOne = false;
};
SDL_Joystick *joystick;
SDL_JoystickGUID guid;
std::string name;
std::vector<bool> buttons;
std::vector<int> hats;
std::vector<Axis> axes;
};
void InitJoysticks(IniConfig *config);
std::map<SDL_JoystickID, JoystickInfo> &GetJoysticks();
// User display name for the joystick from the API/OS.
std::string JoystickName(int joystick);
// fetch the GUID for the named joystick
SDL_JoystickGUID JoystickGUID(int joystick);
std::string JoystickGUIDString(int joystick);
// reverse map a JoystickGUID to the actual internal ID.
int JoystickFromGUIDString(const std::string &guid);
int JoystickFromGUIDString(const char *guid);
int JoystickFromGUID(SDL_JoystickGUID guid);
// We use SDL's joystick IDs because they're stable enough for the job.
inline int JoystickFromID(SDL_JoystickID id) { return id; }
// An adapter to decouple input frame creation from input binding registration.
// The functions registered via AddBindingRegistrar should be thread-safe and
// should not depend on anything but the manager object being passed in.
// The registrars are guaranteed to be called after static initialization has finished.
std::vector<sigc::slot<void, Input::Manager *>> &GetBindingRegistration();
bool AddBindingRegistrar(sigc::slot<void, Input::Manager *> &&fn);
} // namespace Input
class Input::Manager {
public:
Manager(IniConfig *config);
void InitGame();
// Call this at the start of a frame, before passing SDL events in
void NewFrame();
// Call once per SDL event, handles updating all internal state
void HandleSDLEvent(SDL_Event &ev);
// Call immediately after processing events, dispatches events to Action / Axis bindings.
void DispatchEvents();
// When enable is false, this prevents the input system from writing to the config file.
void EnableConfigSaving(bool enable) { m_enableConfigSaving = enable; }
BindingPage *GetBindingPage(std::string id) { return &bindingPages[id]; }
std::map<std::string, BindingPage> GetBindingPages() { return bindingPages; }
// Pushes an InputFrame onto the input stack.
bool AddInputFrame(InputFrame *frame);
// Get a read-only list of input frames.
const std::vector<InputFrame *> &GetInputFrames() { return m_inputFrames; }
// Check if a specific input frame is currently on the stack.
bool HasInputFrame(InputFrame *frame)
{
return std::count(m_inputFrames.begin(), m_inputFrames.end(), frame) > 0;
}
// Remove an arbitrary input frame from the input stack.
void RemoveInputFrame(InputFrame *frame);
// Inform the input system that a binding or frame was changed this frame.
void MarkBindingsDirty() { m_frameListChanged = true; }
// Creates a new action binding, copying the provided binding.
// The returned binding pointer points to the actual binding.
InputBindings::Action *AddActionBinding(std::string id, BindingGroup *group, InputBindings::Action &&binding);
InputBindings::Action *GetActionBinding(std::string id);
// Creates a new axis binding, copying the provided binding.
// The returned binding pointer points to the actual binding.
InputBindings::Axis *AddAxisBinding(std::string id, BindingGroup *group, InputBindings::Axis &&binding);
InputBindings::Axis *GetAxisBinding(std::string id);
// Call EnableBindings() to temporarily disable handling input bindings while
// you're recording a new input binding or are in a modal window.
void EnableBindings(bool enabled) { m_enableBindings = enabled; }
bool KeyState(SDL_Keycode k) { return IsKeyDown(k); }
// returns true if key K is currently pressed
bool IsKeyDown(SDL_Keycode k) { return keyState[k] & 0x3; }
// returns true if key K was pressed this frame
bool IsKeyPressed(SDL_Keycode k) { return keyState[k] == 1; }
// returns true if key K was released this frame
bool IsKeyReleased(SDL_Keycode k) { return keyState[k] == 4; }
int KeyModState() { return keyModState; }
int JoystickButtonState(int joystick, int button);
int JoystickHatState(int joystick, int hat);
float JoystickAxisState(int joystick, int axis);
bool IsJoystickEnabled() { return joystickEnabled; }
void SetJoystickEnabled(bool state);
bool IsMouseYInvert() { return mouseYInvert; }
void SetMouseYInvert(bool state);
int MouseButtonState(int button) { return mouseButton[button]; }
void SetMouseButtonState(int button, bool state) { mouseButton[button] = state; }
void GetMouseMotion(int motion[2])
{
std::copy_n(mouseMotion.data(), mouseMotion.size(), motion);
}
int GetMouseWheel() { return mouseWheel; }
// Capturing the mouse hides the cursor, puts the mouse into relative mode,
// and passes all mouse inputs to the input system, regardless of whether
// ImGui is using them or not.
bool IsCapturingMouse() const { return m_capturingMouse; }
// Set whether the application would like to capture the mouse.
// To avoid contention between different classes, please only call this when the state
// has actually changed.
void SetCapturingMouse(bool enabled);
sigc::signal<void, SDL_Keysym *> onKeyPress;
sigc::signal<void, SDL_Keysym *> onKeyRelease;
sigc::signal<void, int, int, int> onMouseButtonUp;
sigc::signal<void, int, int, int> onMouseButtonDown;
sigc::signal<void, bool> onMouseWheel;
private:
void RebuildInputFrames();
bool GetModifierState(InputBindings::KeyChord *key);
bool GetBindingState(InputBindings::KeyBinding &key);
float GetAxisState(InputBindings::JoyAxis &axis);
IniConfig *m_config;
bool m_enableConfigSaving;
std::map<SDL_Keycode, uint8_t> keyState;
int keyModState;
std::array<char, 6> mouseButton;
std::array<int, 2> mouseMotion;
int mouseWheel;
bool m_capturingMouse;
bool joystickEnabled;
bool mouseYInvert;
std::map<std::string, BindingPage> bindingPages;
std::map<std::string, InputBindings::Action> actionBindings;
std::map<std::string, InputBindings::Axis> axisBindings;
bool m_enableBindings;
std::vector<InputFrame *> m_inputFrames;
bool m_frameListChanged;
std::vector<InputBindings::Action *> m_activeActions;
std::vector<InputBindings::Axis *> m_activeAxes;
std::map<InputBindings::KeyBinding, bool> m_modifiers;
std::vector<InputBindings::KeyChord *> m_chords;
};
#endif