From a35c22257044224e24199358a29f784450b6cf10 Mon Sep 17 00:00:00 2001 From: omigamedev Date: Fri, 16 Jan 2026 16:34:32 +0100 Subject: [PATCH] save state --- designer/CMakeLists.txt | 86 ++--- designer/main.cpp | 359 +++++++++++++++++++ designer/src/desktop_file_interface.cpp | 66 ++++ designer/src/desktop_file_interface.h | 34 ++ designer/src/desktop_platform.cpp | 131 ++++--- designer/src/desktop_platform.h | 72 ++-- designer/src/glad_loader.h | 11 + designer/src/hot_reload.cpp | 45 +-- designer/src/hot_reload.h | 15 +- designer/src/platform_singleton.cpp | 23 ++ designer/vcpkg.json | 7 +- src/main/kernel/include/file_interface.h | 39 +-- src/main/kernel/include/platform.h | 32 +- src/main/kernel/include/service_interface.h | 32 +- src/main/kernel/src/kernel.cpp | 366 ++++++++++++++++++++ 15 files changed, 1052 insertions(+), 266 deletions(-) create mode 100644 designer/main.cpp create mode 100644 designer/src/desktop_file_interface.cpp create mode 100644 designer/src/desktop_file_interface.h create mode 100644 designer/src/glad_loader.h create mode 100644 designer/src/platform_singleton.cpp create mode 100644 src/main/kernel/src/kernel.cpp diff --git a/designer/CMakeLists.txt b/designer/CMakeLists.txt index 07393fe..7825264 100644 --- a/designer/CMakeLists.txt +++ b/designer/CMakeLists.txt @@ -1,16 +1,16 @@ +# D:\Dev\Mosis\MosisService\designer\CMakeLists.txt cmake_minimum_required(VERSION 3.22.1) project(mosis-designer) set(CMAKE_CXX_STANDARD 23) set(CMAKE_CXX_STANDARD_REQUIRED ON) -# Find Lua before RmlUi so it can be used -find_package(Lua REQUIRED) - -# Find other dependencies via vcpkg +# Find dependencies via vcpkg find_package(glfw3 CONFIG REQUIRED) find_package(freetype CONFIG REQUIRED) find_package(PNG REQUIRED) +find_package(Lua REQUIRED) +find_package(glad CONFIG REQUIRED) find_package(nlohmann_json CONFIG REQUIRED) # Fetch RmlUi @@ -20,80 +20,68 @@ FetchContent_Declare( GIT_REPOSITORY https://github.com/mikke89/RmlUi.git GIT_TAG 6.0 ) - -# Enable RmlUi Lua bindings before fetching set(RMLUI_LUA_BINDINGS ON CACHE BOOL "" FORCE) set(BUILD_SHARED_LIBS OFF CACHE BOOL "" FORCE) set(RMLUI_SAMPLES OFF CACHE BOOL "" FORCE) set(RMLUI_TESTS OFF CACHE BOOL "" FORCE) set(RMLUI_FONT_ENGINE "freetype" CACHE STRING "" FORCE) - FetchContent_MakeAvailable(rmlui) -# Get the RmlUi source directory for backend sources -FetchContent_GetProperties(rmlui) -set(RMLUI_SOURCE_DIR ${rmlui_SOURCE_DIR}) +# Get glad include directories explicitly +get_target_property(GLAD_INCLUDE_DIRS glad::glad INTERFACE_INCLUDE_DIRECTORIES) -# Shared kernel library sources (platform-agnostic code) -set(KERNEL_SOURCES - ../src/main/kernel/src/platform.cpp - ../src/main/kernel/src/file_interface.cpp +# Shared kernel library (platform-agnostic code from MosisService) +add_library(mosis-kernel STATIC + ../src/main/cpp/RmlUi_Renderer_GL3.cpp ) - -# Desktop platform sources -set(DESIGNER_SOURCES - src/main.cpp - src/desktop_platform.cpp - src/hot_reload.cpp - src/data_models.cpp - src/kernel_impl.cpp - src/testing/action_recorder.cpp - src/testing/action_player.cpp - src/testing/ui_inspector.cpp - src/testing/visual_capture.cpp - # RmlUi backend sources - ${RMLUI_SOURCE_DIR}/Backends/RmlUi_Backend_GLFW_GL3.cpp - ${RMLUI_SOURCE_DIR}/Backends/RmlUi_Platform_GLFW.cpp - ${RMLUI_SOURCE_DIR}/Backends/RmlUi_Renderer_GL3.cpp +target_include_directories(mosis-kernel PUBLIC + ../src/main/kernel/include + ../src/main/cpp + ${GLAD_INCLUDE_DIRS} + ${LUA_INCLUDE_DIR} +) +target_link_libraries(mosis-kernel PUBLIC + rmlui + rmlui_lua + ${LUA_LIBRARIES} + glad::glad +) +target_include_directories(mosis-kernel PUBLIC + ${CMAKE_CURRENT_SOURCE_DIR}/src +) +target_compile_definitions(mosis-kernel PUBLIC + MOSIS_PLATFORM_DESKTOP + # Use our wrapper header that sets up RMLUI_SHADER_HEADER_VERSION before including glad + RMLUI_GL3_CUSTOM_LOADER="glad_loader.h" ) # Designer executable add_executable(mosis-designer - ${KERNEL_SOURCES} - ${DESIGNER_SOURCES} + main.cpp + src/desktop_platform.cpp + src/desktop_file_interface.cpp + src/hot_reload.cpp + src/platform_singleton.cpp ) target_include_directories(mosis-designer PRIVATE src ../src/main/kernel/include - ${RMLUI_SOURCE_DIR} - ${RMLUI_SOURCE_DIR}/Backends - ${LUA_INCLUDE_DIR} + ../src/main/cpp ) target_link_libraries(mosis-designer PRIVATE + mosis-kernel + glad::glad glfw freetype PNG::PNG - RmlUi::RmlUi - RmlUi::Lua nlohmann_json::nlohmann_json ) -target_compile_definitions(mosis-designer PRIVATE - MOSIS_PLATFORM_DESKTOP - RMLUI_STATIC_LIB -) - -# Platform-specific libraries +# Windows-specific if(WIN32) target_link_libraries(mosis-designer PRIVATE opengl32) -elseif(APPLE) - find_library(OPENGL_LIBRARY OpenGL) - target_link_libraries(mosis-designer PRIVATE ${OPENGL_LIBRARY}) -else() - find_package(OpenGL REQUIRED) - target_link_libraries(mosis-designer PRIVATE OpenGL::GL) endif() # Copy assets for development diff --git a/designer/main.cpp b/designer/main.cpp new file mode 100644 index 0000000..5f1957e --- /dev/null +++ b/designer/main.cpp @@ -0,0 +1,359 @@ +// D:\Dev\Mosis\MosisService\designer\main.cpp +// Mosis Designer - Desktop UI development tool with hot-reload + +#include +#include +#include +#include +#include +#include "RmlUi_Renderer_GL3.h" +#include "platform.h" +#include "desktop_platform.h" +#include "desktop_file_interface.h" +#include "hot_reload.h" +#include +#include +#include + +namespace fs = std::filesystem; + +// Global state +static GLFWwindow* g_window = nullptr; +static Rml::Context* g_context = nullptr; +static RenderInterface_GL3* g_render_interface = nullptr; +static mosis::DesktopPlatform* g_platform = nullptr; +static mosis::HotReload* g_hot_reload = nullptr; +static std::string g_current_document_path; +static bool g_needs_reload = false; + +// Resolution presets +static int g_width = 540; +static int g_height = 960; + +// Forward declarations +bool InitializeRmlUi(const std::string& assets_path); +void ShutdownRmlUi(); +bool LoadDocument(const std::string& path); +void ReloadDocument(); + +// GLFW callbacks +static void ErrorCallback(int error, const char* description) { + std::cerr << "GLFW Error " << error << ": " << description << std::endl; +} + +static void KeyCallback(GLFWwindow* window, int key, int scancode, int action, int mods) { + if (action != GLFW_PRESS) return; + + // F5 - Reload + if (key == GLFW_KEY_F5) { + g_needs_reload = true; + } + // F12 - Toggle debugger + else if (key == GLFW_KEY_F12) { + Rml::Debugger::SetVisible(!Rml::Debugger::IsVisible()); + } + // Escape - Back navigation + else if (key == GLFW_KEY_ESCAPE) { + if (g_context) { + g_context->ProcessKeyDown(Rml::Input::KI_ESCAPE, 0); + g_context->ProcessKeyUp(Rml::Input::KI_ESCAPE, 0); + } + } +} + +static void MouseButtonCallback(GLFWwindow* window, int button, int action, int mods) { + if (!g_context) return; + + double xpos, ypos; + glfwGetCursorPos(window, &xpos, &ypos); + + int key_modifier = 0; + if (mods & GLFW_MOD_CONTROL) key_modifier |= Rml::Input::KM_CTRL; + if (mods & GLFW_MOD_SHIFT) key_modifier |= Rml::Input::KM_SHIFT; + if (mods & GLFW_MOD_ALT) key_modifier |= Rml::Input::KM_ALT; + + // Update mouse position before processing button event + g_context->ProcessMouseMove(static_cast(xpos), static_cast(ypos), key_modifier); + + if (button == GLFW_MOUSE_BUTTON_LEFT) { + if (action == GLFW_PRESS) { + g_context->ProcessMouseButtonDown(0, key_modifier); + } else if (action == GLFW_RELEASE) { + g_context->ProcessMouseButtonUp(0, key_modifier); + } + } +} + +static void CursorPosCallback(GLFWwindow* window, double xpos, double ypos) { + if (!g_context) return; + g_context->ProcessMouseMove(static_cast(xpos), static_cast(ypos), 0); +} + +static void ScrollCallback(GLFWwindow* window, double xoffset, double yoffset) { + if (!g_context) return; + g_context->ProcessMouseWheel(static_cast(-yoffset), 0); +} + +// System interface for RmlUi +class DesktopSystemInterface : public Rml::SystemInterface { +public: + double GetElapsedTime() override { + return glfwGetTime(); + } + + bool LogMessage(Rml::Log::Type type, const Rml::String& message) override { + const char* type_str = ""; + switch (type) { + case Rml::Log::LT_ERROR: type_str = "[ERROR]"; break; + case Rml::Log::LT_WARNING: type_str = "[WARN]"; break; + case Rml::Log::LT_INFO: type_str = "[INFO]"; break; + default: type_str = "[DEBUG]"; break; + } + std::cout << type_str << " " << message << std::endl; + return true; + } +}; + +static DesktopSystemInterface g_system_interface; + + +int main(int argc, char* argv[]) { + std::cout << "Mosis Designer v0.1.0" << std::endl; + std::cout << "Press F5 to reload, F12 for debugger, ESC for back" << std::endl; + + // Parse arguments + std::string document_path; + std::string assets_path = "assets"; // Default relative to executable + + for (int i = 1; i < argc; ++i) { + std::string arg = argv[i]; + if (arg == "--resolution" && i + 1 < argc) { + std::string res = argv[++i]; + size_t x = res.find('x'); + if (x != std::string::npos) { + g_width = std::stoi(res.substr(0, x)); + g_height = std::stoi(res.substr(x + 1)); + } + } else if (arg == "--assets" && i + 1 < argc) { + assets_path = argv[++i]; + } else if (arg[0] != '-') { + document_path = arg; + } + } + + // Default document + if (document_path.empty()) { + document_path = "apps/home/home.rml"; + } + + // Make assets_path absolute + assets_path = fs::absolute(assets_path).string(); + std::cout << "Assets path: " << assets_path << std::endl; + std::cout << "Resolution: " << g_width << "x" << g_height << std::endl; + + // Initialize GLFW + glfwSetErrorCallback(ErrorCallback); + if (!glfwInit()) { + std::cerr << "Failed to initialize GLFW" << std::endl; + return 1; + } + + // Create window with OpenGL 3.3 core context + glfwWindowHint(GLFW_CONTEXT_VERSION_MAJOR, 3); + glfwWindowHint(GLFW_CONTEXT_VERSION_MINOR, 3); + glfwWindowHint(GLFW_OPENGL_PROFILE, GLFW_OPENGL_CORE_PROFILE); + glfwWindowHint(GLFW_RESIZABLE, GLFW_FALSE); + + g_window = glfwCreateWindow(g_width, g_height, "Mosis Designer", nullptr, nullptr); + if (!g_window) { + std::cerr << "Failed to create GLFW window" << std::endl; + glfwTerminate(); + return 1; + } + + glfwMakeContextCurrent(g_window); + glfwSwapInterval(1); // VSync + + // Set callbacks + glfwSetKeyCallback(g_window, KeyCallback); + glfwSetMouseButtonCallback(g_window, MouseButtonCallback); + glfwSetCursorPosCallback(g_window, CursorPosCallback); + glfwSetScrollCallback(g_window, ScrollCallback); + + // Load OpenGL functions with GLAD + if (!gladLoadGLLoader((GLADloadproc)glfwGetProcAddress)) { + std::cerr << "Failed to initialize GLAD" << std::endl; + glfwDestroyWindow(g_window); + glfwTerminate(); + return 1; + } + + std::cout << "OpenGL " << glGetString(GL_VERSION) << std::endl; + + // Create platform abstraction and set as global singleton + auto platform = std::make_unique(g_window, g_width, g_height); + platform->SetAssetsPath(assets_path); + g_platform = platform.get(); + mosis::SetPlatform(std::move(platform)); + + // Initialize RmlUi + if (!InitializeRmlUi(assets_path)) { + std::cerr << "Failed to initialize RmlUi" << std::endl; + glfwDestroyWindow(g_window); + glfwTerminate(); + return 1; + } + + // Load initial document + if (!LoadDocument(document_path)) { + std::cerr << "Failed to load document: " << document_path << std::endl; + } + + // Set up hot-reload + g_hot_reload = new mosis::HotReload(assets_path, []() { + g_needs_reload = true; + }); + g_hot_reload->Start(); + std::cout << "Hot-reload enabled for: " << assets_path << std::endl; + + // Main loop + while (!glfwWindowShouldClose(g_window)) { + glfwPollEvents(); + + // Handle hot-reload + if (g_needs_reload) { + g_needs_reload = false; + ReloadDocument(); + } + + // Clear + glClearColor(0.1f, 0.1f, 0.1f, 1.0f); + glClear(GL_COLOR_BUFFER_BIT); + + // Update and render + if (g_context) { + g_context->Update(); + g_render_interface->BeginFrame(); + g_context->Render(); + g_render_interface->EndFrame(0); + } + + glfwSwapBuffers(g_window); + } + + // Cleanup + if (g_hot_reload) { + g_hot_reload->Stop(); + delete g_hot_reload; + } + ShutdownRmlUi(); + glfwDestroyWindow(g_window); + glfwTerminate(); + + return 0; +} + + +bool InitializeRmlUi(const std::string& assets_path) { + // Create render interface + g_render_interface = new RenderInterface_GL3(); + if (!*g_render_interface) { + std::cerr << "Failed to create GL3 render interface" << std::endl; + return false; + } + g_render_interface->SetViewport(g_width, g_height); + + // Initialize RmlUi + Rml::SetSystemInterface(&g_system_interface); + Rml::SetFileInterface(&g_platform->GetFileInterface()); + Rml::SetRenderInterface(g_render_interface); + + if (!Rml::Initialise()) { + std::cerr << "Failed to initialize RmlUi" << std::endl; + return false; + } + + // Initialize Lua bindings + Rml::Lua::Initialise(); + + // Load fonts + std::vector fonts = { + "fonts/LatoLatin-Regular.ttf", + "fonts/LatoLatin-Bold.ttf", + "fonts/LatoLatin-Light.ttf", + }; + for (const auto& font : fonts) { + if (!Rml::LoadFontFace(font)) { + std::cerr << "Warning: Failed to load font: " << font << std::endl; + } + } + + // Create context + g_context = Rml::CreateContext("main", Rml::Vector2i(g_width, g_height)); + if (!g_context) { + std::cerr << "Failed to create RmlUi context" << std::endl; + return false; + } + + // Initialize debugger + Rml::Debugger::Initialise(g_context); + + return true; +} + +void ShutdownRmlUi() { + if (g_context) { + Rml::RemoveContext("main"); + g_context = nullptr; + } + + // Rml::Lua is shut down automatically when Rml::Shutdown() is called + Rml::Shutdown(); + + delete g_render_interface; + g_render_interface = nullptr; + + // Platform (and its file interface) is managed by the global singleton +} + +bool LoadDocument(const std::string& path) { + if (!g_context) return false; + + // Close existing documents + while (g_context->GetNumDocuments() > 0) { + auto* doc = g_context->GetDocument(0); + if (doc) doc->Close(); + } + + // Load new document + auto* document = g_context->LoadDocument(path); + if (!document) { + std::cerr << "Failed to load: " << path << std::endl; + return false; + } + + document->Show(); + g_current_document_path = path; + std::cout << "Loaded: " << path << std::endl; + + return true; +} + +void ReloadDocument() { + std::cout << "Reloading..." << std::endl; + + // Reload stylesheets + for (int i = 0; i < g_context->GetNumDocuments(); ++i) { + auto* doc = g_context->GetDocument(i); + if (doc) { + doc->ReloadStyleSheet(); + } + } + + // Reload document + if (!g_current_document_path.empty()) { + LoadDocument(g_current_document_path); + } + + std::cout << "Reload complete" << std::endl; +} diff --git a/designer/src/desktop_file_interface.cpp b/designer/src/desktop_file_interface.cpp new file mode 100644 index 0000000..7cdb946 --- /dev/null +++ b/designer/src/desktop_file_interface.cpp @@ -0,0 +1,66 @@ +// D:\Dev\Mosis\MosisService\designer\src\desktop_file_interface.cpp +#include "desktop_file_interface.h" +#include +#include + +namespace fs = std::filesystem; + +namespace mosis { + +void DesktopFileInterface::SetAssetsPath(const std::string& path) { + m_assets_path = path; + // Ensure trailing separator + if (!m_assets_path.empty() && m_assets_path.back() != '/' && m_assets_path.back() != '\\') { + m_assets_path += '/'; + } +} + +std::string DesktopFileInterface::ResolvePath(const std::string& path) const { + // If path is absolute, use it directly + if (fs::path(path).is_absolute()) { + return path; + } + // Otherwise, prepend assets path + return m_assets_path + path; +} + +Rml::FileHandle DesktopFileInterface::Open(const Rml::String& path) { + std::string resolved = ResolvePath(path); + FILE* file = fopen(resolved.c_str(), "rb"); + return reinterpret_cast(file); +} + +void DesktopFileInterface::Close(Rml::FileHandle file) { + if (file) { + fclose(reinterpret_cast(file)); + } +} + +size_t DesktopFileInterface::Read(void* buffer, size_t size, Rml::FileHandle file) { + if (!file) return 0; + return fread(buffer, 1, size, reinterpret_cast(file)); +} + +bool DesktopFileInterface::Seek(Rml::FileHandle file, long offset, int origin) { + if (!file) return false; + return fseek(reinterpret_cast(file), offset, origin) == 0; +} + +size_t DesktopFileInterface::Tell(Rml::FileHandle file) { + if (!file) return 0; + return static_cast(ftell(reinterpret_cast(file))); +} + +size_t DesktopFileInterface::Length(Rml::FileHandle file) { + if (!file) return 0; + + FILE* f = reinterpret_cast(file); + long current = ftell(f); + fseek(f, 0, SEEK_END); + long length = ftell(f); + fseek(f, current, SEEK_SET); + + return static_cast(length); +} + +} // namespace mosis diff --git a/designer/src/desktop_file_interface.h b/designer/src/desktop_file_interface.h new file mode 100644 index 0000000..51bfc6b --- /dev/null +++ b/designer/src/desktop_file_interface.h @@ -0,0 +1,34 @@ +// D:\Dev\Mosis\MosisService\designer\src\desktop_file_interface.h +#pragma once + +#include +#include +#include +#include + +namespace mosis { + +class DesktopFileInterface : public Rml::FileInterface { +public: + DesktopFileInterface() = default; + ~DesktopFileInterface() override = default; + + // Asset path management + void SetAssetsPath(const std::string& path); + std::string GetAssetsPath() const { return m_assets_path; } + + // RmlUi FileInterface + Rml::FileHandle Open(const Rml::String& path) override; + void Close(Rml::FileHandle file) override; + size_t Read(void* buffer, size_t size, Rml::FileHandle file) override; + bool Seek(Rml::FileHandle file, long offset, int origin) override; + size_t Tell(Rml::FileHandle file) override; + size_t Length(Rml::FileHandle file) override; + +private: + std::string ResolvePath(const std::string& path) const; + + std::string m_assets_path; +}; + +} // namespace mosis diff --git a/designer/src/desktop_platform.cpp b/designer/src/desktop_platform.cpp index fafb20d..7ac5df0 100644 --- a/designer/src/desktop_platform.cpp +++ b/designer/src/desktop_platform.cpp @@ -1,89 +1,122 @@ -// Desktop platform implementation (simplified - uses RmlUi backend for graphics) +// D:\Dev\Mosis\MosisService\designer\src\desktop_platform.cpp +// Include glad BEFORE GLFW +#include +#include #include "desktop_platform.h" #include -#include -// Note: Graphics context and rendering is handled by RmlUi's backend. -// This platform implementation provides additional utilities and state management. +namespace mosis { -namespace mosis::desktop { +// DesktopPlatform implementation -DesktopPlatform::DesktopPlatform() - : m_file_interface(std::make_unique()) +DesktopPlatform::DesktopPlatform(GLFWwindow* window, uint32_t width, uint32_t height) + : m_window(window) + , m_width(width) + , m_height(height) { - auto now = std::chrono::steady_clock::now(); - m_start_time = std::chrono::duration(now.time_since_epoch()).count(); -} - -DesktopPlatform::~DesktopPlatform() = default; - -bool DesktopPlatform::Initialize(uint32_t width, uint32_t height, const char* title) { - m_width = width; - m_height = height; - // Graphics initialization is done by RmlUi Backend - return true; -} - -void DesktopPlatform::Shutdown() { - // Graphics shutdown is done by RmlUi Backend } std::unique_ptr DesktopPlatform::CreateGraphicsContext() { - // Graphics context is managed by RmlUi Backend + // On desktop, GLFW manages the context, so we don't need a separate wrapper + // Return nullptr to indicate context is already managed return nullptr; } std::unique_ptr DesktopPlatform::CreateRenderTarget(uint32_t width, uint32_t height) { - // Render targets are managed by RmlUi Backend + auto target = std::make_unique(); + if (target->Create(width, height)) { + return target; + } return nullptr; } -IFileInterface& DesktopPlatform::GetFileInterface() { - return *m_file_interface; +Rml::FileInterface& DesktopPlatform::GetFileInterface() { + return m_file_interface; } void DesktopPlatform::Log(const std::string& message) { - std::cout << "[INFO] " << message << std::endl; -} - -void DesktopPlatform::LogError(const std::string& message) { - std::cerr << "[ERROR] " << message << std::endl; + std::cout << message << std::endl; } bool DesktopPlatform::PollEvents() { - // Events are handled by RmlUi Backend - return true; + glfwPollEvents(); + return !glfwWindowShouldClose(m_window); } void DesktopPlatform::SwapBuffers() { - // Swap is handled by RmlUi Backend -} - -bool DesktopPlatform::ShouldClose() const { - return false; // Determined by RmlUi Backend + glfwSwapBuffers(m_window); } void DesktopPlatform::SetResolution(uint32_t width, uint32_t height) { m_width = width; m_height = height; + glfwSetWindowSize(m_window, width, height); } -float DesktopPlatform::GetDpiScale() const { - return 1.0f; +void DesktopPlatform::SetAssetsPath(const std::string& path) { + m_file_interface.SetAssetsPath(path); } -double DesktopPlatform::GetElapsedTime() const { - auto now = std::chrono::steady_clock::now(); - double current = std::chrono::duration(now.time_since_epoch()).count(); - return current - m_start_time; +// DesktopRenderTarget implementation + +DesktopRenderTarget::~DesktopRenderTarget() { + Destroy(); } -bool DesktopPlatform::IsMouseButtonDown() const { - return false; // Input is handled through RmlUi +bool DesktopRenderTarget::Create(uint32_t width, uint32_t height) { + m_width = width; + m_height = height; + + // Create framebuffer + glGenFramebuffers(1, &m_framebuffer); + glBindFramebuffer(GL_FRAMEBUFFER, m_framebuffer); + + // Create color texture + glGenTextures(1, &m_texture); + glBindTexture(GL_TEXTURE_2D, m_texture); + glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA8, width, height, 0, GL_RGBA, GL_UNSIGNED_BYTE, nullptr); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR); + glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, m_texture, 0); + + // Create depth/stencil renderbuffer + glGenRenderbuffers(1, &m_depth_buffer); + glBindRenderbuffer(GL_RENDERBUFFER, m_depth_buffer); + glRenderbufferStorage(GL_RENDERBUFFER, GL_DEPTH24_STENCIL8, width, height); + glFramebufferRenderbuffer(GL_FRAMEBUFFER, GL_DEPTH_STENCIL_ATTACHMENT, GL_RENDERBUFFER, m_depth_buffer); + + // Check completeness + if (glCheckFramebufferStatus(GL_FRAMEBUFFER) != GL_FRAMEBUFFER_COMPLETE) { + Destroy(); + return false; + } + + glBindFramebuffer(GL_FRAMEBUFFER, 0); + return true; } -void DesktopPlatform::GetMousePosition(double& x, double& y) const { - x = y = 0; // Input is handled through RmlUi +void DesktopRenderTarget::Bind() { + glBindFramebuffer(GL_FRAMEBUFFER, m_framebuffer); + glViewport(0, 0, m_width, m_height); } -} // namespace mosis::desktop +void DesktopRenderTarget::Unbind() { + glBindFramebuffer(GL_FRAMEBUFFER, 0); +} + +void DesktopRenderTarget::Destroy() { + if (m_depth_buffer) { + glDeleteRenderbuffers(1, &m_depth_buffer); + m_depth_buffer = 0; + } + if (m_texture) { + glDeleteTextures(1, &m_texture); + m_texture = 0; + } + if (m_framebuffer) { + glDeleteFramebuffers(1, &m_framebuffer); + m_framebuffer = 0; + } +} + +} // namespace mosis diff --git a/designer/src/desktop_platform.h b/designer/src/desktop_platform.h index 3c25716..4dac1fe 100644 --- a/designer/src/desktop_platform.h +++ b/designer/src/desktop_platform.h @@ -1,50 +1,62 @@ -// Desktop platform implementation using GLFW + OpenGL 3.3 -// Note: Graphics context and rendering is handled by RmlUi's backend. -// This platform implementation provides file interface and utilities. +// D:\Dev\Mosis\MosisService\designer\src\desktop_platform.h #pragma once #include "platform.h" -#include "file_interface.h" -#include +#include "desktop_file_interface.h" -namespace mosis::desktop { +// Forward declare GLFW types to avoid including glfw3.h here +struct GLFWwindow; + +namespace mosis { class DesktopPlatform : public IPlatform { - uint32_t m_width = 540; - uint32_t m_height = 960; - std::unique_ptr m_file_interface; - double m_start_time = 0.0; - public: - DesktopPlatform(); - ~DesktopPlatform() override; + DesktopPlatform(GLFWwindow* window, uint32_t width, uint32_t height); + ~DesktopPlatform() override = default; - // Initialize the platform - bool Initialize(uint32_t width, uint32_t height, const char* title); - void Shutdown(); - - // IPlatform implementation + // IPlatform interface std::unique_ptr CreateGraphicsContext() override; std::unique_ptr CreateRenderTarget(uint32_t width, uint32_t height) override; - IFileInterface& GetFileInterface() override; - + Rml::FileInterface& GetFileInterface() override; void Log(const std::string& message) override; - void LogError(const std::string& message) override; - bool PollEvents() override; void SwapBuffers() override; - bool ShouldClose() const override; - uint32_t GetWidth() const override { return m_width; } uint32_t GetHeight() const override { return m_height; } void SetResolution(uint32_t width, uint32_t height) override; - float GetDpiScale() const override; - double GetElapsedTime() const override; + // Desktop-specific + void SetAssetsPath(const std::string& path); + GLFWwindow* GetWindow() const { return m_window; } - // Input state (delegated to RmlUi backend) - bool IsMouseButtonDown() const; - void GetMousePosition(double& x, double& y) const; +private: + GLFWwindow* m_window; + uint32_t m_width; + uint32_t m_height; + DesktopFileInterface m_file_interface; }; -} // namespace mosis::desktop +// Desktop render target using FBO +class DesktopRenderTarget : public IRenderTarget { +public: + DesktopRenderTarget() = default; + ~DesktopRenderTarget() override; + + bool Create(uint32_t width, uint32_t height) override; + void Bind() override; + void Unbind() override; + void Destroy() override; + uint32_t GetFramebuffer() const override { return m_framebuffer; } + uint32_t GetTexture() const override { return m_texture; } + uint32_t GetWidth() const override { return m_width; } + uint32_t GetHeight() const override { return m_height; } + +private: + uint32_t m_framebuffer = 0; + uint32_t m_texture = 0; + uint32_t m_depth_buffer = 0; + uint32_t m_width = 0; + uint32_t m_height = 0; +}; + +} // namespace mosis diff --git a/designer/src/glad_loader.h b/designer/src/glad_loader.h new file mode 100644 index 0000000..1da013f --- /dev/null +++ b/designer/src/glad_loader.h @@ -0,0 +1,11 @@ +// D:\Dev\Mosis\MosisService\designer\src\glad_loader.h +// Wrapper header for glad that sets up correct shader version for desktop OpenGL + +#pragma once + +// Define the shader version for desktop OpenGL 3.3 core profile +// This must be defined BEFORE the renderer includes glad via RMLUI_GL3_CUSTOM_LOADER +#define RMLUI_SHADER_HEADER_VERSION "#version 330\n" + +// Now include the actual glad header +#include diff --git a/designer/src/hot_reload.cpp b/designer/src/hot_reload.cpp index c0a497d..ebcd586 100644 --- a/designer/src/hot_reload.cpp +++ b/designer/src/hot_reload.cpp @@ -1,4 +1,4 @@ -// Hot-reload file watcher implementation +// D:\Dev\Mosis\MosisService\designer\src\hot_reload.cpp #include "hot_reload.h" #include @@ -6,12 +6,11 @@ #include #endif -namespace mosis::desktop { +namespace mosis { HotReload::HotReload(const std::filesystem::path& watch_path, ReloadCallback callback) : m_watch_path(watch_path) , m_callback(std::move(callback)) - , m_last_change(std::chrono::steady_clock::now()) {} HotReload::~HotReload() { @@ -21,31 +20,28 @@ HotReload::~HotReload() { void HotReload::Start() { if (m_running) return; + m_running = true; #ifdef _WIN32 m_notification_handle = FindFirstChangeNotificationW( m_watch_path.c_str(), TRUE, // Watch subtree FILE_NOTIFY_CHANGE_LAST_WRITE ); - + if (m_notification_handle == INVALID_HANDLE_VALUE) { - std::cerr << "Failed to create file change notification for: " - << m_watch_path << std::endl; + std::cerr << "Failed to set up file watching" << std::endl; + m_running = false; return; } #endif - - m_running = true; m_watch_thread = std::thread(&HotReload::WatchThread, this); } void HotReload::Stop() { m_running = false; - if (m_watch_thread.joinable()) { m_watch_thread.join(); } - #ifdef _WIN32 if (m_notification_handle && m_notification_handle != INVALID_HANDLE_VALUE) { FindCloseChangeNotification(m_notification_handle); @@ -54,40 +50,23 @@ void HotReload::Stop() { #endif } -bool HotReload::CheckForChanges() { - if (m_change_detected) { - auto now = std::chrono::steady_clock::now(); - if (now - m_last_change >= m_debounce_delay) { - m_change_detected = false; - if (m_callback) { - m_callback(); - } - return true; - } - } - return false; -} - void HotReload::WatchThread() { while (m_running) { #ifdef _WIN32 DWORD result = WaitForSingleObject(m_notification_handle, 100); // 100ms timeout if (result == WAIT_OBJECT_0) { - m_last_change = std::chrono::steady_clock::now(); - m_change_detected = true; - - // Reset the notification - if (!FindNextChangeNotification(m_notification_handle)) { - std::cerr << "FindNextChangeNotification failed" << std::endl; - break; + // Debounce - wait a bit for file writes to complete + std::this_thread::sleep_for(std::chrono::milliseconds(100)); + if (m_callback) { + m_callback(); } + FindNextChangeNotification(m_notification_handle); } #else // TODO: Linux inotify / macOS FSEvents implementation - // For now, just sleep to avoid busy loop std::this_thread::sleep_for(std::chrono::milliseconds(100)); #endif } } -} // namespace mosis::desktop +} // namespace mosis diff --git a/designer/src/hot_reload.h b/designer/src/hot_reload.h index 5e90438..08c6cb5 100644 --- a/designer/src/hot_reload.h +++ b/designer/src/hot_reload.h @@ -1,13 +1,12 @@ -// Hot-reload file watcher for development +// D:\Dev\Mosis\MosisService\designer\src\hot_reload.h #pragma once #include #include #include #include -#include -namespace mosis::desktop { +namespace mosis { class HotReload { public: @@ -20,9 +19,6 @@ public: void Stop(); bool IsRunning() const { return m_running; } - // Check for changes (call from main thread if not using threaded mode) - bool CheckForChanges(); - private: void WatchThread(); @@ -30,15 +26,10 @@ private: ReloadCallback m_callback; std::thread m_watch_thread; std::atomic m_running{false}; - std::atomic m_change_detected{false}; #ifdef _WIN32 void* m_notification_handle = nullptr; // HANDLE #endif - - // Debounce settings - std::chrono::milliseconds m_debounce_delay{100}; - std::chrono::steady_clock::time_point m_last_change; }; -} // namespace mosis::desktop +} // namespace mosis diff --git a/designer/src/platform_singleton.cpp b/designer/src/platform_singleton.cpp new file mode 100644 index 0000000..fddbb8e --- /dev/null +++ b/designer/src/platform_singleton.cpp @@ -0,0 +1,23 @@ +// D:\Dev\Mosis\MosisService\designer\src\platform_singleton.cpp +// Platform singleton implementation for desktop + +#include "platform.h" +#include + +namespace mosis { + +// Platform singleton +static std::unique_ptr g_platform; + +IPlatform& GetPlatform() { + if (!g_platform) { + throw std::runtime_error("Platform not initialized. Call SetPlatform first."); + } + return *g_platform; +} + +void SetPlatform(std::unique_ptr platform) { + g_platform = std::move(platform); +} + +} // namespace mosis diff --git a/designer/vcpkg.json b/designer/vcpkg.json index 19be15f..d219f3e 100644 --- a/designer/vcpkg.json +++ b/designer/vcpkg.json @@ -1,12 +1,15 @@ { "name": "mosis-designer", "version-string": "0.1.0", - "description": "Desktop designer and testing tool for Mosis virtual smartphone UI", "dependencies": [ "glfw3", "freetype", "lua", "libpng", - "nlohmann-json" + "nlohmann-json", + { + "name": "glad", + "features": ["gl-api-33"] + } ] } diff --git a/src/main/kernel/include/file_interface.h b/src/main/kernel/include/file_interface.h index 61edf6a..417c9a7 100644 --- a/src/main/kernel/include/file_interface.h +++ b/src/main/kernel/include/file_interface.h @@ -1,11 +1,10 @@ -// File interface abstraction - extends RmlUi FileInterface for cross-platform +// D:\Dev\Mosis\MosisService\src\main\kernel\include\file_interface.h #pragma once #include #include #include #include -#include namespace mosis { @@ -16,17 +15,12 @@ public: // Additional utility methods virtual std::vector ReadAll(const std::string& path) = 0; - virtual bool FileExists(const std::string& path) = 0; - virtual std::vector ListDirectory(const std::string& path) = 0; // Asset path management virtual void SetAssetsPath(const std::string& path) = 0; virtual std::string GetAssetsPath() const = 0; - // Resolve a relative path to full path - virtual std::string ResolvePath(const std::string& relative_path) = 0; - - // RmlUi FileInterface methods (must implement) + // RmlUi FileInterface methods (must implement in derived classes) // Rml::FileHandle Open(const Rml::String& path) override; // void Close(Rml::FileHandle file) override; // size_t Read(void* buffer, size_t size, Rml::FileHandle file) override; @@ -36,33 +30,4 @@ public: // bool LoadFile(const Rml::String& path, Rml::String& out_data) override; }; -// Desktop file interface implementation -class DesktopFileInterface : public IFileInterface { - std::filesystem::path m_assets_path; - -public: - DesktopFileInterface() = default; - explicit DesktopFileInterface(const std::string& assets_path); - - // RmlUi FileInterface implementation - Rml::FileHandle Open(const Rml::String& path) override; - void Close(Rml::FileHandle file) override; - size_t Read(void* buffer, size_t size, Rml::FileHandle file) override; - bool Seek(Rml::FileHandle file, long offset, int origin) override; - size_t Tell(Rml::FileHandle file) override; - size_t Length(Rml::FileHandle file) override; - bool LoadFile(const Rml::String& path, Rml::String& out_data) override; - - // Extended interface - std::vector ReadAll(const std::string& path) override; - bool FileExists(const std::string& path) override; - std::vector ListDirectory(const std::string& path) override; - void SetAssetsPath(const std::string& path) override; - std::string GetAssetsPath() const override; - std::string ResolvePath(const std::string& relative_path) override; - -private: - std::filesystem::path ResolveFilePath(const std::string& path); -}; - } // namespace mosis diff --git a/src/main/kernel/include/platform.h b/src/main/kernel/include/platform.h index c7331f2..3dadd6c 100644 --- a/src/main/kernel/include/platform.h +++ b/src/main/kernel/include/platform.h @@ -1,30 +1,17 @@ -// Platform abstraction layer for cross-platform Mosis kernel +// D:\Dev\Mosis\MosisService\src\main\kernel\include\platform.h #pragma once +#include #include #include #include #include -#include namespace mosis { // Forward declarations class IGraphicsContext; class IRenderTarget; -class IFileInterface; - -// Resolution preset structure -struct Resolution { - uint32_t width; - uint32_t height; - const char* name; - - static constexpr Resolution PhoneSD() { return {540, 960, "Phone SD"}; } - static constexpr Resolution PhoneHD() { return {720, 1280, "Phone HD"}; } - static constexpr Resolution PhoneFHD() { return {1080, 1920, "Phone FHD"}; } - static constexpr Resolution Tablet() { return {800, 1280, "Tablet"}; } -}; // Platform abstraction - implemented differently per platform class IPlatform { @@ -34,25 +21,19 @@ public: // Factory methods virtual std::unique_ptr CreateGraphicsContext() = 0; virtual std::unique_ptr CreateRenderTarget(uint32_t width, uint32_t height) = 0; - virtual IFileInterface& GetFileInterface() = 0; + virtual Rml::FileInterface& GetFileInterface() = 0; // Logging virtual void Log(const std::string& message) = 0; - virtual void LogError(const std::string& message) = 0; // Platform-specific windowing (desktop only, no-op on Android) virtual bool PollEvents() { return true; } virtual void SwapBuffers() {} - virtual bool ShouldClose() const { return false; } // Resolution management virtual uint32_t GetWidth() const = 0; virtual uint32_t GetHeight() const = 0; virtual void SetResolution(uint32_t width, uint32_t height) = 0; - virtual float GetDpiScale() const { return 1.0f; } - - // Time - virtual double GetElapsedTime() const = 0; }; // Graphics context abstraction @@ -63,10 +44,9 @@ public: virtual void Destroy() = 0; virtual void MakeCurrent() = 0; virtual void SwapBuffers() = 0; - virtual bool IsValid() const = 0; }; -// Render target abstraction (framebuffer) +// Render target abstraction class IRenderTarget { public: virtual ~IRenderTarget() = default; @@ -78,14 +58,10 @@ public: virtual uint32_t GetTexture() const = 0; virtual uint32_t GetWidth() const = 0; virtual uint32_t GetHeight() const = 0; - - // Read pixels for screenshot capture - virtual std::vector ReadPixels() const = 0; }; // Global platform accessor IPlatform& GetPlatform(); void SetPlatform(std::unique_ptr platform); -bool HasPlatform(); } // namespace mosis diff --git a/src/main/kernel/include/service_interface.h b/src/main/kernel/include/service_interface.h index ed69e2a..38a9ddd 100644 --- a/src/main/kernel/include/service_interface.h +++ b/src/main/kernel/include/service_interface.h @@ -1,11 +1,9 @@ -// Service interface abstraction - mirrors Android Binder for cross-platform +// D:\Dev\Mosis\MosisService\src\main\kernel\include\service_interface.h #pragma once #include #include #include -#include -#include namespace mosis { @@ -13,14 +11,8 @@ namespace mosis { class IServiceListener { public: virtual ~IServiceListener() = default; - - // Called when kernel initialization completes virtual void OnServiceInitialized(bool success) = 0; - - // Called when a new render buffer is available (with optional shared buffer handle) - virtual void OnBufferAvailable(void* buffer_handle) = 0; - - // Called when a new frame has been rendered + virtual void OnBufferAvailable() = 0; // Simplified - no HardwareBuffer on desktop virtual void OnFrameAvailable() = 0; }; @@ -43,31 +35,19 @@ public: virtual void OnHomeButton() = 0; virtual void OnRecentsButton() = 0; - // Hot-reload support (desktop) + // Hot-reload support virtual void RequestReload() = 0; - // Document loading - virtual void LoadDocument(const std::string& path) = 0; + // Navigation + virtual void NavigateTo(const std::string& screen) = 0; // Lifecycle virtual void Start() = 0; virtual void Stop() = 0; virtual bool IsRunning() const = 0; - - // Single frame update (for non-threaded mode) - virtual void Update() = 0; - virtual void Render() = 0; -}; - -// Configuration for kernel creation -struct KernelConfig { - uint32_t width = 540; - uint32_t height = 960; - std::string initial_document = "apps/home/home.rml"; - bool threaded = true; // false for desktop single-threaded mode }; // Factory for creating kernel implementation -std::unique_ptr CreateKernel(const KernelConfig& config); +std::unique_ptr CreateKernel(); } // namespace mosis diff --git a/src/main/kernel/src/kernel.cpp b/src/main/kernel/src/kernel.cpp new file mode 100644 index 0000000..457a667 --- /dev/null +++ b/src/main/kernel/src/kernel.cpp @@ -0,0 +1,366 @@ +// D:\Dev\Mosis\MosisService\src\main\kernel\src\kernel.cpp +// Platform-agnostic kernel implementation + +#include "service_interface.h" +#include "platform.h" +#include "file_interface.h" +#include "RmlUi_Renderer_GL3.h" +#include +#include +#include +#include +#include +#include +#include +#include +#include + +namespace mosis { + +// Global state for Lua access +static Rml::Context* g_context = nullptr; +static Rml::ElementDocument* g_document = nullptr; + +// System interface using platform abstraction +class PlatformSystemInterface : public Rml::SystemInterface { +public: + double GetElapsedTime() override { + static auto start_time = std::chrono::steady_clock::now(); + auto now = std::chrono::steady_clock::now(); + return std::chrono::duration(now - start_time).count(); + } + + bool LogMessage(Rml::Log::Type type, const Rml::String& message) override { + const char* type_str = ""; + switch (type) { + case Rml::Log::LT_ERROR: type_str = "[ERROR]"; break; + case Rml::Log::LT_WARNING: type_str = "[WARN]"; break; + case Rml::Log::LT_INFO: type_str = "[INFO]"; break; + default: type_str = "[DEBUG]"; break; + } + GetPlatform().Log(std::string(type_str) + " " + message); + return true; + } +}; + +static PlatformSystemInterface g_system_interface; + +// Lua function to load a screen document +static int LuaLoadScreen(lua_State* L) { + if (!g_context) { + lua_pushboolean(L, false); + return 1; + } + + const char* path = luaL_checkstring(L, 1); + GetPlatform().Log(std::string("Loading screen: ") + path); + + // Unload current document + if (g_document) { + g_context->UnloadDocument(g_document); + g_document = nullptr; + } + + // Load new document + g_document = g_context->LoadDocument(path); + if (g_document) { + g_document->Show(); + GetPlatform().Log(std::string("Loaded screen: ") + path); + lua_pushboolean(L, true); + } else { + GetPlatform().Log(std::string("Failed to load screen: ") + path); + lua_pushboolean(L, false); + } + + return 1; +} + +// Register Lua functions for navigation +static void RegisterLuaFunctions() { + lua_State* L = Rml::Lua::Interpreter::GetLuaState(); + lua_pushcfunction(L, LuaLoadScreen); + lua_setglobal(L, "loadScreen"); + GetPlatform().Log("Registered Lua loadScreen function"); +} + +// Kernel implementation +class KernelImpl : public IKernel { +public: + KernelImpl() = default; + ~KernelImpl() override { + Stop(); + } + + void AddListener(std::shared_ptr listener) override { + std::lock_guard lock(m_mutex); + m_listeners.push_back(listener); + if (m_initialized) { + listener->OnServiceInitialized(true); + listener->OnBufferAvailable(); + } + } + + void RemoveListener(std::shared_ptr listener) override { + std::lock_guard lock(m_mutex); + m_listeners.erase( + std::remove(m_listeners.begin(), m_listeners.end(), listener), + m_listeners.end() + ); + } + + void OnTouchDown(float x, float y) override { + std::lock_guard lock(m_mutex); + auto& platform = GetPlatform(); + int px = static_cast(x * platform.GetWidth()); + int py = static_cast(y * platform.GetHeight()); + m_tasks.emplace_back([px, py](Rml::Context* ctx) { + ctx->ProcessMouseMove(px, py, 0); + ctx->ProcessMouseButtonDown(0, 0); + }); + } + + void OnTouchMove(float x, float y) override { + std::lock_guard lock(m_mutex); + auto& platform = GetPlatform(); + int px = static_cast(x * platform.GetWidth()); + int py = static_cast(y * platform.GetHeight()); + m_tasks.emplace_back([px, py](Rml::Context* ctx) { + ctx->ProcessMouseMove(px, py, 0); + }); + } + + void OnTouchUp(float x, float y) override { + std::lock_guard lock(m_mutex); + auto& platform = GetPlatform(); + int px = static_cast(x * platform.GetWidth()); + int py = static_cast(y * platform.GetHeight()); + m_tasks.emplace_back([px, py](Rml::Context* ctx) { + ctx->ProcessMouseMove(px, py, 0); + ctx->ProcessMouseButtonUp(0, 0); + }); + } + + void OnBackButton() override { + std::lock_guard lock(m_mutex); + m_tasks.emplace_back([](Rml::Context* ctx) { + // Execute goBack() Lua function if available + lua_State* L = Rml::Lua::Interpreter::GetLuaState(); + lua_getglobal(L, "goBack"); + if (lua_isfunction(L, -1)) { + lua_call(L, 0, 0); + } else { + lua_pop(L, 1); + } + }); + } + + void OnHomeButton() override { + std::lock_guard lock(m_mutex); + m_tasks.emplace_back([](Rml::Context* ctx) { + // Execute goHome() Lua function if available + lua_State* L = Rml::Lua::Interpreter::GetLuaState(); + lua_getglobal(L, "goHome"); + if (lua_isfunction(L, -1)) { + lua_call(L, 0, 0); + } else { + lua_pop(L, 1); + } + }); + } + + void OnRecentsButton() override { + // TODO: Implement recents/app switcher + GetPlatform().Log("Recents button pressed (not implemented)"); + } + + void RequestReload() override { + m_reload_requested = true; + } + + void NavigateTo(const std::string& screen) override { + std::lock_guard lock(m_mutex); + std::string path = screen; + m_tasks.emplace_back([path](Rml::Context* ctx) { + lua_State* L = Rml::Lua::Interpreter::GetLuaState(); + lua_getglobal(L, "navigateTo"); + if (lua_isfunction(L, -1)) { + lua_pushstring(L, path.c_str()); + lua_call(L, 1, 0); + } else { + lua_pop(L, 1); + } + }); + } + + void Start() override { + if (m_running) return; + m_running = true; + m_main_thread = std::thread(&KernelImpl::MainLoop, this); + } + + void Stop() override { + m_running = false; + if (m_main_thread.joinable()) { + m_main_thread.join(); + } + } + + bool IsRunning() const override { + return m_running; + } + +private: + void MainLoop() { + auto& platform = GetPlatform(); + uint32_t width = platform.GetWidth(); + uint32_t height = platform.GetHeight(); + + // Initialize RmlUi render interface + RenderInterface_GL3 render_interface; + if (!render_interface) { + platform.Log("Failed to create render interface"); + return; + } + + // Set up RmlUi + Rml::SetRenderInterface(&render_interface); + Rml::SetFileInterface(&platform.GetFileInterface()); + Rml::SetSystemInterface(&g_system_interface); + Rml::Initialise(); + Rml::Lua::Initialise(); + platform.Log("RmlUi initialized with Lua bindings"); + + // Register navigation functions + RegisterLuaFunctions(); + + // Create context with platform resolution + g_context = Rml::CreateContext("default", Rml::Vector2i(width, height)); + if (!g_context) { + platform.Log("Failed to create RmlUi context"); + Rml::Shutdown(); + return; + } + + // Load fonts + LoadFonts(); + + // Load initial document + g_document = g_context->LoadDocument("apps/home/home.rml"); + if (!g_document) { + platform.Log("Failed to load home.rml document"); + Rml::Shutdown(); + return; + } + g_document->Show(); + + // Notify listeners + { + std::lock_guard lock(m_mutex); + m_initialized = true; + for (auto& listener : m_listeners) { + listener->OnServiceInitialized(true); + listener->OnBufferAvailable(); + } + } + + // Main loop + while (m_running) { + // Handle reload request + if (m_reload_requested.exchange(false)) { + ReloadCurrentDocument(); + } + + // Process pending tasks + { + std::lock_guard lock(m_mutex); + for (const auto& task : m_tasks) { + task(g_context); + } + m_tasks.clear(); + } + + // Render + glClearColor(0.0f, 0.0f, 0.0f, 1.0f); + glClear(GL_COLOR_BUFFER_BIT); + glViewport(0, 0, width, height); + + g_context->Update(); + render_interface.SetViewport(width, height); + render_interface.BeginFrame(); + g_context->Render(); + render_interface.EndFrame(0); // 0 = default framebuffer on desktop + + glFinish(); + + // Notify listeners + { + std::lock_guard lock(m_mutex); + for (auto& listener : m_listeners) { + listener->OnFrameAvailable(); + } + } + + std::this_thread::sleep_for(std::chrono::milliseconds(16)); // ~60 FPS + } + + // Cleanup + g_document = nullptr; + g_context = nullptr; + Rml::Shutdown(); + } + + void LoadFonts() { + Rml::LoadFontFace("fonts/LatoLatin-Bold.ttf"); + Rml::LoadFontFace("fonts/LatoLatin-BoldItalic.ttf"); + Rml::LoadFontFace("fonts/LatoLatin-Italic.ttf"); + Rml::LoadFontFace("fonts/LatoLatin-Regular.ttf"); + Rml::LoadFontFace("fonts/NotoEmoji-Regular.ttf", true); + Rml::LoadFontFace("fonts/Roboto/Roboto-VariableFont_wdth,wght.ttf"); + Rml::LoadFontFace("fonts/Roboto/Roboto-Italic-VariableFont_wdth,wght.ttf"); + } + + void ReloadCurrentDocument() { + if (!g_context || !g_document) return; + + GetPlatform().Log("Reloading current document..."); + + // Get current document path + std::string current_path = g_document->GetSourceURL(); + + // Unload and reload + g_context->UnloadDocument(g_document); + g_document = g_context->LoadDocument(current_path); + if (g_document) { + g_document->Show(); + GetPlatform().Log("Document reloaded: " + current_path); + } else { + GetPlatform().Log("Failed to reload document: " + current_path); + } + } + + std::mutex m_mutex; + std::vector> m_listeners; + std::vector> m_tasks; + std::thread m_main_thread; + std::atomic m_running{false}; + std::atomic m_reload_requested{false}; + bool m_initialized{false}; +}; + +// Platform singleton +static std::unique_ptr g_platform; + +IPlatform& GetPlatform() { + return *g_platform; +} + +void SetPlatform(std::unique_ptr platform) { + g_platform = std::move(platform); +} + +// Factory function +std::unique_ptr CreateKernel() { + return std::make_unique(); +} + +} // namespace mosis