rutile-game/src/rutile/core/ui/ui_server.cppm

125 lines
4.1 KiB
Text
Raw Normal View History

2026-05-10 23:23:40 +02:00
module;
#include <cassert>
#include <format>
#include <RmlUi/Core.h>
#include <RmlUi/Debugger.h>
export module rutile.core.ui.ui_server;
import rutile.core.ui.backend.rmlui_render_interface;
import rutile.core.ui.backend.rmlui_system_interface;
import rutile.core.ui.backend.rmlui_input_handler;
import wrappers.sdl;
export namespace rutile
{
class UIServer
{
// Whether this class is currently instantiated (to prevent multiple instances because we need to do
// Rml::Initialise() and Rml::Shutdown()).
static bool instantiated_;
RmlUiSystemInterface system_interface_;
RmlUiRenderInterface render_interface_;
RmlUiInputHandler input_handler_;
// RmlUi context (non-owning pointer, lifetime managed by library)
Rml::Context* context_;
public:
explicit UIServer(sdl::Renderer& renderer, sdl::Window& window)
: system_interface_{window},
render_interface_{renderer},
input_handler_{window}
{
// Prevent the class from being instantiated multiple times
assert(!instantiated_);
Rml::SetSystemInterface(&system_interface_);
Rml::SetRenderInterface(&render_interface_);
Rml::Initialise();
// Create the main RmlUi context
auto [window_width, window_height] = window.get_size();
context_ = Rml::CreateContext("main", Rml::Vector2i(window_width, window_height));
if (!context_) {
throw std::runtime_error("RmlUi: Couldn't create context");
}
// TODO: Implement some kind of switch to disable debugger (just use config::debug?)
Rml::Debugger::Initialise(context_);
instantiated_ = true;
}
// No copy or move operations
UIServer(const UIServer&) = delete;
UIServer& operator=(const UIServer&) = delete;
UIServer(UIServer&&) = delete;
UIServer& operator=(UIServer&&) = delete;
~UIServer()
{
Rml::Shutdown();
instantiated_ = false;
}
constexpr Rml::Context& get_context() const
{
// TODO: Can we assume context_ is always non-nullptr?
assert(context_);
return *context_;
}
// Handles an SDL event. Returns true if the event has been handled.
bool handle_event(const sdl::Event& event) const
{
// Toggle RmlUi debugger with F12
if (event.type == sdl::EventTypes::KeyDown && event.key.key == sdl::Key::F12) {
Rml::Debugger::SetVisible(!Rml::Debugger::IsVisible());
return true;
}
// Let RmlUi handle relevant events
if (input_handler_.handle_input_event(get_context(), event)) {
return true;
}
return false;
}
void update() const
{
get_context().Update();
}
void render() const
{
render_interface_.prepare_render();
get_context().Render();
}
static void load_font_face(const std::string& file_path)
{
if (!Rml::LoadFontFace(file_path)) {
throw std::runtime_error(std::format("RmlUi: Couldn't load font face \"{}\"", file_path));
}
}
// TODO: Can/should we avoid the (non-owning) raw pointers here?
Rml::ElementDocument* load_document(const std::string& file_path) const
{
Rml::ElementDocument* document = get_context().LoadDocument(file_path);
if (!document) {
throw std::runtime_error(std::format("RmlUi: Couldn't load document \"{}\"", file_path));
}
return document;
}
};
bool UIServer::instantiated_ = false;
}