diff --git a/CMakeLists.txt b/CMakeLists.txt index 5ad926b..96ebf13 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -114,7 +114,8 @@ set (SOURCES ${CMAKE_SOURCE_DIR}/src/game/controls.cpp ${CMAKE_SOURCE_DIR}/src/ui/ui_renderer.cpp - ${CMAKE_SOURCE_DIR}/src/ui/ui_events.cpp + ${CMAKE_SOURCE_DIR}/src/ui/ui_launcher.cpp + ${CMAKE_SOURCE_DIR}/src/ui/ui_config.cpp ${CMAKE_SOURCE_DIR}/rsp/aspMain.cpp ${CMAKE_SOURCE_DIR}/rsp/njpgdspMain.cpp diff --git a/include/recomp_ui.h b/include/recomp_ui.h index 4c291f0..62fc3ed 100644 --- a/include/recomp_ui.h +++ b/include/recomp_ui.h @@ -2,6 +2,7 @@ #define __RECOMP_UI__ #include +#include #include "SDL.h" @@ -9,15 +10,30 @@ namespace Rml { class ElementDocument; class EventListenerInstancer; class Context; + class Event; } namespace recomp { + class UiEventListenerInstancer; + + class MenuController { + public: + virtual ~MenuController() {} + virtual Rml::ElementDocument* load_document(Rml::Context* context) = 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(Rml::Event&); void queue_event(const SDL_Event& event); bool try_deque_event(SDL_Event& out); - std::unique_ptr make_event_listener_instancer(); - void make_ui_bindings(Rml::Context* context); + std::unique_ptr make_event_listener_instancer(); + void register_event(UiEventListenerInstancer& listener, const std::string& name, event_handler_t* handler); enum class Menu { Launcher, diff --git a/lib/RT64-HLE b/lib/RT64-HLE index f57eeec..a9a74b5 160000 --- a/lib/RT64-HLE +++ b/lib/RT64-HLE @@ -1 +1 @@ -Subproject commit f57eeec44a49cf4fb3ffe6c22d9f0c3d5410fb72 +Subproject commit a9a74b5c1975e081bc0a23f29545a0954f5a2f90 diff --git a/src/ui/ui_config.cpp b/src/ui/ui_config.cpp new file mode 100644 index 0000000..59e05d3 --- /dev/null +++ b/src/ui/ui_config.cpp @@ -0,0 +1,102 @@ +#include "recomp_ui.h" +#include "../../ultramodern/config.hpp" +#include "RmlUi/Core.h" + +ultramodern::GraphicsConfig cur_options; +ultramodern::GraphicsConfig new_options; +Rml::DataModelHandle options_handle; + +NLOHMANN_JSON_SERIALIZE_ENUM(ultramodern::Resolution, { + {ultramodern::Resolution::Original, "Original"}, + {ultramodern::Resolution::Original2x, "Original2x"}, + {ultramodern::Resolution::Auto, "Auto"}, +}); + +NLOHMANN_JSON_SERIALIZE_ENUM(ultramodern::WindowMode, { + {ultramodern::WindowMode::Windowed, "Windowed"}, + {ultramodern::WindowMode::Fullscreen, "Fullscreen"} +}); + +template +void get_option(const T& input, Rml::Variant& output) { + std::string value = ""; + to_json(value, input); + + if (value.empty()) { + throw std::runtime_error("Invalid value :" + std::to_string(int(input))); + } + + output = value; +} + +template +void set_option(T& output, const Rml::Variant& input) { + T value = T::OptionCount; + from_json(input.Get(), value); + + if (value == T::OptionCount) { + throw std::runtime_error("Invalid value :" + input.Get()); + } + + output = value; +} + +template +void bind_option(Rml::DataModelConstructor& constructor, const std::string& name, T* option) { + constructor.BindFunc(name, + [option](Rml::Variant& out) { get_option(*option, out); }, + [option](const Rml::Variant& in) { set_option(*option, in); options_handle.DirtyVariable("options_changed"); } + ); +}; + +class ConfigMenu : public recomp::MenuController { +public: + ConfigMenu() { + + } + ~ConfigMenu() override { + + } + Rml::ElementDocument* load_document(Rml::Context* context) override { + return context->LoadDocument("assets/config_menu.rml"); + } + void register_events(recomp::UiEventListenerInstancer& listener) override { + recomp::register_event(listener, "apply_options", + [](Rml::Event& event) { + cur_options = new_options; + options_handle.DirtyVariable("options_changed"); + update_graphics_config(new_options); + }); + } + void make_bindings(Rml::Context* context) override { + Rml::DataModelConstructor constructor = context->CreateDataModel("graphics_model"); + if (!constructor) { + throw std::runtime_error("Failed to make RmlUi data model for the config menu"); + } + + bind_option(constructor, "res_option", &new_options.res_option); + bind_option(constructor, "wm_option", &new_options.wm_option); + bind_option(constructor, "ar_option", &new_options.ar_option); + bind_option(constructor, "msaa_option", &new_options.msaa_option); + bind_option(constructor, "rr_option", &new_options.rr_option); + constructor.BindFunc("rr_manual_value", + [](Rml::Variant& out) { + out = new_options.rr_manual_value; + }, + [](const Rml::Variant& in) { + new_options.rr_manual_value = in.Get(); + options_handle.DirtyVariable("options_changed"); + }); + + constructor.BindFunc("options_changed", + [](Rml::Variant& out) { + out = (cur_options != new_options); + }); + + options_handle = constructor.GetModelHandle(); + } +}; + +std::unique_ptr recomp::create_config_menu() { + return std::make_unique(); +} diff --git a/src/ui/ui_events.cpp b/src/ui/ui_events.cpp deleted file mode 100644 index 7c4f10e..0000000 --- a/src/ui/ui_events.cpp +++ /dev/null @@ -1,134 +0,0 @@ -#define _CRT_SECURE_NO_WARNINGS - -#include "recomp_ui.h" -#include "../../ultramodern/ultramodern.hpp" -#include "../../ultramodern/config.hpp" -#include "common/rt64_user_configuration.h" - -#include "nfd.h" -#include "RmlUi/Core.h" - -using event_handler_t = void(Rml::Event&); - -class UiEventListener : public Rml::EventListener { - event_handler_t* handler_; -public: - UiEventListener(event_handler_t* handler) : handler_(handler) {} - void ProcessEvent(Rml::Event& event) override { - handler_(event); - } -}; - -class UiEventListenerInstancer : public Rml::EventListenerInstancer { - std::unordered_map listener_map_; -public: - Rml::EventListener* InstanceEventListener(const Rml::String& value, Rml::Element* element) override { - printf("Instancing event listener for %s\n", value.c_str()); - auto find_it = listener_map_.find(value); - - if (find_it != listener_map_.end()) { - return &find_it->second; - } - - return nullptr; - } - - void register_event(const Rml::String& value, event_handler_t* handler) { - listener_map_.emplace(value, UiEventListener{ handler }); - } -}; - -NLOHMANN_JSON_SERIALIZE_ENUM(ultramodern::Resolution, { - {ultramodern::Resolution::Original, "Original"}, - {ultramodern::Resolution::Original2x, "Original2x"}, - {ultramodern::Resolution::Auto, "Auto"}, -}); - -NLOHMANN_JSON_SERIALIZE_ENUM(ultramodern::WindowMode, { - {ultramodern::WindowMode::Windowed, "Windowed"}, - {ultramodern::WindowMode::Fullscreen, "Fullscreen"} -}); - -ultramodern::GraphicsConfig cur_options; -ultramodern::GraphicsConfig new_options; -Rml::DataModelHandle options_handle; - -template -void get_option(const T& input, Rml::Variant& output) { - std::string value = ""; - to_json(value, input); - - if (value.empty()) { - throw std::runtime_error("Invalid value :" + std::to_string(int(input))); - } - - output = value; -} - -template -void set_option(T& output, const Rml::Variant& input) { - T value = T::OptionCount; - from_json(input.Get(), value); - - if (value == T::OptionCount) { - throw std::runtime_error("Invalid value :" + input.Get()); - } - - output = value; -} - -template -void bind_option(Rml::DataModelConstructor& constructor, const std::string& name, T* option) { - constructor.BindFunc(name, - [option](Rml::Variant& out) { get_option(*option, out); }, - [option](const Rml::Variant& in) { set_option(*option, in); options_handle.DirtyVariable("options_changed"); } - ); -}; - -void recomp::make_ui_bindings(Rml::Context* context) { - Rml::DataModelConstructor constructor = context->CreateDataModel("graphics_model"); - if (!constructor) { - throw std::runtime_error("Failed to make RmlUi data model constructor"); - } - - bind_option(constructor, "res_option", &new_options.res_option); - bind_option(constructor, "wm_option", &new_options.wm_option); - bind_option(constructor, "ar_option", &new_options.ar_option); - bind_option(constructor, "msaa_option", &new_options.msaa_option); - bind_option(constructor, "rr_option", &new_options.rr_option); - constructor.BindFunc("rr_manual_value", - [](Rml::Variant& out) { - out = new_options.rr_manual_value; - }, - [](const Rml::Variant& in) { - new_options.rr_manual_value = in.Get(); - options_handle.DirtyVariable("options_changed"); - }); - - constructor.BindFunc("options_changed", - [](Rml::Variant& out) { - out = (cur_options != new_options); - }); - - options_handle = constructor.GetModelHandle(); -} - -std::unique_ptr recomp::make_event_listener_instancer() { - std::unique_ptr ret = std::make_unique(); - - ret->register_event("start_game", - [](Rml::Event& event) { - ultramodern::start_game(0); - set_current_menu(Menu::Config); - } - ); - - ret->register_event("apply_options", - [](Rml::Event& event) { - cur_options = new_options; - options_handle.DirtyVariable("options_changed"); - update_graphics_config(new_options); - }); - - return ret; -} diff --git a/src/ui/ui_launcher.cpp b/src/ui/ui_launcher.cpp new file mode 100644 index 0000000..fb1f6cb --- /dev/null +++ b/src/ui/ui_launcher.cpp @@ -0,0 +1,31 @@ +#include "recomp_ui.h" +#include "../../ultramodern/ultramodern.hpp" +#include "RmlUi/Core.h" + +class LauncherMenu : public recomp::MenuController { +public: + LauncherMenu() { + + } + ~LauncherMenu() override { + + } + Rml::ElementDocument* load_document(Rml::Context* context) override { + return context->LoadDocument("assets/launcher.rml"); + } + void register_events(recomp::UiEventListenerInstancer& listener) override { + recomp::register_event(listener, "start_game", + [](Rml::Event& event) { + ultramodern::start_game(0); + recomp::set_current_menu(recomp::Menu::Config); + } + ); + } + void make_bindings(Rml::Context* context) override { + + } +}; + +std::unique_ptr recomp::create_launcher_menu() { + return std::make_unique(); +} diff --git a/src/ui/ui_renderer.cpp b/src/ui/ui_renderer.cpp index ecf3ed4..5d8fbe2 100644 --- a/src/ui/ui_renderer.cpp +++ b/src/ui/ui_renderer.cpp @@ -586,9 +586,44 @@ Rml::Element* get_target(Rml::ElementDocument* document, Rml::Element* element) return element; } +namespace recomp { + class UiEventListener : public Rml::EventListener { + event_handler_t* handler_; + public: + UiEventListener(event_handler_t* handler) : handler_(handler) {} + void ProcessEvent(Rml::Event& event) override { + handler_(event); + } + }; + + class UiEventListenerInstancer : public Rml::EventListenerInstancer { + std::unordered_map listener_map_; + public: + Rml::EventListener* InstanceEventListener(const Rml::String& value, Rml::Element* element) override { + printf("Instancing event listener for %s\n", value.c_str()); + auto find_it = listener_map_.find(value); + + if (find_it != listener_map_.end()) { + return &find_it->second; + } + + return nullptr; + } + + void register_event(const Rml::String& value, event_handler_t* handler) { + listener_map_.emplace(value, UiEventListener{ handler }); + } + }; +} + +void recomp::register_event(UiEventListenerInstancer& listener, const std::string& name, event_handler_t* handler) { + listener.register_event(name, handler); +} + struct { struct UIRenderContext render; class { + std::unordered_map> menus; std::unordered_map documents; Rml::ElementDocument* current_document; Rml::Element* prev_focused; @@ -596,7 +631,7 @@ struct { SystemInterface_SDL system_interface; std::unique_ptr render_interface; Rml::Context* context; - std::unique_ptr event_listener_instancer; + recomp::UiEventListenerInstancer event_listener_instancer; void unload() { render_interface.reset(); @@ -636,11 +671,24 @@ struct { current_document = nullptr; documents.clear(); - Rml::Factory::RegisterEventListenerInstancer(event_listener_instancer.get()); + Rml::Factory::RegisterEventListenerInstancer(&event_listener_instancer); } - documents.emplace(recomp::Menu::Launcher, context->LoadDocument("assets/launcher.rml")); - documents.emplace(recomp::Menu::Config, context->LoadDocument("assets/config_menu.rml")); + for (auto& [menu, controller]: menus) { + documents.emplace(menu, controller->load_document(context)); + } + } + + void make_event_listeners() { + for (auto& [menu, controller]: menus) { + controller->register_events(event_listener_instancer); + } + } + + void make_bindings() { + for (auto& [menu, controller]: menus) { + controller->make_bindings(context); + } } void update_focus(bool mouse_moved) { @@ -679,6 +727,10 @@ struct { prev_focused = current_document->GetFocusLeafNode(); } } + + void add_menu(recomp::Menu menu, std::unique_ptr&& controller) { + menus.emplace(menu, std::move(controller)); + } } rml; } UIContext; @@ -688,17 +740,20 @@ extern SDL_Window* window; void init_hook(RT64::RenderInterface* interface, RT64::RenderDevice* device) { printf("RT64 hook init\n"); + UIContext.rml.add_menu(recomp::Menu::Config, recomp::create_config_menu()); + UIContext.rml.add_menu(recomp::Menu::Launcher, recomp::create_launcher_menu()); + UIContext.render.interface = interface; UIContext.render.device = device; // Setup RML UIContext.rml.system_interface.SetWindow(window); UIContext.rml.render_interface = std::make_unique(&UIContext.render); - UIContext.rml.event_listener_instancer = recomp::make_event_listener_instancer(); + UIContext.rml.make_event_listeners(); Rml::SetSystemInterface(&UIContext.rml.system_interface); Rml::SetRenderInterface(UIContext.rml.render_interface.get()); - Rml::Factory::RegisterEventListenerInstancer(UIContext.rml.event_listener_instancer.get()); + Rml::Factory::RegisterEventListenerInstancer(&UIContext.rml.event_listener_instancer); Rml::Initialise(); @@ -706,7 +761,7 @@ void init_hook(RT64::RenderInterface* interface, RT64::RenderDevice* device) { SDL_GetWindowSizeInPixels(window, &width, &height); UIContext.rml.context = Rml::CreateContext("main", Rml::Vector2i(width, height)); - recomp::make_ui_bindings(UIContext.rml.context); + UIContext.rml.make_bindings(); Rml::Debugger::Initialise(UIContext.rml.context);