save state
This commit is contained in:
@@ -1,16 +1,16 @@
|
|||||||
|
# D:\Dev\Mosis\MosisService\designer\CMakeLists.txt
|
||||||
cmake_minimum_required(VERSION 3.22.1)
|
cmake_minimum_required(VERSION 3.22.1)
|
||||||
project(mosis-designer)
|
project(mosis-designer)
|
||||||
|
|
||||||
set(CMAKE_CXX_STANDARD 23)
|
set(CMAKE_CXX_STANDARD 23)
|
||||||
set(CMAKE_CXX_STANDARD_REQUIRED ON)
|
set(CMAKE_CXX_STANDARD_REQUIRED ON)
|
||||||
|
|
||||||
# Find Lua before RmlUi so it can be used
|
# Find dependencies via vcpkg
|
||||||
find_package(Lua REQUIRED)
|
|
||||||
|
|
||||||
# Find other dependencies via vcpkg
|
|
||||||
find_package(glfw3 CONFIG REQUIRED)
|
find_package(glfw3 CONFIG REQUIRED)
|
||||||
find_package(freetype CONFIG REQUIRED)
|
find_package(freetype CONFIG REQUIRED)
|
||||||
find_package(PNG REQUIRED)
|
find_package(PNG REQUIRED)
|
||||||
|
find_package(Lua REQUIRED)
|
||||||
|
find_package(glad CONFIG REQUIRED)
|
||||||
find_package(nlohmann_json CONFIG REQUIRED)
|
find_package(nlohmann_json CONFIG REQUIRED)
|
||||||
|
|
||||||
# Fetch RmlUi
|
# Fetch RmlUi
|
||||||
@@ -20,80 +20,68 @@ FetchContent_Declare(
|
|||||||
GIT_REPOSITORY https://github.com/mikke89/RmlUi.git
|
GIT_REPOSITORY https://github.com/mikke89/RmlUi.git
|
||||||
GIT_TAG 6.0
|
GIT_TAG 6.0
|
||||||
)
|
)
|
||||||
|
|
||||||
# Enable RmlUi Lua bindings before fetching
|
|
||||||
set(RMLUI_LUA_BINDINGS ON CACHE BOOL "" FORCE)
|
set(RMLUI_LUA_BINDINGS ON CACHE BOOL "" FORCE)
|
||||||
set(BUILD_SHARED_LIBS OFF CACHE BOOL "" FORCE)
|
set(BUILD_SHARED_LIBS OFF CACHE BOOL "" FORCE)
|
||||||
set(RMLUI_SAMPLES OFF CACHE BOOL "" FORCE)
|
set(RMLUI_SAMPLES OFF CACHE BOOL "" FORCE)
|
||||||
set(RMLUI_TESTS OFF CACHE BOOL "" FORCE)
|
set(RMLUI_TESTS OFF CACHE BOOL "" FORCE)
|
||||||
set(RMLUI_FONT_ENGINE "freetype" CACHE STRING "" FORCE)
|
set(RMLUI_FONT_ENGINE "freetype" CACHE STRING "" FORCE)
|
||||||
|
|
||||||
FetchContent_MakeAvailable(rmlui)
|
FetchContent_MakeAvailable(rmlui)
|
||||||
|
|
||||||
# Get the RmlUi source directory for backend sources
|
# Get glad include directories explicitly
|
||||||
FetchContent_GetProperties(rmlui)
|
get_target_property(GLAD_INCLUDE_DIRS glad::glad INTERFACE_INCLUDE_DIRECTORIES)
|
||||||
set(RMLUI_SOURCE_DIR ${rmlui_SOURCE_DIR})
|
|
||||||
|
|
||||||
# Shared kernel library sources (platform-agnostic code)
|
# Shared kernel library (platform-agnostic code from MosisService)
|
||||||
set(KERNEL_SOURCES
|
add_library(mosis-kernel STATIC
|
||||||
../src/main/kernel/src/platform.cpp
|
../src/main/cpp/RmlUi_Renderer_GL3.cpp
|
||||||
../src/main/kernel/src/file_interface.cpp
|
|
||||||
)
|
)
|
||||||
|
target_include_directories(mosis-kernel PUBLIC
|
||||||
# Desktop platform sources
|
../src/main/kernel/include
|
||||||
set(DESIGNER_SOURCES
|
../src/main/cpp
|
||||||
src/main.cpp
|
${GLAD_INCLUDE_DIRS}
|
||||||
src/desktop_platform.cpp
|
${LUA_INCLUDE_DIR}
|
||||||
src/hot_reload.cpp
|
)
|
||||||
src/data_models.cpp
|
target_link_libraries(mosis-kernel PUBLIC
|
||||||
src/kernel_impl.cpp
|
rmlui
|
||||||
src/testing/action_recorder.cpp
|
rmlui_lua
|
||||||
src/testing/action_player.cpp
|
${LUA_LIBRARIES}
|
||||||
src/testing/ui_inspector.cpp
|
glad::glad
|
||||||
src/testing/visual_capture.cpp
|
)
|
||||||
# RmlUi backend sources
|
target_include_directories(mosis-kernel PUBLIC
|
||||||
${RMLUI_SOURCE_DIR}/Backends/RmlUi_Backend_GLFW_GL3.cpp
|
${CMAKE_CURRENT_SOURCE_DIR}/src
|
||||||
${RMLUI_SOURCE_DIR}/Backends/RmlUi_Platform_GLFW.cpp
|
)
|
||||||
${RMLUI_SOURCE_DIR}/Backends/RmlUi_Renderer_GL3.cpp
|
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
|
# Designer executable
|
||||||
add_executable(mosis-designer
|
add_executable(mosis-designer
|
||||||
${KERNEL_SOURCES}
|
main.cpp
|
||||||
${DESIGNER_SOURCES}
|
src/desktop_platform.cpp
|
||||||
|
src/desktop_file_interface.cpp
|
||||||
|
src/hot_reload.cpp
|
||||||
|
src/platform_singleton.cpp
|
||||||
)
|
)
|
||||||
|
|
||||||
target_include_directories(mosis-designer PRIVATE
|
target_include_directories(mosis-designer PRIVATE
|
||||||
src
|
src
|
||||||
../src/main/kernel/include
|
../src/main/kernel/include
|
||||||
${RMLUI_SOURCE_DIR}
|
../src/main/cpp
|
||||||
${RMLUI_SOURCE_DIR}/Backends
|
|
||||||
${LUA_INCLUDE_DIR}
|
|
||||||
)
|
)
|
||||||
|
|
||||||
target_link_libraries(mosis-designer PRIVATE
|
target_link_libraries(mosis-designer PRIVATE
|
||||||
|
mosis-kernel
|
||||||
|
glad::glad
|
||||||
glfw
|
glfw
|
||||||
freetype
|
freetype
|
||||||
PNG::PNG
|
PNG::PNG
|
||||||
RmlUi::RmlUi
|
|
||||||
RmlUi::Lua
|
|
||||||
nlohmann_json::nlohmann_json
|
nlohmann_json::nlohmann_json
|
||||||
)
|
)
|
||||||
|
|
||||||
target_compile_definitions(mosis-designer PRIVATE
|
# Windows-specific
|
||||||
MOSIS_PLATFORM_DESKTOP
|
|
||||||
RMLUI_STATIC_LIB
|
|
||||||
)
|
|
||||||
|
|
||||||
# Platform-specific libraries
|
|
||||||
if(WIN32)
|
if(WIN32)
|
||||||
target_link_libraries(mosis-designer PRIVATE opengl32)
|
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()
|
endif()
|
||||||
|
|
||||||
# Copy assets for development
|
# Copy assets for development
|
||||||
|
|||||||
359
designer/main.cpp
Normal file
359
designer/main.cpp
Normal 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;
|
||||||
|
}
|
||||||
66
designer/src/desktop_file_interface.cpp
Normal file
66
designer/src/desktop_file_interface.cpp
Normal 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
|
||||||
34
designer/src/desktop_file_interface.h
Normal file
34
designer/src/desktop_file_interface.h
Normal 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
|
||||||
@@ -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 "desktop_platform.h"
|
||||||
#include <iostream>
|
#include <iostream>
|
||||||
#include <chrono>
|
|
||||||
|
|
||||||
// Note: Graphics context and rendering is handled by RmlUi's backend.
|
namespace mosis {
|
||||||
// This platform implementation provides additional utilities and state management.
|
|
||||||
|
|
||||||
namespace mosis::desktop {
|
// DesktopPlatform implementation
|
||||||
|
|
||||||
DesktopPlatform::DesktopPlatform()
|
DesktopPlatform::DesktopPlatform(GLFWwindow* window, uint32_t width, uint32_t height)
|
||||||
: m_file_interface(std::make_unique<DesktopFileInterface>())
|
: 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() {
|
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;
|
return nullptr;
|
||||||
}
|
}
|
||||||
|
|
||||||
std::unique_ptr<IRenderTarget> DesktopPlatform::CreateRenderTarget(uint32_t width, uint32_t height) {
|
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;
|
return nullptr;
|
||||||
}
|
}
|
||||||
|
|
||||||
IFileInterface& DesktopPlatform::GetFileInterface() {
|
Rml::FileInterface& DesktopPlatform::GetFileInterface() {
|
||||||
return *m_file_interface;
|
return m_file_interface;
|
||||||
}
|
}
|
||||||
|
|
||||||
void DesktopPlatform::Log(const std::string& message) {
|
void DesktopPlatform::Log(const std::string& message) {
|
||||||
std::cout << "[INFO] " << message << std::endl;
|
std::cout << message << std::endl;
|
||||||
}
|
|
||||||
|
|
||||||
void DesktopPlatform::LogError(const std::string& message) {
|
|
||||||
std::cerr << "[ERROR] " << message << std::endl;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
bool DesktopPlatform::PollEvents() {
|
bool DesktopPlatform::PollEvents() {
|
||||||
// Events are handled by RmlUi Backend
|
glfwPollEvents();
|
||||||
return true;
|
return !glfwWindowShouldClose(m_window);
|
||||||
}
|
}
|
||||||
|
|
||||||
void DesktopPlatform::SwapBuffers() {
|
void DesktopPlatform::SwapBuffers() {
|
||||||
// Swap is handled by RmlUi Backend
|
glfwSwapBuffers(m_window);
|
||||||
}
|
|
||||||
|
|
||||||
bool DesktopPlatform::ShouldClose() const {
|
|
||||||
return false; // Determined by RmlUi Backend
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void DesktopPlatform::SetResolution(uint32_t width, uint32_t height) {
|
void DesktopPlatform::SetResolution(uint32_t width, uint32_t height) {
|
||||||
m_width = width;
|
m_width = width;
|
||||||
m_height = height;
|
m_height = height;
|
||||||
|
glfwSetWindowSize(m_window, width, height);
|
||||||
}
|
}
|
||||||
|
|
||||||
float DesktopPlatform::GetDpiScale() const {
|
void DesktopPlatform::SetAssetsPath(const std::string& path) {
|
||||||
return 1.0f;
|
m_file_interface.SetAssetsPath(path);
|
||||||
}
|
}
|
||||||
|
|
||||||
double DesktopPlatform::GetElapsedTime() const {
|
// DesktopRenderTarget implementation
|
||||||
auto now = std::chrono::steady_clock::now();
|
|
||||||
double current = std::chrono::duration<double>(now.time_since_epoch()).count();
|
DesktopRenderTarget::~DesktopRenderTarget() {
|
||||||
return current - m_start_time;
|
Destroy();
|
||||||
}
|
}
|
||||||
|
|
||||||
bool DesktopPlatform::IsMouseButtonDown() const {
|
bool DesktopRenderTarget::Create(uint32_t width, uint32_t height) {
|
||||||
return false; // Input is handled through RmlUi
|
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 {
|
void DesktopRenderTarget::Bind() {
|
||||||
x = y = 0; // Input is handled through RmlUi
|
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
|
||||||
|
|||||||
@@ -1,50 +1,62 @@
|
|||||||
// Desktop platform implementation using GLFW + OpenGL 3.3
|
// D:\Dev\Mosis\MosisService\designer\src\desktop_platform.h
|
||||||
// Note: Graphics context and rendering is handled by RmlUi's backend.
|
|
||||||
// This platform implementation provides file interface and utilities.
|
|
||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
#include "platform.h"
|
#include "platform.h"
|
||||||
#include "file_interface.h"
|
#include "desktop_file_interface.h"
|
||||||
#include <memory>
|
|
||||||
|
|
||||||
namespace mosis::desktop {
|
// Forward declare GLFW types to avoid including glfw3.h here
|
||||||
|
struct GLFWwindow;
|
||||||
|
|
||||||
|
namespace mosis {
|
||||||
|
|
||||||
class DesktopPlatform : public IPlatform {
|
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:
|
public:
|
||||||
DesktopPlatform();
|
DesktopPlatform(GLFWwindow* window, uint32_t width, uint32_t height);
|
||||||
~DesktopPlatform() override;
|
~DesktopPlatform() override = default;
|
||||||
|
|
||||||
// Initialize the platform
|
// IPlatform interface
|
||||||
bool Initialize(uint32_t width, uint32_t height, const char* title);
|
|
||||||
void Shutdown();
|
|
||||||
|
|
||||||
// IPlatform implementation
|
|
||||||
std::unique_ptr<IGraphicsContext> CreateGraphicsContext() override;
|
std::unique_ptr<IGraphicsContext> CreateGraphicsContext() override;
|
||||||
std::unique_ptr<IRenderTarget> CreateRenderTarget(uint32_t width, uint32_t height) 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 Log(const std::string& message) override;
|
||||||
void LogError(const std::string& message) override;
|
|
||||||
|
|
||||||
bool PollEvents() override;
|
bool PollEvents() override;
|
||||||
void SwapBuffers() override;
|
void SwapBuffers() override;
|
||||||
bool ShouldClose() const override;
|
|
||||||
|
|
||||||
uint32_t GetWidth() const override { return m_width; }
|
uint32_t GetWidth() const override { return m_width; }
|
||||||
uint32_t GetHeight() const override { return m_height; }
|
uint32_t GetHeight() const override { return m_height; }
|
||||||
void SetResolution(uint32_t width, uint32_t height) override;
|
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)
|
private:
|
||||||
bool IsMouseButtonDown() const;
|
GLFWwindow* m_window;
|
||||||
void GetMousePosition(double& x, double& y) const;
|
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
|
||||||
|
|||||||
11
designer/src/glad_loader.h
Normal file
11
designer/src/glad_loader.h
Normal 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>
|
||||||
@@ -1,4 +1,4 @@
|
|||||||
// Hot-reload file watcher implementation
|
// D:\Dev\Mosis\MosisService\designer\src\hot_reload.cpp
|
||||||
#include "hot_reload.h"
|
#include "hot_reload.h"
|
||||||
#include <iostream>
|
#include <iostream>
|
||||||
|
|
||||||
@@ -6,12 +6,11 @@
|
|||||||
#include <Windows.h>
|
#include <Windows.h>
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
namespace mosis::desktop {
|
namespace mosis {
|
||||||
|
|
||||||
HotReload::HotReload(const std::filesystem::path& watch_path, ReloadCallback callback)
|
HotReload::HotReload(const std::filesystem::path& watch_path, ReloadCallback callback)
|
||||||
: m_watch_path(watch_path)
|
: m_watch_path(watch_path)
|
||||||
, m_callback(std::move(callback))
|
, m_callback(std::move(callback))
|
||||||
, m_last_change(std::chrono::steady_clock::now())
|
|
||||||
{}
|
{}
|
||||||
|
|
||||||
HotReload::~HotReload() {
|
HotReload::~HotReload() {
|
||||||
@@ -21,6 +20,7 @@ HotReload::~HotReload() {
|
|||||||
void HotReload::Start() {
|
void HotReload::Start() {
|
||||||
if (m_running) return;
|
if (m_running) return;
|
||||||
|
|
||||||
|
m_running = true;
|
||||||
#ifdef _WIN32
|
#ifdef _WIN32
|
||||||
m_notification_handle = FindFirstChangeNotificationW(
|
m_notification_handle = FindFirstChangeNotificationW(
|
||||||
m_watch_path.c_str(),
|
m_watch_path.c_str(),
|
||||||
@@ -29,23 +29,19 @@ void HotReload::Start() {
|
|||||||
);
|
);
|
||||||
|
|
||||||
if (m_notification_handle == INVALID_HANDLE_VALUE) {
|
if (m_notification_handle == INVALID_HANDLE_VALUE) {
|
||||||
std::cerr << "Failed to create file change notification for: "
|
std::cerr << "Failed to set up file watching" << std::endl;
|
||||||
<< m_watch_path << std::endl;
|
m_running = false;
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
m_running = true;
|
|
||||||
m_watch_thread = std::thread(&HotReload::WatchThread, this);
|
m_watch_thread = std::thread(&HotReload::WatchThread, this);
|
||||||
}
|
}
|
||||||
|
|
||||||
void HotReload::Stop() {
|
void HotReload::Stop() {
|
||||||
m_running = false;
|
m_running = false;
|
||||||
|
|
||||||
if (m_watch_thread.joinable()) {
|
if (m_watch_thread.joinable()) {
|
||||||
m_watch_thread.join();
|
m_watch_thread.join();
|
||||||
}
|
}
|
||||||
|
|
||||||
#ifdef _WIN32
|
#ifdef _WIN32
|
||||||
if (m_notification_handle && m_notification_handle != INVALID_HANDLE_VALUE) {
|
if (m_notification_handle && m_notification_handle != INVALID_HANDLE_VALUE) {
|
||||||
FindCloseChangeNotification(m_notification_handle);
|
FindCloseChangeNotification(m_notification_handle);
|
||||||
@@ -54,40 +50,23 @@ void HotReload::Stop() {
|
|||||||
#endif
|
#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() {
|
void HotReload::WatchThread() {
|
||||||
while (m_running) {
|
while (m_running) {
|
||||||
#ifdef _WIN32
|
#ifdef _WIN32
|
||||||
DWORD result = WaitForSingleObject(m_notification_handle, 100); // 100ms timeout
|
DWORD result = WaitForSingleObject(m_notification_handle, 100); // 100ms timeout
|
||||||
if (result == WAIT_OBJECT_0) {
|
if (result == WAIT_OBJECT_0) {
|
||||||
m_last_change = std::chrono::steady_clock::now();
|
// Debounce - wait a bit for file writes to complete
|
||||||
m_change_detected = true;
|
std::this_thread::sleep_for(std::chrono::milliseconds(100));
|
||||||
|
if (m_callback) {
|
||||||
// Reset the notification
|
m_callback();
|
||||||
if (!FindNextChangeNotification(m_notification_handle)) {
|
|
||||||
std::cerr << "FindNextChangeNotification failed" << std::endl;
|
|
||||||
break;
|
|
||||||
}
|
}
|
||||||
|
FindNextChangeNotification(m_notification_handle);
|
||||||
}
|
}
|
||||||
#else
|
#else
|
||||||
// TODO: Linux inotify / macOS FSEvents implementation
|
// TODO: Linux inotify / macOS FSEvents implementation
|
||||||
// For now, just sleep to avoid busy loop
|
|
||||||
std::this_thread::sleep_for(std::chrono::milliseconds(100));
|
std::this_thread::sleep_for(std::chrono::milliseconds(100));
|
||||||
#endif
|
#endif
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
} // namespace mosis::desktop
|
} // namespace mosis
|
||||||
|
|||||||
@@ -1,13 +1,12 @@
|
|||||||
// Hot-reload file watcher for development
|
// D:\Dev\Mosis\MosisService\designer\src\hot_reload.h
|
||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
#include <filesystem>
|
#include <filesystem>
|
||||||
#include <functional>
|
#include <functional>
|
||||||
#include <thread>
|
#include <thread>
|
||||||
#include <atomic>
|
#include <atomic>
|
||||||
#include <chrono>
|
|
||||||
|
|
||||||
namespace mosis::desktop {
|
namespace mosis {
|
||||||
|
|
||||||
class HotReload {
|
class HotReload {
|
||||||
public:
|
public:
|
||||||
@@ -20,9 +19,6 @@ public:
|
|||||||
void Stop();
|
void Stop();
|
||||||
bool IsRunning() const { return m_running; }
|
bool IsRunning() const { return m_running; }
|
||||||
|
|
||||||
// Check for changes (call from main thread if not using threaded mode)
|
|
||||||
bool CheckForChanges();
|
|
||||||
|
|
||||||
private:
|
private:
|
||||||
void WatchThread();
|
void WatchThread();
|
||||||
|
|
||||||
@@ -30,15 +26,10 @@ private:
|
|||||||
ReloadCallback m_callback;
|
ReloadCallback m_callback;
|
||||||
std::thread m_watch_thread;
|
std::thread m_watch_thread;
|
||||||
std::atomic<bool> m_running{false};
|
std::atomic<bool> m_running{false};
|
||||||
std::atomic<bool> m_change_detected{false};
|
|
||||||
|
|
||||||
#ifdef _WIN32
|
#ifdef _WIN32
|
||||||
void* m_notification_handle = nullptr; // HANDLE
|
void* m_notification_handle = nullptr; // HANDLE
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
// Debounce settings
|
|
||||||
std::chrono::milliseconds m_debounce_delay{100};
|
|
||||||
std::chrono::steady_clock::time_point m_last_change;
|
|
||||||
};
|
};
|
||||||
|
|
||||||
} // namespace mosis::desktop
|
} // namespace mosis
|
||||||
|
|||||||
23
designer/src/platform_singleton.cpp
Normal file
23
designer/src/platform_singleton.cpp
Normal 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
|
||||||
@@ -1,12 +1,15 @@
|
|||||||
{
|
{
|
||||||
"name": "mosis-designer",
|
"name": "mosis-designer",
|
||||||
"version-string": "0.1.0",
|
"version-string": "0.1.0",
|
||||||
"description": "Desktop designer and testing tool for Mosis virtual smartphone UI",
|
|
||||||
"dependencies": [
|
"dependencies": [
|
||||||
"glfw3",
|
"glfw3",
|
||||||
"freetype",
|
"freetype",
|
||||||
"lua",
|
"lua",
|
||||||
"libpng",
|
"libpng",
|
||||||
"nlohmann-json"
|
"nlohmann-json",
|
||||||
|
{
|
||||||
|
"name": "glad",
|
||||||
|
"features": ["gl-api-33"]
|
||||||
|
}
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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
|
#pragma once
|
||||||
|
|
||||||
#include <RmlUi/Core/FileInterface.h>
|
#include <RmlUi/Core/FileInterface.h>
|
||||||
#include <string>
|
#include <string>
|
||||||
#include <vector>
|
#include <vector>
|
||||||
#include <cstdint>
|
#include <cstdint>
|
||||||
#include <filesystem>
|
|
||||||
|
|
||||||
namespace mosis {
|
namespace mosis {
|
||||||
|
|
||||||
@@ -16,17 +15,12 @@ public:
|
|||||||
|
|
||||||
// Additional utility methods
|
// Additional utility methods
|
||||||
virtual std::vector<uint8_t> ReadAll(const std::string& path) = 0;
|
virtual std::vector<uint8_t> ReadAll(const std::string& path) = 0;
|
||||||
virtual bool FileExists(const std::string& path) = 0;
|
|
||||||
virtual std::vector<std::string> ListDirectory(const std::string& path) = 0;
|
|
||||||
|
|
||||||
// Asset path management
|
// Asset path management
|
||||||
virtual void SetAssetsPath(const std::string& path) = 0;
|
virtual void SetAssetsPath(const std::string& path) = 0;
|
||||||
virtual std::string GetAssetsPath() const = 0;
|
virtual std::string GetAssetsPath() const = 0;
|
||||||
|
|
||||||
// Resolve a relative path to full path
|
// RmlUi FileInterface methods (must implement in derived classes)
|
||||||
virtual std::string ResolvePath(const std::string& relative_path) = 0;
|
|
||||||
|
|
||||||
// RmlUi FileInterface methods (must implement)
|
|
||||||
// Rml::FileHandle Open(const Rml::String& path) override;
|
// Rml::FileHandle Open(const Rml::String& path) override;
|
||||||
// void Close(Rml::FileHandle file) override;
|
// void Close(Rml::FileHandle file) override;
|
||||||
// size_t Read(void* buffer, size_t size, 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;
|
// 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<uint8_t> ReadAll(const std::string& path) override;
|
|
||||||
bool FileExists(const std::string& path) override;
|
|
||||||
std::vector<std::string> 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
|
} // namespace mosis
|
||||||
|
|||||||
@@ -1,30 +1,17 @@
|
|||||||
// Platform abstraction layer for cross-platform Mosis kernel
|
// D:\Dev\Mosis\MosisService\src\main\kernel\include\platform.h
|
||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
|
#include <RmlUi/Core/FileInterface.h>
|
||||||
#include <memory>
|
#include <memory>
|
||||||
#include <string>
|
#include <string>
|
||||||
#include <functional>
|
#include <functional>
|
||||||
#include <cstdint>
|
#include <cstdint>
|
||||||
#include <vector>
|
|
||||||
|
|
||||||
namespace mosis {
|
namespace mosis {
|
||||||
|
|
||||||
// Forward declarations
|
// Forward declarations
|
||||||
class IGraphicsContext;
|
class IGraphicsContext;
|
||||||
class IRenderTarget;
|
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
|
// Platform abstraction - implemented differently per platform
|
||||||
class IPlatform {
|
class IPlatform {
|
||||||
@@ -34,25 +21,19 @@ public:
|
|||||||
// Factory methods
|
// Factory methods
|
||||||
virtual std::unique_ptr<IGraphicsContext> CreateGraphicsContext() = 0;
|
virtual std::unique_ptr<IGraphicsContext> CreateGraphicsContext() = 0;
|
||||||
virtual std::unique_ptr<IRenderTarget> CreateRenderTarget(uint32_t width, uint32_t height) = 0;
|
virtual std::unique_ptr<IRenderTarget> CreateRenderTarget(uint32_t width, uint32_t height) = 0;
|
||||||
virtual IFileInterface& GetFileInterface() = 0;
|
virtual Rml::FileInterface& GetFileInterface() = 0;
|
||||||
|
|
||||||
// Logging
|
// Logging
|
||||||
virtual void Log(const std::string& message) = 0;
|
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)
|
// Platform-specific windowing (desktop only, no-op on Android)
|
||||||
virtual bool PollEvents() { return true; }
|
virtual bool PollEvents() { return true; }
|
||||||
virtual void SwapBuffers() {}
|
virtual void SwapBuffers() {}
|
||||||
virtual bool ShouldClose() const { return false; }
|
|
||||||
|
|
||||||
// Resolution management
|
// Resolution management
|
||||||
virtual uint32_t GetWidth() const = 0;
|
virtual uint32_t GetWidth() const = 0;
|
||||||
virtual uint32_t GetHeight() const = 0;
|
virtual uint32_t GetHeight() const = 0;
|
||||||
virtual void SetResolution(uint32_t width, uint32_t height) = 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
|
// Graphics context abstraction
|
||||||
@@ -63,10 +44,9 @@ public:
|
|||||||
virtual void Destroy() = 0;
|
virtual void Destroy() = 0;
|
||||||
virtual void MakeCurrent() = 0;
|
virtual void MakeCurrent() = 0;
|
||||||
virtual void SwapBuffers() = 0;
|
virtual void SwapBuffers() = 0;
|
||||||
virtual bool IsValid() const = 0;
|
|
||||||
};
|
};
|
||||||
|
|
||||||
// Render target abstraction (framebuffer)
|
// Render target abstraction
|
||||||
class IRenderTarget {
|
class IRenderTarget {
|
||||||
public:
|
public:
|
||||||
virtual ~IRenderTarget() = default;
|
virtual ~IRenderTarget() = default;
|
||||||
@@ -78,14 +58,10 @@ public:
|
|||||||
virtual uint32_t GetTexture() const = 0;
|
virtual uint32_t GetTexture() const = 0;
|
||||||
virtual uint32_t GetWidth() const = 0;
|
virtual uint32_t GetWidth() const = 0;
|
||||||
virtual uint32_t GetHeight() const = 0;
|
virtual uint32_t GetHeight() const = 0;
|
||||||
|
|
||||||
// Read pixels for screenshot capture
|
|
||||||
virtual std::vector<uint8_t> ReadPixels() const = 0;
|
|
||||||
};
|
};
|
||||||
|
|
||||||
// Global platform accessor
|
// Global platform accessor
|
||||||
IPlatform& GetPlatform();
|
IPlatform& GetPlatform();
|
||||||
void SetPlatform(std::unique_ptr<IPlatform> platform);
|
void SetPlatform(std::unique_ptr<IPlatform> platform);
|
||||||
bool HasPlatform();
|
|
||||||
|
|
||||||
} // namespace mosis
|
} // namespace mosis
|
||||||
|
|||||||
@@ -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
|
#pragma once
|
||||||
|
|
||||||
#include <memory>
|
#include <memory>
|
||||||
#include <functional>
|
#include <functional>
|
||||||
#include <string>
|
#include <string>
|
||||||
#include <cstdint>
|
|
||||||
#include <vector>
|
|
||||||
|
|
||||||
namespace mosis {
|
namespace mosis {
|
||||||
|
|
||||||
@@ -13,14 +11,8 @@ namespace mosis {
|
|||||||
class IServiceListener {
|
class IServiceListener {
|
||||||
public:
|
public:
|
||||||
virtual ~IServiceListener() = default;
|
virtual ~IServiceListener() = default;
|
||||||
|
|
||||||
// Called when kernel initialization completes
|
|
||||||
virtual void OnServiceInitialized(bool success) = 0;
|
virtual void OnServiceInitialized(bool success) = 0;
|
||||||
|
virtual void OnBufferAvailable() = 0; // Simplified - no HardwareBuffer on desktop
|
||||||
// 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 OnFrameAvailable() = 0;
|
virtual void OnFrameAvailable() = 0;
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -43,31 +35,19 @@ public:
|
|||||||
virtual void OnHomeButton() = 0;
|
virtual void OnHomeButton() = 0;
|
||||||
virtual void OnRecentsButton() = 0;
|
virtual void OnRecentsButton() = 0;
|
||||||
|
|
||||||
// Hot-reload support (desktop)
|
// Hot-reload support
|
||||||
virtual void RequestReload() = 0;
|
virtual void RequestReload() = 0;
|
||||||
|
|
||||||
// Document loading
|
// Navigation
|
||||||
virtual void LoadDocument(const std::string& path) = 0;
|
virtual void NavigateTo(const std::string& screen) = 0;
|
||||||
|
|
||||||
// Lifecycle
|
// Lifecycle
|
||||||
virtual void Start() = 0;
|
virtual void Start() = 0;
|
||||||
virtual void Stop() = 0;
|
virtual void Stop() = 0;
|
||||||
virtual bool IsRunning() const = 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
|
// Factory for creating kernel implementation
|
||||||
std::unique_ptr<IKernel> CreateKernel(const KernelConfig& config);
|
std::unique_ptr<IKernel> CreateKernel();
|
||||||
|
|
||||||
} // namespace mosis
|
} // namespace mosis
|
||||||
|
|||||||
366
src/main/kernel/src/kernel.cpp
Normal file
366
src/main/kernel/src/kernel.cpp
Normal file
@@ -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 <RmlUi/Core.h>
|
||||||
|
#include <RmlUi/Lua.h>
|
||||||
|
#include <RmlUi/Lua/Interpreter.h>
|
||||||
|
#include <mutex>
|
||||||
|
#include <vector>
|
||||||
|
#include <functional>
|
||||||
|
#include <atomic>
|
||||||
|
#include <thread>
|
||||||
|
#include <chrono>
|
||||||
|
|
||||||
|
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<double>(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<IServiceListener> 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<IServiceListener> 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<int>(x * platform.GetWidth());
|
||||||
|
int py = static_cast<int>(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<int>(x * platform.GetWidth());
|
||||||
|
int py = static_cast<int>(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<int>(x * platform.GetWidth());
|
||||||
|
int py = static_cast<int>(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<std::shared_ptr<IServiceListener>> m_listeners;
|
||||||
|
std::vector<std::function<void(Rml::Context*)>> m_tasks;
|
||||||
|
std::thread m_main_thread;
|
||||||
|
std::atomic<bool> m_running{false};
|
||||||
|
std::atomic<bool> m_reload_requested{false};
|
||||||
|
bool m_initialized{false};
|
||||||
|
};
|
||||||
|
|
||||||
|
// Platform singleton
|
||||||
|
static std::unique_ptr<IPlatform> g_platform;
|
||||||
|
|
||||||
|
IPlatform& GetPlatform() {
|
||||||
|
return *g_platform;
|
||||||
|
}
|
||||||
|
|
||||||
|
void SetPlatform(std::unique_ptr<IPlatform> platform) {
|
||||||
|
g_platform = std::move(platform);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Factory function
|
||||||
|
std::unique_ptr<IKernel> CreateKernel() {
|
||||||
|
return std::make_unique<KernelImpl>();
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace mosis
|
||||||
Reference in New Issue
Block a user