module; #include #include #include export module rutile.core.ui.ui_server; import std; 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; }