save state

This commit is contained in:
2026-01-16 16:34:32 +01:00
parent fbb6917812
commit a35c222570
15 changed files with 1052 additions and 266 deletions

View File

@@ -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

359
designer/main.cpp Normal file
View File

@@ -0,0 +1,359 @@
// D:\Dev\Mosis\MosisService\designer\main.cpp
// Mosis Designer - Desktop UI development tool with hot-reload
#include <glad/glad.h>
#include <GLFW/glfw3.h>
#include <RmlUi/Core.h>
#include <RmlUi/Lua.h>
#include <RmlUi/Debugger.h>
#include "RmlUi_Renderer_GL3.h"
#include "platform.h"
#include "desktop_platform.h"
#include "desktop_file_interface.h"
#include "hot_reload.h"
#include <iostream>
#include <filesystem>
#include <memory>
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<int>(xpos), static_cast<int>(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<int>(xpos), static_cast<int>(ypos), 0);
}
static void ScrollCallback(GLFWwindow* window, double xoffset, double yoffset) {
if (!g_context) return;
g_context->ProcessMouseWheel(static_cast<float>(-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<mosis::DesktopPlatform>(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<std::string> 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;
}

View File

@@ -0,0 +1,66 @@
// D:\Dev\Mosis\MosisService\designer\src\desktop_file_interface.cpp
#include "desktop_file_interface.h"
#include <cstdio>
#include <filesystem>
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<Rml::FileHandle>(file);
}
void DesktopFileInterface::Close(Rml::FileHandle file) {
if (file) {
fclose(reinterpret_cast<FILE*>(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*>(file));
}
bool DesktopFileInterface::Seek(Rml::FileHandle file, long offset, int origin) {
if (!file) return false;
return fseek(reinterpret_cast<FILE*>(file), offset, origin) == 0;
}
size_t DesktopFileInterface::Tell(Rml::FileHandle file) {
if (!file) return 0;
return static_cast<size_t>(ftell(reinterpret_cast<FILE*>(file)));
}
size_t DesktopFileInterface::Length(Rml::FileHandle file) {
if (!file) return 0;
FILE* f = reinterpret_cast<FILE*>(file);
long current = ftell(f);
fseek(f, 0, SEEK_END);
long length = ftell(f);
fseek(f, current, SEEK_SET);
return static_cast<size_t>(length);
}
} // namespace mosis

View File

@@ -0,0 +1,34 @@
// D:\Dev\Mosis\MosisService\designer\src\desktop_file_interface.h
#pragma once
#include <RmlUi/Core/FileInterface.h>
#include <string>
#include <vector>
#include <cstdint>
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

View File

@@ -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 <glad/glad.h>
#include <GLFW/glfw3.h>
#include "desktop_platform.h"
#include <iostream>
#include <chrono>
// 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<DesktopFileInterface>())
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<double>(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<IGraphicsContext> 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<IRenderTarget> DesktopPlatform::CreateRenderTarget(uint32_t width, uint32_t height) {
// Render targets are managed by RmlUi Backend
auto target = std::make_unique<DesktopRenderTarget>();
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<double>(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

View File

@@ -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 <memory>
#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<DesktopFileInterface> 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<IGraphicsContext> CreateGraphicsContext() override;
std::unique_ptr<IRenderTarget> 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

View File

@@ -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 <glad/glad.h>

View File

@@ -1,4 +1,4 @@
// Hot-reload file watcher implementation
// D:\Dev\Mosis\MosisService\designer\src\hot_reload.cpp
#include "hot_reload.h"
#include <iostream>
@@ -6,12 +6,11 @@
#include <Windows.h>
#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

View File

@@ -1,13 +1,12 @@
// Hot-reload file watcher for development
// D:\Dev\Mosis\MosisService\designer\src\hot_reload.h
#pragma once
#include <filesystem>
#include <functional>
#include <thread>
#include <atomic>
#include <chrono>
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<bool> m_running{false};
std::atomic<bool> 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

View File

@@ -0,0 +1,23 @@
// D:\Dev\Mosis\MosisService\designer\src\platform_singleton.cpp
// Platform singleton implementation for desktop
#include "platform.h"
#include <stdexcept>
namespace mosis {
// Platform singleton
static std::unique_ptr<IPlatform> 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<IPlatform> platform) {
g_platform = std::move(platform);
}
} // namespace mosis

View File

@@ -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"]
}
]
}