Refactored menu logic into MenuController interface, updated RT64

This commit is contained in:
Mr-Wiseguy 2024-01-01 22:36:06 -05:00
parent 09bacbe82c
commit e9e42322f0
7 changed files with 216 additions and 145 deletions

View File

@ -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

View File

@ -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
View 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>();
}

View File

@ -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
View 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>();
}

View File

@ -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);