From 6ebc7256eb930d455393e98848eb859ebd53051c Mon Sep 17 00:00:00 2001 From: Mr-Wiseguy Date: Mon, 15 Jan 2024 21:06:52 -0500 Subject: [PATCH] Implemented rebinding inputs from UI (controller only so far) and built minimal UI for doing so --- assets/config_menu.rml | 9 +- include/recomp_input.h | 22 +++- src/game/controls.cpp | 97 ++++++++++---- src/game/input.cpp | 281 +++++++++++++++++++++++++++++------------ src/main/main.cpp | 2 +- src/ui/ui_config.cpp | 121 +++++++++++++++++- src/ui/ui_renderer.cpp | 10 ++ 7 files changed, 426 insertions(+), 116 deletions(-) diff --git a/assets/config_menu.rml b/assets/config_menu.rml index c13160c..b770cb1 100644 --- a/assets/config_menu.rml +++ b/assets/config_menu.rml @@ -123,7 +123,14 @@
Controls
- + +
+
+ + + +
+
Sound
diff --git a/include/recomp_input.h b/include/recomp_input.h index d9d70e6..0f8ecf4 100644 --- a/include/recomp_input.h +++ b/include/recomp_input.h @@ -6,11 +6,14 @@ #include #include #include +#include namespace recomp { struct InputField { - uint32_t device_type; + uint32_t input_type; int32_t input_id; + std::string to_string() const; + auto operator<=>(const InputField& rhs) const = default; }; void poll_inputs(); @@ -18,6 +21,16 @@ namespace recomp { float get_input_analog(const std::span fields); bool get_input_digital(const InputField& field); bool get_input_digital(const std::span fields); + + enum class InputDevice { + Controller, + Keyboard, + COUNT + }; + + void start_scanning_input(InputDevice device); + void finish_scanning_input(InputField scanned_field); + InputField get_scanned_input(); struct DefaultN64Mappings { std::vector a; @@ -46,13 +59,20 @@ namespace recomp { extern const DefaultN64Mappings default_n64_keyboard_mappings; extern const DefaultN64Mappings default_n64_controller_mappings; + constexpr size_t bindings_per_input = 2; + // Loads the user's saved controller mapping if one exists, loads the default mappings if no saved mapping exists. void init_control_mappings(); + size_t get_num_inputs(); + const std::vector& get_input_names(); + InputField& get_input_binding(size_t input_index, size_t binding_index, InputDevice device); + void set_input_binding(size_t input_index, size_t binding_index, InputDevice device, InputField value); void get_n64_input(uint16_t* buttons_out, float* x_out, float* y_out); void handle_events(); bool game_input_disabled(); + bool all_input_disabled(); // TODO move these void quicksave_save(); diff --git a/src/game/controls.cpp b/src/game/controls.cpp index 2f46374..e7a7c00 100644 --- a/src/game/controls.cpp +++ b/src/game/controls.cpp @@ -5,34 +5,38 @@ #include "../ultramodern/ultramodern.hpp" #include "../patches/input.h" -// x-macros to build input enums and arrays. First parameter is the name, second parameter is the bit field for the input (or 0 if there is no associated one) +// x-macros to build input enums and arrays. +// First parameter is the enum name, second parameter is the bit field for the input (or 0 if there is no associated one), third is the readable name. #define DEFINE_N64_BUTTON_INPUTS() \ - DEFINE_INPUT(A, 0x8000) \ - DEFINE_INPUT(B, 0x4000) \ - DEFINE_INPUT(Z, 0x2000) \ - DEFINE_INPUT(START, 0x1000) \ - DEFINE_INPUT(DPAD_UP, 0x0800) \ - DEFINE_INPUT(DPAD_DOWN, 0x0400) \ - DEFINE_INPUT(DPAD_LEFT, 0x0200) \ - DEFINE_INPUT(DPAD_RIGHT, 0x0100) \ - DEFINE_INPUT(L, 0x0020) \ - DEFINE_INPUT(R, 0x0010) \ - DEFINE_INPUT(C_UP, 0x0008) \ - DEFINE_INPUT(C_DOWN, 0x0004) \ - DEFINE_INPUT(C_LEFT, 0x0002) \ - DEFINE_INPUT(C_RIGHT, 0x0001) + DEFINE_INPUT(A, 0x8000, "[A Button]") \ + DEFINE_INPUT(B, 0x4000, "[B Button]") \ + DEFINE_INPUT(Z, 0x2000, "[Z Button]") \ + DEFINE_INPUT(START, 0x1000, "[Start Button]") \ + DEFINE_INPUT(DPAD_UP, 0x0800, "[Dpad Up]") \ + DEFINE_INPUT(DPAD_DOWN, 0x0400, "[Dpad Down]") \ + DEFINE_INPUT(DPAD_LEFT, 0x0200, "[Dpad Left]") \ + DEFINE_INPUT(DPAD_RIGHT, 0x0100, "[Dpad Right]") \ + DEFINE_INPUT(L, 0x0020, "[L Button]") \ + DEFINE_INPUT(R, 0x0010, "[R Button]") \ + DEFINE_INPUT(C_UP, 0x0008, "[C Up]") \ + DEFINE_INPUT(C_DOWN, 0x0004, "[C Down]") \ + DEFINE_INPUT(C_LEFT, 0x0002, "[C Left]") \ + DEFINE_INPUT(C_RIGHT, 0x0001, "[C Right]") #define DEFINE_N64_AXIS_INPUTS() \ - DEFINE_INPUT(X_AXIS_NEG, 0) \ - DEFINE_INPUT(X_AXIS_POS, 0) \ - DEFINE_INPUT(Y_AXIS_NEG, 0) \ - DEFINE_INPUT(Y_AXIS_POS, 0) \ + DEFINE_INPUT(X_AXIS_NEG, 0, "[Analog Left]") \ + DEFINE_INPUT(X_AXIS_POS, 0, "[Analog Right]") \ + DEFINE_INPUT(Y_AXIS_NEG, 0, "[Analog Down]") \ + DEFINE_INPUT(Y_AXIS_POS, 0, "[Analog Up]") \ + +#define DEFINE_ALL_INPUTS() \ + DEFINE_N64_BUTTON_INPUTS() \ + DEFINE_N64_AXIS_INPUTS() // Make the input enum. -#define DEFINE_INPUT(name, value) name, +#define DEFINE_INPUT(name, value, readable) name, enum class GameInput { - DEFINE_N64_BUTTON_INPUTS() - DEFINE_N64_AXIS_INPUTS() + DEFINE_ALL_INPUTS() COUNT, N64_BUTTON_START = A, @@ -43,22 +47,30 @@ enum class GameInput { #undef DEFINE_INPUT // Arrays that hold the mappings for every input for keyboard and controller respectively. -using input_mapping_array = std::array, (size_t)GameInput::COUNT>; -static std::array, (size_t)GameInput::COUNT> keyboard_input_mappings{}; -static std::array, (size_t)GameInput::COUNT> controller_input_mappings{}; +using input_mapping = std::array; +using input_mapping_array = std::array; +static input_mapping_array keyboard_input_mappings{}; +static input_mapping_array controller_input_mappings{}; // Make the button value array, which maps a button index to its bit field. -#define DEFINE_INPUT(name, value) uint16_t(value##u), +#define DEFINE_INPUT(name, value, readable) uint16_t(value##u), static const std::array n64_button_values = { DEFINE_N64_BUTTON_INPUTS() }; #undef DEFINE_INPUT +// Make the input name array. +#define DEFINE_INPUT(name, value, readable) readable, +static const std::vector input_names = { + DEFINE_ALL_INPUTS() +}; + void recomp::init_control_mappings() { // TODO load from a file if one exists. auto assign_mapping = [](input_mapping_array& mapping, GameInput input, const std::vector& value) { - mapping[(size_t)input] = value; + input_mapping& cur_mapping = mapping.at((size_t)input); + std::copy_n(value.begin(), std::min(value.size(), cur_mapping.size()), cur_mapping.begin()); }; auto assign_all_mappings = [&](input_mapping_array& mapping, const recomp::DefaultN64Mappings& values) { @@ -88,6 +100,37 @@ void recomp::init_control_mappings() { assign_all_mappings(controller_input_mappings, recomp::default_n64_controller_mappings); } +size_t recomp::get_num_inputs() { + return (size_t)GameInput::COUNT; +} + +const std::vector& recomp::get_input_names() { + return input_names; +} + +// Due to an RmlUi limitation this can't be const. Ideally it would return a const reference or even just a straight up copy. +recomp::InputField& recomp::get_input_binding(size_t input_index, size_t binding_index, recomp::InputDevice device) { + input_mapping_array& device_mappings = (device == recomp::InputDevice::Controller) ? controller_input_mappings : keyboard_input_mappings; + input_mapping& cur_input_mapping = device_mappings.at(input_index); + + if (binding_index < cur_input_mapping.size()) { + return cur_input_mapping[binding_index]; + } + else { + static recomp::InputField dummy_field = {}; + return dummy_field; + } +} + +void recomp::set_input_binding(size_t input_index, size_t binding_index, recomp::InputDevice device, recomp::InputField value) { + input_mapping_array& device_mappings = (device == recomp::InputDevice::Controller) ? controller_input_mappings : keyboard_input_mappings; + input_mapping& cur_input_mapping = device_mappings.at(input_index); + + if (binding_index < cur_input_mapping.size()) { + cur_input_mapping[binding_index] = value; + } +} + void recomp::get_n64_input(uint16_t* buttons_out, float* x_out, float* y_out) { uint16_t cur_buttons = 0; float cur_x = 0.0f; diff --git a/src/game/input.cpp b/src/game/input.cpp index a3a8bfa..742d3be 100644 --- a/src/game/input.cpp +++ b/src/game/input.cpp @@ -7,6 +7,7 @@ #include "SDL.h" #include "rt64_layer.h" +constexpr float axis_threshold = 0.5f; static struct { const Uint8* keys = nullptr; @@ -16,21 +17,57 @@ static struct { std::vector cur_controllers{}; } InputState; +std::atomic scanning_device = recomp::InputDevice::COUNT; +std::atomic scanned_input; + +enum class InputType { + None = 0, // Using zero for None ensures that default initialized InputFields are unbound. + Keyboard, + Mouse, + ControllerDigital, + ControllerAnalog // Axis input_id values are the SDL value + 1 +}; + +void set_scanned_input(recomp::InputField value) { + scanning_device.store(recomp::InputDevice::COUNT); + scanned_input.store(value); +} + +recomp::InputField recomp::get_scanned_input() { + recomp::InputField ret = scanned_input.load(); + scanned_input.store({}); + return ret; +} + +void recomp::start_scanning_input(recomp::InputDevice device) { + scanned_input.store({}); + scanning_device.store(device); +} + +void queue_if_enabled(SDL_Event* event) { + if (!recomp::all_input_disabled()) { + recomp::queue_event(*event); + } +} + bool sdl_event_filter(void* userdata, SDL_Event* event) { switch (event->type) { case SDL_EventType::SDL_KEYDOWN: { - SDL_KeyboardEvent* keyevent = (SDL_KeyboardEvent*)event; + SDL_KeyboardEvent* keyevent = &event->key; if (keyevent->keysym.scancode == SDL_Scancode::SDL_SCANCODE_RETURN && (keyevent->keysym.mod & SDL_Keymod::KMOD_ALT)) { RT64ChangeWindow(); } + if (scanning_device == recomp::InputDevice::Keyboard) { + set_scanned_input({(uint32_t)InputType::Keyboard, keyevent->keysym.scancode}); + } } - recomp::queue_event(*event); + queue_if_enabled(event); break; case SDL_EventType::SDL_CONTROLLERDEVICEADDED: { - SDL_ControllerDeviceEvent* controller_event = (SDL_ControllerDeviceEvent*)event; + SDL_ControllerDeviceEvent* controller_event = &event->cdevice; SDL_GameController* controller = SDL_GameControllerOpen(controller_event->which); printf("Controller added: %d\n", controller_event->which); if (controller != nullptr) { @@ -41,7 +78,7 @@ bool sdl_event_filter(void* userdata, SDL_Event* event) { break; case SDL_EventType::SDL_CONTROLLERDEVICEREMOVED: { - SDL_ControllerDeviceEvent* controller_event = (SDL_ControllerDeviceEvent*)event; + SDL_ControllerDeviceEvent* controller_event = &event->cdevice; printf("Controller removed: %d\n", controller_event->which); std::erase(InputState.controller_ids, controller_event->which); } @@ -51,13 +88,33 @@ bool sdl_event_filter(void* userdata, SDL_Event* event) { return true; case SDL_EventType::SDL_MOUSEWHEEL: { - SDL_MouseWheelEvent* wheel_event = (SDL_MouseWheelEvent*)event; + SDL_MouseWheelEvent* wheel_event = &event->wheel; InputState.mouse_wheel_pos.fetch_add(wheel_event->y * (wheel_event->direction == SDL_MOUSEWHEEL_FLIPPED ? -1 : 1)); } - recomp::queue_event(*event); + queue_if_enabled(event); + break; + case SDL_EventType::SDL_CONTROLLERBUTTONDOWN: + if (scanning_device == recomp::InputDevice::Controller) { + SDL_ControllerButtonEvent* button_event = &event->cbutton; + set_scanned_input({(uint32_t)InputType::ControllerDigital, button_event->button}); + } + queue_if_enabled(event); + break; + case SDL_EventType::SDL_CONTROLLERAXISMOTION: + if (scanning_device == recomp::InputDevice::Controller) { + SDL_ControllerAxisEvent* axis_event = &event->caxis; + float axis_value = axis_event->value * (1/32768.0f); + if (axis_value > axis_threshold) { + set_scanned_input({(uint32_t)InputType::ControllerAnalog, axis_event->axis + 1}); + } + else if (axis_value < -axis_threshold) { + set_scanned_input({(uint32_t)InputType::ControllerAnalog, -axis_event->axis - 1}); + } + } + queue_if_enabled(event); break; default: - recomp::queue_event(*event); + queue_if_enabled(event); break; } return false; @@ -71,13 +128,6 @@ void recomp::handle_events() { } } -enum class DeviceType { - Keyboard, - Mouse, - ControllerDigital, - ControllerAnalog // Axis input_id values are the SDL value + 1 -}; - constexpr SDL_GameControllerButton SDL_CONTROLLER_BUTTON_SOUTH = SDL_CONTROLLER_BUTTON_A; constexpr SDL_GameControllerButton SDL_CONTROLLER_BUTTON_EAST = SDL_CONTROLLER_BUTTON_B; constexpr SDL_GameControllerButton SDL_CONTROLLER_BUTTON_WEST = SDL_CONTROLLER_BUTTON_X; @@ -85,117 +135,117 @@ constexpr SDL_GameControllerButton SDL_CONTROLLER_BUTTON_NORTH = SDL_CONTROLLER_ const recomp::DefaultN64Mappings recomp::default_n64_keyboard_mappings = { .a = { - {.device_type = (uint32_t)DeviceType::Keyboard, .input_id = SDL_SCANCODE_SPACE} + {.input_type = (uint32_t)InputType::Keyboard, .input_id = SDL_SCANCODE_SPACE} }, .b = { - {.device_type = (uint32_t)DeviceType::Keyboard, .input_id = SDL_SCANCODE_LSHIFT} + {.input_type = (uint32_t)InputType::Keyboard, .input_id = SDL_SCANCODE_LSHIFT} }, .l = { - {.device_type = (uint32_t)DeviceType::Keyboard, .input_id = SDL_SCANCODE_E} + {.input_type = (uint32_t)InputType::Keyboard, .input_id = SDL_SCANCODE_E} }, .r = { - {.device_type = (uint32_t)DeviceType::Keyboard, .input_id = SDL_SCANCODE_R} + {.input_type = (uint32_t)InputType::Keyboard, .input_id = SDL_SCANCODE_R} }, .z = { - {.device_type = (uint32_t)DeviceType::Keyboard, .input_id = SDL_SCANCODE_Q} + {.input_type = (uint32_t)InputType::Keyboard, .input_id = SDL_SCANCODE_Q} }, .start = { - {.device_type = (uint32_t)DeviceType::Keyboard, .input_id = SDL_SCANCODE_RETURN} + {.input_type = (uint32_t)InputType::Keyboard, .input_id = SDL_SCANCODE_RETURN} }, .c_left = { - {.device_type = (uint32_t)DeviceType::Keyboard, .input_id = SDL_SCANCODE_LEFT} + {.input_type = (uint32_t)InputType::Keyboard, .input_id = SDL_SCANCODE_LEFT} }, .c_right = { - {.device_type = (uint32_t)DeviceType::Keyboard, .input_id = SDL_SCANCODE_RIGHT} + {.input_type = (uint32_t)InputType::Keyboard, .input_id = SDL_SCANCODE_RIGHT} }, .c_up = { - {.device_type = (uint32_t)DeviceType::Keyboard, .input_id = SDL_SCANCODE_UP} + {.input_type = (uint32_t)InputType::Keyboard, .input_id = SDL_SCANCODE_UP} }, .c_down = { - {.device_type = (uint32_t)DeviceType::Keyboard, .input_id = SDL_SCANCODE_DOWN} + {.input_type = (uint32_t)InputType::Keyboard, .input_id = SDL_SCANCODE_DOWN} }, .dpad_left = { - {.device_type = (uint32_t)DeviceType::Keyboard, .input_id = SDL_SCANCODE_J} + {.input_type = (uint32_t)InputType::Keyboard, .input_id = SDL_SCANCODE_J} }, .dpad_right = { - {.device_type = (uint32_t)DeviceType::Keyboard, .input_id = SDL_SCANCODE_L} + {.input_type = (uint32_t)InputType::Keyboard, .input_id = SDL_SCANCODE_L} }, .dpad_up = { - {.device_type = (uint32_t)DeviceType::Keyboard, .input_id = SDL_SCANCODE_I} + {.input_type = (uint32_t)InputType::Keyboard, .input_id = SDL_SCANCODE_I} }, .dpad_down = { - {.device_type = (uint32_t)DeviceType::Keyboard, .input_id = SDL_SCANCODE_K} + {.input_type = (uint32_t)InputType::Keyboard, .input_id = SDL_SCANCODE_K} }, .analog_left = { - {.device_type = (uint32_t)DeviceType::Keyboard, .input_id = SDL_SCANCODE_A} + {.input_type = (uint32_t)InputType::Keyboard, .input_id = SDL_SCANCODE_A} }, .analog_right = { - {.device_type = (uint32_t)DeviceType::Keyboard, .input_id = SDL_SCANCODE_D} + {.input_type = (uint32_t)InputType::Keyboard, .input_id = SDL_SCANCODE_D} }, .analog_up = { - {.device_type = (uint32_t)DeviceType::Keyboard, .input_id = SDL_SCANCODE_W} + {.input_type = (uint32_t)InputType::Keyboard, .input_id = SDL_SCANCODE_W} }, .analog_down = { - {.device_type = (uint32_t)DeviceType::Keyboard, .input_id = SDL_SCANCODE_S} + {.input_type = (uint32_t)InputType::Keyboard, .input_id = SDL_SCANCODE_S} }, }; const recomp::DefaultN64Mappings recomp::default_n64_controller_mappings = { .a = { - {.device_type = (uint32_t)DeviceType::ControllerDigital, .input_id = SDL_CONTROLLER_BUTTON_SOUTH}, + {.input_type = (uint32_t)InputType::ControllerDigital, .input_id = SDL_CONTROLLER_BUTTON_SOUTH}, }, .b = { - {.device_type = (uint32_t)DeviceType::ControllerDigital, .input_id = SDL_CONTROLLER_BUTTON_EAST}, - {.device_type = (uint32_t)DeviceType::ControllerDigital, .input_id = SDL_CONTROLLER_BUTTON_WEST}, + {.input_type = (uint32_t)InputType::ControllerDigital, .input_id = SDL_CONTROLLER_BUTTON_EAST}, + {.input_type = (uint32_t)InputType::ControllerDigital, .input_id = SDL_CONTROLLER_BUTTON_WEST}, }, .l = { - {.device_type = (uint32_t)DeviceType::ControllerDigital, .input_id = SDL_CONTROLLER_BUTTON_LEFTSHOULDER}, + {.input_type = (uint32_t)InputType::ControllerDigital, .input_id = SDL_CONTROLLER_BUTTON_LEFTSHOULDER}, }, .r = { - {.device_type = (uint32_t)DeviceType::ControllerDigital, .input_id = SDL_CONTROLLER_BUTTON_RIGHTSHOULDER}, - {.device_type = (uint32_t)DeviceType::ControllerAnalog, .input_id = SDL_CONTROLLER_AXIS_TRIGGERRIGHT + 1}, + {.input_type = (uint32_t)InputType::ControllerDigital, .input_id = SDL_CONTROLLER_BUTTON_RIGHTSHOULDER}, + {.input_type = (uint32_t)InputType::ControllerAnalog, .input_id = SDL_CONTROLLER_AXIS_TRIGGERRIGHT + 1}, }, .z = { - {.device_type = (uint32_t)DeviceType::ControllerAnalog, .input_id = SDL_CONTROLLER_AXIS_TRIGGERLEFT + 1}, + {.input_type = (uint32_t)InputType::ControllerAnalog, .input_id = SDL_CONTROLLER_AXIS_TRIGGERLEFT + 1}, }, .start = { - {.device_type = (uint32_t)DeviceType::ControllerDigital, .input_id = SDL_CONTROLLER_BUTTON_START}, + {.input_type = (uint32_t)InputType::ControllerDigital, .input_id = SDL_CONTROLLER_BUTTON_START}, }, .c_left = { - {.device_type = (uint32_t)DeviceType::ControllerAnalog, .input_id = -(SDL_CONTROLLER_AXIS_RIGHTX + 1)}, + {.input_type = (uint32_t)InputType::ControllerAnalog, .input_id = -(SDL_CONTROLLER_AXIS_RIGHTX + 1)}, }, .c_right = { - {.device_type = (uint32_t)DeviceType::ControllerAnalog, .input_id = SDL_CONTROLLER_AXIS_RIGHTX + 1}, + {.input_type = (uint32_t)InputType::ControllerAnalog, .input_id = SDL_CONTROLLER_AXIS_RIGHTX + 1}, }, .c_up = { - {.device_type = (uint32_t)DeviceType::ControllerAnalog, .input_id = -(SDL_CONTROLLER_AXIS_RIGHTY + 1)}, + {.input_type = (uint32_t)InputType::ControllerAnalog, .input_id = -(SDL_CONTROLLER_AXIS_RIGHTY + 1)}, }, .c_down = { - {.device_type = (uint32_t)DeviceType::ControllerAnalog, .input_id = SDL_CONTROLLER_AXIS_RIGHTY + 1}, + {.input_type = (uint32_t)InputType::ControllerAnalog, .input_id = SDL_CONTROLLER_AXIS_RIGHTY + 1}, }, .dpad_left = { - {.device_type = (uint32_t)DeviceType::ControllerDigital, .input_id = SDL_CONTROLLER_BUTTON_DPAD_LEFT}, + {.input_type = (uint32_t)InputType::ControllerDigital, .input_id = SDL_CONTROLLER_BUTTON_DPAD_LEFT}, }, .dpad_right = { - {.device_type = (uint32_t)DeviceType::ControllerDigital, .input_id = SDL_CONTROLLER_BUTTON_DPAD_RIGHT}, + {.input_type = (uint32_t)InputType::ControllerDigital, .input_id = SDL_CONTROLLER_BUTTON_DPAD_RIGHT}, }, .dpad_up = { - {.device_type = (uint32_t)DeviceType::ControllerDigital, .input_id = SDL_CONTROLLER_BUTTON_DPAD_UP}, + {.input_type = (uint32_t)InputType::ControllerDigital, .input_id = SDL_CONTROLLER_BUTTON_DPAD_UP}, }, .dpad_down = { - {.device_type = (uint32_t)DeviceType::ControllerDigital, .input_id = SDL_CONTROLLER_BUTTON_DPAD_DOWN}, + {.input_type = (uint32_t)InputType::ControllerDigital, .input_id = SDL_CONTROLLER_BUTTON_DPAD_DOWN}, }, .analog_left = { - {.device_type = (uint32_t)DeviceType::ControllerAnalog, .input_id = -(SDL_CONTROLLER_AXIS_LEFTX + 1)}, + {.input_type = (uint32_t)InputType::ControllerAnalog, .input_id = -(SDL_CONTROLLER_AXIS_LEFTX + 1)}, }, .analog_right = { - {.device_type = (uint32_t)DeviceType::ControllerAnalog, .input_id = SDL_CONTROLLER_AXIS_LEFTX + 1}, + {.input_type = (uint32_t)InputType::ControllerAnalog, .input_id = SDL_CONTROLLER_AXIS_LEFTX + 1}, }, .analog_up = { - {.device_type = (uint32_t)DeviceType::ControllerAnalog, .input_id = -(SDL_CONTROLLER_AXIS_LEFTY + 1)}, + {.input_type = (uint32_t)InputType::ControllerAnalog, .input_id = -(SDL_CONTROLLER_AXIS_LEFTY + 1)}, }, .analog_down = { - {.device_type = (uint32_t)DeviceType::ControllerAnalog, .input_id = SDL_CONTROLLER_AXIS_LEFTY + 1}, + {.input_type = (uint32_t)InputType::ControllerAnalog, .input_id = SDL_CONTROLLER_AXIS_LEFTY + 1}, }, }; @@ -264,27 +314,21 @@ float controller_axis_state(int32_t input_id) { } float recomp::get_input_analog(const recomp::InputField& field) { - switch ((DeviceType)field.device_type) { - case DeviceType::Keyboard: - { + switch ((InputType)field.input_type) { + case InputType::Keyboard: if (InputState.keys && field.input_id >= 0 && field.input_id < InputState.numkeys) { return InputState.keys[field.input_id] ? 1.0f : 0.0f; } return 0.0f; - } - case DeviceType::ControllerDigital: - { + case InputType::ControllerDigital: return controller_button_state(field.input_id) ? 1.0f : 0.0f; - } - case DeviceType::ControllerAnalog: - { + case InputType::ControllerAnalog: return controller_axis_state(field.input_id); - } - case DeviceType::Mouse: - { + case InputType::Mouse: // TODO mouse support return 0.0f; - } + case InputType::None: + return false; } } @@ -297,28 +341,22 @@ float recomp::get_input_analog(const std::span fields) } bool recomp::get_input_digital(const recomp::InputField& field) { - switch ((DeviceType)field.device_type) { - case DeviceType::Keyboard: - { + switch ((InputType)field.input_type) { + case InputType::Keyboard: if (InputState.keys && field.input_id >= 0 && field.input_id < InputState.numkeys) { return InputState.keys[field.input_id] != 0; } return false; - } - case DeviceType::ControllerDigital: - { + case InputType::ControllerDigital: return controller_button_state(field.input_id); - } - case DeviceType::ControllerAnalog: - { + case InputType::ControllerAnalog: // TODO adjustable threshold - return controller_axis_state(field.input_id) >= 0.5f; - } - case DeviceType::Mouse: - { + return controller_axis_state(field.input_id) >= axis_threshold; + case InputType::Mouse: // TODO mouse support return false; - } + case InputType::None: + return false; } } @@ -334,3 +372,88 @@ bool recomp::game_input_disabled() { // Disable input if any menu is open. return recomp::get_current_menu() != recomp::Menu::None; } + +bool recomp::all_input_disabled() { + // Disable all input if an input is being polled. + return scanning_device != recomp::InputDevice::COUNT; +} + +std::string controller_button_to_string(SDL_GameControllerButton button) { + switch (button) { + case SDL_GameControllerButton::SDL_CONTROLLER_BUTTON_A: + return "\u21A7"; + case SDL_GameControllerButton::SDL_CONTROLLER_BUTTON_B: + return "\u21A6"; + case SDL_GameControllerButton::SDL_CONTROLLER_BUTTON_X: + return "\u21A4"; + case SDL_GameControllerButton::SDL_CONTROLLER_BUTTON_Y: + return "\u21A5"; + case SDL_GameControllerButton::SDL_CONTROLLER_BUTTON_BACK: + return "\u21FA"; + // case SDL_GameControllerButton::SDL_CONTROLLER_BUTTON_GUIDE: + // return ""; + case SDL_GameControllerButton::SDL_CONTROLLER_BUTTON_START: + return "\u21FB"; + case SDL_GameControllerButton::SDL_CONTROLLER_BUTTON_LEFTSTICK: + return "\u21BA"; + case SDL_GameControllerButton::SDL_CONTROLLER_BUTTON_RIGHTSTICK: + return "\u21BB"; + case SDL_GameControllerButton::SDL_CONTROLLER_BUTTON_LEFTSHOULDER: + return "\u2198"; + case SDL_GameControllerButton::SDL_CONTROLLER_BUTTON_RIGHTSHOULDER: + return "\u2199"; + case SDL_GameControllerButton::SDL_CONTROLLER_BUTTON_DPAD_UP: + return "\u219F"; + case SDL_GameControllerButton::SDL_CONTROLLER_BUTTON_DPAD_DOWN: + return "\u21A1"; + case SDL_GameControllerButton::SDL_CONTROLLER_BUTTON_DPAD_LEFT: + return "\u219E"; + case SDL_GameControllerButton::SDL_CONTROLLER_BUTTON_DPAD_RIGHT: + return "\u21A0"; + // case SDL_GameControllerButton::SDL_CONTROLLER_BUTTON_MISC1: + // return ""; + // case SDL_GameControllerButton::SDL_CONTROLLER_BUTTON_PADDLE1: + // return ""; + // case SDL_GameControllerButton::SDL_CONTROLLER_BUTTON_PADDLE2: + // return ""; + // case SDL_GameControllerButton::SDL_CONTROLLER_BUTTON_PADDLE3: + // return ""; + // case SDL_GameControllerButton::SDL_CONTROLLER_BUTTON_PADDLE4: + // return ""; + case SDL_GameControllerButton::SDL_CONTROLLER_BUTTON_TOUCHPAD: + return "\u21E7"; + } + return "Button " + std::to_string(button); +} + +std::string controller_axis_to_string(int axis) { + bool positive = axis > 0; + SDL_GameControllerAxis actual_axis = SDL_GameControllerAxis(abs(axis) - 1); + switch (actual_axis) { + case SDL_GameControllerAxis::SDL_CONTROLLER_AXIS_LEFTX: + return positive ? "\u21C0" : "\u21BC"; + case SDL_GameControllerAxis::SDL_CONTROLLER_AXIS_LEFTY: + return positive ? "\u21C2" : "\u21BE"; + case SDL_GameControllerAxis::SDL_CONTROLLER_AXIS_RIGHTX: + return positive ? "\u21C1" : "\u21BD"; + case SDL_GameControllerAxis::SDL_CONTROLLER_AXIS_RIGHTY: + return positive ? "\u21C3" : "\u21BF"; + case SDL_GameControllerAxis::SDL_CONTROLLER_AXIS_TRIGGERLEFT: + return positive ? "\u219A" : "\u21DC"; + case SDL_GameControllerAxis::SDL_CONTROLLER_AXIS_TRIGGERRIGHT: + return positive ? "\u219B" : "\u21DD"; + } + return "Axis " + std::to_string(actual_axis) + (positive ? '+' : '-'); +} + +std::string recomp::InputField::to_string() const { + switch ((InputType)input_type) { + case InputType::None: + return ""; + case InputType::ControllerDigital: + return controller_button_to_string((SDL_GameControllerButton)input_id); + case InputType::ControllerAnalog: + return controller_axis_to_string(input_id); + } + return std::to_string(input_type) + "," + std::to_string(input_id); +} diff --git a/src/main/main.cpp b/src/main/main.cpp index e867a22..daa88a0 100644 --- a/src/main/main.cpp +++ b/src/main/main.cpp @@ -49,7 +49,7 @@ ultramodern::gfx_callbacks_t::gfx_data_t create_gfx() { SDL_Window* window; ultramodern::WindowHandle create_window(ultramodern::gfx_callbacks_t::gfx_data_t) { - window = SDL_CreateWindow("Recomp", SDL_WINDOWPOS_CENTERED, SDL_WINDOWPOS_CENTERED, 1280, 720, SDL_WINDOW_RESIZABLE ); + window = SDL_CreateWindow("Zelda 64: Recompiled", SDL_WINDOWPOS_CENTERED, SDL_WINDOWPOS_CENTERED, 1280, 720, SDL_WINDOW_RESIZABLE ); if (window == nullptr) { exit_error("Failed to create window: %s\n", SDL_GetError()); diff --git a/src/ui/ui_config.cpp b/src/ui/ui_config.cpp index a544233..0f32f1a 100644 --- a/src/ui/ui_config.cpp +++ b/src/ui/ui_config.cpp @@ -1,11 +1,15 @@ #include "recomp_ui.h" +#include "recomp_input.h" #include "../../ultramodern/config.hpp" #include "../../ultramodern/ultramodern.hpp" #include "RmlUi/Core.h" ultramodern::GraphicsConfig cur_options; ultramodern::GraphicsConfig new_options; -Rml::DataModelHandle options_handle; +Rml::DataModelHandle graphics_model_handle; +Rml::DataModelHandle controls_model_handle; +// True if controller config menu is open, false if keyboard config menu is open, undefined otherwise +bool configuring_controller = false; NLOHMANN_JSON_SERIALIZE_ENUM(ultramodern::Resolution, { {ultramodern::Resolution::Original, "Original"}, @@ -46,10 +50,20 @@ template void bind_option(Rml::DataModelConstructor& constructor, const std::string& name, T* option) { constructor.BindFunc(name, [option](Rml::Variant& out) { get_option(*option, out); }, - [option](const Rml::Variant& in) { set_option(*option, in); options_handle.DirtyVariable("options_changed"); } + [option](const Rml::Variant& in) { set_option(*option, in); graphics_model_handle.DirtyVariable("options_changed"); } ); }; +static size_t scanned_binding_index; +static size_t scanned_input_index; + +constexpr recomp::InputDevice cur_device = recomp::InputDevice::Controller; + +void recomp::finish_scanning_input(recomp::InputField scanned_field) { + recomp::set_input_binding(scanned_input_index, scanned_binding_index, cur_device, scanned_field); + controls_model_handle.DirtyVariable("inputs"); +} + class ConfigMenu : public recomp::MenuController { public: ConfigMenu() { @@ -65,9 +79,18 @@ public: recomp::register_event(listener, "apply_options", [](const std::string& param, Rml::Event& event) { cur_options = new_options; - options_handle.DirtyVariable("options_changed"); + graphics_model_handle.DirtyVariable("options_changed"); update_graphics_config(new_options); }); + recomp::register_event(listener, "rebind_input_bindings", + [](const std::string& param, Rml::Event& event) { + }); + recomp::register_event(listener, "clear_input_bindings", + [](const std::string& param, Rml::Event& event) { + }); + recomp::register_event(listener, "add_input_binding", + [](const std::string& param, Rml::Event& event) { + }); recomp::register_event(listener, "config_keydown", [](const std::string& param, Rml::Event& event) { if (event.GetId() == Rml::EventId::Keydown) { @@ -82,10 +105,10 @@ public: } }); } - void make_bindings(Rml::Context* context) override { + void make_graphics_bindings(Rml::Context* context) { Rml::DataModelConstructor constructor = context->CreateDataModel("graphics_model"); if (!constructor) { - throw std::runtime_error("Failed to make RmlUi data model for the config menu"); + throw std::runtime_error("Failed to make RmlUi data model for the graphics config menu"); } bind_option(constructor, "res_option", &new_options.res_option); @@ -99,7 +122,7 @@ public: }, [](const Rml::Variant& in) { new_options.rr_manual_value = in.Get(); - options_handle.DirtyVariable("options_changed"); + graphics_model_handle.DirtyVariable("options_changed"); }); constructor.BindFunc("options_changed", @@ -107,7 +130,91 @@ public: out = (cur_options != new_options); }); - options_handle = constructor.GetModelHandle(); + graphics_model_handle = constructor.GetModelHandle(); + } + + void make_controls_bindings(Rml::Context* context) { + Rml::DataModelConstructor constructor = context->CreateDataModel("controls_model"); + if (!constructor) { + throw std::runtime_error("Failed to make RmlUi data model for the controls config menu"); + } + + constructor.BindFunc("input_count", [](Rml::Variant& out) { out = recomp::get_num_inputs(); } ); + + constructor.RegisterTransformFunc("get_input_name", [](const Rml::VariantList& inputs) { + return Rml::Variant{recomp::get_input_names().at(inputs.at(0).Get())}; + }); + + constructor.BindEventCallback("set_input_binding", + [](Rml::DataModelHandle model_handle, Rml::Event& event, const Rml::VariantList& inputs) { + scanned_input_index = inputs.at(0).Get(); + scanned_binding_index = inputs.at(1).Get(); + recomp::start_scanning_input(cur_device); + }); + + constructor.BindEventCallback("clear_input_bindings", + [](Rml::DataModelHandle model_handle, Rml::Event& event, const Rml::VariantList& inputs) { + size_t input_index = inputs.at(0).Get(); + for (size_t binding_index = 0; binding_index < recomp::bindings_per_input; binding_index++) { + recomp::set_input_binding(input_index, binding_index, cur_device, recomp::InputField{}); + } + model_handle.DirtyVariable("inputs"); + }); + + // Rml variable definition for an individual InputField. + struct InputFieldVariableDefinition : public Rml::VariableDefinition { + InputFieldVariableDefinition() : Rml::VariableDefinition(Rml::DataVariableType::Scalar) {} + + virtual bool Get(void* ptr, Rml::Variant& variant) override { variant = reinterpret_cast(ptr)->to_string(); return true; } + virtual bool Set(void* ptr, const Rml::Variant& variant) override { return false; } + }; + // Static instance of the InputField variable definition to have a pointer to return to RmlUi. + static InputFieldVariableDefinition input_field_definition_instance{}; + + // Rml variable definition for an array of InputField values (e.g. all the bindings for a single input). + struct BindingContainerVariableDefinition : public Rml::VariableDefinition { + BindingContainerVariableDefinition() : Rml::VariableDefinition(Rml::DataVariableType::Array) {} + + virtual bool Get(void* ptr, Rml::Variant& variant) override { return false; } + virtual bool Set(void* ptr, const Rml::Variant& variant) override { return false; } + + virtual int Size(void* ptr) override { return recomp::bindings_per_input; } + virtual Rml::DataVariable Child(void* ptr, const Rml::DataAddressEntry& address) override { + uintptr_t input_index = (uintptr_t)ptr; + return Rml::DataVariable{&input_field_definition_instance, & recomp::get_input_binding(input_index, address.index, recomp::InputDevice::Controller)}; + } + }; + // Static instance of the InputField array variable definition to a fixed pointer to return to RmlUi. + static BindingContainerVariableDefinition binding_container_var_instance{}; + + // Rml variable definition for an array of an array of InputField values (e.g. all the bindings for all inputs). + struct InputContainerVariableDefinition : public Rml::VariableDefinition { + InputContainerVariableDefinition() : Rml::VariableDefinition(Rml::DataVariableType::Array) {} + + virtual bool Get(void* ptr, Rml::Variant& variant) override { return false; } + virtual bool Set(void* ptr, const Rml::Variant& variant) override { return false; } + + virtual int Size(void* ptr) override { return recomp::get_num_inputs(); } + virtual Rml::DataVariable Child(void* ptr, const Rml::DataAddressEntry& address) override { + // Encode the input index as the pointer to avoid needing to do any allocations. + return Rml::DataVariable(&binding_container_var_instance, (void*)(uintptr_t)address.index); + } + }; + + // Dummy type to associate with the variable definition. + struct InputContainer {}; + constructor.RegisterCustomDataVariableDefinition(Rml::MakeUnique()); + + // Dummy instance of the dummy type to bind to the variable. + static InputContainer dummy_container; + constructor.Bind("inputs", &dummy_container); + + controls_model_handle = constructor.GetModelHandle(); + } + + void make_bindings(Rml::Context* context) override { + make_graphics_bindings(context); + make_controls_bindings(context); } }; diff --git a/src/ui/ui_renderer.cpp b/src/ui/ui_renderer.cpp index 47a33ef..805d2ae 100644 --- a/src/ui/ui_renderer.cpp +++ b/src/ui/ui_renderer.cpp @@ -6,6 +6,7 @@ #include #include "recomp_ui.h" +#include "recomp_input.h" #include "concurrentqueue.h" @@ -677,6 +678,7 @@ struct { else { current_document = nullptr; } + prev_focused = nullptr; } void swap_config_menu(recomp::ConfigSubmenu submenu) { @@ -686,6 +688,7 @@ struct { Rml::ElementTabSet* config_tabset = rmlui_dynamic_cast(config_tabset_base); if (config_tabset != nullptr) { config_tabset->SetActiveTab(static_cast(submenu)); + prev_focused = nullptr; } } } @@ -714,6 +717,8 @@ struct { for (auto& [menu, controller]: menus) { documents.emplace(menu, controller->load_document(context)); } + + prev_focused = nullptr; } void make_event_listeners() { @@ -917,6 +922,11 @@ void draw_hook(RT64::RenderCommandList* command_list, RT64::RenderTexture* swap_ } } + recomp::InputField scanned_field = recomp::get_scanned_input(); + if (scanned_field != recomp::InputField{}) { + recomp::finish_scanning_input(scanned_field); + } + UIContext.rml.update_focus(mouse_moved); if (cur_menu != recomp::Menu::None) {