mirror of
				https://github.com/Zelda64Recomp/Zelda64Recomp.git
				synced 2025-10-30 08:03:03 +00:00 
			
		
		
		
	Refactored menu logic into MenuController interface, updated RT64
This commit is contained in:
		
							parent
							
								
									09bacbe82c
								
							
						
					
					
						commit
						e9e42322f0
					
				
					 7 changed files with 216 additions and 145 deletions
				
			
		|  | @ -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 | ||||
|  |  | |||
|  | @ -2,6 +2,7 @@ | |||
| #define __RECOMP_UI__ | ||||
| 
 | ||||
| #include <memory> | ||||
| #include <string> | ||||
| 
 | ||||
| #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<MenuController> create_launcher_menu(); | ||||
| 	std::unique_ptr<MenuController> 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<Rml::EventListenerInstancer> make_event_listener_instancer(); | ||||
| 	void make_ui_bindings(Rml::Context* context); | ||||
| 	std::unique_ptr<UiEventListenerInstancer> make_event_listener_instancer(); | ||||
| 	void register_event(UiEventListenerInstancer& listener, const std::string& name, event_handler_t* handler); | ||||
| 
 | ||||
| 	enum class Menu { | ||||
| 		Launcher, | ||||
|  |  | |||
|  | @ -1 +1 @@ | |||
| Subproject commit f57eeec44a49cf4fb3ffe6c22d9f0c3d5410fb72 | ||||
| Subproject commit a9a74b5c1975e081bc0a23f29545a0954f5a2f90 | ||||
							
								
								
									
										102
									
								
								src/ui/ui_config.cpp
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										102
									
								
								src/ui/ui_config.cpp
									
										
									
									
									
										Normal file
									
								
							|  | @ -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 <typename T> | ||||
| 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 <typename T> | ||||
| void set_option(T& output, const Rml::Variant& input) { | ||||
| 	T value = T::OptionCount; | ||||
| 	from_json(input.Get<std::string>(), value); | ||||
| 
 | ||||
| 	if (value == T::OptionCount) { | ||||
| 		throw std::runtime_error("Invalid value :" + input.Get<std::string>()); | ||||
| 	} | ||||
| 
 | ||||
| 	output = value; | ||||
| } | ||||
| 
 | ||||
| template <typename T> | ||||
| 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<int>(); | ||||
| 				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::MenuController> recomp::create_config_menu() { | ||||
| 	return std::make_unique<ConfigMenu>(); | ||||
| } | ||||
|  | @ -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<Rml::String, UiEventListener> 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 <typename T> | ||||
| 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 <typename T> | ||||
| void set_option(T& output, const Rml::Variant& input) { | ||||
| 	T value = T::OptionCount; | ||||
| 	from_json(input.Get<std::string>(), value); | ||||
| 
 | ||||
| 	if (value == T::OptionCount) { | ||||
| 		throw std::runtime_error("Invalid value :" + input.Get<std::string>()); | ||||
| 	} | ||||
| 
 | ||||
| 	output = value; | ||||
| } | ||||
| 
 | ||||
| template <typename T> | ||||
| 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<int>(); | ||||
| 			options_handle.DirtyVariable("options_changed"); | ||||
| 		}); | ||||
| 
 | ||||
| 	constructor.BindFunc("options_changed", | ||||
| 		[](Rml::Variant& out) { | ||||
| 			out = (cur_options != new_options); | ||||
| 		}); | ||||
| 
 | ||||
| 	options_handle = constructor.GetModelHandle(); | ||||
| } | ||||
| 
 | ||||
| std::unique_ptr<Rml::EventListenerInstancer> recomp::make_event_listener_instancer() { | ||||
| 	std::unique_ptr<UiEventListenerInstancer> ret = std::make_unique<UiEventListenerInstancer>(); | ||||
| 
 | ||||
| 	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; | ||||
| } | ||||
							
								
								
									
										31
									
								
								src/ui/ui_launcher.cpp
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										31
									
								
								src/ui/ui_launcher.cpp
									
										
									
									
									
										Normal file
									
								
							|  | @ -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::MenuController> recomp::create_launcher_menu() { | ||||
|     return std::make_unique<LauncherMenu>(); | ||||
| } | ||||
|  | @ -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<Rml::String, UiEventListener> 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<recomp::Menu, std::unique_ptr<recomp::MenuController>> menus; | ||||
|         std::unordered_map<recomp::Menu, Rml::ElementDocument*> documents; | ||||
|         Rml::ElementDocument* current_document; | ||||
|         Rml::Element* prev_focused; | ||||
|  | @ -596,7 +631,7 @@ struct { | |||
|         SystemInterface_SDL system_interface; | ||||
|         std::unique_ptr<RmlRenderInterface_RT64> render_interface; | ||||
|         Rml::Context* context; | ||||
|         std::unique_ptr<Rml::EventListenerInstancer> 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<recomp::MenuController>&& 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<RmlRenderInterface_RT64>(&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); | ||||
| 
 | ||||
|  |  | |||
		Loading…
	
	Add table
		
		Reference in a new issue
	
	 Mr-Wiseguy
						Mr-Wiseguy