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

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;
}