add sandbox support to desktop designer, fix mouse coordinates and UI issues
- Add DesktopSandbox class that integrates timer, JSON, crypto, and VirtualFS APIs - Fix mouse coordinate handling: GLFW reports window coordinates, not physical pixels - Fix font path resolution to search multiple locations for test apps - Fix screenshot capture timing (capture before buffer swap) - Fix test app CSS: use border-width instead of border:none, add display:block - Fix test app Lua: add document nil checks, use HTML entities for symbols - Update hot_reload to reset sandbox state on reload Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
@@ -22,6 +22,7 @@
|
||||
#include <memory>
|
||||
#include <chrono>
|
||||
#include <ctime>
|
||||
#include "src/desktop_sandbox.h"
|
||||
|
||||
namespace fs = std::filesystem;
|
||||
|
||||
@@ -34,6 +35,7 @@ static mosis::HotReload* g_hot_reload = nullptr;
|
||||
static std::string g_current_document_path;
|
||||
static std::string g_current_screen_url; // For hierarchy dump - tracks current screen
|
||||
static bool g_needs_reload = false;
|
||||
static std::unique_ptr<mosis::DesktopSandbox> g_sandbox;
|
||||
|
||||
// Resolution presets
|
||||
static int g_width = 540;
|
||||
@@ -126,36 +128,28 @@ static void MouseButtonCallback(GLFWwindow* window, int button, int action, int
|
||||
double xpos, ypos;
|
||||
glfwGetCursorPos(window, &xpos, &ypos);
|
||||
|
||||
// Convert from physical (GLFW) to logical (RmlUi) coordinates
|
||||
// GLFW reports physical pixels, but RmlUi context uses logical pixels
|
||||
float scaleX, scaleY;
|
||||
glfwGetWindowContentScale(window, &scaleX, &scaleY);
|
||||
int logicalX = static_cast<int>(xpos / scaleX);
|
||||
int logicalY = static_cast<int>(ypos / scaleY);
|
||||
|
||||
// Debug log for click detection
|
||||
Rml::Log::Message(Rml::Log::LT_INFO, "Mouse %s at physical(%.0f, %.0f) logical(%d, %d) button=%d",
|
||||
action == GLFW_PRESS ? "down" : "up", xpos, ypos, logicalX, logicalY, button);
|
||||
// GLFW cursor callbacks report coordinates in screen/window coordinates (logical pixels)
|
||||
// which match the RmlUi context dimensions, so no scaling needed
|
||||
int mouseX = static_cast<int>(xpos);
|
||||
int mouseY = static_cast<int>(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 (using logical coords)
|
||||
g_context->ProcessMouseMove(logicalX, logicalY, key_modifier);
|
||||
// Update mouse position before processing button event
|
||||
g_context->ProcessMouseMove(mouseX, mouseY, key_modifier);
|
||||
|
||||
if (button == GLFW_MOUSE_BUTTON_LEFT) {
|
||||
if (action == GLFW_PRESS) {
|
||||
// Record mouse down in record mode (use logical coords)
|
||||
if (g_action_recorder && g_action_recorder->IsRecording()) {
|
||||
g_action_recorder->RecordMouseDown(logicalX, logicalY);
|
||||
g_action_recorder->RecordMouseDown(mouseX, mouseY);
|
||||
}
|
||||
g_context->ProcessMouseButtonDown(0, key_modifier);
|
||||
} else if (action == GLFW_RELEASE) {
|
||||
// Record mouse up in record mode
|
||||
if (g_action_recorder && g_action_recorder->IsRecording()) {
|
||||
g_action_recorder->RecordMouseUp(logicalX, logicalY);
|
||||
g_action_recorder->RecordMouseUp(mouseX, mouseY);
|
||||
}
|
||||
g_context->ProcessMouseButtonUp(0, key_modifier);
|
||||
}
|
||||
@@ -165,13 +159,12 @@ static void MouseButtonCallback(GLFWwindow* window, int button, int action, int
|
||||
static void CursorPosCallback(GLFWwindow* window, double xpos, double ypos) {
|
||||
if (!g_context) return;
|
||||
|
||||
// Convert from physical to logical coordinates
|
||||
float scaleX, scaleY;
|
||||
glfwGetWindowContentScale(window, &scaleX, &scaleY);
|
||||
int logicalX = static_cast<int>(xpos / scaleX);
|
||||
int logicalY = static_cast<int>(ypos / scaleY);
|
||||
// GLFW cursor callbacks report coordinates in screen/window coordinates (logical pixels)
|
||||
// which match the RmlUi context dimensions, so no scaling needed
|
||||
int mouseX = static_cast<int>(xpos);
|
||||
int mouseY = static_cast<int>(ypos);
|
||||
|
||||
g_context->ProcessMouseMove(logicalX, logicalY, 0);
|
||||
g_context->ProcessMouseMove(mouseX, mouseY, 0);
|
||||
}
|
||||
|
||||
static void ScrollCallback(GLFWwindow* window, double xoffset, double yoffset) {
|
||||
@@ -291,10 +284,11 @@ int main(int argc, char* argv[]) {
|
||||
if (!assets_path_specified) {
|
||||
fs::path doc_path = fs::absolute(document_path);
|
||||
fs::path current = doc_path.parent_path();
|
||||
fs::path prev_path;
|
||||
|
||||
// Walk up the directory tree looking for a folder that ends with "assets"
|
||||
// or contains typical asset folders like "apps", "ui", "fonts"
|
||||
while (!current.empty() && current.has_parent_path()) {
|
||||
while (!current.empty() && current != prev_path) {
|
||||
std::string folder_name = current.filename().string();
|
||||
if (folder_name == "assets") {
|
||||
assets_path = current.string();
|
||||
@@ -305,12 +299,38 @@ int main(int argc, char* argv[]) {
|
||||
assets_path = current.string();
|
||||
break;
|
||||
}
|
||||
prev_path = current;
|
||||
current = current.parent_path();
|
||||
}
|
||||
|
||||
// Fall back to "assets" relative to executable if not found
|
||||
// Fall back options if no standard assets folder found
|
||||
if (assets_path.empty()) {
|
||||
assets_path = "assets";
|
||||
// If the document exists, use its parent directory as assets path
|
||||
fs::path doc_path = fs::absolute(document_path);
|
||||
if (fs::exists(doc_path)) {
|
||||
assets_path = doc_path.parent_path().string();
|
||||
} else {
|
||||
// Try executable's assets folder
|
||||
fs::path exe_path = fs::path(argv[0]).parent_path();
|
||||
if (exe_path.empty()) {
|
||||
exe_path = ".";
|
||||
}
|
||||
fs::path exe_assets = fs::absolute(exe_path) / "assets";
|
||||
if (fs::exists(exe_assets)) {
|
||||
assets_path = exe_assets.string();
|
||||
} else {
|
||||
// Last resort: current directory
|
||||
assets_path = fs::absolute(".").string();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Make document_path absolute if it's relative and exists
|
||||
if (!fs::path(document_path).is_absolute()) {
|
||||
fs::path abs_doc = fs::absolute(document_path);
|
||||
if (fs::exists(abs_doc)) {
|
||||
document_path = abs_doc.string();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -452,8 +472,7 @@ int main(int argc, char* argv[]) {
|
||||
g_render_interface->EndFrame(0);
|
||||
}
|
||||
|
||||
glfwSwapBuffers(g_window);
|
||||
|
||||
// Capture screenshot BEFORE swap (glReadPixels reads from back buffer)
|
||||
if (g_test_mode == TestMode::Screenshot) {
|
||||
mosis::testing::VisualCapture capture(fb_width, fb_height);
|
||||
if (capture.CaptureScreenshot(g_test_output_path)) {
|
||||
@@ -470,6 +489,8 @@ int main(int argc, char* argv[]) {
|
||||
}
|
||||
}
|
||||
|
||||
glfwSwapBuffers(g_window);
|
||||
|
||||
// Cleanup and exit
|
||||
ShutdownRmlUi();
|
||||
glfwDestroyWindow(g_window);
|
||||
@@ -503,6 +524,11 @@ int main(int argc, char* argv[]) {
|
||||
glClearColor(0.1f, 0.1f, 0.1f, 1.0f);
|
||||
glClear(GL_COLOR_BUFFER_BIT);
|
||||
|
||||
// Update sandbox (process timers)
|
||||
if (g_sandbox) {
|
||||
g_sandbox->Update();
|
||||
}
|
||||
|
||||
// Update and render
|
||||
if (g_context) {
|
||||
g_context->Update();
|
||||
@@ -541,6 +567,7 @@ int main(int argc, char* argv[]) {
|
||||
if (g_log_file.is_open()) {
|
||||
g_log_file.close();
|
||||
}
|
||||
g_sandbox.reset();
|
||||
ShutdownRmlUi();
|
||||
glfwDestroyWindow(g_window);
|
||||
glfwTerminate();
|
||||
@@ -572,8 +599,16 @@ bool InitializeRmlUi(const std::string& assets_path, int fb_width, int fb_height
|
||||
// Initialize Lua bindings
|
||||
Rml::Lua::Initialise();
|
||||
|
||||
// Register loadScreen function for navigation
|
||||
// Get Lua state
|
||||
lua_State* L = Rml::Lua::Interpreter::GetLuaState();
|
||||
|
||||
// Initialize sandbox with APIs (timers, JSON, crypto, fs)
|
||||
mosis::DesktopSandboxConfig sandbox_config;
|
||||
sandbox_config.data_root = assets_path + "/sandbox_data";
|
||||
g_sandbox = std::make_unique<mosis::DesktopSandbox>(sandbox_config);
|
||||
g_sandbox->RegisterAPIs(L);
|
||||
|
||||
// Register loadScreen function for navigation
|
||||
lua_pushcfunction(L, [](lua_State* L) -> int {
|
||||
const char* path = luaL_checkstring(L, 1);
|
||||
|
||||
@@ -610,15 +645,33 @@ bool InitializeRmlUi(const std::string& assets_path, int fb_width, int fb_height
|
||||
lua_setglobal(L, "loadScreen");
|
||||
std::cout << "Registered Lua loadScreen function" << std::endl;
|
||||
|
||||
// Load fonts
|
||||
std::vector<std::string> fonts = {
|
||||
"fonts/LatoLatin-Regular.ttf",
|
||||
"fonts/LatoLatin-Bold.ttf",
|
||||
"fonts/LatoLatin-Light.ttf",
|
||||
// Load fonts - search for fonts directory in multiple locations
|
||||
std::string fonts_root;
|
||||
std::vector<std::string> font_search_paths = {
|
||||
assets_path + "/fonts",
|
||||
assets_path + "/../src/main/assets/fonts", // If assets_path is test-app dir
|
||||
assets_path + "/../../src/main/assets/fonts",
|
||||
std::filesystem::absolute("src/main/assets/fonts").string(),
|
||||
};
|
||||
for (const auto& font : fonts) {
|
||||
if (!Rml::LoadFontFace(font)) {
|
||||
std::cerr << "Warning: Failed to load font: " << font << std::endl;
|
||||
for (const auto& search_path : font_search_paths) {
|
||||
if (std::filesystem::exists(search_path + "/LatoLatin-Regular.ttf")) {
|
||||
fonts_root = search_path;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (fonts_root.empty()) {
|
||||
std::cerr << "Warning: Could not find fonts directory" << std::endl;
|
||||
} else {
|
||||
std::cout << "Fonts path: " << fonts_root << std::endl;
|
||||
std::vector<std::string> fonts = {
|
||||
fonts_root + "/LatoLatin-Regular.ttf",
|
||||
fonts_root + "/LatoLatin-Bold.ttf",
|
||||
fonts_root + "/LatoLatin-Italic.ttf",
|
||||
};
|
||||
for (const auto& font : fonts) {
|
||||
if (!Rml::LoadFontFace(font)) {
|
||||
std::cerr << "Warning: Failed to load font: " << font << std::endl;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -676,7 +729,15 @@ bool LoadDocument(const std::string& path) {
|
||||
|
||||
void ReloadDocument() {
|
||||
std::cout << "Reloading..." << std::endl;
|
||||
|
||||
|
||||
// Reset sandbox (clear timers, re-register APIs)
|
||||
if (g_sandbox) {
|
||||
lua_State* L = Rml::Lua::Interpreter::GetLuaState();
|
||||
g_sandbox->UnregisterAPIs(L);
|
||||
g_sandbox->Reset();
|
||||
g_sandbox->RegisterAPIs(L);
|
||||
}
|
||||
|
||||
// Reload stylesheets
|
||||
for (int i = 0; i < g_context->GetNumDocuments(); ++i) {
|
||||
auto* doc = g_context->GetDocument(i);
|
||||
|
||||
Reference in New Issue
Block a user