Refactored menu logic into MenuController interface, updated RT64
This commit is contained in:
parent
09bacbe82c
commit
e9e42322f0
|
@ -114,7 +114,8 @@ set (SOURCES
|
||||||
${CMAKE_SOURCE_DIR}/src/game/controls.cpp
|
${CMAKE_SOURCE_DIR}/src/game/controls.cpp
|
||||||
|
|
||||||
${CMAKE_SOURCE_DIR}/src/ui/ui_renderer.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/aspMain.cpp
|
||||||
${CMAKE_SOURCE_DIR}/rsp/njpgdspMain.cpp
|
${CMAKE_SOURCE_DIR}/rsp/njpgdspMain.cpp
|
||||||
|
|
|
@ -2,6 +2,7 @@
|
||||||
#define __RECOMP_UI__
|
#define __RECOMP_UI__
|
||||||
|
|
||||||
#include <memory>
|
#include <memory>
|
||||||
|
#include <string>
|
||||||
|
|
||||||
#include "SDL.h"
|
#include "SDL.h"
|
||||||
|
|
||||||
|
@ -9,15 +10,30 @@ namespace Rml {
|
||||||
class ElementDocument;
|
class ElementDocument;
|
||||||
class EventListenerInstancer;
|
class EventListenerInstancer;
|
||||||
class Context;
|
class Context;
|
||||||
|
class Event;
|
||||||
}
|
}
|
||||||
|
|
||||||
namespace recomp {
|
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);
|
void queue_event(const SDL_Event& event);
|
||||||
bool try_deque_event(SDL_Event& out);
|
bool try_deque_event(SDL_Event& out);
|
||||||
|
|
||||||
std::unique_ptr<Rml::EventListenerInstancer> make_event_listener_instancer();
|
std::unique_ptr<UiEventListenerInstancer> make_event_listener_instancer();
|
||||||
void make_ui_bindings(Rml::Context* context);
|
void register_event(UiEventListenerInstancer& listener, const std::string& name, event_handler_t* handler);
|
||||||
|
|
||||||
enum class Menu {
|
enum class Menu {
|
||||||
Launcher,
|
Launcher,
|
||||||
|
|
|
@ -1 +1 @@
|
||||||
Subproject commit f57eeec44a49cf4fb3ffe6c22d9f0c3d5410fb72
|
Subproject commit a9a74b5c1975e081bc0a23f29545a0954f5a2f90
|
|
@ -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;
|
|
||||||
}
|
|
|
@ -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;
|
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 {
|
||||||
struct UIRenderContext render;
|
struct UIRenderContext render;
|
||||||
class {
|
class {
|
||||||
|
std::unordered_map<recomp::Menu, std::unique_ptr<recomp::MenuController>> menus;
|
||||||
std::unordered_map<recomp::Menu, Rml::ElementDocument*> documents;
|
std::unordered_map<recomp::Menu, Rml::ElementDocument*> documents;
|
||||||
Rml::ElementDocument* current_document;
|
Rml::ElementDocument* current_document;
|
||||||
Rml::Element* prev_focused;
|
Rml::Element* prev_focused;
|
||||||
|
@ -596,7 +631,7 @@ struct {
|
||||||
SystemInterface_SDL system_interface;
|
SystemInterface_SDL system_interface;
|
||||||
std::unique_ptr<RmlRenderInterface_RT64> render_interface;
|
std::unique_ptr<RmlRenderInterface_RT64> render_interface;
|
||||||
Rml::Context* context;
|
Rml::Context* context;
|
||||||
std::unique_ptr<Rml::EventListenerInstancer> event_listener_instancer;
|
recomp::UiEventListenerInstancer event_listener_instancer;
|
||||||
|
|
||||||
void unload() {
|
void unload() {
|
||||||
render_interface.reset();
|
render_interface.reset();
|
||||||
|
@ -636,11 +671,24 @@ struct {
|
||||||
current_document = nullptr;
|
current_document = nullptr;
|
||||||
|
|
||||||
documents.clear();
|
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"));
|
for (auto& [menu, controller]: menus) {
|
||||||
documents.emplace(recomp::Menu::Config, context->LoadDocument("assets/config_menu.rml"));
|
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) {
|
void update_focus(bool mouse_moved) {
|
||||||
|
@ -679,6 +727,10 @@ struct {
|
||||||
prev_focused = current_document->GetFocusLeafNode();
|
prev_focused = current_document->GetFocusLeafNode();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void add_menu(recomp::Menu menu, std::unique_ptr<recomp::MenuController>&& controller) {
|
||||||
|
menus.emplace(menu, std::move(controller));
|
||||||
|
}
|
||||||
} rml;
|
} rml;
|
||||||
} UIContext;
|
} UIContext;
|
||||||
|
|
||||||
|
@ -688,17 +740,20 @@ extern SDL_Window* window;
|
||||||
void init_hook(RT64::RenderInterface* interface, RT64::RenderDevice* device) {
|
void init_hook(RT64::RenderInterface* interface, RT64::RenderDevice* device) {
|
||||||
printf("RT64 hook init\n");
|
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.interface = interface;
|
||||||
UIContext.render.device = device;
|
UIContext.render.device = device;
|
||||||
|
|
||||||
// Setup RML
|
// Setup RML
|
||||||
UIContext.rml.system_interface.SetWindow(window);
|
UIContext.rml.system_interface.SetWindow(window);
|
||||||
UIContext.rml.render_interface = std::make_unique<RmlRenderInterface_RT64>(&UIContext.render);
|
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::SetSystemInterface(&UIContext.rml.system_interface);
|
||||||
Rml::SetRenderInterface(UIContext.rml.render_interface.get());
|
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();
|
Rml::Initialise();
|
||||||
|
|
||||||
|
@ -706,7 +761,7 @@ void init_hook(RT64::RenderInterface* interface, RT64::RenderDevice* device) {
|
||||||
SDL_GetWindowSizeInPixels(window, &width, &height);
|
SDL_GetWindowSizeInPixels(window, &width, &height);
|
||||||
|
|
||||||
UIContext.rml.context = Rml::CreateContext("main", Rml::Vector2i(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);
|
Rml::Debugger::Initialise(UIContext.rml.context);
|
||||||
|
|
||||||
|
|
Loading…
Reference in New Issue