#include "MetaCoreRuntimeUi/MetaCoreRuntimeUiSystem.h" #include "MetaCorePlatform/MetaCoreInput.h" #include "MetaCorePlatform/MetaCoreWindow.h" #include "MetaCoreRender/MetaCoreEditorViewportRenderer.h" #include "stb/stb_image.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include namespace MetaCore { namespace { [[nodiscard]] bool IsPathInsideRoot(const std::filesystem::path& root, const std::filesystem::path& candidate) { std::error_code error; const std::filesystem::path canonicalRoot = std::filesystem::weakly_canonical(root, error); if (error) return false; const std::filesystem::path canonicalCandidate = std::filesystem::weakly_canonical(candidate, error); if (error) return false; const std::filesystem::path relative = canonicalCandidate.lexically_relative(canonicalRoot); return !relative.empty() && !relative.is_absolute() && *relative.begin() != ".."; } [[nodiscard]] std::string FormatValue(const MetaCoreRuntimeDataValue& value, const std::string& format) { std::ostringstream stream; if (!format.empty()) stream << std::fixed << std::setprecision(std::max(0, std::atoi(format.c_str()))); switch (value.Type) { case MetaCoreRuntimeValueType::Bool: return value.BoolValue ? "true" : "false"; case MetaCoreRuntimeValueType::Int64: stream << value.Int64Value; break; case MetaCoreRuntimeValueType::Double: stream << value.DoubleValue; break; case MetaCoreRuntimeValueType::String: return value.StringValue; case MetaCoreRuntimeValueType::Vec3: stream << value.Vec3Value.x << ", " << value.Vec3Value.y << ", " << value.Vec3Value.z; break; } return stream.str(); } [[nodiscard]] Rml::Input::KeyIdentifier MetaCoreMapGlfwKeyToRml(int key) { if (key >= GLFW_KEY_0 && key <= GLFW_KEY_9) { return static_cast(Rml::Input::KI_0 + (key - GLFW_KEY_0)); } if (key >= GLFW_KEY_A && key <= GLFW_KEY_Z) { return static_cast(Rml::Input::KI_A + (key - GLFW_KEY_A)); } switch (key) { case GLFW_KEY_SPACE: return Rml::Input::KI_SPACE; case GLFW_KEY_BACKSPACE: return Rml::Input::KI_BACK; case GLFW_KEY_TAB: return Rml::Input::KI_TAB; case GLFW_KEY_ENTER: return Rml::Input::KI_RETURN; case GLFW_KEY_ESCAPE: return Rml::Input::KI_ESCAPE; case GLFW_KEY_LEFT: return Rml::Input::KI_LEFT; case GLFW_KEY_UP: return Rml::Input::KI_UP; case GLFW_KEY_RIGHT: return Rml::Input::KI_RIGHT; case GLFW_KEY_DOWN: return Rml::Input::KI_DOWN; case GLFW_KEY_HOME: return Rml::Input::KI_HOME; case GLFW_KEY_END: return Rml::Input::KI_END; case GLFW_KEY_PAGE_UP: return Rml::Input::KI_PRIOR; case GLFW_KEY_PAGE_DOWN: return Rml::Input::KI_NEXT; case GLFW_KEY_INSERT: return Rml::Input::KI_INSERT; case GLFW_KEY_DELETE: return Rml::Input::KI_DELETE; case GLFW_KEY_LEFT_SHIFT: return Rml::Input::KI_LSHIFT; case GLFW_KEY_RIGHT_SHIFT: return Rml::Input::KI_RSHIFT; case GLFW_KEY_LEFT_CONTROL: return Rml::Input::KI_LCONTROL; case GLFW_KEY_RIGHT_CONTROL: return Rml::Input::KI_RCONTROL; case GLFW_KEY_LEFT_ALT: return Rml::Input::KI_LMENU; case GLFW_KEY_RIGHT_ALT: return Rml::Input::KI_RMENU; default: return Rml::Input::KI_UNKNOWN; } } [[nodiscard]] int MetaCoreMapGlfwModifiersToRml(int modifiers) { int result = 0; if ((modifiers & GLFW_MOD_CONTROL) != 0) result |= Rml::Input::KM_CTRL; if ((modifiers & GLFW_MOD_SHIFT) != 0) result |= Rml::Input::KM_SHIFT; if ((modifiers & GLFW_MOD_ALT) != 0) result |= Rml::Input::KM_ALT; if ((modifiers & GLFW_MOD_SUPER) != 0) result |= Rml::Input::KM_META; if ((modifiers & GLFW_MOD_CAPS_LOCK) != 0) result |= Rml::Input::KM_CAPSLOCK; if ((modifiers & GLFW_MOD_NUM_LOCK) != 0) result |= Rml::Input::KM_NUMLOCK; return result; } [[nodiscard]] std::vector MetaCoreLoadRuntimeUiMaterialPackage() { for (const std::filesystem::path& candidate : { std::filesystem::path("rml_ui.filamat"), std::filesystem::path("Engine/Materials/rml_ui.filamat"), std::filesystem::path("../rml_ui.filamat"), std::filesystem::path("uiBlit.filamat"), std::filesystem::path("Engine/Materials/uiBlit.filamat"), std::filesystem::path("../uiBlit.filamat") }) { std::ifstream input(candidate, std::ios::binary); if (!input.is_open()) { continue; } std::vector package((std::istreambuf_iterator(input)), std::istreambuf_iterator()); if (!package.empty()) { return package; } } return {}; } class MetaCoreRmlSystemInterface final : public Rml::SystemInterface { public: explicit MetaCoreRmlSystemInterface(MetaCoreWindow& window) : Window_(window) {} void SetClipboardText(const Rml::String& text) override { Window_.SetClipboardText(text); } void GetClipboardText(Rml::String& text) override { text = Window_.GetClipboardText(); } private: MetaCoreWindow& Window_; }; class MetaCoreRmlFilamentRenderInterface final : public Rml::RenderInterface { public: struct DrawCommand { Rml::CompiledGeometryHandle Geometry = 0; Rml::Vector2f Translation{}; Rml::TextureHandle Texture = 0; bool ScissorEnabled = false; Rml::Rectanglei Scissor{}; }; Rml::CompiledGeometryHandle CompileGeometry(Rml::Span vertices, Rml::Span indices) override { if (vertices.empty() || indices.empty() || vertices.size() > std::numeric_limits::max()) { return 0; } Geometry geometry; geometry.Vertices.reserve(vertices.size()); for (const Rml::Vertex& vertex : vertices) { geometry.Vertices.push_back({ vertex.position.x, vertex.position.y, vertex.tex_coord.x, vertex.tex_coord.y, {vertex.colour.red, vertex.colour.green, vertex.colour.blue, vertex.colour.alpha} }); } geometry.Indices.reserve(indices.size()); for (const int index : indices) { if (index < 0 || static_cast(index) >= vertices.size()) return 0; geometry.Indices.push_back(static_cast(index)); } const Rml::CompiledGeometryHandle handle = NextGeometryHandle_++; Geometries_.emplace(handle, std::move(geometry)); return handle; } void RenderGeometry(Rml::CompiledGeometryHandle geometry, Rml::Vector2f translation, Rml::TextureHandle texture) override { if (!Geometries_.contains(geometry)) return; DrawCommands_.push_back({geometry, translation, texture, ScissorEnabled_, Scissor_}); } void ReleaseGeometry(Rml::CompiledGeometryHandle geometry) override { Geometries_.erase(geometry); } Rml::TextureHandle LoadTexture(Rml::Vector2i& dimensions, const Rml::String& source) override { std::filesystem::path path(source); if (path.is_relative()) path = UiRoot_ / path; if (!IsPathInsideRoot(UiRoot_, path)) return 0; int width = 0; int height = 0; int channels = 0; stbi_uc* pixels = stbi_load(path.string().c_str(), &width, &height, &channels, STBI_rgb_alpha); if (pixels == nullptr || width <= 0 || height <= 0) { stbi_image_free(pixels); return 0; } Texture texture; texture.Width = width; texture.Height = height; texture.Pixels.assign(pixels, pixels + static_cast(width) * static_cast(height) * 4U); stbi_image_free(pixels); dimensions = {width, height}; return StoreTexture(std::move(texture)); } Rml::TextureHandle GenerateTexture(Rml::Span source, Rml::Vector2i dimensions) override { if (dimensions.x <= 0 || dimensions.y <= 0) return 0; const std::size_t byteCount = static_cast(dimensions.x) * static_cast(dimensions.y) * 4U; if (source.size() != byteCount) return 0; Texture texture; texture.Width = dimensions.x; texture.Height = dimensions.y; texture.Pixels.assign(source.begin(), source.end()); return StoreTexture(std::move(texture)); } void ReleaseTexture(Rml::TextureHandle texture) override { const auto found = Textures_.find(texture); if (found == Textures_.end()) return; if (Engine_ != nullptr && found->second.FilamentTexture != nullptr) { Engine_->destroy(found->second.FilamentTexture); } Textures_.erase(found); } void EnableScissorRegion(bool enable) override { ScissorEnabled_ = enable; } void SetScissorRegion(Rml::Rectanglei region) override { Scissor_ = region; } void BeginFrame() { DrawCommands_.clear(); } void Render(filament::Engine& engine, filament::Renderer& renderer, uint32_t width, uint32_t height) { if (width == 0 || height == 0 || DrawCommands_.empty()) return; EnsureFilamentResources(engine); if (Scene_ == nullptr || View_ == nullptr || Material_ == nullptr || Camera_ == nullptr) return; ClearFrameResources(); View_->setViewport({0, 0, width, height}); Camera_->setProjection(filament::Camera::Projection::ORTHO, 0.0, static_cast(width), static_cast(height), 0.0, 0.0, 1.0); std::vector commands; commands.reserve(DrawCommands_.size()); for (const DrawCommand& command : DrawCommands_) { if (Geometries_.contains(command.Geometry)) commands.push_back(command); } if (commands.empty()) return; FrameRenderable_ = utils::EntityManager::get().create(); filament::RenderableManager::Builder builder(static_cast(commands.size())); builder.boundingBox({{0.0f, 0.0f, -1.0f}, {static_cast(width), static_cast(height), 1.0f}}) .culling(false) .castShadows(false) .receiveShadows(false); for (std::size_t primitiveIndex = 0; primitiveIndex < commands.size(); ++primitiveIndex) { const DrawCommand& command = commands[primitiveIndex]; const Geometry& geometry = Geometries_.at(command.Geometry); if (!CreateFrameGeometry(engine, geometry, command.Translation)) continue; filament::MaterialInstance* materialInstance = Material_->createInstance(); FrameMaterialInstances_.push_back(materialInstance); const filament::Texture* texture = ResolveTexture(engine, command.Texture); if (texture != nullptr) { materialInstance->setParameter("albedo", texture, filament::TextureSampler( filament::TextureSampler::MinFilter::LINEAR, filament::TextureSampler::MagFilter::LINEAR )); } if (command.ScissorEnabled) { const uint32_t left = static_cast(std::max(0, command.Scissor.Left())); const uint32_t bottom = static_cast(std::max(0, static_cast(height) - command.Scissor.Bottom())); const uint32_t scissorWidth = static_cast(std::max(0, command.Scissor.Width())); const uint32_t scissorHeight = static_cast(std::max(0, command.Scissor.Height())); materialInstance->setScissor(left, bottom, scissorWidth, scissorHeight); } else { materialInstance->unsetScissor(); } builder.geometry(static_cast(primitiveIndex), filament::RenderableManager::PrimitiveType::TRIANGLES, FrameVertexBuffers_.back(), FrameIndexBuffers_.back(), 0, static_cast(geometry.Indices.size())) .material(static_cast(primitiveIndex), materialInstance) .blendOrder(static_cast(primitiveIndex), static_cast(primitiveIndex)); } if (FrameVertexBuffers_.empty()) { utils::EntityManager::get().destroy(FrameRenderable_); FrameRenderable_ = {}; return; } builder.build(engine, FrameRenderable_); Scene_->addEntity(FrameRenderable_); renderer.render(View_); } void Shutdown() { if (Engine_ == nullptr) return; ClearFrameResources(); for (auto& [_, texture] : Textures_) { if (texture.FilamentTexture != nullptr) Engine_->destroy(texture.FilamentTexture); } Textures_.clear(); if (WhiteTexture_ != nullptr) Engine_->destroy(WhiteTexture_); if (Material_ != nullptr) Engine_->destroy(Material_); if (View_ != nullptr) Engine_->destroy(View_); if (Camera_ != nullptr) { const utils::Entity cameraEntity = Camera_->getEntity(); Engine_->destroyCameraComponent(cameraEntity); utils::EntityManager::get().destroy(cameraEntity); } if (Scene_ != nullptr) Engine_->destroy(Scene_); Material_ = nullptr; WhiteTexture_ = nullptr; View_ = nullptr; Camera_ = nullptr; Scene_ = nullptr; Engine_ = nullptr; } void SetUiRoot(std::filesystem::path root) { UiRoot_ = std::move(root); } private: struct GpuVertex { float Position[2]; float Uv[2]; std::array Color{}; }; struct Geometry { std::vector Vertices; std::vector Indices; }; struct Texture { int Width = 0; int Height = 0; std::vector Pixels; filament::Texture* FilamentTexture = nullptr; }; [[nodiscard]] Rml::TextureHandle StoreTexture(Texture texture) { const Rml::TextureHandle handle = NextTextureHandle_++; Textures_.emplace(handle, std::move(texture)); return handle; } void EnsureFilamentResources(filament::Engine& engine) { if (Engine_ != nullptr && Engine_ != &engine) Shutdown(); if (Engine_ != nullptr) return; Engine_ = &engine; Scene_ = Engine_->createScene(); View_ = Engine_->createView(); View_->setScene(Scene_); View_->setPostProcessingEnabled(false); View_->setBlendMode(filament::View::BlendMode::TRANSLUCENT); View_->setShadowingEnabled(false); const utils::Entity cameraEntity = utils::EntityManager::get().create(); Camera_ = Engine_->createCamera(cameraEntity); View_->setCamera(Camera_); const std::vector package = MetaCoreLoadRuntimeUiMaterialPackage(); if (!package.empty()) Material_ = filament::Material::Builder().package(package.data(), package.size()).build(*Engine_); CreateWhiteTexture(); } [[nodiscard]] const filament::Texture* ResolveTexture(filament::Engine& engine, Rml::TextureHandle handle) { const auto found = Textures_.find(handle); if (found == Textures_.end()) return WhiteTexture_; Texture& texture = found->second; if (texture.FilamentTexture != nullptr) return texture.FilamentTexture; if (texture.Width <= 0 || texture.Height <= 0 || texture.Pixels.empty()) return WhiteTexture_; void* pixelBytes = std::malloc(texture.Pixels.size()); if (pixelBytes == nullptr) return nullptr; std::memcpy(pixelBytes, texture.Pixels.data(), texture.Pixels.size()); filament::Texture::PixelBufferDescriptor descriptor(pixelBytes, texture.Pixels.size(), filament::Texture::Format::RGBA, filament::Texture::Type::UBYTE, [](void* buffer, std::size_t, void*) { std::free(buffer); }, nullptr); texture.FilamentTexture = filament::Texture::Builder() .width(static_cast(texture.Width)) .height(static_cast(texture.Height)) .levels(1) .format(filament::Texture::InternalFormat::RGBA8) .sampler(filament::Texture::Sampler::SAMPLER_2D) .build(engine); texture.FilamentTexture->setImage(engine, 0, std::move(descriptor)); return texture.FilamentTexture; } void CreateWhiteTexture() { if (Engine_ == nullptr || WhiteTexture_ != nullptr) return; std::array pixels{255, 255, 255, 255}; void* pixelBytes = std::malloc(pixels.size()); if (pixelBytes == nullptr) return; std::memcpy(pixelBytes, pixels.data(), pixels.size()); filament::Texture::PixelBufferDescriptor descriptor(pixelBytes, pixels.size(), filament::Texture::Format::RGBA, filament::Texture::Type::UBYTE, [](void* buffer, std::size_t, void*) { std::free(buffer); }, nullptr); WhiteTexture_ = filament::Texture::Builder() .width(1) .height(1) .levels(1) .format(filament::Texture::InternalFormat::RGBA8) .sampler(filament::Texture::Sampler::SAMPLER_2D) .build(*Engine_); WhiteTexture_->setImage(*Engine_, 0, std::move(descriptor)); } [[nodiscard]] bool CreateFrameGeometry(filament::Engine& engine, const Geometry& geometry, Rml::Vector2f translation) { if (geometry.Vertices.empty() || geometry.Indices.empty()) return false; std::vector translatedVertices = geometry.Vertices; for (GpuVertex& vertex : translatedVertices) { vertex.Position[0] += translation.x; vertex.Position[1] += translation.y; } auto* vertexBuffer = filament::VertexBuffer::Builder() .vertexCount(static_cast(translatedVertices.size())) .bufferCount(1) .attribute(filament::VertexAttribute::POSITION, 0, filament::VertexBuffer::AttributeType::FLOAT2, 0, sizeof(GpuVertex)) .attribute(filament::VertexAttribute::UV0, 0, filament::VertexBuffer::AttributeType::FLOAT2, sizeof(float) * 2, sizeof(GpuVertex)) .attribute(filament::VertexAttribute::COLOR, 0, filament::VertexBuffer::AttributeType::UBYTE4, sizeof(float) * 4, sizeof(GpuVertex)) .normalized(filament::VertexAttribute::COLOR) .build(engine); auto* indexBuffer = filament::IndexBuffer::Builder() .indexCount(static_cast(geometry.Indices.size())) .bufferType(filament::IndexBuffer::IndexType::UINT) .build(engine); const std::size_t vertexBytes = translatedVertices.size() * sizeof(GpuVertex); void* copiedVertices = std::malloc(vertexBytes); const std::size_t indexBytes = geometry.Indices.size() * sizeof(std::uint32_t); void* copiedIndices = std::malloc(indexBytes); if (copiedVertices == nullptr || copiedIndices == nullptr) { std::free(copiedVertices); std::free(copiedIndices); engine.destroy(vertexBuffer); engine.destroy(indexBuffer); return false; } std::memcpy(copiedVertices, translatedVertices.data(), vertexBytes); std::memcpy(copiedIndices, geometry.Indices.data(), indexBytes); vertexBuffer->setBufferAt(engine, 0, filament::VertexBuffer::BufferDescriptor(copiedVertices, vertexBytes, [](void* buffer, std::size_t, void*) { std::free(buffer); }, nullptr)); indexBuffer->setBuffer(engine, filament::IndexBuffer::BufferDescriptor(copiedIndices, indexBytes, [](void* buffer, std::size_t, void*) { std::free(buffer); }, nullptr)); FrameVertexBuffers_.push_back(vertexBuffer); FrameIndexBuffers_.push_back(indexBuffer); return true; } void ClearFrameResources() { if (Engine_ == nullptr) return; if (FrameRenderable_) { Engine_->getRenderableManager().destroy(FrameRenderable_); if (Scene_ != nullptr) Scene_->remove(FrameRenderable_); utils::EntityManager::get().destroy(FrameRenderable_); FrameRenderable_ = {}; } for (filament::MaterialInstance* instance : FrameMaterialInstances_) Engine_->destroy(instance); for (filament::VertexBuffer* buffer : FrameVertexBuffers_) Engine_->destroy(buffer); for (filament::IndexBuffer* buffer : FrameIndexBuffers_) Engine_->destroy(buffer); FrameMaterialInstances_.clear(); FrameVertexBuffers_.clear(); FrameIndexBuffers_.clear(); } std::filesystem::path UiRoot_; std::unordered_map Geometries_; std::unordered_map Textures_; std::vector DrawCommands_; Rml::CompiledGeometryHandle NextGeometryHandle_ = 1; Rml::TextureHandle NextTextureHandle_ = 1; bool ScissorEnabled_ = false; Rml::Rectanglei Scissor_{}; filament::Engine* Engine_ = nullptr; filament::Scene* Scene_ = nullptr; filament::View* View_ = nullptr; filament::Camera* Camera_ = nullptr; filament::Material* Material_ = nullptr; filament::Texture* WhiteTexture_ = nullptr; utils::Entity FrameRenderable_{}; std::vector FrameMaterialInstances_; std::vector FrameVertexBuffers_; std::vector FrameIndexBuffers_; }; class MetaCoreRmlFileInterface final : public Rml::FileInterface { public: explicit MetaCoreRmlFileInterface(std::filesystem::path root) : Root_(std::move(root)) {} Rml::FileHandle Open(const Rml::String& path) override { std::filesystem::path candidate(path); if (candidate.is_relative()) candidate = Root_ / candidate; if (!IsPathInsideRoot(Root_, candidate)) return 0; auto* file = new std::ifstream(candidate, std::ios::binary); if (!file->is_open()) { delete file; return 0; } return reinterpret_cast(file); } void Close(Rml::FileHandle file) override { delete reinterpret_cast(file); } size_t Read(void* buffer, size_t size, Rml::FileHandle file) override { auto& input = *reinterpret_cast(file); input.read(static_cast(buffer), static_cast(size)); return static_cast(input.gcount()); } bool Seek(Rml::FileHandle file, long offset, int origin) override { auto& input = *reinterpret_cast(file); input.seekg(offset, static_cast(origin)); return input.good(); } size_t Tell(Rml::FileHandle file) override { return static_cast(reinterpret_cast(file)->tellg()); } private: std::filesystem::path Root_; }; } // namespace class MetaCoreRuntimeUiSystem::Impl { public: struct DocumentState { MetaCoreRuntimeUiDocumentEntry Entry{}; Rml::ElementDocument* Document = nullptr; }; class EventListener final : public Rml::EventListener { public: EventListener(Impl& owner, std::string documentId) : Owner_(owner), DocumentId_(std::move(documentId)) {} void ProcessEvent(Rml::Event& event) override { Rml::Element* target = event.GetTargetElement(); if (target == nullptr) return; const std::string action = target->GetAttribute("data-metacore-action", ""); if (action.empty()) return; MetaCoreRuntimeUiEvent uiEvent; uiEvent.DocumentId = DocumentId_; uiEvent.ElementId = target->GetId(); uiEvent.Action = action; uiEvent.Type = event.GetType(); uiEvent.Value = target->GetAttribute("value", ""); Owner_.Events_.push_back(uiEvent); if (Owner_.Callback_) Owner_.Callback_(uiEvent); } private: Impl& Owner_; std::string DocumentId_; }; MetaCoreWindow* Window_ = nullptr; MetaCoreEditorViewportRenderer* ViewportRenderer_ = nullptr; std::filesystem::path UiRoot_; MetaCoreRmlFilamentRenderInterface RenderInterface_; std::unique_ptr FileInterface_; std::unique_ptr SystemInterface_; Rml::Context* Context_ = nullptr; std::vector Documents_; std::vector> Listeners_; std::unordered_map Values_; std::vector Events_; std::vector Errors_; EventCallback Callback_; bool Initialized_ = false; [[nodiscard]] bool HasInputEnabledDocument() const { return std::any_of(Documents_.begin(), Documents_.end(), [](const DocumentState& state) { return state.Entry.Visible && state.Entry.InputEnabled && state.Document != nullptr; }); } void ApplyBindings() { for (DocumentState& state : Documents_) { if (state.Document == nullptr) continue; Rml::ElementList elements; state.Document->QuerySelectorAll(elements, "[data-metacore-bind]"); for (Rml::Element* element : elements) { const std::string dataPointId = element->GetAttribute("data-metacore-bind", ""); const auto value = Values_.find(dataPointId); if (value == Values_.end()) continue; const std::string format = element->GetAttribute("data-metacore-format", ""); element->SetInnerRML(FormatValue(value->second, format)); } } } }; MetaCoreRuntimeUiSystem::MetaCoreRuntimeUiSystem() : Impl_(std::make_unique()) {} MetaCoreRuntimeUiSystem::~MetaCoreRuntimeUiSystem() { Shutdown(); } bool MetaCoreRuntimeUiSystem::Initialize(MetaCoreWindow& window, const std::filesystem::path&, const std::filesystem::path& uiRoot, const MetaCoreRuntimeUiManifest& manifest) { Shutdown(); if (uiRoot.empty() || !std::filesystem::is_directory(uiRoot)) return false; Impl_->Window_ = &window; Impl_->UiRoot_ = std::filesystem::weakly_canonical(uiRoot); Impl_->RenderInterface_.SetUiRoot(Impl_->UiRoot_); Impl_->FileInterface_ = std::make_unique(Impl_->UiRoot_); Impl_->SystemInterface_ = std::make_unique(window); Rml::SetRenderInterface(&Impl_->RenderInterface_); Rml::SetFileInterface(Impl_->FileInterface_.get()); Rml::SetSystemInterface(Impl_->SystemInterface_.get()); if (!Rml::Initialise()) return false; const auto [width, height] = window.GetFramebufferSize(); Impl_->Context_ = Rml::CreateContext("MetaCoreRuntime", {width, height}); if (Impl_->Context_ == nullptr) { Rml::Shutdown(); return false; } std::vector entries = manifest.Documents; std::sort(entries.begin(), entries.end(), [](const auto& lhs, const auto& rhs) { return lhs.Layer < rhs.Layer; }); for (const MetaCoreRuntimeUiDocumentEntry& entry : entries) { const std::filesystem::path absolutePath = Impl_->UiRoot_ / entry.RmlPath; if (entry.DocumentId.empty() || entry.RmlPath.empty() || !IsPathInsideRoot(Impl_->UiRoot_, absolutePath)) { Impl_->Errors_.push_back("Invalid UI manifest entry: " + entry.DocumentId); continue; } Rml::ElementDocument* document = Impl_->Context_->LoadDocument(absolutePath.string()); if (document == nullptr) { Impl_->Errors_.push_back("Failed to load RML: " + entry.RmlPath.generic_string()); continue; } if (entry.Visible) document->Show(); else document->Hide(); Impl_->Listeners_.push_back(std::make_unique(*Impl_, entry.DocumentId)); document->AddEventListener("click", Impl_->Listeners_.back().get()); document->AddEventListener("submit", Impl_->Listeners_.back().get()); document->AddEventListener("change", Impl_->Listeners_.back().get()); Impl_->Documents_.push_back({entry, document}); } Impl_->Initialized_ = true; return true; } void MetaCoreRuntimeUiSystem::AttachToViewportRenderer(MetaCoreEditorViewportRenderer& viewportRenderer) { Impl_->ViewportRenderer_ = &viewportRenderer; viewportRenderer.SetRuntimeOverlayRenderCallback([this](filament::Engine& engine, filament::Renderer& renderer, uint32_t width, uint32_t height) { if (Impl_ && Impl_->Initialized_) Impl_->RenderInterface_.Render(engine, renderer, width, height); }); } void MetaCoreRuntimeUiSystem::Shutdown() { if (!Impl_) return; if (Impl_->ViewportRenderer_ != nullptr) { Impl_->ViewportRenderer_->SetRuntimeOverlayRenderCallback({}); Impl_->ViewportRenderer_ = nullptr; } if (Impl_->Initialized_) Rml::Shutdown(); Impl_->RenderInterface_.Shutdown(); Impl_->Documents_.clear(); Impl_->Listeners_.clear(); Impl_->Context_ = nullptr; Impl_->FileInterface_.reset(); Impl_->SystemInterface_.reset(); Impl_->Values_.clear(); Impl_->Initialized_ = false; } void MetaCoreRuntimeUiSystem::Update(float) { if (!Impl_->Initialized_ || Impl_->Context_ == nullptr || Impl_->Window_ == nullptr) return; const auto [width, height] = Impl_->Window_->GetFramebufferSize(); Impl_->Context_->SetDimensions({width, height}); MetaCoreInput& input = Impl_->Window_->GetInput(); if (Impl_->HasInputEnabledDocument()) { const glm::vec2 cursor = input.GetCursorPosition(); Impl_->Context_->ProcessMouseMove(static_cast(cursor.x), static_cast(cursor.y), 0); for (int button = 0; button < 3; ++button) { const auto mouseButton = static_cast(button); if (input.WasMouseButtonPressed(mouseButton)) Impl_->Context_->ProcessMouseButtonDown(button, 0); if (input.WasMouseButtonReleased(mouseButton)) Impl_->Context_->ProcessMouseButtonUp(button, 0); } if (std::abs(input.GetMouseWheelDelta()) > 0.001F) Impl_->Context_->ProcessMouseWheel(input.GetMouseWheelDelta(), 0); for (const MetaCoreKeyEvent& event : input.ConsumeKeyEvents()) { const Rml::Input::KeyIdentifier key = MetaCoreMapGlfwKeyToRml(event.Key); const int modifiers = MetaCoreMapGlfwModifiersToRml(event.Modifiers); if (event.Pressed) Impl_->Context_->ProcessKeyDown(key, modifiers); else Impl_->Context_->ProcessKeyUp(key, modifiers); } for (const char32_t codePoint : input.ConsumeTextInput()) { Impl_->Context_->ProcessTextInput(static_cast(codePoint)); } } else { (void)input.ConsumeKeyEvents(); (void)input.ConsumeTextInput(); } Impl_->RenderInterface_.BeginFrame(); Impl_->Context_->Update(); Impl_->Context_->Render(); } void MetaCoreRuntimeUiSystem::ApplyDataUpdates(const std::vector& updates) { if (!Impl_->Initialized_) return; for (const MetaCoreRuntimeDataUpdate& update : updates) Impl_->Values_[update.DataPointId] = update.Value; Impl_->ApplyBindings(); } bool MetaCoreRuntimeUiSystem::SetDocumentVisible(const std::string& documentId, bool visible) { for (Impl::DocumentState& state : Impl_->Documents_) { if (state.Entry.DocumentId != documentId || state.Document == nullptr) continue; if (visible) state.Document->Show(); else state.Document->Hide(); state.Entry.Visible = visible; return true; } return false; } std::vector MetaCoreRuntimeUiSystem::ConsumeEvents() { return std::exchange(Impl_->Events_, {}); } void MetaCoreRuntimeUiSystem::SetEventCallback(EventCallback callback) { Impl_->Callback_ = std::move(callback); } bool MetaCoreRuntimeUiSystem::IsInitialized() const { return Impl_->Initialized_; } std::size_t MetaCoreRuntimeUiSystem::GetLoadedDocumentCount() const { return Impl_->Documents_.size(); } const std::vector& MetaCoreRuntimeUiSystem::GetErrors() const { return Impl_->Errors_; } } // namespace MetaCore