From 8c4631294826c9af34553af72e87bacc896d9961 Mon Sep 17 00:00:00 2001 From: thecozies <79979276+thecozies@users.noreply.github.com> Date: Tue, 9 Sep 2025 13:50:53 -0500 Subject: [PATCH] switch ui/input to be using recompfrontend library (unfinished) --- CMakeLists.txt | 68 +- include/banjo_config.h | 60 +- include/banjo_sound.h | 4 - include/recomp_input.h | 317 ----- include/recomp_ui.h | 175 --- lib/N64ModernRuntime | 2 +- src/game/config.cpp | 627 +-------- src/game/controls.cpp | 472 ------- src/game/input.cpp | 1175 ----------------- src/game/recomp_actor_api.cpp | 2 +- src/game/recomp_api.cpp | 21 +- src/game/recomp_data_api.cpp | 2 +- src/main/main.cpp | 48 +- src/main/rt64_render_context.cpp | 548 -------- src/main/support.cpp | 94 -- src/main/theme.cpp | 2 +- src/ui/config/ui_config_common.h | 72 - src/ui/config/ui_config_option.cpp | 293 ---- src/ui/config/ui_config_option.h | 131 -- src/ui/config/ui_config_page_controls.cpp | 513 ------- src/ui/config/ui_config_page_controls.h | 140 -- src/ui/config/ui_config_page_options_menu.cpp | 307 ----- src/ui/config/ui_config_page_options_menu.h | 71 - src/ui/config/ui_config_tab_controls.cpp | 29 - src/ui/config/ui_config_tab_controls.h | 16 - src/ui/config/ui_config_tab_graphics.cpp | 385 ------ src/ui/config/ui_config_tab_graphics.h | 19 - src/ui/config/ui_config_tab_manifest.cpp | 109 -- src/ui/config/ui_config_tab_manifest.h | 22 - src/ui/core/ui_context.cpp | 772 ----------- src/ui/core/ui_context.h | 72 - src/ui/core/ui_resource.h | 24 - src/ui/elements/ui_binding_button.cpp | 207 --- src/ui/elements/ui_binding_button.h | 45 - src/ui/elements/ui_button.cpp | 182 --- src/ui/elements/ui_button.h | 49 - src/ui/elements/ui_clickable.cpp | 77 -- src/ui/elements/ui_clickable.h | 23 - src/ui/elements/ui_config_page.cpp | 138 -- src/ui/elements/ui_config_page.h | 54 - src/ui/elements/ui_container.cpp | 13 - src/ui/elements/ui_container.h | 14 - src/ui/elements/ui_element.cpp | 1070 --------------- src/ui/elements/ui_element.h | 166 --- src/ui/elements/ui_icon_button.cpp | 181 --- src/ui/elements/ui_icon_button.h | 43 - src/ui/elements/ui_image.cpp | 11 - src/ui/elements/ui_image.h | 14 - src/ui/elements/ui_label.cpp | 37 - src/ui/elements/ui_label.h | 24 - src/ui/elements/ui_modal.cpp | 272 ---- src/ui/elements/ui_modal.h | 81 -- src/ui/elements/ui_pill_button.cpp | 206 --- src/ui/elements/ui_pill_button.h | 43 - src/ui/elements/ui_pseudo_border.cpp | 43 - src/ui/elements/ui_pseudo_border.h | 22 - src/ui/elements/ui_radio.cpp | 291 ---- src/ui/elements/ui_radio.h | 59 - src/ui/elements/ui_scroll_container.cpp | 27 - src/ui/elements/ui_scroll_container.h | 19 - src/ui/elements/ui_select.cpp | 296 ----- src/ui/elements/ui_select.h | 78 -- src/ui/elements/ui_slider.cpp | 252 ---- src/ui/elements/ui_slider.h | 61 - src/ui/elements/ui_span.cpp | 15 - src/ui/elements/ui_span.h | 16 - src/ui/elements/ui_style.cpp | 747 ----------- src/ui/elements/ui_style.h | 129 -- src/ui/elements/ui_svg.cpp | 11 - src/ui/elements/ui_svg.h | 14 - src/ui/elements/ui_tab_set.cpp | 168 --- src/ui/elements/ui_tab_set.h | 52 - src/ui/elements/ui_text_input.cpp | 65 - src/ui/elements/ui_text_input.h | 24 - src/ui/elements/ui_theme.cpp | 267 ---- src/ui/elements/ui_theme.h | 137 -- src/ui/elements/ui_toggle.cpp | 238 ---- src/ui/elements/ui_toggle.h | 59 - src/ui/elements/ui_types.h | 317 ----- src/ui/ui_api.cpp | 1031 --------------- src/ui/ui_api_events.cpp | 124 -- src/ui/ui_api_images.cpp | 131 -- src/ui/ui_api_images.h | 10 - src/ui/ui_assign_players_modal.cpp | 190 --- src/ui/ui_assign_players_modal.h | 38 - src/ui/ui_color_element.cpp | 32 - src/ui/ui_color_element.h | 19 - src/ui/ui_color_hack.cpp | 181 --- src/ui/ui_config.cpp | 747 ----------- src/ui/ui_config_modal.cpp | 238 ---- src/ui/ui_config_modal.h | 14 - src/ui/ui_config_page_controls_element.cpp | 39 - src/ui/ui_config_page_controls_element.h | 15 - src/ui/ui_config_page_example.cpp | 65 - src/ui/ui_config_page_example.h | 13 - src/ui/ui_config_sub_menu.cpp | 269 ---- src/ui/ui_config_sub_menu.h | 117 -- src/ui/ui_elements.cpp | 45 - src/ui/ui_elements.h | 19 - src/ui/ui_helpers.h | 154 --- src/ui/ui_launcher.cpp | 127 -- src/ui/ui_mod_details_panel.cpp | 154 --- src/ui/ui_mod_details_panel.h | 52 - src/ui/ui_mod_installer.cpp | 343 ----- src/ui/ui_mod_installer.h | 42 - src/ui/ui_mod_menu.cpp | 804 ----------- src/ui/ui_mod_menu.h | 131 -- src/ui/ui_player_card.cpp | 186 --- src/ui/ui_player_card.h | 45 - src/ui/ui_prompt.cpp | 286 ---- src/ui/ui_renderer.cpp | 721 ---------- src/ui/ui_renderer.h | 38 - src/ui/ui_rml_hacks.cpp | 106 -- src/ui/ui_rml_hacks.hpp | 13 - src/ui/ui_state.cpp | 1052 --------------- src/ui/ui_utils.cpp | 25 - src/ui/ui_utils.h | 11 - src/ui/util/bem.h | 12 - src/ui/util/hsv.cpp | 145 -- src/ui/util/hsv.h | 77 -- 120 files changed, 141 insertions(+), 20669 deletions(-) delete mode 100644 include/recomp_input.h delete mode 100644 include/recomp_ui.h delete mode 100644 src/game/controls.cpp delete mode 100644 src/game/input.cpp delete mode 100644 src/main/rt64_render_context.cpp delete mode 100644 src/main/support.cpp delete mode 100644 src/ui/config/ui_config_common.h delete mode 100644 src/ui/config/ui_config_option.cpp delete mode 100644 src/ui/config/ui_config_option.h delete mode 100644 src/ui/config/ui_config_page_controls.cpp delete mode 100644 src/ui/config/ui_config_page_controls.h delete mode 100644 src/ui/config/ui_config_page_options_menu.cpp delete mode 100644 src/ui/config/ui_config_page_options_menu.h delete mode 100644 src/ui/config/ui_config_tab_controls.cpp delete mode 100644 src/ui/config/ui_config_tab_controls.h delete mode 100644 src/ui/config/ui_config_tab_graphics.cpp delete mode 100644 src/ui/config/ui_config_tab_graphics.h delete mode 100644 src/ui/config/ui_config_tab_manifest.cpp delete mode 100644 src/ui/config/ui_config_tab_manifest.h delete mode 100644 src/ui/core/ui_context.cpp delete mode 100644 src/ui/core/ui_context.h delete mode 100644 src/ui/core/ui_resource.h delete mode 100644 src/ui/elements/ui_binding_button.cpp delete mode 100644 src/ui/elements/ui_binding_button.h delete mode 100644 src/ui/elements/ui_button.cpp delete mode 100644 src/ui/elements/ui_button.h delete mode 100644 src/ui/elements/ui_clickable.cpp delete mode 100644 src/ui/elements/ui_clickable.h delete mode 100644 src/ui/elements/ui_config_page.cpp delete mode 100644 src/ui/elements/ui_config_page.h delete mode 100644 src/ui/elements/ui_container.cpp delete mode 100644 src/ui/elements/ui_container.h delete mode 100644 src/ui/elements/ui_element.cpp delete mode 100644 src/ui/elements/ui_element.h delete mode 100644 src/ui/elements/ui_icon_button.cpp delete mode 100644 src/ui/elements/ui_icon_button.h delete mode 100644 src/ui/elements/ui_image.cpp delete mode 100644 src/ui/elements/ui_image.h delete mode 100644 src/ui/elements/ui_label.cpp delete mode 100644 src/ui/elements/ui_label.h delete mode 100644 src/ui/elements/ui_modal.cpp delete mode 100644 src/ui/elements/ui_modal.h delete mode 100644 src/ui/elements/ui_pill_button.cpp delete mode 100644 src/ui/elements/ui_pill_button.h delete mode 100644 src/ui/elements/ui_pseudo_border.cpp delete mode 100644 src/ui/elements/ui_pseudo_border.h delete mode 100644 src/ui/elements/ui_radio.cpp delete mode 100644 src/ui/elements/ui_radio.h delete mode 100644 src/ui/elements/ui_scroll_container.cpp delete mode 100644 src/ui/elements/ui_scroll_container.h delete mode 100644 src/ui/elements/ui_select.cpp delete mode 100644 src/ui/elements/ui_select.h delete mode 100644 src/ui/elements/ui_slider.cpp delete mode 100644 src/ui/elements/ui_slider.h delete mode 100644 src/ui/elements/ui_span.cpp delete mode 100644 src/ui/elements/ui_span.h delete mode 100644 src/ui/elements/ui_style.cpp delete mode 100644 src/ui/elements/ui_style.h delete mode 100644 src/ui/elements/ui_svg.cpp delete mode 100644 src/ui/elements/ui_svg.h delete mode 100644 src/ui/elements/ui_tab_set.cpp delete mode 100644 src/ui/elements/ui_tab_set.h delete mode 100644 src/ui/elements/ui_text_input.cpp delete mode 100644 src/ui/elements/ui_text_input.h delete mode 100644 src/ui/elements/ui_theme.cpp delete mode 100644 src/ui/elements/ui_theme.h delete mode 100644 src/ui/elements/ui_toggle.cpp delete mode 100644 src/ui/elements/ui_toggle.h delete mode 100644 src/ui/elements/ui_types.h delete mode 100644 src/ui/ui_api.cpp delete mode 100644 src/ui/ui_api_events.cpp delete mode 100644 src/ui/ui_api_images.cpp delete mode 100644 src/ui/ui_api_images.h delete mode 100644 src/ui/ui_assign_players_modal.cpp delete mode 100644 src/ui/ui_assign_players_modal.h delete mode 100644 src/ui/ui_color_element.cpp delete mode 100644 src/ui/ui_color_element.h delete mode 100644 src/ui/ui_color_hack.cpp delete mode 100644 src/ui/ui_config.cpp delete mode 100644 src/ui/ui_config_modal.cpp delete mode 100644 src/ui/ui_config_modal.h delete mode 100644 src/ui/ui_config_page_controls_element.cpp delete mode 100644 src/ui/ui_config_page_controls_element.h delete mode 100644 src/ui/ui_config_page_example.cpp delete mode 100644 src/ui/ui_config_page_example.h delete mode 100644 src/ui/ui_config_sub_menu.cpp delete mode 100644 src/ui/ui_config_sub_menu.h delete mode 100644 src/ui/ui_elements.cpp delete mode 100644 src/ui/ui_elements.h delete mode 100644 src/ui/ui_helpers.h delete mode 100644 src/ui/ui_launcher.cpp delete mode 100644 src/ui/ui_mod_details_panel.cpp delete mode 100644 src/ui/ui_mod_details_panel.h delete mode 100644 src/ui/ui_mod_installer.cpp delete mode 100644 src/ui/ui_mod_installer.h delete mode 100644 src/ui/ui_mod_menu.cpp delete mode 100644 src/ui/ui_mod_menu.h delete mode 100644 src/ui/ui_player_card.cpp delete mode 100644 src/ui/ui_player_card.h delete mode 100644 src/ui/ui_prompt.cpp delete mode 100644 src/ui/ui_renderer.cpp delete mode 100644 src/ui/ui_renderer.h delete mode 100644 src/ui/ui_rml_hacks.cpp delete mode 100644 src/ui/ui_rml_hacks.hpp delete mode 100644 src/ui/ui_state.cpp delete mode 100644 src/ui/ui_utils.cpp delete mode 100644 src/ui/ui_utils.h delete mode 100644 src/ui/util/bem.h delete mode 100644 src/ui/util/hsv.cpp delete mode 100644 src/ui/util/hsv.h diff --git a/CMakeLists.txt b/CMakeLists.txt index b6c985e..67c9844 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -62,6 +62,7 @@ add_subdirectory(${CMAKE_SOURCE_DIR}/lib/RmlUi) target_compile_definitions(rmlui_core PRIVATE LUNASVG_BUILD_STATIC) add_subdirectory(${CMAKE_SOURCE_DIR}/lib/N64ModernRuntime) +add_subdirectory(${CMAKE_SOURCE_DIR}/lib/RecompFrontend) target_include_directories(rt64 PRIVATE ${CMAKE_BINARY_DIR}/rt64/src) @@ -149,14 +150,10 @@ add_executable(BanjoRecompiled) set (SOURCES ${CMAKE_SOURCE_DIR}/src/main/main.cpp - ${CMAKE_SOURCE_DIR}/src/main/support.cpp ${CMAKE_SOURCE_DIR}/src/main/register_overlays.cpp ${CMAKE_SOURCE_DIR}/src/main/register_patches.cpp - ${CMAKE_SOURCE_DIR}/src/main/rt64_render_context.cpp ${CMAKE_SOURCE_DIR}/src/main/theme.cpp - ${CMAKE_SOURCE_DIR}/src/game/input.cpp - ${CMAKE_SOURCE_DIR}/src/game/controls.cpp ${CMAKE_SOURCE_DIR}/src/game/config.cpp ${CMAKE_SOURCE_DIR}/src/game/debug.cpp ${CMAKE_SOURCE_DIR}/src/game/recomp_api.cpp @@ -164,69 +161,11 @@ set (SOURCES ${CMAKE_SOURCE_DIR}/src/game/recomp_data_api.cpp ${CMAKE_SOURCE_DIR}/src/game/rom_decompression.cpp - ${CMAKE_SOURCE_DIR}/src/ui/ui_renderer.cpp - ${CMAKE_SOURCE_DIR}/src/ui/ui_state.cpp - ${CMAKE_SOURCE_DIR}/src/ui/ui_launcher.cpp - ${CMAKE_SOURCE_DIR}/src/ui/ui_config.cpp - ${CMAKE_SOURCE_DIR}/src/ui/ui_assign_players_modal.cpp - ${CMAKE_SOURCE_DIR}/src/ui/ui_color_element.cpp - ${CMAKE_SOURCE_DIR}/src/ui/ui_config_modal.cpp - ${CMAKE_SOURCE_DIR}/src/ui/ui_config_page_example.cpp - ${CMAKE_SOURCE_DIR}/src/ui/ui_config_page_controls_element.cpp - ${CMAKE_SOURCE_DIR}/src/ui/ui_prompt.cpp - ${CMAKE_SOURCE_DIR}/src/ui/ui_player_card.cpp - ${CMAKE_SOURCE_DIR}/src/ui/ui_config_sub_menu.cpp - ${CMAKE_SOURCE_DIR}/src/ui/ui_color_hack.cpp - ${CMAKE_SOURCE_DIR}/src/ui/ui_rml_hacks.cpp - ${CMAKE_SOURCE_DIR}/src/ui/ui_elements.cpp - ${CMAKE_SOURCE_DIR}/src/ui/ui_mod_details_panel.cpp - ${CMAKE_SOURCE_DIR}/src/ui/ui_mod_installer.cpp - ${CMAKE_SOURCE_DIR}/src/ui/ui_mod_menu.cpp - ${CMAKE_SOURCE_DIR}/src/ui/ui_api.cpp - ${CMAKE_SOURCE_DIR}/src/ui/ui_api_events.cpp - ${CMAKE_SOURCE_DIR}/src/ui/ui_api_images.cpp - ${CMAKE_SOURCE_DIR}/src/ui/ui_utils.cpp - ${CMAKE_SOURCE_DIR}/src/ui/util/hsv.cpp - ${CMAKE_SOURCE_DIR}/src/ui/config/ui_config_page_options_menu.cpp - ${CMAKE_SOURCE_DIR}/src/ui/config/ui_config_page_controls.cpp - ${CMAKE_SOURCE_DIR}/src/ui/config/ui_config_tab_controls.cpp - ${CMAKE_SOURCE_DIR}/src/ui/config/ui_config_tab_graphics.cpp - ${CMAKE_SOURCE_DIR}/src/ui/config/ui_config_tab_manifest.cpp - ${CMAKE_SOURCE_DIR}/src/ui/config/ui_config_option.cpp - ${CMAKE_SOURCE_DIR}/src/ui/core/ui_context.cpp - ${CMAKE_SOURCE_DIR}/src/ui/elements/ui_binding_button.cpp - ${CMAKE_SOURCE_DIR}/src/ui/elements/ui_button.cpp - ${CMAKE_SOURCE_DIR}/src/ui/elements/ui_icon_button.cpp - ${CMAKE_SOURCE_DIR}/src/ui/elements/ui_clickable.cpp - ${CMAKE_SOURCE_DIR}/src/ui/elements/ui_config_page.cpp - ${CMAKE_SOURCE_DIR}/src/ui/elements/ui_container.cpp - ${CMAKE_SOURCE_DIR}/src/ui/elements/ui_element.cpp - ${CMAKE_SOURCE_DIR}/src/ui/elements/ui_image.cpp - ${CMAKE_SOURCE_DIR}/src/ui/elements/ui_label.cpp - ${CMAKE_SOURCE_DIR}/src/ui/elements/ui_modal.cpp - ${CMAKE_SOURCE_DIR}/src/ui/elements/ui_pill_button.cpp - ${CMAKE_SOURCE_DIR}/src/ui/elements/ui_pseudo_border.cpp - ${CMAKE_SOURCE_DIR}/src/ui/elements/ui_radio.cpp - ${CMAKE_SOURCE_DIR}/src/ui/elements/ui_scroll_container.cpp - ${CMAKE_SOURCE_DIR}/src/ui/elements/ui_select.cpp - ${CMAKE_SOURCE_DIR}/src/ui/elements/ui_slider.cpp - ${CMAKE_SOURCE_DIR}/src/ui/elements/ui_span.cpp - ${CMAKE_SOURCE_DIR}/src/ui/elements/ui_style.cpp - ${CMAKE_SOURCE_DIR}/src/ui/elements/ui_svg.cpp - ${CMAKE_SOURCE_DIR}/src/ui/elements/ui_tab_set.cpp - ${CMAKE_SOURCE_DIR}/src/ui/elements/ui_text_input.cpp - ${CMAKE_SOURCE_DIR}/src/ui/elements/ui_theme.cpp - ${CMAKE_SOURCE_DIR}/src/ui/elements/ui_toggle.cpp - ${CMAKE_SOURCE_DIR}/rsp/n_aspMain.cpp ${CMAKE_SOURCE_DIR}/lib/RmlUi/Backends/RmlUi_Platform_SDL.cpp ) -if (APPLE) - list(APPEND SOURCES ${CMAKE_SOURCE_DIR}/src/main/support_apple.mm) -endif() - target_include_directories(BanjoRecompiled PRIVATE ${CMAKE_SOURCE_DIR}/include ${CMAKE_SOURCE_DIR}/lib/N64ModernRuntime/N64Recomp/include @@ -240,6 +179,9 @@ target_include_directories(BanjoRecompiled PRIVATE ${CMAKE_SOURCE_DIR}/lib/rt64/src ${CMAKE_SOURCE_DIR}/lib/rt64/src/rhi ${CMAKE_SOURCE_DIR}/lib/rt64/src/render + ${CMAKE_SOURCE_DIR}/lib/RecompFrontend/recompinput/include + ${CMAKE_SOURCE_DIR}/lib/RecompFrontend/recompui/include + ${CMAKE_SOURCE_DIR}/lib/RecompFrontend/recompui/src ${CMAKE_SOURCE_DIR}/lib/freetype-windows-binaries/include ${CMAKE_SOURCE_DIR}/lib/rt64/src/contrib/nativefiledialog-extended/src/include ${CMAKE_SOURCE_DIR}/lib/slot_map/slot_map @@ -377,6 +319,8 @@ target_link_libraries(BanjoRecompiled PRIVATE rt64 RmlUi::Core RmlUi::Debugger + recompinput + recompui nfd lunasvg ) diff --git a/include/banjo_config.h b/include/banjo_config.h index ff7009b..cde8f7d 100644 --- a/include/banjo_config.h +++ b/include/banjo_config.h @@ -2,64 +2,40 @@ #define __BANJO_CONFIG_H__ #include +#include #include -#include "ultramodern/config.hpp" -#include "recomp_input.h" namespace banjo { - constexpr std::u8string_view program_id = u8"BanjoRecompiled"; - constexpr std::string_view program_name = "Banjo: Recompiled"; + inline const std::u8string program_id = u8"BanjoRecompiled"; + inline const std::string program_name = "Banjo: Recompiled"; + + namespace configkeys { + namespace general { + inline const std::string camera_invert_mode = "camera_invert_mode"; + inline const std::string analog_cam_mode = "analog_cam_mode"; + inline const std::string analog_camera_invert_mode = "analog_camera_invert_mode"; + } + + namespace sound { + inline const std::string bgm_volume = "bgm_volume"; + } + } // TODO: Move loading configs to the runtime once we have a way to allow per-project customization. - void load_config(); - void save_config(); - bool read_json_with_backups(const std::filesystem::path& path, nlohmann::json& json_out); - bool save_json_with_backups(const std::filesystem::path& path, const nlohmann::json& json_data); + void init_config(); - void reset_input_bindings(); - void reset_cont_input_bindings(int profile_index); - void reset_kb_input_bindings(int profile_index); - void reset_single_input_binding(int profile_index, recomp::InputDevice device, recomp::GameInput input); - - std::filesystem::path get_app_folder_path(); - - bool get_debug_mode_enabled(); - void set_debug_mode_enabled(bool enabled); - enum class CameraInvertMode { InvertNone, InvertX, InvertY, - InvertBoth, - OptionCount + InvertBoth }; - NLOHMANN_JSON_SERIALIZE_ENUM(banjo::CameraInvertMode, { - {banjo::CameraInvertMode::InvertNone, "InvertNone"}, - {banjo::CameraInvertMode::InvertX, "InvertX"}, - {banjo::CameraInvertMode::InvertY, "InvertY"}, - {banjo::CameraInvertMode::InvertBoth, "InvertBoth"} - }); - CameraInvertMode get_camera_invert_mode(); - void set_camera_invert_mode(CameraInvertMode mode); CameraInvertMode get_analog_camera_invert_mode(); - void set_analog_camera_invert_mode(CameraInvertMode mode); - enum class AnalogCamMode { - On, - Off, - OptionCount - }; - - NLOHMANN_JSON_SERIALIZE_ENUM(banjo::AnalogCamMode, { - {banjo::AnalogCamMode::On, "On"}, - {banjo::AnalogCamMode::Off, "Off"} - }); - - AnalogCamMode get_analog_cam_mode(); - void set_analog_cam_mode(AnalogCamMode mode); + bool get_analog_cam_mode(); void open_quit_game_prompt(); }; diff --git a/include/banjo_sound.h b/include/banjo_sound.h index acebdee..aff5459 100644 --- a/include/banjo_sound.h +++ b/include/banjo_sound.h @@ -2,10 +2,6 @@ #define __BANJO_SOUND_H__ namespace banjo { - void reset_sound_settings(); - void set_main_volume(int volume); - int get_main_volume(); - void set_bgm_volume(int volume); int get_bgm_volume(); } diff --git a/include/recomp_input.h b/include/recomp_input.h deleted file mode 100644 index fdc0517..0000000 --- a/include/recomp_input.h +++ /dev/null @@ -1,317 +0,0 @@ -#ifndef __RECOMP_INPUT_H__ -#define __RECOMP_INPUT_H__ - -#include -#include -#include -#include -#include -#include -#include - -#include "ultramodern/input.hpp" - -#include "json/json.hpp" - -#include "SDL.h" -#include "chrono" - -namespace recomp { - // 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. - // TODO refactor this to allow projects to rename these, or get rid of the readable name and leave that up to individual projects to map. - #define DEFINE_N64_BUTTON_INPUTS() \ - DEFINE_INPUT(A, 0x8000, "A") \ - DEFINE_INPUT(B, 0x4000, "B") \ - DEFINE_INPUT(Z, 0x2000, "Z") \ - DEFINE_INPUT(START, 0x1000, "Start") \ - DEFINE_INPUT(L, 0x0020, "L") \ - DEFINE_INPUT(R, 0x0010, "R") \ - DEFINE_INPUT(C_UP, 0x0008, "C Up") \ - DEFINE_INPUT(C_LEFT, 0x0002, "C Left") \ - DEFINE_INPUT(C_DOWN, 0x0004, "C Down") \ - DEFINE_INPUT(C_RIGHT, 0x0001, "C Right") \ - DEFINE_INPUT(DPAD_UP, 0x0800, "D Pad Down") \ - DEFINE_INPUT(DPAD_RIGHT, 0x0100, "D-Pad Down") \ - DEFINE_INPUT(DPAD_DOWN, 0x0400, "D-Pad Down") \ - DEFINE_INPUT(DPAD_LEFT, 0x0200, "D-Pad Down") - - #define DEFINE_N64_AXIS_INPUTS() \ - DEFINE_INPUT(Y_AXIS_POS, 0, "Up") \ - DEFINE_INPUT(Y_AXIS_NEG, 0, "Down") \ - DEFINE_INPUT(X_AXIS_NEG, 0, "Left") \ - DEFINE_INPUT(X_AXIS_POS, 0, "Right") \ - - #define DEFINE_RECOMP_UI_INPUTS() \ - DEFINE_INPUT(TOGGLE_MENU, 0, "Toggle Menu") \ - DEFINE_INPUT(ACCEPT_MENU, 0, "Accept (Menu)") \ - DEFINE_INPUT(BACK_MENU, 0, "Back (Menu)") \ - DEFINE_INPUT(APPLY_MENU, 0, "Apply (Menu)") \ - DEFINE_INPUT(TAB_LEFT_MENU, 0, "Tab Left (Menu)") \ - DEFINE_INPUT(TAB_RIGHT_MENU, 0, "Tab Right (Menu)") - - #define DEFINE_ALL_INPUTS() \ - DEFINE_N64_BUTTON_INPUTS() \ - DEFINE_N64_AXIS_INPUTS() \ - DEFINE_RECOMP_UI_INPUTS() - - // Enum containing every recomp input. - #define DEFINE_INPUT(name, value, readable) name, - enum class GameInput { - DEFINE_ALL_INPUTS() - - COUNT, - N64_BUTTON_START = A, - N64_BUTTON_COUNT = C_RIGHT - N64_BUTTON_START + 1, - N64_AXIS_START = X_AXIS_NEG, - N64_AXIS_COUNT = Y_AXIS_POS - N64_AXIS_START + 1, - }; - #undef DEFINE_INPUT - - // What type of source an input comes from (SDL_Scancode, SDL_GameControllerButton, SDL_GameControllerAxis, SDL_BUTTON, etc.) - 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 - }; - - // A single input. Combines the source of the input (see InputType) and a specific key/button/axis. - struct InputField { - InputType input_type; - // Represents a single source input. e.g. A keyboard's shift key, or a controller's R trigger - int32_t input_id; - std::string to_string() const; - auto operator<=>(const InputField& rhs) const = default; - }; - - void poll_inputs(); - float get_input_analog(int controller_num, const InputField& field); - float get_input_analog(int controller_num, const std::span fields); - bool get_input_digital(int controller_num, const InputField& field); - bool get_input_digital(int controller_num, const std::span fields); - void get_gyro_deltas(float* x, float* y); - void get_mouse_deltas(float* x, float* y); - void get_right_analog(int controller_num, float* x, float* y); - - enum class InputDevice { - Controller, - Keyboard, - COUNT - }; - - NLOHMANN_JSON_SERIALIZE_ENUM(recomp::InputDevice, { - { recomp::InputDevice::Controller, "Controller" }, - { recomp::InputDevice::Keyboard, "Keyboard" }, - }); - - void config_menu_set_cont_or_kb(bool cont_interacted); - - struct DefaultN64Mappings { - std::vector a; - std::vector b; - std::vector l; - std::vector r; - std::vector z; - std::vector start; - - std::vector c_left; - std::vector c_right; - std::vector c_up; - std::vector c_down; - - std::vector dpad_left; - std::vector dpad_right; - std::vector dpad_up; - std::vector dpad_down; - - std::vector analog_left; - std::vector analog_right; - std::vector analog_up; - std::vector analog_down; - - std::vector toggle_menu; - std::vector accept_menu; - std::vector back_menu; - std::vector apply_menu; - std::vector tab_left_menu; - std::vector tab_right_menu; - }; - - inline const std::vector& get_default_mapping_for_input(const DefaultN64Mappings& defaults, const GameInput input) { - static const std::vector empty_input_field{}; - switch (input) { - case GameInput::A: return defaults.a; - case GameInput::B: return defaults.b; - case GameInput::L: return defaults.l; - case GameInput::R: return defaults.r; - case GameInput::Z: return defaults.z; - case GameInput::START: return defaults.start; - case GameInput::C_LEFT: return defaults.c_left; - case GameInput::C_RIGHT: return defaults.c_right; - case GameInput::C_UP: return defaults.c_up; - case GameInput::C_DOWN: return defaults.c_down; - case GameInput::DPAD_LEFT: return defaults.dpad_left; - case GameInput::DPAD_RIGHT: return defaults.dpad_right; - case GameInput::DPAD_UP: return defaults.dpad_up; - case GameInput::DPAD_DOWN: return defaults.dpad_down; - case GameInput::X_AXIS_NEG: return defaults.analog_left; - case GameInput::X_AXIS_POS: return defaults.analog_right; - case GameInput::Y_AXIS_POS: return defaults.analog_up; - case GameInput::Y_AXIS_NEG: return defaults.analog_down; - case GameInput::TOGGLE_MENU: return defaults.toggle_menu; - case GameInput::ACCEPT_MENU: return defaults.accept_menu; - case GameInput::BACK_MENU: return defaults.back_menu; - case GameInput::APPLY_MENU: return defaults.apply_menu; - case GameInput::TAB_LEFT_MENU: return defaults.tab_left_menu; - case GameInput::TAB_RIGHT_MENU: return defaults.tab_right_menu; - default: return empty_input_field; - } - } - - extern const DefaultN64Mappings default_n64_keyboard_mappings; - extern const DefaultN64Mappings default_n64_controller_mappings; - - constexpr size_t bindings_per_input = 2; - - size_t get_num_inputs(); - const std::string& get_input_name(GameInput input); - const std::string& get_input_enum_name(GameInput input); - GameInput get_input_from_enum_name(const std::string_view name); - InputField& get_input_binding(int profile_index, GameInput input, size_t binding_index); - void set_input_binding(int profile_index, GameInput input, size_t binding_index, InputField value); - void clear_input_binding(int profile_index, GameInput input); - void reset_input_binding(int profile_index, InputDevice device, GameInput input); - void reset_profile_bindings(int profile_index, recomp::InputDevice device); - int add_input_profile(const std::string &key, const std::string &name, InputDevice device, bool custom); - int get_input_profile_by_key(const std::string &key); - const std::string &get_input_profile_key(int profile_index); - const std::string &get_input_profile_name(int profile_index); - InputDevice get_input_profile_device(int profile_index); - bool is_input_profile_custom(int profile_index); - int get_input_profile_count(); - const std::vector get_indices_for_custom_profiles(InputDevice device); - void set_input_profile_for_player(int player_index, int profile_index, InputDevice device); - int get_input_profile_for_player(int player_index, InputDevice device); - - struct ControllerGUID { - uint64_t hash; - std::string serial; - int vendor{}; - int product{}; - int version{}; - int crc16{}; - }; - - int add_controller(ControllerGUID guid, int profile_index); - const ControllerGUID &get_controller_guid(int controller_index); - int get_controller_profile_index(int controller_index); - int get_controller_by_guid(ControllerGUID guid); - int get_controller_count(); - ControllerGUID get_guid_from_sdl_controller(SDL_GameController* game_controller); - int get_controller_profile_index_from_sdl_controller(SDL_GameController* game_controller); - std::string get_string_from_controller_guid(ControllerGUID guid); - - void initialize_input_bindings(); - int get_sp_controller_profile_index(); - int get_sp_keyboard_profile_index(); - int get_mp_keyboard_profile_index(int player_index); - - bool get_n64_input(int player_index, uint16_t* buttons_out, float* x_out, float* y_out); - void set_rumble(int player_index, bool); - void update_rumble(); - void handle_events(); - - ultramodern::input::connected_device_info_t get_connected_device_info(int controller_num); - - // Rumble strength ranges from 0 to 100. - int get_rumble_strength(); - void set_rumble_strength(int strength); - - // Gyro and mouse sensitivities range from 0 to 100. - int get_gyro_sensitivity(); - int get_mouse_sensitivity(); - int get_joystick_deadzone(); - void set_gyro_sensitivity(int strength); - void set_mouse_sensitivity(int strength); - void set_joystick_deadzone(int strength); - void apply_joystick_deadzone(float x_in, float y_in, float* x_out, float* y_out); - void set_right_analog_suppressed(bool suppressed); - - enum class BackgroundInputMode { - On, - Off, - OptionCount - }; - - NLOHMANN_JSON_SERIALIZE_ENUM(recomp::BackgroundInputMode, { - {recomp::BackgroundInputMode::On, "On"}, - {recomp::BackgroundInputMode::Off, "Off"} - }); - - BackgroundInputMode get_background_input_mode(); - void set_background_input_mode(BackgroundInputMode mode); - - bool get_single_controller_mode(); - void set_single_controller_mode(bool single_controller); - - bool game_input_disabled(); - bool all_input_disabled(); - -} - -namespace recompinput { - struct BindingState { - bool active = false; - // Designates when binding has been cancelled or completed and the event queue should be purged/ignored. - bool skip_events = false; - int player_index = -1; - recomp::GameInput game_input = recomp::GameInput::COUNT; - int binding_index = -1; - recomp::InputField new_binding = {}; - recomp::InputDevice device = recomp::InputDevice::COUNT; - }; - - void start_scanning_for_binding(int player_index, recomp::GameInput game_input, int binding_index, recomp::InputDevice device); - void stop_scanning_for_binding(); - bool is_binding(); - - BindingState& get_binding_state(); - - int get_num_players(); - - struct AssignedPlayer { - SDL_GameController* controller = nullptr; - bool is_assigned = false; - bool keyboard_enabled = false; - std::chrono::high_resolution_clock::duration last_button_press_timestamp = std::chrono::high_resolution_clock::duration::zero(); - - AssignedPlayer() : controller(nullptr), is_assigned(false), keyboard_enabled(false), last_button_press_timestamp(std::chrono::high_resolution_clock::duration::zero()) {}; - }; - - constexpr size_t temp_max_players = 4; - - struct PlayerAssignmentState { - bool is_assigning = false; - int player_index = 0; - std::array temp_assigned_players; - - PlayerAssignmentState() : is_assigning(false), player_index(0), temp_assigned_players{} {}; - }; - - AssignedPlayer& get_assigned_player(int player_index, bool temp_player = false); - - bool get_player_is_assigned(int player_index); - recomp::InputDevice get_assigned_player_input_device(int player_index); - void start_player_assignment(void); - void stop_player_assignment(void); - void stop_player_assignment_and_close_modal(void); - void commit_player_assignment(void); - bool is_player_assignment_active(); - std::chrono::steady_clock::duration get_player_time_since_last_button_press(int player_index); - - bool does_player_have_controller(int player_index); -} - -#endif diff --git a/include/recomp_ui.h b/include/recomp_ui.h deleted file mode 100644 index f0246f8..0000000 --- a/include/recomp_ui.h +++ /dev/null @@ -1,175 +0,0 @@ -#ifndef __RECOMP_UI__ -#define __RECOMP_UI__ - -#include -#include -#include -#include - -// TODO move this file into src/ui - -#include "SDL.h" -#include "RmlUi/Core.h" -#include "recomp_input.h" - -#include "../src/ui/util/hsv.h" -#include "../src/ui/util/bem.h" -#include "../src/ui/elements/ui_button.h" -#include "../src/ui/elements/ui_theme.h" -#include "../src/ui/elements/ui_types.h" - -#include "../src/ui/core/ui_context.h" - -namespace Rml { - class ElementDocument; - class EventListenerInstancer; - class Context; - class Event; -} - -namespace recompui { - class UiEventListenerInstancer; - - // TODO remove this once the UI has been ported over to the new system. - class MenuController { - public: - virtual ~MenuController() {} - virtual void load_document() = 0; - virtual void register_events(UiEventListenerInstancer& listener) = 0; - virtual void make_bindings(Rml::Context* context) = 0; - }; - - std::unique_ptr create_launcher_menu(); - std::unique_ptr create_config_menu(); - - using event_handler_t = void(const std::string& param, Rml::Event&); - - void queue_event(const SDL_Event& event); - bool try_deque_event(SDL_Event& out); - - std::unique_ptr make_event_listener_instancer(); - void register_event(UiEventListenerInstancer& listener, const std::string& name, event_handler_t* handler); - - void show_context(ContextId context, std::string_view param); - void hide_context(ContextId context); - void hide_all_contexts(); - bool is_context_shown(ContextId context); - bool is_context_capturing_input(); - bool is_context_capturing_mouse(); - bool is_any_context_shown(); - ContextId try_close_current_context(); - - ContextId get_launcher_context_id(); - ContextId get_config_context_id(); - ContextId get_config_sub_menu_context_id(); - - enum class ConfigTabId { - General, - Controls, - Graphics, - Sound, - Mods, - Debug, - }; - - void set_config_tab(ConfigTabId tab); - int config_tab_to_index(ConfigTabId tab); - Rml::ElementTabSet* get_config_tabset(); - Rml::Element* get_mod_tab(); - void set_config_tabset_mod_nav(); - void focus_mod_configure_button(); - - void init_styling(const std::filesystem::path& rcss_file); - void init_prompt_context(); - void open_choice_prompt( - const std::string& header_text, - const std::string& content_text, - const std::string& confirm_label_text, - const std::string& cancel_label_text, - std::function confirm_action, - std::function cancel_action, - ButtonStyle confirm_variant = ButtonStyle::Success, - ButtonStyle cancel_variant = ButtonStyle::Danger, - bool focus_on_cancel = true, - const std::string& return_element_id = "" - ); - void open_info_prompt( - const std::string& header_text, - const std::string& content_text, - const std::string& okay_label_text, - std::function okay_action, - ButtonStyle okay_variant = ButtonStyle::Danger, - const std::string& return_element_id = "" - ); - void open_notification( - const std::string& header_text, - const std::string& content_text, - const std::string& return_element_id = "" - ); - void close_prompt(); - bool is_prompt_open(); - void update_mod_list(bool scan_mods = true); - void process_game_started(); - - void apply_color_hack(); - void get_window_size(int& width, int& height); - void set_cursor_visible(bool visible); - void update_supported_options(); - void toggle_fullscreen(); - - bool get_cont_active(void); - void set_cont_active(bool active); - void activate_mouse(); - - void message_box(const char* msg); - - void set_render_hooks(); - - Rml::ElementPtr create_custom_element(Rml::Element* parent, std::string tag); - Rml::ElementDocument* load_document(const std::filesystem::path& path); - Rml::ElementDocument* create_empty_document(); - Rml::Element* get_child_by_tag(Rml::Element* parent, const std::string& tag); - - void queue_image_from_bytes_rgba32(const std::string &src, const std::vector &bytes, uint32_t width, uint32_t height); - void queue_image_from_bytes_file(const std::string &src, const std::vector &bytes); - void release_image(const std::string &src); - - void drop_files(const std::list &file_list); - void report_removed_element(Rml::Element* element); - - namespace menu_action_mapping { - struct key_map { - // End result menu action - const MenuAction action; - // Mapped input from controller - const recomp::GameInput input; - // SDL key code from controller -> keyboard - const int sdl; - // RML key identifier passed to rmlui - const Rml::Input::KeyIdentifier rml; - }; - - static const key_map accept = { MenuAction::Accept, recomp::GameInput::ACCEPT_MENU, SDLK_RETURN, Rml::Input::KI_RETURN }; - static const key_map apply = { MenuAction::Apply, recomp::GameInput::APPLY_MENU, SDLK_f, Rml::Input::KI_F }; - static const key_map back = { MenuAction::Back, recomp::GameInput::BACK_MENU, SDLK_F15, Rml::Input::KI_F15 }; - static const key_map toggle = { MenuAction::Toggle, recomp::GameInput::TOGGLE_MENU, SDLK_ESCAPE, Rml::Input::KI_ESCAPE }; - static const key_map tab_left = { MenuAction::TabLeft, recomp::GameInput::TAB_LEFT_MENU, SDLK_F16, Rml::Input::KI_F16 }; - static const key_map tab_right = { MenuAction::TabRight, recomp::GameInput::TAB_RIGHT_MENU, SDLK_F17, Rml::Input::KI_F17 }; - - static const std::unordered_map rml_key_to_action { - { accept.rml, accept }, - { apply.rml, apply }, - { back.rml, back }, - { toggle.rml, toggle }, - { tab_left.rml, tab_left }, - { tab_right.rml, tab_right } - }; - - MenuAction menu_action_from_rml_key(const Rml::Input::KeyIdentifier& key); - }; - - // Constant that represents an input that doesn't have a promptfont icon associated with it. - extern const std::string unknown_input; -} - -#endif diff --git a/lib/N64ModernRuntime b/lib/N64ModernRuntime index d46ed00..995cdf2 160000 --- a/lib/N64ModernRuntime +++ b/lib/N64ModernRuntime @@ -1 +1 @@ -Subproject commit d46ed007a56d6ad1d6c9d687889ffc9acab1eb19 +Subproject commit 995cdf2ad4bf10d988aa950ce016176a7eb4a1e1 diff --git a/src/game/config.cpp b/src/game/config.cpp index 5d58ff6..6eda8f0 100644 --- a/src/game/config.cpp +++ b/src/game/config.cpp @@ -1,10 +1,14 @@ #include "banjo_config.h" -#include "recomp_input.h" +#include "recompui/recompui.h" +#include "recompui/config.h" +#include "recompinput/recompinput.h" #include "banjo_sound.h" #include "banjo_render.h" #include "banjo_support.h" #include "ultramodern/config.hpp" #include "librecomp/files.hpp" +#include "librecomp/config.hpp" +#include "util/file.h" #include #include #include @@ -18,584 +22,99 @@ #include "apple/rt64_apple.h" #endif -constexpr std::u8string_view general_filename = u8"general.json"; -constexpr std::u8string_view graphics_filename = u8"graphics.json"; -constexpr std::u8string_view controls_filename = u8"controls.json"; -constexpr std::u8string_view sound_filename = u8"sound.json"; +static void add_general_options(recomp::config::Config &config) { + using EnumOptionVector = const std::vector; -constexpr auto res_default = ultramodern::renderer::Resolution::Auto; -constexpr auto hr_default = ultramodern::renderer::HUDRatioMode::Clamp16x9; -constexpr auto api_default = ultramodern::renderer::GraphicsApi::Auto; -constexpr auto ar_default = ultramodern::renderer::AspectRatio::Expand; -constexpr auto msaa_default = ultramodern::renderer::Antialiasing::MSAA2X; -constexpr auto rr_default = ultramodern::renderer::RefreshRate::Display; -constexpr auto hpfb_default = ultramodern::renderer::HighPrecisionFramebuffer::Off; -constexpr int ds_default = 1; -constexpr int rr_manual_default = 60; -constexpr bool developer_mode_default = false; + static EnumOptionVector camera_invert_mode_options = { + {banjo::CameraInvertMode::InvertNone, "InvertNone", "None"}, + {banjo::CameraInvertMode::InvertX, "InvertX", "Invert X"}, + {banjo::CameraInvertMode::InvertY, "InvertY", "Invert Y"}, + {banjo::CameraInvertMode::InvertBoth, "InvertBoth", "Invert Both"} + }; + config.add_enum_option( + banjo::configkeys::general::camera_invert_mode, + "Aiming Camera Mode", + // TODO: Update for banjo + "Inverts the camera controls. Invert Y is the default and matches the original game.", + camera_invert_mode_options, + banjo::CameraInvertMode::InvertY + ); -static bool is_steam_deck = false; - -ultramodern::renderer::WindowMode wm_default() { - return is_steam_deck ? ultramodern::renderer::WindowMode::Fullscreen : ultramodern::renderer::WindowMode::Windowed; -} - -#ifdef __gnu_linux__ -void detect_steam_deck() { - // Check if the board vendor is Valve. - std::ifstream board_vendor_file("/sys/devices/virtual/dmi/id/board_vendor"); - std::string line; - if (std::getline(board_vendor_file, line).good() && line == "Valve") { - is_steam_deck = true; - return; - } - - // Check if the SteamDeck variable is set to 1. - const char* steam_deck_env = getenv("SteamDeck"); - if (steam_deck_env != nullptr && std::string{steam_deck_env} == "1") { - is_steam_deck = true; - return; - } - - is_steam_deck = false; - return; -} -#else -void detect_steam_deck() { is_steam_deck = false; } -#endif - -template -T from_or_default(const json& j, const std::string& key, T default_value) { - T ret; - auto find_it = j.find(key); - if (find_it != j.end()) { - find_it->get_to(ret); - } - else { - ret = default_value; - } - - return ret; -} - -template -void call_if_key_exists(void (*func)(T), const json& j, const std::string& key) { - auto find_it = j.find(key); - if (find_it != j.end()) { - T val; - find_it->get_to(val); - func(val); - } -} - -namespace ultramodern { - void to_json(json& j, const renderer::GraphicsConfig& config) { - j = json{ - {"res_option", config.res_option}, - {"wm_option", config.wm_option}, - {"hr_option", config.hr_option}, - {"api_option", config.api_option}, - {"ds_option", config.ds_option}, - {"ar_option", config.ar_option}, - {"msaa_option", config.msaa_option}, - {"rr_option", config.rr_option}, - {"hpfb_option", config.hpfb_option}, - {"rr_manual_value", config.rr_manual_value}, - {"developer_mode", config.developer_mode}, - }; - } - - void from_json(const json& j, renderer::GraphicsConfig& config) { - config.res_option = from_or_default(j, "res_option", res_default); - config.wm_option = from_or_default(j, "wm_option", wm_default()); - config.hr_option = from_or_default(j, "hr_option", hr_default); - config.api_option = from_or_default(j, "api_option", api_default); - config.ds_option = from_or_default(j, "ds_option", ds_default); - config.ar_option = from_or_default(j, "ar_option", ar_default); - config.msaa_option = from_or_default(j, "msaa_option", msaa_default); - config.rr_option = from_or_default(j, "rr_option", rr_default); - config.hpfb_option = from_or_default(j, "hpfb_option", hpfb_default); - config.rr_manual_value = from_or_default(j, "rr_manual_value", rr_manual_default); - config.developer_mode = from_or_default(j, "developer_mode", developer_mode_default); - } -} - -namespace recomp { - void to_json(json& j, const InputField& field) { - j = json{ {"input_type", field.input_type}, {"input_id", field.input_id} }; - } - - void from_json(const json& j, InputField& field) { - j.at("input_type").get_to(field.input_type); - j.at("input_id").get_to(field.input_id); - } - - void to_json(json& j, const ControllerGUID& guid) { - j = json{ {"serial", guid.serial}, {"vendor", guid.vendor}, {"product", guid.product}, {"version", guid.version}, {"crc16", guid.crc16} }; - } - - void from_json(const json& j, ControllerGUID& guid) { - j.at("serial").get_to(guid.serial); - j.at("vendor").get_to(guid.vendor); - j.at("product").get_to(guid.product); - j.at("version").get_to(guid.version); - j.at("crc16").get_to(guid.crc16); - } -} - -std::filesystem::path banjo::get_app_folder_path() { - // directly check for portable.txt (windows and native linux binary) - if (std::filesystem::exists("portable.txt")) { - return std::filesystem::current_path(); - } - -#if defined(__APPLE__) - // Check for portable file in the directory containing the app bundle. - const auto app_bundle_path = banjo::get_bundle_directory().parent_path(); - if (std::filesystem::exists(app_bundle_path / "portable.txt")) { - return app_bundle_path; - } -#endif - - std::filesystem::path recomp_dir{}; - -#if defined(_WIN32) - // Deduce local app data path. - PWSTR known_path = NULL; - HRESULT result = SHGetKnownFolderPath(FOLDERID_LocalAppData, 0, NULL, &known_path); - if (result == S_OK) { - recomp_dir = std::filesystem::path{known_path} / banjo::program_id; - } - - CoTaskMemFree(known_path); -#elif defined(__linux__) || defined(__APPLE__) - // check for APP_FOLDER_PATH env var - if (getenv("APP_FOLDER_PATH") != nullptr) { - return std::filesystem::path{getenv("APP_FOLDER_PATH")}; - } - -#if defined(__APPLE__) - const auto supportdir = banjo::get_application_support_directory(); - if (supportdir) { - return *supportdir / banjo::program_id; - } -#endif - - const char *homedir; - - if ((homedir = getenv("HOME")) == nullptr) { - #if defined(__linux__) - homedir = getpwuid(getuid())->pw_dir; - #elif defined(__APPLE__) - homedir = GetHomeDirectory(); - #endif - } - - if (homedir != nullptr) { - recomp_dir = std::filesystem::path{homedir} / (std::u8string{u8".config/"} + std::u8string{banjo::program_id}); - } -#endif - - return recomp_dir; -} - -bool read_json(std::ifstream input_file, nlohmann::json& json_out) { - if (!input_file.good()) { - return false; - } - - try { - input_file >> json_out; - } - catch (nlohmann::json::parse_error&) { - return false; - } - return true; -} - -bool banjo::read_json_with_backups(const std::filesystem::path& path, nlohmann::json& json_out) { - // Try reading and parsing the base file. - if (read_json(std::ifstream{path}, json_out)) { - return true; - } - - // Try reading and parsing the backup file. - if (read_json(recomp::open_input_backup_file(path), json_out)) { - return true; - } - - // Both reads failed. - return false; -} - -bool banjo::save_json_with_backups(const std::filesystem::path& path, const nlohmann::json& json_data) { - { - std::ofstream output_file = recomp::open_output_file_with_backup(path); - if (!output_file.good()) { - return false; - } - - output_file << std::setw(4) << json_data; - } - return recomp::finalize_output_file_with_backup(path); -} - -bool save_general_config(const std::filesystem::path& path) { - nlohmann::json config_json{}; - - recomp::to_json(config_json["background_input_mode"], recomp::get_background_input_mode()); - config_json["rumble_strength"] = recomp::get_rumble_strength(); - config_json["gyro_sensitivity"] = recomp::get_gyro_sensitivity(); - config_json["mouse_sensitivity"] = recomp::get_mouse_sensitivity(); - config_json["joystick_deadzone"] = recomp::get_joystick_deadzone(); - config_json["camera_invert_mode"] = banjo::get_camera_invert_mode(); - config_json["analog_cam_mode"] = banjo::get_analog_cam_mode(); - config_json["analog_camera_invert_mode"] = banjo::get_analog_camera_invert_mode(); - config_json["debug_mode"] = banjo::get_debug_mode_enabled(); - - return banjo::save_json_with_backups(path, config_json); -} - -void set_general_settings_from_json(const nlohmann::json& config_json) { - recomp::set_background_input_mode(from_or_default(config_json, "background_input_mode", recomp::BackgroundInputMode::On)); - recomp::set_rumble_strength(from_or_default(config_json, "rumble_strength", 25)); - recomp::set_gyro_sensitivity(from_or_default(config_json, "gyro_sensitivity", 50)); - recomp::set_mouse_sensitivity(from_or_default(config_json, "mouse_sensitivity", is_steam_deck ? 50 : 0)); - recomp::set_joystick_deadzone(from_or_default(config_json, "joystick_deadzone", 5)); - banjo::set_camera_invert_mode(from_or_default(config_json, "camera_invert_mode", banjo::CameraInvertMode::InvertY)); - banjo::set_analog_cam_mode(from_or_default(config_json, "analog_cam_mode", banjo::AnalogCamMode::Off)); - banjo::set_analog_camera_invert_mode(from_or_default(config_json, "analog_camera_invert_mode", banjo::CameraInvertMode::InvertNone)); - banjo::set_debug_mode_enabled(from_or_default(config_json, "debug_mode", false)); -} - -bool load_general_config(const std::filesystem::path& path) { - nlohmann::json config_json{}; - if (!banjo::read_json_with_backups(path, config_json)) { - return false; - } - - set_general_settings_from_json(config_json); - return true; -} - -void assign_mapping(int profile_index, recomp::GameInput input, const std::vector& value) { - for (size_t binding_index = 0; binding_index < std::min(value.size(), recomp::bindings_per_input); binding_index++) { - recomp::set_input_binding(profile_index, input, binding_index, value[binding_index]); - } -}; - -// same as assign_mapping, except will clear unassigned bindings if not in value -void assign_mapping_complete(int profile_index, recomp::GameInput input, const std::vector& value) { - for (size_t binding_index = 0; binding_index < recomp::bindings_per_input; binding_index++) { - if (binding_index >= value.size()) { - recomp::set_input_binding(profile_index, input, binding_index, recomp::InputField{}); - } else { - recomp::set_input_binding(profile_index, input, binding_index, value[binding_index]); - } - } -}; - -void assign_all_mappings(int profile_index, const recomp::DefaultN64Mappings& values) { - assign_mapping_complete(profile_index, recomp::GameInput::A, values.a); - assign_mapping_complete(profile_index, recomp::GameInput::B, values.b); - assign_mapping_complete(profile_index, recomp::GameInput::Z, values.z); - assign_mapping_complete(profile_index, recomp::GameInput::START, values.start); - assign_mapping_complete(profile_index, recomp::GameInput::DPAD_UP, values.dpad_up); - assign_mapping_complete(profile_index, recomp::GameInput::DPAD_DOWN, values.dpad_down); - assign_mapping_complete(profile_index, recomp::GameInput::DPAD_LEFT, values.dpad_left); - assign_mapping_complete(profile_index, recomp::GameInput::DPAD_RIGHT, values.dpad_right); - assign_mapping_complete(profile_index, recomp::GameInput::L, values.l); - assign_mapping_complete(profile_index, recomp::GameInput::R, values.r); - assign_mapping_complete(profile_index, recomp::GameInput::C_UP, values.c_up); - assign_mapping_complete(profile_index, recomp::GameInput::C_DOWN, values.c_down); - assign_mapping_complete(profile_index, recomp::GameInput::C_LEFT, values.c_left); - assign_mapping_complete(profile_index, recomp::GameInput::C_RIGHT, values.c_right); - - assign_mapping_complete(profile_index, recomp::GameInput::X_AXIS_NEG, values.analog_left); - assign_mapping_complete(profile_index, recomp::GameInput::X_AXIS_POS, values.analog_right); - assign_mapping_complete(profile_index, recomp::GameInput::Y_AXIS_NEG, values.analog_down); - assign_mapping_complete(profile_index, recomp::GameInput::Y_AXIS_POS, values.analog_up); - - assign_mapping_complete(profile_index, recomp::GameInput::TOGGLE_MENU, values.toggle_menu); - assign_mapping_complete(profile_index, recomp::GameInput::ACCEPT_MENU, values.accept_menu); - assign_mapping_complete(profile_index, recomp::GameInput::BACK_MENU, values.back_menu); - assign_mapping_complete(profile_index, recomp::GameInput::APPLY_MENU, values.apply_menu); - assign_mapping_complete(profile_index, recomp::GameInput::TAB_LEFT_MENU, values.tab_left_menu); - assign_mapping_complete(profile_index, recomp::GameInput::TAB_RIGHT_MENU, values.tab_right_menu); -}; - -void banjo::reset_input_bindings() { - assign_all_mappings(recomp::get_sp_keyboard_profile_index(), recomp::default_n64_keyboard_mappings); - assign_all_mappings(recomp::get_sp_controller_profile_index(), recomp::default_n64_controller_mappings); -} - -void banjo::reset_cont_input_bindings(int profile_index) { - assign_all_mappings(profile_index, recomp::default_n64_controller_mappings); -} - -void banjo::reset_kb_input_bindings(int profile_index) { - assign_all_mappings(profile_index, recomp::default_n64_keyboard_mappings); -} - -void banjo::reset_single_input_binding(int profile_index, recomp::InputDevice device, recomp::GameInput input) { - assign_mapping_complete( - profile_index, - input, - recomp::get_default_mapping_for_input( - device == recomp::InputDevice::Keyboard ? - recomp::default_n64_keyboard_mappings : - recomp::default_n64_controller_mappings, - input - ) + config.add_bool_option( + banjo::configkeys::general::analog_cam_mode, + "Analog Camera", + // TODO: Update for banjo + "Enables the analog camera.", + false + ); + config.add_enum_option( + banjo::configkeys::general::analog_camera_invert_mode, + "Analog Camera Mode", + // TODO: Update for banjo + "Inverts the camera controls for the analog camera if it's enabled. None is the default.", + camera_invert_mode_options, + banjo::CameraInvertMode::InvertNone + ); + config.add_option_disable_dependency( + banjo::configkeys::general::analog_camera_invert_mode, + banjo::configkeys::general::analog_cam_mode, + false ); } -void reset_graphics_options() { - ultramodern::renderer::GraphicsConfig new_config{}; - new_config.res_option = res_default; - new_config.wm_option = wm_default(); - new_config.hr_option = hr_default; - new_config.ds_option = ds_default; - new_config.ar_option = ar_default; - new_config.msaa_option = msaa_default; - new_config.rr_option = rr_default; - new_config.hpfb_option = hpfb_default; - new_config.rr_manual_value = rr_manual_default; - new_config.developer_mode = developer_mode_default; - ultramodern::renderer::set_graphics_config(new_config); +template +T get_general_config_enum_value(const std::string& option_id) { + return static_cast(std::get(recompui::config::get_general_config().get_option_value(option_id))); } -bool save_graphics_config(const std::filesystem::path& path) { - nlohmann::json config_json{}; - ultramodern::to_json(config_json, ultramodern::renderer::get_graphics_config()); - return banjo::save_json_with_backups(path, config_json); +banjo::CameraInvertMode banjo::get_camera_invert_mode() { + return get_general_config_enum_value(banjo::configkeys::general::camera_invert_mode); } -bool load_graphics_config(const std::filesystem::path& path) { - nlohmann::json config_json{}; - if (!banjo::read_json_with_backups(path, config_json)) { - return false; - } - - ultramodern::renderer::GraphicsConfig new_config{}; - ultramodern::from_json(config_json, new_config); - ultramodern::renderer::set_graphics_config(new_config); - return true; +banjo::CameraInvertMode banjo::get_analog_camera_invert_mode() { + return get_general_config_enum_value(banjo::configkeys::general::analog_camera_invert_mode); } -void add_input_bindings(nlohmann::json& out, int profile_index, recomp::GameInput input) { - const std::string& input_name = recomp::get_input_enum_name(input); - nlohmann::json& out_array = out[input_name]; - out_array = nlohmann::json::array(); - for (size_t binding_index = 0; binding_index < recomp::bindings_per_input; binding_index++) { - out_array[binding_index] = recomp::get_input_binding(profile_index, input, binding_index); - } -}; - -constexpr int controls_version = 3; - -bool save_controls_config(const std::filesystem::path& path) { - int profile_count = recomp::get_input_profile_count(); - int controller_count = recomp::get_controller_count(); - nlohmann::json config_json{}; - config_json["version"] = controls_version; - config_json["profiles"] = std::vector(profile_count); - config_json["controllers"] = std::vector(controller_count); - - nlohmann::json &profiles = config_json["profiles"]; - for (int i = 0; i < profile_count; i++) { - nlohmann::json &profile = profiles[i]; - profile["key"] = recomp::get_input_profile_key(i); - profile["name"] = recomp::get_input_profile_name(i); - profile["device"] = recomp::get_input_profile_device(i); - profile["custom"] = recomp::is_input_profile_custom(i); - profile["mappings"] = nlohmann::json(); - - for (int j = 0; j < (int)(recomp::GameInput::COUNT); j++) { - add_input_bindings(profile["mappings"], i, (recomp::GameInput)(j)); - } - } - - nlohmann::json &controllers = config_json["controllers"]; - for (int i = 0; i < controller_count; i++) { - nlohmann::json &controller = controllers[i]; - controller["guid"] = recomp::get_controller_guid(i); - controller["profile"] = recomp::get_input_profile_key(recomp::get_controller_profile_index(i)); - } - - return banjo::save_json_with_backups(path, config_json); +bool banjo::get_analog_cam_mode() { + return std::get(recompui::config::get_general_config().get_option_value(banjo::configkeys::general::analog_cam_mode)); } -bool load_input_device_from_json(const nlohmann::json& config_json, int profile_index, recomp::InputDevice device, const std::string& key) { - // Check if the json object for the given key exists. - auto find_it = config_json.find(key); - if (find_it == config_json.end()) { - return false; - } - - const nlohmann::json& mappings_json = *find_it; - - for (size_t i = 0; i < recomp::get_num_inputs(); i++) { - recomp::GameInput cur_input = static_cast(i); - const std::string& input_name = recomp::get_input_enum_name(cur_input); - - // Check if the json object for the given input exists and that it's an array. - auto find_input_it = mappings_json.find(input_name); - if (find_input_it == mappings_json.end() || !find_input_it->is_array()) { - assign_mapping( - profile_index, - cur_input, - recomp::get_default_mapping_for_input( - device == recomp::InputDevice::Keyboard ? - recomp::default_n64_keyboard_mappings : - recomp::default_n64_controller_mappings, - cur_input - ) - ); - continue; - } - const nlohmann::json& input_json = *find_input_it; - - // Deserialize all the bindings from the json array (up to the max number of bindings per input). - for (size_t binding_index = 0; binding_index < std::min(recomp::bindings_per_input, input_json.size()); binding_index++) { - recomp::InputField cur_field{}; - recomp::from_json(input_json[binding_index], cur_field); - recomp::set_input_binding(profile_index, cur_input, binding_index, cur_field); - } - } - - return true; +static void add_sound_options(recomp::config::Config &config) { + config.add_percent_number_option( + banjo::configkeys::sound::bgm_volume, + "Background Music Volume", + "Controls the overall volume of background music.", + 100.0f + ); +} +template +T get_sound_config_number_value(const std::string& option_id) { + return static_cast(std::get(recompui::config::get_sound_config().get_option_value(option_id))); } -bool load_controls_config(const std::filesystem::path& path) { - nlohmann::json config_json{}; - if (!banjo::read_json_with_backups(path, config_json)) { - return false; - } - - auto version_it = config_json.find("version"); - if (version_it != config_json.end()) { - auto profiles = config_json.find("profiles"); - if (profiles == config_json.end() || !profiles->is_array()) { - return false; - } - - for (const nlohmann::json &profile : *profiles) { - std::string key = profile.value("key", std::string()); - std::string name = profile.value("name", std::string()); - recomp::InputDevice device = profile.value("device", recomp::InputDevice::COUNT); - bool custom = profile.value("custom", false); - if (!key.empty() && !name.empty() && device != recomp::InputDevice::COUNT) { - int profile_index = recomp::add_input_profile(key, name, device, custom); - if (!load_input_device_from_json(profile, profile_index, device, "mappings")) { - assign_all_mappings(profile_index, device == recomp::InputDevice::Keyboard ? recomp::default_n64_keyboard_mappings : recomp::default_n64_controller_mappings); - } - } - } - - auto controllers = config_json.find("controllers"); - if (controllers == config_json.end() || !controllers->is_array()) { - return false; - } - - for (const nlohmann::json &controller : *controllers) { - auto guid = controller.find("guid"); - auto profile = controller.find("profile"); - if (guid != controller.end() && guid->is_object() && profile != controller.end() && profile->is_string()) { - int profile_index = recomp::get_input_profile_by_key(*profile); - if (profile_index >= 0) { - recomp::add_controller(*guid, profile_index); - } - } - } - } - else { - // Version 1 of the format only had bindings for Player 1 on the root element. - if (!load_input_device_from_json(config_json, recomp::get_sp_keyboard_profile_index(), recomp::InputDevice::Keyboard, "keyboard")) { - assign_all_mappings(recomp::get_sp_keyboard_profile_index(), recomp::default_n64_keyboard_mappings); - } - - if (!load_input_device_from_json(config_json, recomp::get_sp_controller_profile_index(), recomp::InputDevice::Controller, "controller")) { - assign_all_mappings(recomp::get_sp_controller_profile_index(), recomp::default_n64_controller_mappings); - } - } - - return true; +int banjo::get_bgm_volume() { + return get_sound_config_number_value(banjo::configkeys::sound::bgm_volume); } -bool save_sound_config(const std::filesystem::path& path) { - nlohmann::json config_json{}; - - config_json["main_volume"] = banjo::get_main_volume(); - config_json["bgm_volume"] = banjo::get_bgm_volume(); - - return banjo::save_json_with_backups(path, config_json); -} - -bool load_sound_config(const std::filesystem::path& path) { - nlohmann::json config_json{}; - if (!banjo::read_json_with_backups(path, config_json)) { - return false; - } - - banjo::reset_sound_settings(); - call_if_key_exists(banjo::set_main_volume, config_json, "main_volume"); - call_if_key_exists(banjo::set_bgm_volume, config_json, "bgm_volume"); - return true; -} - -void banjo::load_config() { - detect_steam_deck(); - - std::filesystem::path recomp_dir = banjo::get_app_folder_path(); - std::filesystem::path general_path = recomp_dir / general_filename; - std::filesystem::path graphics_path = recomp_dir / graphics_filename; - std::filesystem::path controls_path = recomp_dir / controls_filename; - std::filesystem::path sound_path = recomp_dir / sound_filename; +void banjo::init_config() { + std::filesystem::path recomp_dir = recompui::file::get_app_folder_path(); if (!recomp_dir.empty()) { std::filesystem::create_directories(recomp_dir); } - // TODO error handling for failing to save config files after resetting them. + auto &general_config = recompui::config::create_general_tab(); + add_general_options(general_config); - if (!load_general_config(general_path)) { - // Set the general settings from an empty json to use defaults. - set_general_settings_from_json({}); - save_general_config(general_path); - } + auto &graphics_config = recompui::config::create_graphics_tab(); - if (!load_graphics_config(graphics_path)) { - reset_graphics_options(); - save_graphics_config(graphics_path); - } + recompui::config::create_controls_tab(); - recomp::initialize_input_bindings(); + auto &sound_config = recompui::config::create_sound_tab(); + add_sound_options(sound_config); - if (!load_controls_config(controls_path)) { - banjo::reset_input_bindings(); - save_controls_config(controls_path); - } + recompui::config::create_mods_tab(); + + recompui::config::finalize(); - if (!load_sound_config(sound_path)) { - banjo::reset_sound_settings(); - save_sound_config(sound_path); - } -} - -void banjo::save_config() { - std::filesystem::path recomp_dir = banjo::get_app_folder_path(); - - if (recomp_dir.empty()) { - return; - } - - std::filesystem::create_directories(recomp_dir); - - // TODO error handling for failing to save config files. - - save_general_config(recomp_dir / general_filename); - save_graphics_config(recomp_dir / graphics_filename); - save_controls_config(recomp_dir / controls_filename); - save_sound_config(recomp_dir / sound_filename); } diff --git a/src/game/controls.cpp b/src/game/controls.cpp deleted file mode 100644 index 5b39b81..0000000 --- a/src/game/controls.cpp +++ /dev/null @@ -1,472 +0,0 @@ -#include - -#include "xxHash/xxh3.h" - -#include "librecomp/helpers.hpp" -#include "recomp_input.h" -#include "ultramodern/ultramodern.hpp" - -// Arrays that hold the mappings for every input for keyboard and controller respectively. -using input_mapping = std::array; -using input_mapping_array = std::array(recomp::GameInput::COUNT)>; - -struct InputProfile { - std::string key; - std::string name; - recomp::InputDevice device; - input_mapping_array mappings; - bool custom = false; -}; - -static std::vector input_profiles{}; -static std::array, 4> players_input_profile_indices{}; -static std::unordered_map input_profile_key_index_map{}; -static std::vector input_profile_custom_indices[2]{}; - -struct Controller { - recomp::ControllerGUID guid; - int profile_index; -}; - -static std::vector controllers; -static std::unordered_map controller_hash_index_map{}; - -static int keyboard_sp_profile_index = -1; -static int controller_sp_profile_index = -1; - -static const std::string keyboard_sp_profile_key = "keyboard_sp"; -static const std::string controller_sp_profile_key = "controller_sp"; -static const std::string keyboard_sp_profile_name = "Keyboard (SP)"; -static const std::string controller_sp_profile_name = "Controller (SP)"; - -static const std::string keyboard_mp_profile_key = "keyboard_mp_player_"; // + player index -static const std::string keyboard_mp_profile_name = "Keyboard "; // + "(player number)" - -// Make the button value array, which maps a button index to its bit field. -#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() -}; -#undef DEFINE_INPUT - -// Make the input enum name array. -#define DEFINE_INPUT(name, value, readable) #name, -static const std::vector input_enum_names = { - DEFINE_ALL_INPUTS() -}; -#undef DEFINE_INPUT - -size_t recomp::get_num_inputs() { - return (size_t)GameInput::COUNT; -} - -const std::string& recomp::get_input_name(GameInput input) { - return input_names.at(static_cast(input)); -} - -const std::string& recomp::get_input_enum_name(GameInput input) { - return input_enum_names.at(static_cast(input)); -} - -recomp::GameInput recomp::get_input_from_enum_name(const std::string_view enum_name) { - auto find_it = std::find(input_enum_names.begin(), input_enum_names.end(), enum_name); - if (find_it == input_enum_names.end()) { - return recomp::GameInput::COUNT; - } - - return static_cast(find_it - input_enum_names.begin()); -} - -// 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(int profile_index, GameInput input, size_t binding_index) { - input_mapping& cur_input_mapping = input_profiles[profile_index].mappings.at(static_cast(input)); - 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(int profile_index, recomp::GameInput input, size_t binding_index, recomp::InputField value) { - input_mapping& cur_input_mapping = input_profiles[profile_index].mappings.at(static_cast(input)); - if (binding_index < cur_input_mapping.size()) { - cur_input_mapping[binding_index] = value; - } -} - -void recomp::clear_input_binding(int profile_index, GameInput input) { - for (size_t binding_index = 0; binding_index < recomp::bindings_per_input; binding_index++) { - recomp::set_input_binding(profile_index, input, binding_index, recomp::InputField{}); - } -} - -void recomp::reset_input_binding(int profile_index, InputDevice device, GameInput input) { - std::vector new_mappings = recomp::get_default_mapping_for_input( - device == recomp::InputDevice::Keyboard ? - recomp::default_n64_keyboard_mappings : - recomp::default_n64_controller_mappings, - input - ); - for (size_t binding_index = 0; binding_index < recomp::bindings_per_input; binding_index++) { - if (binding_index >= new_mappings.size()) { - recomp::set_input_binding(profile_index, input, binding_index, recomp::InputField{}); - } else { - recomp::set_input_binding(profile_index, input, binding_index, new_mappings[binding_index]); - } - } -} - -void recomp::reset_profile_bindings(int profile_index, recomp::InputDevice device) { - const recomp::DefaultN64Mappings &defaults = (device == recomp::InputDevice::Keyboard) - ? recomp::default_n64_keyboard_mappings - : recomp::default_n64_controller_mappings; - - // multiplayer keyboard profiles just get cleared completely because of overlapping key bindings. - bool is_multiplayer_kb = false; - if (device == recomp::InputDevice::Keyboard) { - for (size_t i = 0; i < recompinput::get_num_players(); i++) { - if (recomp::get_mp_keyboard_profile_index(i) == profile_index) { - is_multiplayer_kb = true; - break; - } - } - } - - for (size_t i = 0; i < recomp::get_num_inputs(); i++) { - recomp::GameInput input = static_cast(i); - if (is_multiplayer_kb) { - recomp::clear_input_binding(profile_index, input); - continue; - } - - auto &new_mappings = recomp::get_default_mapping_for_input(defaults, input); - for (size_t binding_index = 0; binding_index < recomp::bindings_per_input; binding_index++) { - if (binding_index >= new_mappings.size()) { - recomp::set_input_binding(profile_index, input, binding_index, recomp::InputField{}); - } else { - recomp::set_input_binding(profile_index, input, binding_index, new_mappings[binding_index]); - } - } - } -} - -int recomp::add_input_profile(const std::string &key, const std::string &name, InputDevice device, bool custom) { - auto it = input_profile_key_index_map.find(key); - if (it != input_profile_key_index_map.end()) { - return it->second; - } - - int index = (int)(input_profiles.size()); - InputProfile profile; - profile.key = key; - profile.name = name; - profile.device = device; - profile.custom = custom; - input_profiles.emplace_back(profile); - input_profile_key_index_map.emplace(key, index); - - if (custom) { - switch (device) { - case InputDevice::Controller: - input_profile_custom_indices[0].emplace_back(index); - break; - case InputDevice::Keyboard: - input_profile_custom_indices[1].emplace_back(index); - break; - default: - assert(false && "Unknown input device."); - break; - } - } - - return index; -} - -int recomp::get_input_profile_by_key(const std::string &key) { - auto it = input_profile_key_index_map.find(key); - if (it != input_profile_key_index_map.end()) { - return it->second; - } - else { - return -1; - } -} - -const std::string &recomp::get_input_profile_key(int profile_index) { - return input_profiles[profile_index].key; -} - -const std::string &recomp::get_input_profile_name(int profile_index) { - return input_profiles[profile_index].name; -} - -recomp::InputDevice recomp::get_input_profile_device(int profile_index) { - return input_profiles[profile_index].device; -} - -bool recomp::is_input_profile_custom(int profile_index) { - return input_profiles[profile_index].custom; -} - -int recomp::get_input_profile_count() { - return (int)(input_profiles.size()); -} - -const std::vector recomp::get_indices_for_custom_profiles(InputDevice device) { - static std::vector empty; - - switch (device) { - case InputDevice::Controller: - return input_profile_custom_indices[0]; - case InputDevice::Keyboard: - return input_profile_custom_indices[1]; - default: - assert(false && "Unknown input device."); - return empty; - } -} - -void recomp::set_input_profile_for_player(int player_index, int profile_index, InputDevice device) { - switch (device) { - case InputDevice::Controller: - players_input_profile_indices[player_index].first = profile_index; - break; - case InputDevice::Keyboard: - players_input_profile_indices[player_index].second = profile_index; - break; - default: - assert(false && "Unknown input device."); - break; - } -} - -int recomp::get_input_profile_for_player(int player_index, InputDevice device) { - switch (device) { - case InputDevice::Controller: - return players_input_profile_indices[player_index].first; - case InputDevice::Keyboard: - return players_input_profile_indices[player_index].second; - default: - assert(false && "Unknown input device."); - return -1; - } -} - -int recomp::add_controller(ControllerGUID guid, int profile_index) { - auto it = controller_hash_index_map.find(guid.hash); - if (it != controller_hash_index_map.end()) { - controllers[it->second].profile_index = profile_index; - return it->second; - } - - int index = (int)(controllers.size()); - Controller controller; - controller.guid = guid; - controller.profile_index = profile_index; - controllers.emplace_back(controller); - controller_hash_index_map.emplace(guid.hash, index); - return index; -} - -const recomp::ControllerGUID &recomp::get_controller_guid(int controller_index) { - return controllers[controller_index].guid; -} - -int recomp::get_controller_profile_index(int controller_index) { - return controllers[controller_index].profile_index; -} - -int recomp::get_controller_by_guid(ControllerGUID guid) { - auto it = controller_hash_index_map.find(guid.hash); - if (it != controller_hash_index_map.end()) { - return it->second; - } - else { - return -1; - } -} - -int recomp::get_controller_count() { - return (int)(controllers.size()); -} - -recomp::ControllerGUID recomp::get_guid_from_sdl_controller(SDL_GameController *game_controller) { - if (game_controller == nullptr) { - return {}; - } - - SDL_Joystick *joystick = SDL_GameControllerGetJoystick(game_controller); - if (joystick == nullptr) { - return {}; - } - - Uint16 vendor, product, version, crc16; - const char *joystick_serial = SDL_JoystickGetSerial(joystick); - SDL_JoystickGUID joystick_guid = SDL_JoystickGetGUID(joystick); - SDL_GetJoystickGUIDInfo(joystick_guid, &vendor, &product, &version, &crc16); - - recomp::ControllerGUID guid; - guid.serial = joystick_serial != nullptr ? joystick_serial : ""; - guid.vendor = vendor; - guid.product = product; - guid.version = version; - guid.crc16 = crc16; - - // Compute the hash from the GUID. - XXH3_state_t state; - XXH3_64bits_reset(&state); - XXH3_64bits_update(&state, &guid.vendor, sizeof(guid.vendor)); - XXH3_64bits_update(&state, &guid.product, sizeof(guid.product)); - XXH3_64bits_update(&state, &guid.version, sizeof(guid.version)); - XXH3_64bits_update(&state, &guid.crc16, sizeof(guid.crc16)); - guid.hash = XXH3_64bits_digest(&state); - - return guid; -} - -int recomp::get_controller_profile_index_from_sdl_controller(SDL_GameController *game_controller) { - if (game_controller == nullptr) { - return -1; - } - - ControllerGUID guid = recomp::get_guid_from_sdl_controller(game_controller); - if (guid.hash == 0) { - return -1; - } - - int controller_index = recomp::get_controller_by_guid(guid); - if (controller_index < 0) { - return -1; - } - - return recomp::get_controller_profile_index(controller_index); -} - -std::string get_mp_keyboard_profile_key(int player_index) { - return keyboard_mp_profile_key + std::to_string(player_index); -} - -std::string get_mp_keyboard_profile_name(int player_index) { - return keyboard_mp_profile_name + "(Player " + std::to_string(player_index + 1) + ")"; -} - -void clear_mapping(int profile_index, recomp::GameInput input) { - for (size_t binding_index = 0; binding_index < recomp::bindings_per_input; binding_index++) { - recomp::set_input_binding(profile_index, input, binding_index, recomp::InputField{}); - } -}; - -// Used primarily for multiplayer keyboard input profiles -void clear_all_mappings(int profile_index) { - clear_mapping(profile_index, recomp::GameInput::A); - clear_mapping(profile_index, recomp::GameInput::B); - clear_mapping(profile_index, recomp::GameInput::Z); - clear_mapping(profile_index, recomp::GameInput::START); - clear_mapping(profile_index, recomp::GameInput::DPAD_UP); - clear_mapping(profile_index, recomp::GameInput::DPAD_DOWN); - clear_mapping(profile_index, recomp::GameInput::DPAD_LEFT); - clear_mapping(profile_index, recomp::GameInput::DPAD_RIGHT); - clear_mapping(profile_index, recomp::GameInput::L); - clear_mapping(profile_index, recomp::GameInput::R); - clear_mapping(profile_index, recomp::GameInput::C_UP); - clear_mapping(profile_index, recomp::GameInput::C_DOWN); - clear_mapping(profile_index, recomp::GameInput::C_LEFT); - clear_mapping(profile_index, recomp::GameInput::C_RIGHT); - - clear_mapping(profile_index, recomp::GameInput::X_AXIS_NEG); - clear_mapping(profile_index, recomp::GameInput::X_AXIS_POS); - clear_mapping(profile_index, recomp::GameInput::Y_AXIS_NEG); - clear_mapping(profile_index, recomp::GameInput::Y_AXIS_POS); - - clear_mapping(profile_index, recomp::GameInput::TOGGLE_MENU); - clear_mapping(profile_index, recomp::GameInput::ACCEPT_MENU); - clear_mapping(profile_index, recomp::GameInput::BACK_MENU); - clear_mapping(profile_index, recomp::GameInput::APPLY_MENU); - clear_mapping(profile_index, recomp::GameInput::TAB_LEFT_MENU); - clear_mapping(profile_index, recomp::GameInput::TAB_RIGHT_MENU); -}; - -void recomp::initialize_input_bindings() { - keyboard_sp_profile_index = recomp::add_input_profile(keyboard_sp_profile_key, keyboard_sp_profile_name, recomp::InputDevice::Keyboard, false); - controller_sp_profile_index = recomp::add_input_profile(controller_sp_profile_key, controller_sp_profile_name, recomp::InputDevice::Controller, false); - - // Set Player 1 to the SP profiles by default. - recomp::set_input_profile_for_player(0, keyboard_sp_profile_index, recomp::InputDevice::Keyboard); - recomp::set_input_profile_for_player(0, controller_sp_profile_index, recomp::InputDevice::Controller); - - for (int i = 0; i < recompinput::get_num_players(); i++) { - int profile_index = recomp::add_input_profile( - get_mp_keyboard_profile_key(i), - get_mp_keyboard_profile_name(i), - recomp::InputDevice::Keyboard, - false - ); - clear_all_mappings(profile_index); - } -} - -int recomp::get_sp_controller_profile_index() { - return controller_sp_profile_index; -} - -int recomp::get_sp_keyboard_profile_index() { - return keyboard_sp_profile_index; -} - -int recomp::get_mp_keyboard_profile_index(int player_index) { - return recomp::get_input_profile_by_key(get_mp_keyboard_profile_key(player_index)); -} - -std::string recomp::get_string_from_controller_guid(ControllerGUID guid) { - return "SERIAL_" + guid.serial + "_VID_" + std::to_string(guid.vendor) + "_PID_" + std::to_string(guid.product) +"_VERSION_" + std::to_string(guid.version) +"_CRC16_" + std::to_string(guid.crc16); -} - -bool recomp::get_n64_input(int player_index, uint16_t* buttons_out, float* x_out, float* y_out) { - uint16_t cur_buttons = 0; - float cur_x = 0.0f; - float cur_y = 0.0f; - if (!recomp::game_input_disabled()) { - auto check_buttons = [&](int profile_index) { - if (profile_index < 0) { - return; - } - - const input_mapping_array &mappings = input_profiles[profile_index].mappings; - for (size_t i = 0; i < n64_button_values.size(); i++) { - cur_buttons |= recomp::get_input_digital(player_index, mappings[(size_t)(GameInput::N64_BUTTON_START) + i]) ? n64_button_values[i] : 0; - } - }; - - auto check_joystick = [&](int profile_index) { - if (profile_index < 0) { - return; - } - - const input_mapping_array &mappings = input_profiles[profile_index].mappings; - cur_x += recomp::get_input_analog(player_index, mappings[(size_t)GameInput::X_AXIS_POS]) - recomp::get_input_analog(player_index, mappings[(size_t)GameInput::X_AXIS_NEG]); - cur_y += recomp::get_input_analog(player_index, mappings[(size_t)GameInput::Y_AXIS_POS]) - recomp::get_input_analog(player_index, mappings[(size_t)GameInput::Y_AXIS_NEG]); - }; - - check_buttons(players_input_profile_indices[player_index].first); - check_buttons(players_input_profile_indices[player_index].second); - - check_joystick(players_input_profile_indices[player_index].first); - recomp::apply_joystick_deadzone(cur_x, cur_y, &cur_x, &cur_y); - check_joystick(players_input_profile_indices[player_index].second); - } - - *buttons_out = cur_buttons; - *x_out = std::clamp(cur_x, -1.0f, 1.0f); - *y_out = std::clamp(cur_y, -1.0f, 1.0f); - - return true; -} diff --git a/src/game/input.cpp b/src/game/input.cpp deleted file mode 100644 index 89826a1..0000000 --- a/src/game/input.cpp +++ /dev/null @@ -1,1175 +0,0 @@ -#include -#include - -#include "ultramodern/ultramodern.hpp" -#include "recomp.h" -#include "recomp_input.h" -#include "banjo_config.h" -#include "recomp_ui.h" -#include "SDL.h" -#include "promptfont.h" -#include "GamepadMotion.hpp" -#include "../ui/ui_assign_players_modal.h" -#include "../ui/ui_config_page_controls_element.h" - -constexpr float axis_threshold = 0.5f; - -struct ControllerState { - SDL_GameController* controller; - std::array latest_accelerometer; - GamepadMotion motion; - uint32_t prev_gyro_timestamp; - ControllerState() : controller{}, latest_accelerometer{}, motion{}, prev_gyro_timestamp{} { - motion.Reset(); - motion.SetCalibrationMode(GamepadMotionHelpers::CalibrationMode::Stillness | GamepadMotionHelpers::CalibrationMode::SensorFusion); - }; -}; - -static struct { - const Uint8* keys = nullptr; - SDL_Keymod keymod = SDL_Keymod::KMOD_NONE; - int numkeys = 0; - std::atomic_int32_t mouse_wheel_pos = 0; - std::mutex controllers_mutex; - std::vector detected_controllers{}; - std::array assigned_controllers{}; // Only used when Multiplayer is enabled. - std::unordered_map controller_states; - bool single_controller = false; - - std::array rotation_delta{}; - std::array mouse_delta{}; - std::mutex pending_input_mutex; - std::array pending_rotation_delta{}; - std::array pending_mouse_delta{}; - - std::array cur_rumble{}; - std::array rumble_active{}; -} InputState; - -static struct { - std::list files_dropped; -} DropState; - -static recompinput::BindingState binding_state; -static recompinput::PlayerAssignmentState player_assignment_state{}; - -void recompinput::start_scanning_for_binding(int player_index, recomp::GameInput game_input, int binding_index, recomp::InputDevice device) { - binding_state.active = true; - binding_state.skip_events = false; - binding_state.player_index = player_index; - binding_state.game_input = game_input; - binding_state.binding_index = binding_index; - binding_state.device = device; -} - -void recompinput::stop_scanning_for_binding() { - binding_state = recompinput::BindingState{}; - binding_state.skip_events = true; -} - -bool recompinput::is_binding() { - return binding_state.active; -} - -bool is_controller_being_bound(SDL_JoystickID joystick_id) { - if (binding_state.device != recomp::InputDevice::Controller) { - return false; - } - - if (recompinput::get_num_players() == 1) { - return true; - } - - const auto& player = InputState.assigned_controllers[binding_state.player_index]; - if (player.controller != nullptr) { - SDL_JoystickID assigned_id = SDL_JoystickInstanceID(SDL_GameControllerGetJoystick(player.controller)); - if (assigned_id == joystick_id) { - return true; - } - } - - return false; -} - -recompinput::BindingState& recompinput::get_binding_state() { - return binding_state; -} - -int recompinput::get_num_players() { - return static_cast(InputState.assigned_controllers.size()); -} - -recompinput::AssignedPlayer& recompinput::get_assigned_player(int player_index, bool temp_player) { - if (temp_player) { - return player_assignment_state.temp_assigned_players[player_index]; - } else { - return InputState.assigned_controllers[player_index]; - } -} - -recomp::InputDevice recompinput::get_assigned_player_input_device(int player_index) { - if (player_index < 0 || player_index >= recompinput::get_num_players()) { - return recomp::InputDevice::COUNT; - } - - const auto& assigned_player = InputState.assigned_controllers[player_index]; - if (assigned_player.controller != nullptr) { - return recomp::InputDevice::Controller; - } else if (assigned_player.keyboard_enabled) { - return recomp::InputDevice::Keyboard; - } else { - return recomp::InputDevice::COUNT; - } -} - -bool recompinput::get_player_is_assigned(int player_index) { - if (player_index < 0 || player_index >= recompinput::get_num_players()) { - return false; - } - - return player_assignment_state.temp_assigned_players[player_index].is_assigned; -} - -void recompinput::start_player_assignment() { - player_assignment_state.is_assigning = true; - player_assignment_state.player_index = 0; - - for (auto& player : player_assignment_state.temp_assigned_players) { - player = AssignedPlayer{}; - } -} - -static bool queue_close_player_assignment_modal = false; - -void recompinput::stop_player_assignment() { - player_assignment_state.is_assigning = false; - player_assignment_state.player_index = -1; -} - -void recompinput::stop_player_assignment_and_close_modal() { - recompinput::stop_player_assignment(); - queue_close_player_assignment_modal = true; -} - -void recompinput::commit_player_assignment() { - recompinput::stop_player_assignment_and_close_modal(); - - for (int i = 0; i < recompinput::get_num_players(); i++) { - InputState.assigned_controllers[i] = player_assignment_state.temp_assigned_players[i]; - if (InputState.assigned_controllers[i].controller != nullptr) { - int cont_profile_index = recomp::get_controller_profile_index_from_sdl_controller(InputState.assigned_controllers[i].controller); - if (cont_profile_index >= 0) { - recomp::set_input_profile_for_player(i, cont_profile_index, InputDevice::Controller); - } - } else { - recomp::set_input_profile_for_player(i, recomp::get_mp_keyboard_profile_index(i), InputDevice::Keyboard); - } - } -} - -bool recompinput::is_player_assignment_active() { - return player_assignment_state.is_assigning; -} - -bool recompinput::does_player_have_controller(int player_index) { - if (player_index < 0 || player_index >= recompinput::get_num_players()) { - return false; - } - return player_assignment_state.temp_assigned_players[player_index].controller != nullptr; -} - -std::chrono::steady_clock::duration recompinput::get_player_time_since_last_button_press(int player_index) { - if (player_index < 0 || player_index >= recompinput::get_num_players()) { - return std::chrono::steady_clock::duration::zero(); - } - return ultramodern::time_since_start() - player_assignment_state.temp_assigned_players[player_index].last_button_press_timestamp; -} - -void process_player_assignment(SDL_Event* event) { - if (queue_close_player_assignment_modal) { - recompui::assign_players_modal->close(); - queue_close_player_assignment_modal = false; - if (recompui::controls_page != nullptr) { - recompui::controls_page->force_update(); - } - return; - } - - if (!recompinput::is_player_assignment_active()) { - return; - } - - switch (event->type) { - case SDL_EventType::SDL_KEYDOWN: { - SDL_KeyboardEvent* keyevent = &event->key; - - switch (keyevent->keysym.scancode) { - case SDL_Scancode::SDL_SCANCODE_ESCAPE: - // TODO: Restore previous assignment? - recompinput::stop_player_assignment(); - return; - case SDL_Scancode::SDL_SCANCODE_SPACE: - player_assignment_state.temp_assigned_players[player_assignment_state.player_index].is_assigned = true; - player_assignment_state.temp_assigned_players[player_assignment_state.player_index].keyboard_enabled = true; - player_assignment_state.player_index++; - printf("Assigned keyboard to player %d\n", player_assignment_state.player_index - 1); - break; - default: - for (int i = 0; i < player_assignment_state.player_index; i++) { - if (player_assignment_state.temp_assigned_players[i].keyboard_enabled && player_assignment_state.temp_assigned_players[i].controller == nullptr) { - player_assignment_state.temp_assigned_players[i].last_button_press_timestamp = ultramodern::time_since_start(); - } - } - break; - } - break; - } - case SDL_EventType::SDL_CONTROLLERBUTTONDOWN: { - SDL_ControllerButtonEvent* button_event = &event->cbutton; - SDL_JoystickID joystick_id = button_event->which; - auto controller_state = InputState.controller_states[joystick_id]; - - bool can_be_mapped = true; - for (int i = 0; i < player_assignment_state.player_index; i++) { - if (player_assignment_state.temp_assigned_players[i].controller == controller_state.controller) { - can_be_mapped = false; - player_assignment_state.temp_assigned_players[i].last_button_press_timestamp = ultramodern::time_since_start(); - break; - } - } - - if (can_be_mapped) { - recompinput::AssignedPlayer& assigned_player = player_assignment_state.temp_assigned_players[player_assignment_state.player_index]; - assigned_player.is_assigned = true; - assigned_player.controller = controller_state.controller; - assigned_player.last_button_press_timestamp = ultramodern::time_since_start(); - player_assignment_state.player_index++; - printf("Assigned controller %d to player %d\n", joystick_id, player_assignment_state.player_index - 1); - } - - break; - } - } - - if (player_assignment_state.player_index >= recompinput::get_num_players()) { - recompinput::stop_player_assignment(); - } -} - -void set_scanned_input(recomp::InputField value) { - recomp::set_input_binding( - recomp::get_input_profile_for_player(binding_state.player_index, binding_state.device), - binding_state.game_input, - binding_state.binding_index, - value - ); - recompinput::stop_scanning_for_binding(); -} - -void queue_if_enabled(SDL_Event* event) { - if (!recomp::all_input_disabled() && !binding_state.skip_events) { - recompui::queue_event(*event); - } -} - -static std::atomic_bool cursor_enabled = true; - -void recompui::set_cursor_visible(bool visible) { - cursor_enabled.store(visible); -} - -bool should_override_keystate(SDL_Scancode key, SDL_Keymod mod) { - // Override Enter when Alt is held. - if (key == SDL_Scancode::SDL_SCANCODE_RETURN) { - if (mod & SDL_Keymod::KMOD_ALT) { - return true; - } - } - - return false; -} - -bool sdl_event_filter(void* userdata, SDL_Event* event) { - switch (event->type) { - case SDL_EventType::SDL_KEYDOWN: - { - SDL_KeyboardEvent* keyevent = &event->key; - - // Skip repeated events when not in the menu - if (!recompui::is_context_capturing_input() && - event->key.repeat) { - break; - } - - if ((keyevent->keysym.scancode == SDL_Scancode::SDL_SCANCODE_RETURN && (keyevent->keysym.mod & SDL_Keymod::KMOD_ALT)) || - keyevent->keysym.scancode == SDL_Scancode::SDL_SCANCODE_F11 - ) { - recompui::toggle_fullscreen(); - } - if (recompinput::is_binding()) { - if (keyevent->keysym.scancode == SDL_Scancode::SDL_SCANCODE_ESCAPE) { - recompinput::stop_scanning_for_binding(); - } - else if (binding_state.device == recomp::InputDevice::Keyboard) { - set_scanned_input({ recomp::InputType::Keyboard, keyevent->keysym.scancode }); - } - } - else { - if (!should_override_keystate(keyevent->keysym.scancode, static_cast(keyevent->keysym.mod))) { - queue_if_enabled(event); - } - } - } - break; - case SDL_EventType::SDL_CONTROLLERDEVICEADDED: - { - 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) { - printf(" Instance ID: %d\n", SDL_JoystickInstanceID(SDL_GameControllerGetJoystick(controller))); - printf(" Path: %s\n", SDL_JoystickPath(SDL_GameControllerGetJoystick(controller))); - ControllerState& state = InputState.controller_states[SDL_JoystickInstanceID(SDL_GameControllerGetJoystick(controller))]; - state.controller = controller; - - if (SDL_GameControllerHasSensor(controller, SDL_SensorType::SDL_SENSOR_GYRO) && SDL_GameControllerHasSensor(controller, SDL_SensorType::SDL_SENSOR_ACCEL)) { - SDL_GameControllerSetSensorEnabled(controller, SDL_SensorType::SDL_SENSOR_GYRO, SDL_TRUE); - SDL_GameControllerSetSensorEnabled(controller, SDL_SensorType::SDL_SENSOR_ACCEL, SDL_TRUE); - } - } - - recomp::ControllerGUID guid = recomp::get_guid_from_sdl_controller(controller); - if (recomp::get_controller_by_guid(guid) < 0) { - std::string default_profile_key = recomp::get_string_from_controller_guid(guid); - int profile_index = recomp::get_input_profile_by_key(default_profile_key); - if (profile_index < 0) { - profile_index = recomp::add_input_profile(default_profile_key, "Controller", recomp::InputDevice::Controller, false); - banjo::reset_cont_input_bindings(profile_index); - } - - recomp::add_controller(guid, profile_index); - } - } - break; - case SDL_EventType::SDL_CONTROLLERDEVICEREMOVED: - { - SDL_ControllerDeviceEvent* controller_event = &event->cdevice; - printf("Controller removed: %d\n", controller_event->which); - InputState.controller_states.erase(controller_event->which); - } - break; - case SDL_EventType::SDL_QUIT: { - if (!ultramodern::is_game_started()) { - ultramodern::quit(); - return true; - } - - banjo::open_quit_game_prompt(); - recompui::activate_mouse(); - break; - } - case SDL_EventType::SDL_MOUSEWHEEL: - { - SDL_MouseWheelEvent* wheel_event = &event->wheel; - InputState.mouse_wheel_pos.fetch_add(wheel_event->y * (wheel_event->direction == SDL_MOUSEWHEEL_FLIPPED ? -1 : 1)); - } - queue_if_enabled(event); - break; - case SDL_EventType::SDL_CONTROLLERBUTTONDOWN: - if (recompinput::is_binding() && is_controller_being_bound(event->cbutton.which)) { - // TODO: Needs the controller profile index. - auto menuToggleBinding0 = recomp::get_input_binding(0, recomp::GameInput::TOGGLE_MENU, 0); - auto menuToggleBinding1 = recomp::get_input_binding(0, recomp::GameInput::TOGGLE_MENU, 1); - // note - magic number: 0 is InputType::None - if ((menuToggleBinding0.input_type != recomp::InputType::None && event->cbutton.button == menuToggleBinding0.input_id) || - (menuToggleBinding1.input_type != recomp::InputType::None && event->cbutton.button == menuToggleBinding1.input_id)) { - recompinput::stop_scanning_for_binding(); - } - else if (binding_state.device == recomp::InputDevice::Controller) { - SDL_ControllerButtonEvent* button_event = &event->cbutton; - recomp::GameInput scanning_game_input = binding_state.game_input; - if ((scanning_game_input == recomp::GameInput::TOGGLE_MENU || - scanning_game_input == recomp::GameInput::ACCEPT_MENU || - scanning_game_input == recomp::GameInput::APPLY_MENU) && ( - button_event->button == SDL_GameControllerButton::SDL_CONTROLLER_BUTTON_DPAD_UP || - button_event->button == SDL_GameControllerButton::SDL_CONTROLLER_BUTTON_DPAD_DOWN || - button_event->button == SDL_GameControllerButton::SDL_CONTROLLER_BUTTON_DPAD_LEFT || - button_event->button == SDL_GameControllerButton::SDL_CONTROLLER_BUTTON_DPAD_RIGHT)) { - break; - } - - set_scanned_input({ recomp::InputType::ControllerDigital, button_event->button }); - } - } - else { - queue_if_enabled(event); - } - break; - case SDL_EventType::SDL_CONTROLLERAXISMOTION: - if (is_controller_being_bound(event->caxis.which)) { - recomp::GameInput scanning_game_input = binding_state.game_input; - if (scanning_game_input == recomp::GameInput::TOGGLE_MENU || - scanning_game_input == recomp::GameInput::ACCEPT_MENU || - scanning_game_input == recomp::GameInput::APPLY_MENU) { - break; - } - - SDL_ControllerAxisEvent* axis_event = &event->caxis; - float axis_value = axis_event->value * (1/32768.0f); - if (axis_value > axis_threshold) { - SDL_Event set_stick_return_event; - set_stick_return_event.type = SDL_USEREVENT; - set_stick_return_event.user.code = axis_event->axis; - set_stick_return_event.user.data1 = nullptr; - set_stick_return_event.user.data2 = nullptr; - recompui::queue_event(set_stick_return_event); - - set_scanned_input({ recomp::InputType::ControllerAnalog, axis_event->axis + 1 }); - } - else if (axis_value < -axis_threshold) { - SDL_Event set_stick_return_event; - set_stick_return_event.type = SDL_USEREVENT; - set_stick_return_event.user.code = axis_event->axis; - set_stick_return_event.user.data1 = nullptr; - set_stick_return_event.user.data2 = nullptr; - recompui::queue_event(set_stick_return_event); - - set_scanned_input({ recomp::InputType::ControllerAnalog, -axis_event->axis - 1 }); - } - } - else { - queue_if_enabled(event); - } - break; - case SDL_EventType::SDL_CONTROLLERSENSORUPDATE: - if (event->csensor.sensor == SDL_SensorType::SDL_SENSOR_ACCEL) { - // Convert acceleration to g's. - float x = event->csensor.data[0] / SDL_STANDARD_GRAVITY; - float y = event->csensor.data[1] / SDL_STANDARD_GRAVITY; - float z = event->csensor.data[2] / SDL_STANDARD_GRAVITY; - ControllerState& state = InputState.controller_states[event->csensor.which]; - state.latest_accelerometer[0] = x; - state.latest_accelerometer[1] = y; - state.latest_accelerometer[2] = z; - } - else if (event->csensor.sensor == SDL_SensorType::SDL_SENSOR_GYRO) { - // constexpr float gyro_threshold = 0.05f; - // Convert rotational velocity to degrees per second. - constexpr float rad_to_deg = 180.0f / M_PI; - float x = event->csensor.data[0] * rad_to_deg; - float y = event->csensor.data[1] * rad_to_deg; - float z = event->csensor.data[2] * rad_to_deg; - ControllerState& state = InputState.controller_states[event->csensor.which]; - uint64_t cur_timestamp = event->csensor.timestamp; - uint32_t delta_ms = cur_timestamp - state.prev_gyro_timestamp; - state.motion.ProcessMotion(x, y, z, state.latest_accelerometer[0], state.latest_accelerometer[1], state.latest_accelerometer[2], delta_ms * 0.001f); - state.prev_gyro_timestamp = cur_timestamp; - - float rot_x = 0.0f; - float rot_y = 0.0f; - state.motion.GetPlayerSpaceGyro(rot_x, rot_y); - - { - std::lock_guard lock{ InputState.pending_input_mutex }; - InputState.pending_rotation_delta[0] += rot_x; - InputState.pending_rotation_delta[1] += rot_y; - } - } - break; - case SDL_EventType::SDL_MOUSEMOTION: - if (!recomp::game_input_disabled()) { - SDL_MouseMotionEvent* motion_event = &event->motion; - std::lock_guard lock{ InputState.pending_input_mutex }; - InputState.pending_mouse_delta[0] += motion_event->xrel; - InputState.pending_mouse_delta[1] += motion_event->yrel; - } - queue_if_enabled(event); - break; - case SDL_EventType::SDL_DROPBEGIN: - DropState.files_dropped.clear(); - break; - case SDL_EventType::SDL_DROPFILE: - DropState.files_dropped.emplace_back(std::filesystem::path(std::u8string_view((const char8_t*)(event->drop.file)))); - SDL_free(event->drop.file); - break; - case SDL_EventType::SDL_DROPCOMPLETE: - recompui::drop_files(DropState.files_dropped); - break; - case SDL_EventType::SDL_CONTROLLERBUTTONUP: - // Always queue button up events to avoid missing them during binding. - recompui::queue_event(*event); - break; - default: - queue_if_enabled(event); - break; - } - process_player_assignment(event); - return false; -} - -void recomp::handle_events() { - SDL_Event cur_event; - static bool started = false; - static bool exited = false; - while (SDL_PollEvent(&cur_event) && !exited) { - exited = sdl_event_filter(nullptr, &cur_event); - - // Lock the cursor if all three conditions are true: mouse aiming is enabled, game input is not disabled, and the game has been started. - bool cursor_locked = (recomp::get_mouse_sensitivity() != 0) && !recomp::game_input_disabled() && ultramodern::is_game_started(); - - // Hide the cursor based on its enable state, but override visibility to false if the cursor is locked. - bool cursor_visible = cursor_enabled; - if (cursor_locked) { - cursor_visible = false; - } - - SDL_ShowCursor(cursor_visible ? SDL_ENABLE : SDL_DISABLE); - SDL_SetRelativeMouseMode(cursor_locked ? SDL_TRUE : SDL_FALSE); - } - - binding_state.skip_events = false; - - if (!started && ultramodern::is_game_started()) { - started = true; - recompui::process_game_started(); - } -} - -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; -constexpr SDL_GameControllerButton SDL_CONTROLLER_BUTTON_NORTH = SDL_CONTROLLER_BUTTON_Y; - -const recomp::DefaultN64Mappings recomp::default_n64_keyboard_mappings = { - .a = { - {.input_type = InputType::Keyboard, .input_id = SDL_SCANCODE_SPACE} - }, - .b = { - {.input_type = InputType::Keyboard, .input_id = SDL_SCANCODE_LSHIFT} - }, - .l = { - {.input_type = InputType::Keyboard, .input_id = SDL_SCANCODE_E} - }, - .r = { - {.input_type = InputType::Keyboard, .input_id = SDL_SCANCODE_R} - }, - .z = { - {.input_type = InputType::Keyboard, .input_id = SDL_SCANCODE_Q} - }, - .start = { - {.input_type = InputType::Keyboard, .input_id = SDL_SCANCODE_RETURN} - }, - .c_left = { - {.input_type = InputType::Keyboard, .input_id = SDL_SCANCODE_LEFT} - }, - .c_right = { - {.input_type = InputType::Keyboard, .input_id = SDL_SCANCODE_RIGHT} - }, - .c_up = { - {.input_type = InputType::Keyboard, .input_id = SDL_SCANCODE_UP} - }, - .c_down = { - {.input_type = InputType::Keyboard, .input_id = SDL_SCANCODE_DOWN} - }, - .dpad_left = { - {.input_type = InputType::Keyboard, .input_id = SDL_SCANCODE_J} - }, - .dpad_right = { - {.input_type = InputType::Keyboard, .input_id = SDL_SCANCODE_L} - }, - .dpad_up = { - {.input_type = InputType::Keyboard, .input_id = SDL_SCANCODE_I} - }, - .dpad_down = { - {.input_type = InputType::Keyboard, .input_id = SDL_SCANCODE_K} - }, - .analog_left = { - {.input_type = InputType::Keyboard, .input_id = SDL_SCANCODE_A} - }, - .analog_right = { - {.input_type = InputType::Keyboard, .input_id = SDL_SCANCODE_D} - }, - .analog_up = { - {.input_type = InputType::Keyboard, .input_id = SDL_SCANCODE_W} - }, - .analog_down = { - {.input_type = InputType::Keyboard, .input_id = SDL_SCANCODE_S} - }, - .toggle_menu = { - {.input_type = InputType::Keyboard, .input_id = SDL_SCANCODE_ESCAPE} - }, - .accept_menu = { - {.input_type = InputType::Keyboard, .input_id = SDL_SCANCODE_RETURN} - }, - .back_menu = { - {.input_type = InputType::Keyboard, .input_id = SDL_SCANCODE_F15} - }, - .apply_menu = { - {.input_type = InputType::Keyboard, .input_id = SDL_SCANCODE_F} - }, - .tab_left_menu = { - {.input_type = InputType::Keyboard, .input_id = SDL_SCANCODE_F16} - }, - .tab_right_menu = { - {.input_type = InputType::Keyboard, .input_id = SDL_SCANCODE_F17} - } -}; - -const recomp::DefaultN64Mappings recomp::default_n64_controller_mappings = { - .a = { - {.input_type = InputType::ControllerDigital, .input_id = SDL_CONTROLLER_BUTTON_SOUTH}, - }, - .b = { - {.input_type = InputType::ControllerDigital, .input_id = SDL_CONTROLLER_BUTTON_WEST}, - }, - .l = { - {.input_type = InputType::ControllerDigital, .input_id = SDL_CONTROLLER_BUTTON_LEFTSHOULDER}, - }, - .r = { - {.input_type = InputType::ControllerAnalog, .input_id = SDL_CONTROLLER_AXIS_TRIGGERRIGHT + 1}, - }, - .z = { - {.input_type = InputType::ControllerAnalog, .input_id = SDL_CONTROLLER_AXIS_TRIGGERLEFT + 1}, - }, - .start = { - {.input_type = InputType::ControllerDigital, .input_id = SDL_CONTROLLER_BUTTON_START}, - }, - .c_left = { - {.input_type = InputType::ControllerAnalog, .input_id = -(SDL_CONTROLLER_AXIS_RIGHTX + 1)}, - {.input_type = InputType::ControllerDigital, .input_id = SDL_CONTROLLER_BUTTON_NORTH}, - }, - .c_right = { - {.input_type = InputType::ControllerAnalog, .input_id = SDL_CONTROLLER_AXIS_RIGHTX + 1}, - {.input_type = InputType::ControllerDigital, .input_id = SDL_CONTROLLER_BUTTON_EAST}, - }, - .c_up = { - {.input_type = InputType::ControllerAnalog, .input_id = -(SDL_CONTROLLER_AXIS_RIGHTY + 1)}, - {.input_type = InputType::ControllerDigital, .input_id = SDL_CONTROLLER_BUTTON_RIGHTSTICK}, - }, - .c_down = { - {.input_type = InputType::ControllerAnalog, .input_id = SDL_CONTROLLER_AXIS_RIGHTY + 1}, - {.input_type = InputType::ControllerDigital, .input_id = SDL_CONTROLLER_BUTTON_RIGHTSHOULDER}, - }, - .dpad_left = { - {.input_type = InputType::ControllerDigital, .input_id = SDL_CONTROLLER_BUTTON_DPAD_LEFT}, - }, - .dpad_right = { - {.input_type = InputType::ControllerDigital, .input_id = SDL_CONTROLLER_BUTTON_DPAD_RIGHT}, - }, - .dpad_up = { - {.input_type = InputType::ControllerDigital, .input_id = SDL_CONTROLLER_BUTTON_DPAD_UP}, - }, - .dpad_down = { - {.input_type = InputType::ControllerDigital, .input_id = SDL_CONTROLLER_BUTTON_DPAD_DOWN}, - }, - .analog_left = { - {.input_type = InputType::ControllerAnalog, .input_id = -(SDL_CONTROLLER_AXIS_LEFTX + 1)}, - }, - .analog_right = { - {.input_type = InputType::ControllerAnalog, .input_id = SDL_CONTROLLER_AXIS_LEFTX + 1}, - }, - .analog_up = { - {.input_type = InputType::ControllerAnalog, .input_id = -(SDL_CONTROLLER_AXIS_LEFTY + 1)}, - }, - .analog_down = { - {.input_type = InputType::ControllerAnalog, .input_id = SDL_CONTROLLER_AXIS_LEFTY + 1}, - }, - .toggle_menu = { - {.input_type = InputType::ControllerDigital, .input_id = SDL_CONTROLLER_BUTTON_BACK}, - }, - .accept_menu = { - {.input_type = InputType::ControllerDigital, .input_id = SDL_CONTROLLER_BUTTON_SOUTH}, - }, - .back_menu = { - {.input_type = InputType::ControllerDigital, .input_id = SDL_CONTROLLER_BUTTON_WEST}, - }, - .apply_menu = { - {.input_type = InputType::ControllerDigital, .input_id = SDL_CONTROLLER_BUTTON_NORTH}, - {.input_type = InputType::ControllerDigital, .input_id = SDL_CONTROLLER_BUTTON_START} - }, - .tab_left_menu = { - {.input_type = InputType::ControllerDigital, .input_id = SDL_CONTROLLER_BUTTON_LEFTSHOULDER} - }, - .tab_right_menu = { - {.input_type = InputType::ControllerDigital, .input_id = SDL_CONTROLLER_BUTTON_RIGHTSHOULDER} - } -}; - -void recomp::poll_inputs() { - InputState.keys = SDL_GetKeyboardState(&InputState.numkeys); - InputState.keymod = SDL_GetModState(); - static bool first_poll = true; - - { - std::lock_guard lock{ InputState.controllers_mutex }; - InputState.detected_controllers.clear(); - - static std::vector free_controllers; - free_controllers.clear(); - - for (const auto& [id, state] : InputState.controller_states) { - (void)id; // Avoid unused variable warning. - SDL_GameController* controller = state.controller; - if (controller != nullptr) { - free_controllers.emplace_back(InputState.detected_controllers.size()); - InputState.detected_controllers.push_back(controller); - } - } - } - - // Read the deltas while resetting them to zero. - { - std::lock_guard lock{ InputState.pending_input_mutex }; - - InputState.rotation_delta = InputState.pending_rotation_delta; - InputState.pending_rotation_delta = { 0.0f, 0.0f }; - - InputState.mouse_delta = InputState.pending_mouse_delta; - InputState.pending_mouse_delta = { 0.0f, 0.0f }; - } -} - -void recomp::set_rumble(int controller_num, bool on) { - InputState.rumble_active[controller_num] = on; -} - -ultramodern::input::connected_device_info_t recomp::get_connected_device_info(int controller_num) { - switch (controller_num) { - case 0: - return ultramodern::input::connected_device_info_t{ - .connected_device = ultramodern::input::Device::Controller, - .connected_pak = ultramodern::input::Pak::RumblePak, - }; - } - - return ultramodern::input::connected_device_info_t{ - .connected_device = ultramodern::input::Device::None, - .connected_pak = ultramodern::input::Pak::None, - }; -} - -static float smoothstep(float from, float to, float amount) { - amount = (amount * amount) * (3.0f - 2.0f * amount); - return std::lerp(from, to, amount); -} - -// Update rumble to attempt to mimic the way n64 rumble ramps up and falls off -void recomp::update_rumble() { - for (size_t i = 0; i < InputState.cur_rumble.size(); i++) { - // Note: values are not accurate! just approximations based on feel - if (InputState.rumble_active[i]) { - InputState.cur_rumble[i] += 0.17f; - if (InputState.cur_rumble[i] > 1) InputState.cur_rumble[i] = 1; - } - else { - InputState.cur_rumble[i] *= 0.92f; - InputState.cur_rumble[i] -= 0.01f; - if (InputState.cur_rumble[i] < 0) InputState.cur_rumble[i] = 0; - } - float smooth_rumble = smoothstep(0, 1, InputState.cur_rumble[i]); - - uint16_t rumble_strength = smooth_rumble * (recomp::get_rumble_strength() * 0xFFFF / 100); - uint32_t duration = 1000000; // Dummy duration value that lasts long enough to matter as the game will reset rumble on its own. - { - std::lock_guard lock{ InputState.controllers_mutex }; - if (InputState.single_controller) { - for (const auto &controller : InputState.detected_controllers) { - SDL_GameControllerRumble(controller, 0, rumble_strength, duration); - } - } - else { - if (InputState.assigned_controllers[i].controller != nullptr) { - SDL_GameControllerRumble(InputState.assigned_controllers[i].controller, 0, rumble_strength, duration); - } - } - } - } -} - -bool controller_button_state(int controller_num, int32_t input_id) { - if (input_id >= 0 && input_id < SDL_GameControllerButton::SDL_CONTROLLER_BUTTON_MAX) { - SDL_GameControllerButton button = (SDL_GameControllerButton)input_id; - bool ret = false; - { - std::lock_guard lock{ InputState.controllers_mutex }; - if (InputState.single_controller) { - for (const auto &controller : InputState.detected_controllers) { - ret |= SDL_GameControllerGetButton(controller, button); - } - } - else { - if (InputState.assigned_controllers[controller_num].controller != nullptr) { - ret |= SDL_GameControllerGetButton(InputState.assigned_controllers[controller_num].controller, button); - } - } - } - - return ret; - } - return false; -} - -static std::atomic_bool right_analog_suppressed = false; - -float controller_axis_state(int controller_num, int32_t input_id, bool allow_suppression) { - if (abs(input_id) - 1 < SDL_GameControllerAxis::SDL_CONTROLLER_AXIS_MAX) { - SDL_GameControllerAxis axis = (SDL_GameControllerAxis)(abs(input_id) - 1); - bool negative_range = input_id < 0; - float ret = 0.0f; - - { - auto gather_axis_state = [&](SDL_GameController* controller) { - float cur_val = SDL_GameControllerGetAxis(controller, axis) * (1 / 32768.0f); - if (negative_range) { - cur_val = -cur_val; - } - - // Check if this input is a right analog axis and suppress it accordingly. - if (allow_suppression && right_analog_suppressed.load() && - (axis == SDL_GameControllerAxis::SDL_CONTROLLER_AXIS_RIGHTX || axis == SDL_GameControllerAxis::SDL_CONTROLLER_AXIS_RIGHTY)) { - cur_val = 0; - } - ret += std::clamp(cur_val, 0.0f, 1.0f); - }; - - std::lock_guard lock{ InputState.controllers_mutex }; - if (InputState.single_controller) { - for (SDL_GameController *controller : InputState.detected_controllers) { - gather_axis_state(controller); - } - } - else { - if (InputState.assigned_controllers[controller_num].controller != nullptr) { - gather_axis_state(InputState.assigned_controllers[controller_num].controller); - } - } - } - - return std::clamp(ret, 0.0f, 1.0f); - } - return false; -} - -float recomp::get_input_analog(int controller_num, const recomp::InputField& field) { - switch (field.input_type) { - case InputType::Keyboard: - if (InputState.keys && field.input_id >= 0 && field.input_id < InputState.numkeys) { - if (should_override_keystate(static_cast(field.input_id), InputState.keymod)) { - return 0.0f; - } - return InputState.keys[field.input_id] ? 1.0f : 0.0f; - } - return 0.0f; - case InputType::ControllerDigital: - return controller_button_state(controller_num, field.input_id) ? 1.0f : 0.0f; - case InputType::ControllerAnalog: - return controller_axis_state(controller_num, field.input_id, true); - case InputType::Mouse: - // TODO mouse support - return 0.0f; - case InputType::None: - return false; - } -} - -float recomp::get_input_analog(int controller_num, const std::span fields) { - float ret = 0.0f; - for (const auto& field : fields) { - ret += get_input_analog(controller_num, field); - } - return std::clamp(ret, 0.0f, 1.0f); -} - -bool recomp::get_input_digital(int controller_num, const recomp::InputField& field) { - switch (field.input_type) { - case InputType::Keyboard: - if (InputState.keys && field.input_id >= 0 && field.input_id < InputState.numkeys) { - if (should_override_keystate(static_cast(field.input_id), InputState.keymod)) { - return false; - } - return InputState.keys[field.input_id] != 0; - } - return false; - case InputType::ControllerDigital: - return controller_button_state(controller_num, field.input_id); - case InputType::ControllerAnalog: - // TODO adjustable threshold - return controller_axis_state(controller_num, field.input_id, true) >= axis_threshold; - case InputType::Mouse: - // TODO mouse support - return false; - case InputType::None: - return false; - } -} - -bool recomp::get_input_digital(int controller_num, const std::span fields) { - bool ret = 0; - for (const auto& field : fields) { - ret |= get_input_digital(controller_num, field); - } - return ret; -} - -void recomp::get_gyro_deltas(float* x, float* y) { - std::array cur_rotation_delta = InputState.rotation_delta; - float sensitivity = (float)recomp::get_gyro_sensitivity() / 100.0f; - *x = cur_rotation_delta[0] * sensitivity; - *y = cur_rotation_delta[1] * sensitivity; -} - -void recomp::get_mouse_deltas(float* x, float* y) { - std::array cur_mouse_delta = InputState.mouse_delta; - float sensitivity = (float)recomp::get_mouse_sensitivity() / 100.0f; - *x = cur_mouse_delta[0] * sensitivity; - *y = cur_mouse_delta[1] * sensitivity; -} - -void recomp::apply_joystick_deadzone(float x_in, float y_in, float* x_out, float* y_out) { - float joystick_deadzone = (float)recomp::get_joystick_deadzone() / 100.0f; - - if (fabsf(x_in) < joystick_deadzone) { - x_in = 0.0f; - } - else { - if (x_in > 0.0f) { - x_in -= joystick_deadzone; - } - else { - x_in += joystick_deadzone; - } - - x_in /= (1.0f - joystick_deadzone); - } - - if (fabsf(y_in) < joystick_deadzone) { - y_in = 0.0f; - } - else { - if (y_in > 0.0f) { - y_in -= joystick_deadzone; - } - else { - y_in += joystick_deadzone; - } - - y_in /= (1.0f - joystick_deadzone); - } - - *x_out = x_in; - *y_out = y_in; -} - -void recomp::get_right_analog(int controller_num, float* x, float* y) { - float x_val = - controller_axis_state(controller_num, (SDL_GameControllerAxis::SDL_CONTROLLER_AXIS_RIGHTX + 1), false) - - controller_axis_state(controller_num, -(SDL_GameControllerAxis::SDL_CONTROLLER_AXIS_RIGHTX + 1), false); - float y_val = - controller_axis_state(controller_num, (SDL_GameControllerAxis::SDL_CONTROLLER_AXIS_RIGHTY + 1), false) - - controller_axis_state(controller_num, -(SDL_GameControllerAxis::SDL_CONTROLLER_AXIS_RIGHTY + 1), false); - recomp::apply_joystick_deadzone(x_val, y_val, x, y); -} - -void recomp::set_right_analog_suppressed(bool suppressed) { - right_analog_suppressed.store(suppressed); -} - -bool recomp::game_input_disabled() { - // Disable input if any menu that blocks input is open. - return recompui::is_context_capturing_input(); -} - -bool recomp::all_input_disabled() { - // Disable all input if an input is being polled. - return - recompinput::is_player_assignment_active() || - recompinput::is_binding(); -} - -bool recomp::get_single_controller_mode() { - return InputState.single_controller; -} - -void recomp::set_single_controller_mode(bool single_controller) { - InputState.single_controller = single_controller; -} - -const std::string recompui::unknown_input = "UNKNOWN"; - -std::string controller_button_to_string(SDL_GameControllerButton button) { - switch (button) { - case SDL_GameControllerButton::SDL_CONTROLLER_BUTTON_A: - return PF_GAMEPAD_A; - case SDL_GameControllerButton::SDL_CONTROLLER_BUTTON_B: - return PF_GAMEPAD_B; - case SDL_GameControllerButton::SDL_CONTROLLER_BUTTON_X: - return PF_GAMEPAD_X; - case SDL_GameControllerButton::SDL_CONTROLLER_BUTTON_Y: - return PF_GAMEPAD_Y; - case SDL_GameControllerButton::SDL_CONTROLLER_BUTTON_BACK: - return PF_XBOX_VIEW; - case SDL_GameControllerButton::SDL_CONTROLLER_BUTTON_GUIDE: - return PF_GAMEPAD_HOME; - case SDL_GameControllerButton::SDL_CONTROLLER_BUTTON_START: - return PF_XBOX_MENU; - case SDL_GameControllerButton::SDL_CONTROLLER_BUTTON_LEFTSTICK: - return PF_ANALOG_L_CLICK; - case SDL_GameControllerButton::SDL_CONTROLLER_BUTTON_RIGHTSTICK: - return PF_ANALOG_R_CLICK; - case SDL_GameControllerButton::SDL_CONTROLLER_BUTTON_LEFTSHOULDER: - return PF_XBOX_LEFT_SHOULDER; - case SDL_GameControllerButton::SDL_CONTROLLER_BUTTON_RIGHTSHOULDER: - return PF_XBOX_RIGHT_SHOULDER; - case SDL_GameControllerButton::SDL_CONTROLLER_BUTTON_DPAD_UP: - return PF_DPAD_UP; - case SDL_GameControllerButton::SDL_CONTROLLER_BUTTON_DPAD_DOWN: - return PF_DPAD_DOWN; - case SDL_GameControllerButton::SDL_CONTROLLER_BUTTON_DPAD_LEFT: - return PF_DPAD_LEFT; - case SDL_GameControllerButton::SDL_CONTROLLER_BUTTON_DPAD_RIGHT: - return PF_DPAD_RIGHT; - case SDL_GameControllerButton::SDL_CONTROLLER_BUTTON_MISC1: - return PF_STEAM_OPTIONS; - case SDL_GameControllerButton::SDL_CONTROLLER_BUTTON_PADDLE1: - return PF_GAMEPAD_L4; - case SDL_GameControllerButton::SDL_CONTROLLER_BUTTON_PADDLE2: - return PF_GAMEPAD_R4; - case SDL_GameControllerButton::SDL_CONTROLLER_BUTTON_PADDLE3: - return PF_GAMEPAD_L5; - case SDL_GameControllerButton::SDL_CONTROLLER_BUTTON_PADDLE4: - return PF_GAMEPAD_R5; - case SDL_GameControllerButton::SDL_CONTROLLER_BUTTON_TOUCHPAD: - return PF_SONY_TOUCHPAD; - default: - return recompui::unknown_input; - } -} - -std::unordered_map scancode_codepoints{ - {SDL_SCANCODE_LEFT, PF_KEYBOARD_LEFT}, - // NOTE: UP and RIGHT are swapped with promptfont. - {SDL_SCANCODE_UP, PF_KEYBOARD_RIGHT}, - {SDL_SCANCODE_RIGHT, PF_KEYBOARD_UP}, - {SDL_SCANCODE_DOWN, PF_KEYBOARD_DOWN}, - {SDL_SCANCODE_A, PF_KEYBOARD_A}, - {SDL_SCANCODE_B, PF_KEYBOARD_B}, - {SDL_SCANCODE_C, PF_KEYBOARD_C}, - {SDL_SCANCODE_D, PF_KEYBOARD_D}, - {SDL_SCANCODE_E, PF_KEYBOARD_E}, - {SDL_SCANCODE_F, PF_KEYBOARD_F}, - {SDL_SCANCODE_G, PF_KEYBOARD_G}, - {SDL_SCANCODE_H, PF_KEYBOARD_H}, - {SDL_SCANCODE_I, PF_KEYBOARD_I}, - {SDL_SCANCODE_J, PF_KEYBOARD_J}, - {SDL_SCANCODE_K, PF_KEYBOARD_K}, - {SDL_SCANCODE_L, PF_KEYBOARD_L}, - {SDL_SCANCODE_M, PF_KEYBOARD_M}, - {SDL_SCANCODE_N, PF_KEYBOARD_N}, - {SDL_SCANCODE_O, PF_KEYBOARD_O}, - {SDL_SCANCODE_P, PF_KEYBOARD_P}, - {SDL_SCANCODE_Q, PF_KEYBOARD_Q}, - {SDL_SCANCODE_R, PF_KEYBOARD_R}, - {SDL_SCANCODE_S, PF_KEYBOARD_S}, - {SDL_SCANCODE_T, PF_KEYBOARD_T}, - {SDL_SCANCODE_U, PF_KEYBOARD_U}, - {SDL_SCANCODE_V, PF_KEYBOARD_V}, - {SDL_SCANCODE_W, PF_KEYBOARD_W}, - {SDL_SCANCODE_X, PF_KEYBOARD_X}, - {SDL_SCANCODE_Y, PF_KEYBOARD_Y}, - {SDL_SCANCODE_Z, PF_KEYBOARD_Z}, - {SDL_SCANCODE_0, PF_KEYBOARD_0}, - {SDL_SCANCODE_1, PF_KEYBOARD_1}, - {SDL_SCANCODE_2, PF_KEYBOARD_2}, - {SDL_SCANCODE_3, PF_KEYBOARD_3}, - {SDL_SCANCODE_4, PF_KEYBOARD_4}, - {SDL_SCANCODE_5, PF_KEYBOARD_5}, - {SDL_SCANCODE_6, PF_KEYBOARD_6}, - {SDL_SCANCODE_7, PF_KEYBOARD_7}, - {SDL_SCANCODE_8, PF_KEYBOARD_8}, - {SDL_SCANCODE_9, PF_KEYBOARD_9}, - {SDL_SCANCODE_ESCAPE, PF_KEYBOARD_ESCAPE}, - {SDL_SCANCODE_F1, PF_KEYBOARD_F1}, - {SDL_SCANCODE_F2, PF_KEYBOARD_F2}, - {SDL_SCANCODE_F3, PF_KEYBOARD_F3}, - {SDL_SCANCODE_F4, PF_KEYBOARD_F4}, - {SDL_SCANCODE_F5, PF_KEYBOARD_F5}, - {SDL_SCANCODE_F6, PF_KEYBOARD_F6}, - {SDL_SCANCODE_F7, PF_KEYBOARD_F7}, - {SDL_SCANCODE_F8, PF_KEYBOARD_F8}, - {SDL_SCANCODE_F9, PF_KEYBOARD_F9}, - {SDL_SCANCODE_F10, PF_KEYBOARD_F10}, - {SDL_SCANCODE_F11, PF_KEYBOARD_F11}, - {SDL_SCANCODE_F12, PF_KEYBOARD_F12}, - {SDL_SCANCODE_F13, "F13"}, - {SDL_SCANCODE_F14, "F14"}, - {SDL_SCANCODE_F15, "F15"}, - {SDL_SCANCODE_F16, "F16"}, - {SDL_SCANCODE_F17, "F17"}, - {SDL_SCANCODE_F18, "F18"}, - {SDL_SCANCODE_F19, "F19"}, - {SDL_SCANCODE_F20, "F20"}, - {SDL_SCANCODE_F21, "F21"}, - {SDL_SCANCODE_F22, "F22"}, - {SDL_SCANCODE_F23, "F23"}, - {SDL_SCANCODE_F24, "F24"}, - {SDL_SCANCODE_PRINTSCREEN, PF_KEYBOARD_PRINT_SCREEN}, - {SDL_SCANCODE_SCROLLLOCK, PF_KEYBOARD_SCROLL_LOCK}, - {SDL_SCANCODE_PAUSE, PF_KEYBOARD_PAUSE}, - {SDL_SCANCODE_INSERT, PF_KEYBOARD_INSERT}, - {SDL_SCANCODE_HOME, PF_KEYBOARD_HOME}, - {SDL_SCANCODE_PAGEUP, PF_KEYBOARD_PAGE_UP}, - {SDL_SCANCODE_DELETE, PF_KEYBOARD_DELETE}, - {SDL_SCANCODE_END, PF_KEYBOARD_END}, - {SDL_SCANCODE_PAGEDOWN, PF_KEYBOARD_PAGE_DOWN}, - {SDL_SCANCODE_SPACE, PF_KEYBOARD_SPACE}, - {SDL_SCANCODE_BACKSPACE, PF_KEYBOARD_BACKSPACE}, - {SDL_SCANCODE_TAB, PF_KEYBOARD_TAB}, - {SDL_SCANCODE_RETURN, PF_KEYBOARD_ENTER}, - {SDL_SCANCODE_CAPSLOCK, PF_KEYBOARD_CAPS}, - {SDL_SCANCODE_NUMLOCKCLEAR, PF_KEYBOARD_NUM_LOCK}, - {SDL_SCANCODE_LSHIFT, "L" PF_KEYBOARD_SHIFT}, - {SDL_SCANCODE_RSHIFT, "R" PF_KEYBOARD_SHIFT}, -}; - -std::string keyboard_input_to_string(SDL_Scancode key) { - if (scancode_codepoints.find(key) != scancode_codepoints.end()) { - return scancode_codepoints[key]; - } - return recompui::unknown_input; -} - -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 ? "\u2196" : "\u21DC"; - case SDL_GameControllerAxis::SDL_CONTROLLER_AXIS_TRIGGERRIGHT: - return positive ? "\u2197" : "\u21DD"; - default: - return "Axis " + std::to_string(actual_axis) + (positive ? '+' : '-'); - } -} - -std::string recomp::InputField::to_string() const { - switch (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); - case InputType::Keyboard: - return keyboard_input_to_string((SDL_Scancode)input_id); - default: - return recompui::unknown_input; - } -} diff --git a/src/game/recomp_actor_api.cpp b/src/game/recomp_actor_api.cpp index 8cbe5a6..71bfb99 100644 --- a/src/game/recomp_actor_api.cpp +++ b/src/game/recomp_actor_api.cpp @@ -6,7 +6,7 @@ #include "librecomp/helpers.hpp" #include "librecomp/addresses.hpp" #include "ultramodern/error_handling.hpp" -#include "recomp_ui.h" +#include "recompui/recompui.h" #include "recomp_data.h" #include "../patches/actor_funcs.h" diff --git a/src/game/recomp_api.cpp b/src/game/recomp_api.cpp index 1bc5952..3492ed3 100644 --- a/src/game/recomp_api.cpp +++ b/src/game/recomp_api.cpp @@ -4,8 +4,9 @@ #include "librecomp/overlays.hpp" #include "librecomp/addresses.hpp" #include "banjo_config.h" -#include "recomp_input.h" -#include "recomp_ui.h" +#include "recompinput/recompinput.h" +#include "recompui/recompui.h" +#include "recompui/renderer.h" #include "banjo_render.h" #include "banjo_sound.h" #include "librecomp/helpers.hpp" @@ -16,7 +17,7 @@ #include "ultramodern/config.hpp" extern "C" void recomp_update_inputs(uint8_t* rdram, recomp_context* ctx) { - recomp::poll_inputs(); + recompinput::poll_inputs(); } extern "C" void recomp_puts(uint8_t* rdram, recomp_context* ctx) { @@ -49,14 +50,15 @@ extern "C" void recomp_get_gyro_deltas(uint8_t* rdram, recomp_context* ctx) { float* x_out = _arg<0, float*>(rdram, ctx); float* y_out = _arg<1, float*>(rdram, ctx); - recomp::get_gyro_deltas(x_out, y_out); + // TODO: use controller number + recompinput::get_gyro_deltas(0, x_out, y_out); } extern "C" void recomp_get_mouse_deltas(uint8_t* rdram, recomp_context* ctx) { float* x_out = _arg<0, float*>(rdram, ctx); float* y_out = _arg<1, float*>(rdram, ctx); - recomp::get_mouse_deltas(x_out, y_out); + recompinput::get_mouse_deltas(x_out, y_out); } extern "C" void recomp_powf(uint8_t* rdram, recomp_context* ctx) { @@ -117,7 +119,7 @@ extern "C" void recomp_load_overlays(uint8_t * rdram, recomp_context * ctx) { } extern "C" void recomp_high_precision_fb_enabled(uint8_t * rdram, recomp_context * ctx) { - _return(ctx, static_cast(banjo::renderer::RT64HighPrecisionFBEnabled())); + _return(ctx, static_cast(recompui::renderer::RT64HighPrecisionFBEnabled())); } extern "C" void recomp_get_resolution_scale(uint8_t* rdram, recomp_context* ctx) { @@ -145,7 +147,7 @@ extern "C" void recomp_get_analog_inverted_axes(uint8_t* rdram, recomp_context* } extern "C" void recomp_get_analog_cam_enabled(uint8_t* rdram, recomp_context* ctx) { - _return(ctx, banjo::get_analog_cam_mode() == banjo::AnalogCamMode::On); + _return(ctx, banjo::get_analog_cam_mode()); } extern "C" void recomp_get_camera_inputs(uint8_t* rdram, recomp_context* ctx) { @@ -157,7 +159,8 @@ extern "C" void recomp_get_camera_inputs(uint8_t* rdram, recomp_context* ctx) { float x, y; - recomp::get_right_analog(0, &x, &y); + // TODO: use controller number + recompinput::get_right_analog(0, &x, &y); float magnitude = sqrtf(x * x + y * y); @@ -177,7 +180,7 @@ extern "C" void recomp_get_camera_inputs(uint8_t* rdram, recomp_context* ctx) { extern "C" void recomp_set_right_analog_suppressed(uint8_t* rdram, recomp_context* ctx) { s32 suppressed = _arg<0, s32>(rdram, ctx); - recomp::set_right_analog_suppressed(suppressed); + recompinput::set_right_analog_suppressed(suppressed); } // Function with typo in decomp diff --git a/src/game/recomp_data_api.cpp b/src/game/recomp_data_api.cpp index 2557abf..6aa5621 100644 --- a/src/game/recomp_data_api.cpp +++ b/src/game/recomp_data_api.cpp @@ -5,7 +5,7 @@ #include "slot_map.h" #include "recomp_data.h" -#include "recomp_ui.h" +#include "recompui/recompui.h" #include "librecomp/helpers.hpp" #include "librecomp/overlays.hpp" #include "librecomp/addresses.hpp" diff --git a/src/main/main.cpp b/src/main/main.cpp index deb157a..d0cef25 100644 --- a/src/main/main.cpp +++ b/src/main/main.cpp @@ -20,8 +20,14 @@ #include "SDL2/SDL_syswm.h" #endif -#include "recomp_ui.h" -#include "recomp_input.h" +#include "recompui/recompui.h" +#include "recompui/program_config.h" +#include "recompui/renderer.h" +#include "recompui/config.h" +#include "util/file.h" +#include "recompinput/input_events.h" +#include "recompinput/recompinput.h" +#include "recompinput/profiles.h" #include "banjo_config.h" #include "banjo_sound.h" #include "banjo_render.h" @@ -166,7 +172,7 @@ ultramodern::renderer::WindowHandle create_window(ultramodern::gfx_callbacks_t:: } void update_gfx(void*) { - recomp::handle_events(); + recompinput::handle_events(); } static SDL_AudioCVT audio_convert; @@ -208,7 +214,7 @@ void queue_samples(int16_t* audio_data, size_t sample_count) { // Convert the audio from 16-bit values to floats and swap the audio channels into the // swap buffer to correct for the address xor caused by endianness handling. - float cur_main_volume = banjo::get_main_volume() / 100.0f; // Get the current main volume, normalized to 0.0-1.0. + float cur_main_volume = static_cast(recompui::config::sound::get_main_volume()) / 100.0f; // Get the current main volume, normalized to 0.0-1.0. for (size_t i = 0; i < sample_count; i += input_channels) { swap_buffer[i + 0 + duplicated_input_frames * input_channels] = audio_data[i + 1] * (0.5f / 32768.0f) * cur_main_volume; swap_buffer[i + 1 + duplicated_input_frames * input_channels] = audio_data[i + 0] * (0.5f / 32768.0f) * cur_main_volume; @@ -511,15 +517,15 @@ void release_preload(PreloadContext& context) { #endif void enable_texture_pack(recomp::mods::ModContext& context, const recomp::mods::ModHandle& mod) { - banjo::renderer::enable_texture_pack(context, mod); + recompui::renderer::enable_texture_pack(context, mod); } void disable_texture_pack(recomp::mods::ModContext&, const recomp::mods::ModHandle& mod) { - banjo::renderer::disable_texture_pack(mod); + recompui::renderer::disable_texture_pack(mod); } void reorder_texture_pack(recomp::mods::ModContext&) { - banjo::renderer::trigger_texture_pack_update(); + recompui::renderer::trigger_texture_pack_update(); } #define REGISTER_FUNC(name) recomp::overlays::register_base_export(#name, name) @@ -579,12 +585,14 @@ int main(int argc, char** argv) { reset_audio(48000); // Source controller mappings file - std::u8string controller_db_path = (banjo::get_program_path() / "recompcontrollerdb.txt").u8string(); + std::u8string controller_db_path = (recompui::file::get_program_path() / "recompcontrollerdb.txt").u8string(); if (SDL_GameControllerAddMappingsFromFile(reinterpret_cast(controller_db_path.c_str())) < 0) { fprintf(stderr, "Failed to load controller mappings: %s\n", SDL_GetError()); } - recomp::register_config_path(banjo::get_app_folder_path()); + recompui::programconfig::set_program_name(banjo::program_name); + recompui::programconfig::set_program_id(banjo::program_id); + recomp::register_config_path(recompui::file::get_app_folder_path()); // Register supported games and patches for (const auto& game : supported_games) { @@ -608,14 +616,17 @@ int main(int argc, char** argv) { banjo::register_bk_overlays(); banjo::register_bk_patches(); recomputil::init_extended_actor_data(); - banjo::load_config(); + + recompinput::players::set_max_number_of_players(4); + + banjo::init_config(); recomp::rsp::callbacks_t rsp_callbacks{ .get_rsp_microcode = get_rsp_microcode, }; ultramodern::renderer::callbacks_t renderer_callbacks{ - .create_render_context = banjo::renderer::create_render_context, + .create_render_context = recompui::renderer::create_render_context, }; ultramodern::gfx_callbacks_t gfx_callbacks{ @@ -631,15 +642,15 @@ int main(int argc, char** argv) { }; ultramodern::input::callbacks_t input_callbacks{ - .poll_input = recomp::poll_inputs, - .get_input = recomp::get_n64_input, - .set_rumble = recomp::set_rumble, - .get_connected_device_info = recomp::get_connected_device_info, + .poll_input = recompinput::poll_inputs, + .get_input = recompinput::profiles::get_n64_input, + .set_rumble = recompinput::set_rumble, + .get_connected_device_info = recompinput::get_connected_device_info, }; ultramodern::events::callbacks_t thread_callbacks{ - .vi_callback = recomp::update_rumble, - .gfx_init_callback = recompui::update_supported_options, + .vi_callback = recompinput::update_rumble, + .gfx_init_callback = nullptr, }; ultramodern::error_handling::callbacks_t error_handling_callbacks{ @@ -663,9 +674,6 @@ int main(int argc, char** argv) { // Register the .rtz texture pack file format with the previous content type as its only allowed content type. recomp::mods::register_mod_container_type("rtz", std::vector{ texture_pack_content_type_id }, false); - // TODO: Where is it best to place this? - recomp::set_single_controller_mode(false); - recomp::start( project_version, {}, diff --git a/src/main/rt64_render_context.cpp b/src/main/rt64_render_context.cpp deleted file mode 100644 index 00d9f60..0000000 --- a/src/main/rt64_render_context.cpp +++ /dev/null @@ -1,548 +0,0 @@ -#include -#include -#include -#include - -#define HLSL_CPU -#include "hle/rt64_application.h" -#include "rt64_render_hooks.h" -#include "overloaded.h" - -#include "ultramodern/ultramodern.hpp" -#include "ultramodern/config.hpp" - -#include "banjo_render.h" -#include "recomp_ui.h" -#include "concurrentqueue.h" -#include "../ui/config/ui_config_tab_graphics.h" - -static RT64::UserConfiguration::Antialiasing device_max_msaa = RT64::UserConfiguration::Antialiasing::None; -static bool sample_positions_supported = false; -static bool high_precision_fb_enabled = false; - -static uint8_t DMEM[0x1000]; -static uint8_t IMEM[0x1000]; - -struct TexturePackEnableAction { - std::string mod_id; -}; - -struct TexturePackDisableAction { - std::string mod_id; -}; - -struct TexturePackSecondaryEnableAction { - std::string mod_id; -}; - -struct TexturePackSecondaryDisableAction { - std::string mod_id; -}; - -struct TexturePackUpdateAction { -}; - -using TexturePackAction = std::variant; - -static moodycamel::ConcurrentQueue texture_pack_action_queue; - -unsigned int MI_INTR_REG = 0; - -unsigned int DPC_START_REG = 0; -unsigned int DPC_END_REG = 0; -unsigned int DPC_CURRENT_REG = 0; -unsigned int DPC_STATUS_REG = 0; -unsigned int DPC_CLOCK_REG = 0; -unsigned int DPC_BUFBUSY_REG = 0; -unsigned int DPC_PIPEBUSY_REG = 0; -unsigned int DPC_TMEM_REG = 0; - -unsigned int VI_STATUS_REG = 0; -unsigned int VI_ORIGIN_REG = 0; -unsigned int VI_WIDTH_REG = 0; -unsigned int VI_INTR_REG = 0; -unsigned int VI_V_CURRENT_LINE_REG = 0; -unsigned int VI_TIMING_REG = 0; -unsigned int VI_V_SYNC_REG = 0; -unsigned int VI_H_SYNC_REG = 0; -unsigned int VI_LEAP_REG = 0; -unsigned int VI_H_START_REG = 0; -unsigned int VI_V_START_REG = 0; -unsigned int VI_V_BURST_REG = 0; -unsigned int VI_X_SCALE_REG = 0; -unsigned int VI_Y_SCALE_REG = 0; - -void dummy_check_interrupts() {} - -RT64::UserConfiguration::Antialiasing compute_max_supported_aa(RT64::RenderSampleCounts bits) { - if (bits & RT64::RenderSampleCount::Bits::COUNT_2) { - if (bits & RT64::RenderSampleCount::Bits::COUNT_4) { - if (bits & RT64::RenderSampleCount::Bits::COUNT_8) { - return RT64::UserConfiguration::Antialiasing::MSAA8X; - } - return RT64::UserConfiguration::Antialiasing::MSAA4X; - } - return RT64::UserConfiguration::Antialiasing::MSAA2X; - }; - return RT64::UserConfiguration::Antialiasing::None; -} - -RT64::UserConfiguration::AspectRatio to_rt64(ultramodern::renderer::AspectRatio option) { - switch (option) { - case ultramodern::renderer::AspectRatio::Original: - return RT64::UserConfiguration::AspectRatio::Original; - case ultramodern::renderer::AspectRatio::Expand: - return RT64::UserConfiguration::AspectRatio::Expand; - case ultramodern::renderer::AspectRatio::Manual: - return RT64::UserConfiguration::AspectRatio::Manual; - case ultramodern::renderer::AspectRatio::OptionCount: - return RT64::UserConfiguration::AspectRatio::OptionCount; - } -} - -RT64::UserConfiguration::Antialiasing to_rt64(ultramodern::renderer::Antialiasing option) { - switch (option) { - case ultramodern::renderer::Antialiasing::None: - return RT64::UserConfiguration::Antialiasing::None; - case ultramodern::renderer::Antialiasing::MSAA2X: - return RT64::UserConfiguration::Antialiasing::MSAA2X; - case ultramodern::renderer::Antialiasing::MSAA4X: - return RT64::UserConfiguration::Antialiasing::MSAA4X; - case ultramodern::renderer::Antialiasing::MSAA8X: - return RT64::UserConfiguration::Antialiasing::MSAA8X; - case ultramodern::renderer::Antialiasing::OptionCount: - return RT64::UserConfiguration::Antialiasing::OptionCount; - } -} - -RT64::UserConfiguration::RefreshRate to_rt64(ultramodern::renderer::RefreshRate option) { - switch (option) { - case ultramodern::renderer::RefreshRate::Original: - return RT64::UserConfiguration::RefreshRate::Original; - case ultramodern::renderer::RefreshRate::Display: - return RT64::UserConfiguration::RefreshRate::Display; - case ultramodern::renderer::RefreshRate::Manual: - return RT64::UserConfiguration::RefreshRate::Manual; - case ultramodern::renderer::RefreshRate::OptionCount: - return RT64::UserConfiguration::RefreshRate::OptionCount; - } -} - -RT64::UserConfiguration::InternalColorFormat to_rt64(ultramodern::renderer::HighPrecisionFramebuffer option) { - switch (option) { - case ultramodern::renderer::HighPrecisionFramebuffer::Off: - return RT64::UserConfiguration::InternalColorFormat::Standard; - case ultramodern::renderer::HighPrecisionFramebuffer::On: - return RT64::UserConfiguration::InternalColorFormat::High; - case ultramodern::renderer::HighPrecisionFramebuffer::Auto: - return RT64::UserConfiguration::InternalColorFormat::Automatic; - case ultramodern::renderer::HighPrecisionFramebuffer::OptionCount: - return RT64::UserConfiguration::InternalColorFormat::OptionCount; - } -} - -void set_application_user_config(RT64::Application* application, const ultramodern::renderer::GraphicsConfig& config) { - switch (config.res_option) { - default: - case ultramodern::renderer::Resolution::Auto: - application->userConfig.resolution = RT64::UserConfiguration::Resolution::WindowIntegerScale; - application->userConfig.downsampleMultiplier = 1; - break; - case ultramodern::renderer::Resolution::Original: - application->userConfig.resolution = RT64::UserConfiguration::Resolution::Manual; - application->userConfig.resolutionMultiplier = std::max(config.ds_option, 1); - application->userConfig.downsampleMultiplier = std::max(config.ds_option, 1); - break; - case ultramodern::renderer::Resolution::Original2x: - application->userConfig.resolution = RT64::UserConfiguration::Resolution::Manual; - application->userConfig.resolutionMultiplier = 2.0 * std::max(config.ds_option, 1); - application->userConfig.downsampleMultiplier = std::max(config.ds_option, 1); - break; - } - - switch (config.hr_option) { - default: - case ultramodern::renderer::HUDRatioMode::Original: - application->userConfig.extAspectRatio = RT64::UserConfiguration::AspectRatio::Original; - break; - case ultramodern::renderer::HUDRatioMode::Clamp16x9: - application->userConfig.extAspectRatio = RT64::UserConfiguration::AspectRatio::Manual; - application->userConfig.extAspectTarget = 16.0/9.0; - break; - case ultramodern::renderer::HUDRatioMode::Full: - application->userConfig.extAspectRatio = RT64::UserConfiguration::AspectRatio::Expand; - break; - } - - application->userConfig.aspectRatio = to_rt64(config.ar_option); - application->userConfig.antialiasing = to_rt64(config.msaa_option); - application->userConfig.refreshRate = to_rt64(config.rr_option); - application->userConfig.refreshRateTarget = config.rr_manual_value; - application->userConfig.internalColorFormat = to_rt64(config.hpfb_option); - application->userConfig.displayBuffering = RT64::UserConfiguration::DisplayBuffering::Triple; -} - -ultramodern::renderer::SetupResult map_setup_result(RT64::Application::SetupResult rt64_result) { - switch (rt64_result) { - case RT64::Application::SetupResult::Success: - return ultramodern::renderer::SetupResult::Success; - case RT64::Application::SetupResult::DynamicLibrariesNotFound: - return ultramodern::renderer::SetupResult::DynamicLibrariesNotFound; - case RT64::Application::SetupResult::InvalidGraphicsAPI: - return ultramodern::renderer::SetupResult::InvalidGraphicsAPI; - case RT64::Application::SetupResult::GraphicsAPINotFound: - return ultramodern::renderer::SetupResult::GraphicsAPINotFound; - case RT64::Application::SetupResult::GraphicsDeviceNotFound: - return ultramodern::renderer::SetupResult::GraphicsDeviceNotFound; - } - - fprintf(stderr, "Unhandled `RT64::Application::SetupResult` ?\n"); - assert(false); - std::exit(EXIT_FAILURE); -} - -ultramodern::renderer::GraphicsApi map_graphics_api(RT64::UserConfiguration::GraphicsAPI api) { - switch (api) { - case RT64::UserConfiguration::GraphicsAPI::D3D12: - return ultramodern::renderer::GraphicsApi::D3D12; - case RT64::UserConfiguration::GraphicsAPI::Vulkan: - return ultramodern::renderer::GraphicsApi::Vulkan; - case RT64::UserConfiguration::GraphicsAPI::Metal: - return ultramodern::renderer::GraphicsApi::Metal; - case RT64::UserConfiguration::GraphicsAPI::Automatic: - return ultramodern::renderer::GraphicsApi::Auto; - } - - fprintf(stderr, "Unhandled `RT64::UserConfiguration::GraphicsAPI` ?\n"); - assert(false); - std::exit(EXIT_FAILURE); -} - -banjo::renderer::RT64Context::RT64Context(uint8_t* rdram, ultramodern::renderer::WindowHandle window_handle, bool debug) { - static unsigned char dummy_rom_header[0x40]; - recompui::set_render_hooks(); - - // Set up the RT64 application core fields. - RT64::Application::Core appCore{}; -#if defined(_WIN32) - appCore.window = window_handle.window; -#elif defined(__linux__) || defined(__ANDROID__) - appCore.window = window_handle; -#elif defined(__APPLE__) - appCore.window.window = window_handle.window; - appCore.window.view = window_handle.view; -#endif - - appCore.checkInterrupts = dummy_check_interrupts; - - appCore.HEADER = dummy_rom_header; - appCore.RDRAM = rdram; - appCore.DMEM = DMEM; - appCore.IMEM = IMEM; - - appCore.MI_INTR_REG = &MI_INTR_REG; - - appCore.DPC_START_REG = &DPC_START_REG; - appCore.DPC_END_REG = &DPC_END_REG; - appCore.DPC_CURRENT_REG = &DPC_CURRENT_REG; - appCore.DPC_STATUS_REG = &DPC_STATUS_REG; - appCore.DPC_CLOCK_REG = &DPC_CLOCK_REG; - appCore.DPC_BUFBUSY_REG = &DPC_BUFBUSY_REG; - appCore.DPC_PIPEBUSY_REG = &DPC_PIPEBUSY_REG; - appCore.DPC_TMEM_REG = &DPC_TMEM_REG; - - appCore.VI_STATUS_REG = &VI_STATUS_REG; - appCore.VI_ORIGIN_REG = &VI_ORIGIN_REG; - appCore.VI_WIDTH_REG = &VI_WIDTH_REG; - appCore.VI_INTR_REG = &VI_INTR_REG; - appCore.VI_V_CURRENT_LINE_REG = &VI_V_CURRENT_LINE_REG; - appCore.VI_TIMING_REG = &VI_TIMING_REG; - appCore.VI_V_SYNC_REG = &VI_V_SYNC_REG; - appCore.VI_H_SYNC_REG = &VI_H_SYNC_REG; - appCore.VI_LEAP_REG = &VI_LEAP_REG; - appCore.VI_H_START_REG = &VI_H_START_REG; - appCore.VI_V_START_REG = &VI_V_START_REG; - appCore.VI_V_BURST_REG = &VI_V_BURST_REG; - appCore.VI_X_SCALE_REG = &VI_X_SCALE_REG; - appCore.VI_Y_SCALE_REG = &VI_Y_SCALE_REG; - - // Set up the RT64 application configuration fields. - RT64::ApplicationConfiguration appConfig; - appConfig.useConfigurationFile = false; - - // Create the RT64 application. - app = std::make_unique(appCore, appConfig); - - // Set initial user config settings based on the current settings. - auto& cur_config = ultramodern::renderer::get_graphics_config(); - set_application_user_config(app.get(), cur_config); - app->userConfig.developerMode = debug; - // Force gbi depth branches to prevent LODs from kicking in. - app->enhancementConfig.f3dex.forceBranch = true; - // Scale LODs based on the output resolution. - app->enhancementConfig.textureLOD.scale = true; - // Pick an API if the user has set an override. - switch (cur_config.api_option) { - case ultramodern::renderer::GraphicsApi::D3D12: - app->userConfig.graphicsAPI = RT64::UserConfiguration::GraphicsAPI::D3D12; - break; - case ultramodern::renderer::GraphicsApi::Vulkan: - app->userConfig.graphicsAPI = RT64::UserConfiguration::GraphicsAPI::Vulkan; - break; - case ultramodern::renderer::GraphicsApi::Metal: - app->userConfig.graphicsAPI = RT64::UserConfiguration::GraphicsAPI::Metal; - break; - case ultramodern::renderer::GraphicsApi::Auto: - app->userConfig.graphicsAPI = RT64::UserConfiguration::GraphicsAPI::Automatic; - break; - } - - // Set up the RT64 application. - uint32_t thread_id = 0; -#ifdef _WIN32 - thread_id = window_handle.thread_id; -#endif - setup_result = map_setup_result(app->setup(thread_id)); - // Get the API that RT64 chose. - chosen_api = map_graphics_api(app->chosenGraphicsAPI); - if (setup_result != ultramodern::renderer::SetupResult::Success) { - app = nullptr; - return; - } - - // Set the application's fullscreen state. - app->setFullScreen(cur_config.wm_option == ultramodern::renderer::WindowMode::Fullscreen); - - // Check if the selected device actually supports MSAA sample positions and MSAA for for the formats that will be used - // and downgrade the configuration accordingly. - if (app->device->getCapabilities().sampleLocations) { - RT64::RenderSampleCounts color_sample_counts = app->device->getSampleCountsSupported(RT64::RenderFormat::R8G8B8A8_UNORM); - RT64::RenderSampleCounts depth_sample_counts = app->device->getSampleCountsSupported(RT64::RenderFormat::D32_FLOAT); - RT64::RenderSampleCounts common_sample_counts = color_sample_counts & depth_sample_counts; - device_max_msaa = compute_max_supported_aa(common_sample_counts); - sample_positions_supported = true; - } - else { - device_max_msaa = RT64::UserConfiguration::Antialiasing::None; - sample_positions_supported = false; - } - - recompui::update_msaa_supported(sample_positions_supported); - - high_precision_fb_enabled = app->shaderLibrary->usesHDR; -} - -banjo::renderer::RT64Context::~RT64Context() = default; - -void banjo::renderer::RT64Context::send_dl(const OSTask* task) { - check_texture_pack_actions(); - app->state->rsp->reset(); - app->interpreter->loadUCodeGBI(task->t.ucode & 0x3FFFFFF, task->t.ucode_data & 0x3FFFFFF, true); - app->processDisplayLists(app->core.RDRAM, task->t.data_ptr & 0x3FFFFFF, 0, true); -} - -void banjo::renderer::RT64Context::update_screen(uint32_t vi_origin) { - VI_ORIGIN_REG = vi_origin; - - app->updateScreen(); -} - -void banjo::renderer::RT64Context::shutdown() { - if (app != nullptr) { - app->end(); - } -} - -bool banjo::renderer::RT64Context::update_config(const ultramodern::renderer::GraphicsConfig& old_config, const ultramodern::renderer::GraphicsConfig& new_config) { - if (old_config == new_config) { - return false; - } - - if (new_config.wm_option != old_config.wm_option) { - app->setFullScreen(new_config.wm_option == ultramodern::renderer::WindowMode::Fullscreen); - } - - set_application_user_config(app.get(), new_config); - - app->updateUserConfig(true); - - if (new_config.msaa_option != old_config.msaa_option) { - app->updateMultisampling(); - } - return true; -} - -void banjo::renderer::RT64Context::enable_instant_present() { - // Enable the present early presentation mode for minimal latency. - app->enhancementConfig.presentation.mode = RT64::EnhancementConfiguration::Presentation::Mode::PresentEarly; - - app->updateEnhancementConfig(); -} - -uint32_t banjo::renderer::RT64Context::get_display_framerate() const { - return app->presentQueue->ext.sharedResources->swapChainRate; -} - -float banjo::renderer::RT64Context::get_resolution_scale() const { - constexpr int ReferenceHeight = 240; - switch (app->userConfig.resolution) { - case RT64::UserConfiguration::Resolution::WindowIntegerScale: - if (app->sharedQueueResources->swapChainHeight > 0) { - return std::max(float((app->sharedQueueResources->swapChainHeight + ReferenceHeight - 1) / ReferenceHeight), 1.0f); - } - else { - return 1.0f; - } - case RT64::UserConfiguration::Resolution::Manual: - return float(app->userConfig.resolutionMultiplier); - case RT64::UserConfiguration::Resolution::Original: - default: - return 1.0f; - } -} - -void banjo::renderer::RT64Context::check_texture_pack_actions() { - bool packs_changed = false; - TexturePackAction cur_action; - while (texture_pack_action_queue.try_dequeue(cur_action)) { - std::visit(overloaded{ - [&](TexturePackDisableAction &to_disable) { - enabled_texture_packs.erase(to_disable.mod_id); - packs_changed = true; - }, - [&](TexturePackEnableAction &to_enable) { - enabled_texture_packs.insert(to_enable.mod_id); - packs_changed = true; - }, - [&](TexturePackSecondaryDisableAction &to_override_disable) { - secondary_disabled_texture_packs.insert(to_override_disable.mod_id); - packs_changed = true; - }, - [&](TexturePackSecondaryEnableAction &to_override_enable) { - secondary_disabled_texture_packs.erase(to_override_enable.mod_id); - packs_changed = true; - }, - [&](TexturePackUpdateAction &) { - packs_changed = true; - } - }, cur_action); - } - - // If any packs were disabled, unload all packs and load all the active ones. - if (packs_changed) { - // Sort the enabled texture packs in reverse order so that earlier ones override later ones. - std::vector sorted_texture_packs{}; - sorted_texture_packs.reserve(enabled_texture_packs.size()); - for (const std::string& mod : enabled_texture_packs) { - if (!secondary_disabled_texture_packs.contains(mod)) { - sorted_texture_packs.emplace_back(mod); - } - } - - std::sort(sorted_texture_packs.begin(), sorted_texture_packs.end(), - [](const std::string& lhs, const std::string& rhs) { - return recomp::mods::get_mod_order_index(lhs) > recomp::mods::get_mod_order_index(rhs); - } - ); - - // Build the path list from the sorted mod list. - std::vector replacement_directories; - replacement_directories.reserve(enabled_texture_packs.size()); - for (const std::string &mod_id : sorted_texture_packs) { - replacement_directories.emplace_back(RT64::ReplacementDirectory(recomp::mods::get_mod_filename(mod_id))); - } - - if (!replacement_directories.empty()) { - app->textureCache->loadReplacementDirectories(replacement_directories); - } - else { - app->textureCache->clearReplacementDirectories(); - } - } -} - -RT64::UserConfiguration::Antialiasing banjo::renderer::RT64MaxMSAA() { - return device_max_msaa; -} - -std::unique_ptr banjo::renderer::create_render_context(uint8_t* rdram, ultramodern::renderer::WindowHandle window_handle, bool developer_mode) { - return std::make_unique(rdram, window_handle, developer_mode); -} - -bool banjo::renderer::RT64SamplePositionsSupported() { - return sample_positions_supported; -} - -bool banjo::renderer::RT64HighPrecisionFBEnabled() { - return high_precision_fb_enabled; -} - -void banjo::renderer::trigger_texture_pack_update() { - texture_pack_action_queue.enqueue(TexturePackUpdateAction{}); -} - -void banjo::renderer::enable_texture_pack(const recomp::mods::ModContext& context, const recomp::mods::ModHandle& mod) { - texture_pack_action_queue.enqueue(TexturePackEnableAction{mod.manifest.mod_id}); - - // Check for the texture pack enabled config option. - const recomp::config::ConfigSchema& config_schema = context.get_mod_config_schema(mod.manifest.mod_id); - auto find_it = config_schema.options_by_id.find(banjo::renderer::special_option_texture_pack_enabled); - if (find_it != config_schema.options_by_id.end()) { - const recomp::config::ConfigOption& config_option = config_schema.options[find_it->second]; - - if (is_texture_pack_enable_config_option(config_option, false)) { - recomp::config::ConfigValueVariant value_variant = context.get_mod_config_value(mod.manifest.mod_id, config_option.id); - uint32_t value; - if (uint32_t* value_ptr = std::get_if(&value_variant)) { - value = *value_ptr; - } - else { - value = 0; - } - - if (value) { - banjo::renderer::secondary_enable_texture_pack(mod.manifest.mod_id); - } - else { - banjo::renderer::secondary_disable_texture_pack(mod.manifest.mod_id); - } - } - } -} - -void banjo::renderer::disable_texture_pack(const recomp::mods::ModHandle& mod) { - texture_pack_action_queue.enqueue(TexturePackDisableAction{mod.manifest.mod_id}); -} - -void banjo::renderer::secondary_enable_texture_pack(const std::string& mod_id) { - texture_pack_action_queue.enqueue(TexturePackSecondaryEnableAction{mod_id}); -} - -void banjo::renderer::secondary_disable_texture_pack(const std::string& mod_id) { - texture_pack_action_queue.enqueue(TexturePackSecondaryDisableAction{mod_id}); -} - - -// HD texture enable option. Must be an enum with two options. -// The first option is treated as disabled and the second option is treated as enabled. -bool banjo::renderer::is_texture_pack_enable_config_option(const recomp::config::ConfigOption& option, bool show_errors) { - if (option.id == banjo::renderer::special_option_texture_pack_enabled) { - if (option.type != recomp::config::ConfigOptionType::Enum) { - if (show_errors) { - recompui::message_box(("Mod has the special config option id for enabling an HD texture pack (\"" + banjo::renderer::special_option_texture_pack_enabled + "\"), but the config option is not an enum.").c_str()); - } - return false; - } - - const recomp::config::ConfigOptionEnum &option_enum = std::get(option.variant); - if (option_enum.options.size() != 2) { - if (show_errors) { - recompui::message_box(("Mod has the special config option id for enabling an HD texture pack (\"" + banjo::renderer::special_option_texture_pack_enabled + "\"), but the config option doesn't have exactly 2 values.").c_str()); - } - return false; - } - - return true; - } - return false; -} diff --git a/src/main/support.cpp b/src/main/support.cpp deleted file mode 100644 index fb4649a..0000000 --- a/src/main/support.cpp +++ /dev/null @@ -1,94 +0,0 @@ -#include "banjo_support.h" -#include -#include "nfd.h" -#include "RmlUi/Core.h" - -namespace banjo { - // MARK: - Internal Helpers - void perform_file_dialog_operation(const std::function& callback) { - nfdnchar_t* native_path = nullptr; - nfdresult_t result = NFD_OpenDialogN(&native_path, nullptr, 0, nullptr); - - bool success = (result == NFD_OKAY); - std::filesystem::path path; - - if (success) { - path = std::filesystem::path{native_path}; - NFD_FreePathN(native_path); - } - - callback(success, path); - } - - void perform_file_dialog_operation_multiple(const std::function&)>& callback) { - const nfdpathset_t* native_paths = nullptr; - nfdresult_t result = NFD_OpenDialogMultipleN(&native_paths, nullptr, 0, nullptr); - - bool success = (result == NFD_OKAY); - std::list paths; - nfdpathsetsize_t count = 0; - - if (success) { - NFD_PathSet_GetCount(native_paths, &count); - for (nfdpathsetsize_t i = 0; i < count; i++) { - nfdnchar_t* cur_path = nullptr; - nfdresult_t cur_result = NFD_PathSet_GetPathN(native_paths, i, &cur_path); - if (cur_result == NFD_OKAY) { - paths.emplace_back(std::filesystem::path{cur_path}); - } - } - NFD_PathSet_Free(native_paths); - } - - callback(success, paths); - } - - // MARK: - Public API - - std::filesystem::path get_program_path() { -#if defined(__APPLE__) - return get_bundle_resource_directory(); -#elif defined(__linux__) && defined(RECOMP_FLATPAK) - return "/app/bin"; -#else - return ""; -#endif - } - - std::filesystem::path get_asset_path(const char* asset) { - return get_program_path() / "assets" / asset; - } - - void open_file_dialog(std::function callback) { -#ifdef __APPLE__ - dispatch_on_ui_thread([callback]() { - perform_file_dialog_operation(callback); - }); -#else - perform_file_dialog_operation(callback); -#endif - } - - void open_file_dialog_multiple(std::function& paths)> callback) { -#ifdef __APPLE__ - dispatch_on_ui_thread([callback]() { - perform_file_dialog_operation_multiple(callback); - }); -#else - perform_file_dialog_operation_multiple(callback); -#endif - } - - void show_error_message_box(const char *title, const char *message) { -#ifdef __APPLE__ - std::string title_copy(title); - std::string message_copy(message); - - dispatch_on_ui_thread([title_copy, message_copy] { - SDL_ShowSimpleMessageBox(SDL_MESSAGEBOX_ERROR, title_copy.c_str(), message_copy.c_str(), nullptr); - }); -#else - SDL_ShowSimpleMessageBox(SDL_MESSAGEBOX_ERROR, title, message, nullptr); -#endif - } -} diff --git a/src/main/theme.cpp b/src/main/theme.cpp index 8d6139c..ad655e5 100644 --- a/src/main/theme.cpp +++ b/src/main/theme.cpp @@ -1,4 +1,4 @@ -#include "recomp_ui.h" +#include "elements/ui_theme.h" #include "theme.h" void recomptheme::set_custom_theme() { diff --git a/src/ui/config/ui_config_common.h b/src/ui/config/ui_config_common.h deleted file mode 100644 index 80ff2e6..0000000 --- a/src/ui/config/ui_config_common.h +++ /dev/null @@ -1,72 +0,0 @@ -#pragma once -#include "librecomp/config.hpp" -#include "librecomp/mods.hpp" - -namespace recompui { - // Callbacks for render updates relating to config option changes. - using get_config_option_updates_callback = std::function()>; - using clear_config_option_updates_callback = std::function; - using check_page_dirty_callback = std::function; - - using set_option_value_callback = std::function; - // Callbacks for various config option properties - using get_option_value_callback = std::function; - using get_option_name_callback = std::function; - using get_option_description_callback = std::function; - using get_option_disabled_callback = std::function; - using get_option_hidden_callback = std::function; - using get_option_configuration_callback = std::function; - - // Text that displays on the right side of the options - using get_enum_option_details_callback = std::function; - // Fetches if an individual enum option is disabled - using get_enum_option_disabled_callback = std::function; - - using on_option_hover_callback = std::function; - - class ConfigOptionPropertyCallbacks { - // private: - // const get_option_configuration_callback get_configuration; - public: - const get_option_configuration_callback get_configuration; - const get_option_value_callback get_value; - const get_option_name_callback get_name; - const get_option_description_callback get_description; - const get_option_hidden_callback get_hidden; - const get_option_disabled_callback get_disabled = nullptr; - - const get_enum_option_details_callback get_enum_option_details = nullptr; - const get_enum_option_disabled_callback get_enum_option_disabled = nullptr; - - ConfigOptionPropertyCallbacks( - get_option_configuration_callback get_configuration, - get_option_value_callback get_value, - get_option_name_callback get_name, - get_option_description_callback get_description, - get_option_hidden_callback get_hidden, - get_option_disabled_callback get_disabled = nullptr, - get_enum_option_details_callback get_enum_option_details = nullptr, - get_enum_option_disabled_callback get_enum_option_disabled = nullptr - ) : - get_configuration(get_configuration), - get_value(get_value), - get_name(get_name), - get_description(get_description), - get_hidden(get_hidden), - get_disabled(get_disabled), - get_enum_option_details(get_enum_option_details), - get_enum_option_disabled(get_enum_option_disabled) {} - - ConfigOptionPropertyCallbacks(const ConfigOptionPropertyCallbacks&) = default; - - recomp::config::ConfigOptionEnum get_enum_configuration(const std::string &option_id) { - return std::get(get_configuration(option_id)); - } - recomp::config::ConfigOptionNumber get_number_configuration(const std::string &option_id) { - return std::get(get_configuration(option_id)); - } - recomp::config::ConfigOptionString get_string_configuration(const std::string &option_id) { - return std::get(get_configuration(option_id)); - } - }; -}; diff --git a/src/ui/config/ui_config_option.cpp b/src/ui/config/ui_config_option.cpp deleted file mode 100644 index a063460..0000000 --- a/src/ui/config/ui_config_option.cpp +++ /dev/null @@ -1,293 +0,0 @@ -#include "ui_config_option.h" - -namespace recompui { - -constexpr float config_option_element_margin_vertical = 12.0f; -NovaConfigOptionElement::NovaConfigOptionElement( - Element *parent, - std::string option_id, - size_t option_index, - ConfigOptionPropertyCallbacks *callbacks, - set_option_value_callback set_option_value, - on_option_hover_callback on_hover -) : - Element(parent, Events(EventType::Hover, EventType::Focus), "div", false), - option_id(option_id), - option_index(option_index), - callbacks(callbacks), - set_option_value(set_option_value), - on_hover(on_hover) -{ - set_display(Display::Flex); - set_position(Position::Relative); - set_flex_direction(FlexDirection::Column); - set_align_items(AlignItems::FlexStart); - set_justify_content(JustifyContent::FlexStart); - set_padding_top(config_option_element_margin_vertical); - set_padding_left(12.0f); - set_padding_right(12.0f); - set_padding_bottom(config_option_element_margin_vertical); - set_height_auto(); - set_width(100.0f, Unit::Percent); - set_as_navigation_container(NavigationType::Auto); - - ContextId context = recompui::get_current_context(); - auto name_text = callbacks->get_name(option_id); - if (!name_text.empty()) { - name_label = context.create_element