more on testing framework
This commit is contained in:
@@ -11,9 +11,16 @@
|
||||
#include "desktop_platform.h"
|
||||
#include "desktop_file_interface.h"
|
||||
#include "hot_reload.h"
|
||||
#include "testing/action_recorder.h"
|
||||
#include "testing/action_player.h"
|
||||
#include "testing/ui_inspector.h"
|
||||
#include "testing/visual_capture.h"
|
||||
#include <RmlUi/Lua/Interpreter.h>
|
||||
#include <iostream>
|
||||
#include <filesystem>
|
||||
#include <memory>
|
||||
#include <chrono>
|
||||
#include <ctime>
|
||||
|
||||
namespace fs = std::filesystem;
|
||||
|
||||
@@ -30,6 +37,21 @@ static bool g_needs_reload = false;
|
||||
static int g_width = 540;
|
||||
static int g_height = 960;
|
||||
|
||||
// Test mode
|
||||
enum class TestMode {
|
||||
Interactive, // Normal interactive mode with hot-reload
|
||||
Record, // Record user actions to JSON
|
||||
Playback, // Playback actions from JSON
|
||||
Screenshot, // Take a screenshot and exit
|
||||
DumpHierarchy // Dump UI hierarchy to JSON and exit
|
||||
};
|
||||
|
||||
static TestMode g_test_mode = TestMode::Interactive;
|
||||
static std::string g_test_input_path; // Input file for playback
|
||||
static std::string g_test_output_path; // Output file for record/screenshot/hierarchy
|
||||
static mosis::testing::ActionRecorder* g_action_recorder = nullptr;
|
||||
static mosis::testing::ActionPlayer* g_action_player = nullptr;
|
||||
|
||||
// Forward declarations
|
||||
bool InitializeRmlUi(const std::string& assets_path);
|
||||
void ShutdownRmlUi();
|
||||
@@ -43,7 +65,7 @@ static void ErrorCallback(int error, const char* description) {
|
||||
|
||||
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;
|
||||
@@ -54,11 +76,41 @@ static void KeyCallback(GLFWwindow* window, int key, int scancode, int action, i
|
||||
}
|
||||
// Escape - Back navigation
|
||||
else if (key == GLFW_KEY_ESCAPE) {
|
||||
// Record back button action if recording
|
||||
if (g_action_recorder && g_action_recorder->IsRecording()) {
|
||||
g_action_recorder->RecordButton("back");
|
||||
}
|
||||
if (g_context) {
|
||||
g_context->ProcessKeyDown(Rml::Input::KI_ESCAPE, 0);
|
||||
g_context->ProcessKeyUp(Rml::Input::KI_ESCAPE, 0);
|
||||
}
|
||||
}
|
||||
// R - Toggle recording (in interactive mode)
|
||||
else if (key == GLFW_KEY_R && g_test_mode == TestMode::Interactive) {
|
||||
if (!g_action_recorder) {
|
||||
g_action_recorder = new mosis::testing::ActionRecorder(g_width, g_height);
|
||||
}
|
||||
|
||||
if (g_action_recorder->IsRecording()) {
|
||||
g_action_recorder->StopRecording();
|
||||
// Generate filename with timestamp
|
||||
auto now = std::chrono::system_clock::now();
|
||||
auto time_t = std::chrono::system_clock::to_time_t(now);
|
||||
std::tm tm_buf;
|
||||
localtime_s(&tm_buf, &time_t);
|
||||
char buffer[64];
|
||||
std::strftime(buffer, sizeof(buffer), "recording_%Y%m%d_%H%M%S.json", &tm_buf);
|
||||
std::string filename = buffer;
|
||||
if (g_action_recorder->SaveToFile(filename)) {
|
||||
std::cout << "Recording saved to: " << filename << std::endl;
|
||||
} else {
|
||||
std::cerr << "Failed to save recording" << std::endl;
|
||||
}
|
||||
} else {
|
||||
g_action_recorder->StartRecording();
|
||||
std::cout << "Recording started. Press R again to stop." << std::endl;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static void MouseButtonCallback(GLFWwindow* window, int button, int action, int mods) {
|
||||
@@ -77,8 +129,16 @@ static void MouseButtonCallback(GLFWwindow* window, int button, int action, int
|
||||
|
||||
if (button == GLFW_MOUSE_BUTTON_LEFT) {
|
||||
if (action == GLFW_PRESS) {
|
||||
// Record mouse down in record mode
|
||||
if (g_action_recorder && g_action_recorder->IsRecording()) {
|
||||
g_action_recorder->RecordMouseDown(static_cast<int>(xpos), static_cast<int>(ypos));
|
||||
}
|
||||
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(static_cast<int>(xpos), static_cast<int>(ypos));
|
||||
}
|
||||
g_context->ProcessMouseButtonUp(0, key_modifier);
|
||||
}
|
||||
}
|
||||
@@ -117,17 +177,37 @@ public:
|
||||
static DesktopSystemInterface g_system_interface;
|
||||
|
||||
|
||||
static void PrintUsage() {
|
||||
std::cout << "Usage: mosis-designer [options] [document.rml]\n"
|
||||
<< "\nOptions:\n"
|
||||
<< " --resolution WxH Set window resolution (default: 540x960)\n"
|
||||
<< " --assets PATH Set assets directory (default: assets)\n"
|
||||
<< "\nTest modes:\n"
|
||||
<< " --record FILE Record user actions to JSON file\n"
|
||||
<< " --playback FILE Playback actions from JSON file\n"
|
||||
<< " --screenshot FILE Take screenshot and exit\n"
|
||||
<< " --dump-hierarchy FILE Dump UI hierarchy to JSON and exit\n"
|
||||
<< "\nKeys:\n"
|
||||
<< " F5 Reload document\n"
|
||||
<< " F12 Toggle RmlUi debugger\n"
|
||||
<< " ESC Back navigation\n"
|
||||
<< " R Start/Stop recording (in interactive mode)\n"
|
||||
<< std::endl;
|
||||
}
|
||||
|
||||
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) {
|
||||
if (arg == "--help" || arg == "-h") {
|
||||
PrintUsage();
|
||||
return 0;
|
||||
} else if (arg == "--resolution" && i + 1 < argc) {
|
||||
std::string res = argv[++i];
|
||||
size_t x = res.find('x');
|
||||
if (x != std::string::npos) {
|
||||
@@ -136,6 +216,18 @@ int main(int argc, char* argv[]) {
|
||||
}
|
||||
} else if (arg == "--assets" && i + 1 < argc) {
|
||||
assets_path = argv[++i];
|
||||
} else if (arg == "--record" && i + 1 < argc) {
|
||||
g_test_mode = TestMode::Record;
|
||||
g_test_output_path = argv[++i];
|
||||
} else if (arg == "--playback" && i + 1 < argc) {
|
||||
g_test_mode = TestMode::Playback;
|
||||
g_test_input_path = argv[++i];
|
||||
} else if (arg == "--screenshot" && i + 1 < argc) {
|
||||
g_test_mode = TestMode::Screenshot;
|
||||
g_test_output_path = argv[++i];
|
||||
} else if (arg == "--dump-hierarchy" && i + 1 < argc) {
|
||||
g_test_mode = TestMode::DumpHierarchy;
|
||||
g_test_output_path = argv[++i];
|
||||
} else if (arg[0] != '-') {
|
||||
document_path = arg;
|
||||
}
|
||||
@@ -145,11 +237,24 @@ int main(int argc, char* argv[]) {
|
||||
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;
|
||||
|
||||
// Print mode info
|
||||
if (g_test_mode == TestMode::Interactive) {
|
||||
std::cout << "Press F5 to reload, F12 for debugger, ESC for back, R to record" << std::endl;
|
||||
} else if (g_test_mode == TestMode::Record) {
|
||||
std::cout << "Recording mode: actions will be saved to " << g_test_output_path << std::endl;
|
||||
} else if (g_test_mode == TestMode::Playback) {
|
||||
std::cout << "Playback mode: playing " << g_test_input_path << std::endl;
|
||||
} else if (g_test_mode == TestMode::Screenshot) {
|
||||
std::cout << "Screenshot mode: will save to " << g_test_output_path << std::endl;
|
||||
} else if (g_test_mode == TestMode::DumpHierarchy) {
|
||||
std::cout << "Hierarchy dump mode: will save to " << g_test_output_path << std::endl;
|
||||
}
|
||||
|
||||
// Initialize GLFW
|
||||
glfwSetErrorCallback(ErrorCallback);
|
||||
@@ -208,28 +313,97 @@ int main(int argc, char* argv[]) {
|
||||
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;
|
||||
|
||||
|
||||
// Initialize test mode components
|
||||
if (g_test_mode == TestMode::Record) {
|
||||
g_action_recorder = new mosis::testing::ActionRecorder(g_width, g_height);
|
||||
g_action_recorder->StartRecording();
|
||||
std::cout << "Recording started. Close window to save." << std::endl;
|
||||
} else if (g_test_mode == TestMode::Playback) {
|
||||
g_action_player = new mosis::testing::ActionPlayer(g_context);
|
||||
if (!g_action_player->LoadFromFile(g_test_input_path)) {
|
||||
std::cerr << "Failed to load actions from: " << g_test_input_path << std::endl;
|
||||
delete g_action_player;
|
||||
g_action_player = nullptr;
|
||||
} else {
|
||||
g_action_player->Start();
|
||||
std::cout << "Playback started..." << std::endl;
|
||||
}
|
||||
}
|
||||
|
||||
// Set up hot-reload (only in interactive mode)
|
||||
if (g_test_mode == TestMode::Interactive) {
|
||||
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;
|
||||
}
|
||||
|
||||
// For screenshot/hierarchy modes, render one frame then capture
|
||||
if (g_test_mode == TestMode::Screenshot || g_test_mode == TestMode::DumpHierarchy) {
|
||||
// Render one frame
|
||||
glClearColor(0.1f, 0.1f, 0.1f, 1.0f);
|
||||
glClear(GL_COLOR_BUFFER_BIT);
|
||||
|
||||
if (g_context) {
|
||||
g_context->Update();
|
||||
g_render_interface->BeginFrame();
|
||||
g_context->Render();
|
||||
g_render_interface->EndFrame(0);
|
||||
}
|
||||
|
||||
glfwSwapBuffers(g_window);
|
||||
|
||||
if (g_test_mode == TestMode::Screenshot) {
|
||||
mosis::testing::VisualCapture capture(g_width, g_height);
|
||||
if (capture.CaptureScreenshot(g_test_output_path)) {
|
||||
std::cout << "Screenshot saved to: " << g_test_output_path << std::endl;
|
||||
} else {
|
||||
std::cerr << "Failed to save screenshot" << std::endl;
|
||||
}
|
||||
} else {
|
||||
mosis::testing::UIInspector inspector(g_context);
|
||||
if (inspector.SaveHierarchy(g_test_output_path)) {
|
||||
std::cout << "UI hierarchy saved to: " << g_test_output_path << std::endl;
|
||||
} else {
|
||||
std::cerr << "Failed to save hierarchy" << std::endl;
|
||||
}
|
||||
}
|
||||
|
||||
// Cleanup and exit
|
||||
ShutdownRmlUi();
|
||||
glfwDestroyWindow(g_window);
|
||||
glfwTerminate();
|
||||
return 0;
|
||||
}
|
||||
|
||||
// Main loop
|
||||
while (!glfwWindowShouldClose(g_window)) {
|
||||
glfwPollEvents();
|
||||
|
||||
|
||||
// Handle hot-reload
|
||||
if (g_needs_reload) {
|
||||
g_needs_reload = false;
|
||||
ReloadDocument();
|
||||
}
|
||||
|
||||
|
||||
// Update action playback
|
||||
if (g_action_player && g_action_player->IsPlaying()) {
|
||||
g_action_player->Update();
|
||||
|
||||
// Check if playback finished
|
||||
if (g_action_player->IsFinished()) {
|
||||
std::cout << "Playback complete" << std::endl;
|
||||
// Optionally exit after playback
|
||||
glfwSetWindowShouldClose(g_window, GLFW_TRUE);
|
||||
}
|
||||
}
|
||||
|
||||
// Clear
|
||||
glClearColor(0.1f, 0.1f, 0.1f, 1.0f);
|
||||
glClear(GL_COLOR_BUFFER_BIT);
|
||||
|
||||
|
||||
// Update and render
|
||||
if (g_context) {
|
||||
g_context->Update();
|
||||
@@ -237,11 +411,23 @@ int main(int argc, char* argv[]) {
|
||||
g_context->Render();
|
||||
g_render_interface->EndFrame(0);
|
||||
}
|
||||
|
||||
|
||||
glfwSwapBuffers(g_window);
|
||||
}
|
||||
|
||||
|
||||
// Save recording if in record mode
|
||||
if (g_action_recorder && g_action_recorder->IsRecording()) {
|
||||
g_action_recorder->StopRecording();
|
||||
if (g_action_recorder->SaveToFile(g_test_output_path)) {
|
||||
std::cout << "Recording saved to: " << g_test_output_path << std::endl;
|
||||
} else {
|
||||
std::cerr << "Failed to save recording" << std::endl;
|
||||
}
|
||||
}
|
||||
|
||||
// Cleanup
|
||||
delete g_action_recorder;
|
||||
delete g_action_player;
|
||||
if (g_hot_reload) {
|
||||
g_hot_reload->Stop();
|
||||
delete g_hot_reload;
|
||||
@@ -249,7 +435,7 @@ int main(int argc, char* argv[]) {
|
||||
ShutdownRmlUi();
|
||||
glfwDestroyWindow(g_window);
|
||||
glfwTerminate();
|
||||
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
@@ -275,7 +461,44 @@ bool InitializeRmlUi(const std::string& assets_path) {
|
||||
|
||||
// Initialize Lua bindings
|
||||
Rml::Lua::Initialise();
|
||||
|
||||
|
||||
// Register loadScreen function for navigation
|
||||
lua_State* L = Rml::Lua::Interpreter::GetLuaState();
|
||||
lua_pushcfunction(L, [](lua_State* L) -> int {
|
||||
const char* path = luaL_checkstring(L, 1);
|
||||
std::cout << "loadScreen called: " << path << std::endl;
|
||||
|
||||
if (!g_context) {
|
||||
lua_pushboolean(L, false);
|
||||
return 1;
|
||||
}
|
||||
|
||||
// Close existing documents (except debugger)
|
||||
while (g_context->GetNumDocuments() > 1) {
|
||||
auto* doc = g_context->GetDocument(0);
|
||||
if (doc && doc->GetSourceURL().find("__rmlui") == std::string::npos) {
|
||||
doc->Close();
|
||||
} else {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// Load new document
|
||||
auto* document = g_context->LoadDocument(path);
|
||||
if (document) {
|
||||
document->Show();
|
||||
g_current_document_path = path;
|
||||
std::cout << "Loaded: " << path << std::endl;
|
||||
lua_pushboolean(L, true);
|
||||
} else {
|
||||
std::cerr << "Failed to load: " << path << std::endl;
|
||||
lua_pushboolean(L, false);
|
||||
}
|
||||
return 1;
|
||||
});
|
||||
lua_setglobal(L, "loadScreen");
|
||||
std::cout << "Registered Lua loadScreen function" << std::endl;
|
||||
|
||||
// Load fonts
|
||||
std::vector<std::string> fonts = {
|
||||
"fonts/LatoLatin-Regular.ttf",
|
||||
|
||||
Reference in New Issue
Block a user