complete milestone 1
This commit is contained in:
@@ -1,6 +1,7 @@
|
||||
// D:\Dev\Mosis\MosisService\designer\src\desktop_file_interface.cpp
|
||||
#include "desktop_file_interface.h"
|
||||
#include <cstdio>
|
||||
#include <cctype>
|
||||
#include <filesystem>
|
||||
|
||||
namespace fs = std::filesystem;
|
||||
@@ -16,12 +17,20 @@ void DesktopFileInterface::SetAssetsPath(const std::string& path) {
|
||||
}
|
||||
|
||||
std::string DesktopFileInterface::ResolvePath(const std::string& path) const {
|
||||
std::string resolved = path;
|
||||
|
||||
// Handle URL-encoded Windows drive letters (D| -> D:)
|
||||
// RmlUi sometimes encodes the colon in Windows paths
|
||||
if (resolved.size() >= 2 && std::isalpha(resolved[0]) && resolved[1] == '|') {
|
||||
resolved[1] = ':';
|
||||
}
|
||||
|
||||
// If path is absolute, use it directly
|
||||
if (fs::path(path).is_absolute()) {
|
||||
return path;
|
||||
if (fs::path(resolved).is_absolute()) {
|
||||
return resolved;
|
||||
}
|
||||
// Otherwise, prepend assets path
|
||||
return m_assets_path + path;
|
||||
return m_assets_path + resolved;
|
||||
}
|
||||
|
||||
Rml::FileHandle DesktopFileInterface::Open(const Rml::String& path) {
|
||||
|
||||
@@ -2,11 +2,27 @@
|
||||
#include "ui_inspector.h"
|
||||
#include <fstream>
|
||||
#include <sstream>
|
||||
#include <filesystem>
|
||||
|
||||
namespace mosis::testing {
|
||||
|
||||
using json = nlohmann::json;
|
||||
|
||||
// Helper to normalize RmlUi URLs for comparison
|
||||
// RmlUi uses '|' instead of ':' for Windows drive letters
|
||||
static std::string NormalizeUrl(const std::string& url) {
|
||||
std::string normalized = url;
|
||||
// Replace | with : (for Windows drive letters)
|
||||
for (char& c : normalized) {
|
||||
if (c == '|') c = ':';
|
||||
}
|
||||
// Normalize backslashes to forward slashes
|
||||
for (char& c : normalized) {
|
||||
if (c == '\\') c = '/';
|
||||
}
|
||||
return normalized;
|
||||
}
|
||||
|
||||
UIInspector::UIInspector(Rml::Context* context)
|
||||
: m_context(context)
|
||||
{
|
||||
@@ -24,10 +40,16 @@ json UIInspector::ElementToJson(Rml::Element* element) const {
|
||||
j["id"] = id;
|
||||
}
|
||||
|
||||
// Classes
|
||||
// Classes - split into array
|
||||
std::string class_str = element->GetAttribute<Rml::String>("class", "");
|
||||
if (!class_str.empty()) {
|
||||
j["classes"] = class_str;
|
||||
json classes_arr = json::array();
|
||||
std::istringstream iss(class_str);
|
||||
std::string cls;
|
||||
while (iss >> cls) {
|
||||
classes_arr.push_back(cls);
|
||||
}
|
||||
j["classes"] = classes_arr;
|
||||
}
|
||||
|
||||
// Bounds
|
||||
@@ -75,32 +97,63 @@ json UIInspector::ElementToJson(Rml::Element* element) const {
|
||||
|
||||
json UIInspector::DumpHierarchy() const {
|
||||
json result;
|
||||
|
||||
|
||||
// Get current timestamp
|
||||
auto now = std::chrono::system_clock::now();
|
||||
auto time_t = std::chrono::system_clock::to_time_t(now);
|
||||
std::stringstream ss;
|
||||
ss << std::put_time(std::localtime(&time_t), "%Y-%m-%dT%H:%M:%S");
|
||||
result["timestamp"] = ss.str();
|
||||
|
||||
|
||||
// Resolution
|
||||
auto dimensions = m_context->GetDimensions();
|
||||
result["resolution"] = {
|
||||
{"width", dimensions.x},
|
||||
{"height", dimensions.y}
|
||||
};
|
||||
|
||||
// Current screen (document URL)
|
||||
std::string screen = "";
|
||||
if (m_context->GetNumDocuments() > 0) {
|
||||
auto* doc = m_context->GetDocument(0);
|
||||
|
||||
// Current screen - use override if set, otherwise detect from documents
|
||||
std::string screen = m_current_screen;
|
||||
Rml::ElementDocument* main_doc = nullptr;
|
||||
|
||||
// Normalize the current screen path for comparison
|
||||
std::string normalized_current = NormalizeUrl(m_current_screen);
|
||||
|
||||
// Find the document matching the current screen, or first non-debugger document
|
||||
for (int i = 0; i < m_context->GetNumDocuments(); i++) {
|
||||
auto* doc = m_context->GetDocument(i);
|
||||
if (doc) {
|
||||
screen = doc->GetSourceURL();
|
||||
std::string url = doc->GetSourceURL();
|
||||
// Skip debugger documents
|
||||
if (url.find("__rmlui") == std::string::npos) {
|
||||
// Normalize the document URL for comparison
|
||||
std::string normalized_url = NormalizeUrl(url);
|
||||
|
||||
// If we have a current screen override, match it
|
||||
if (!m_current_screen.empty()) {
|
||||
if (normalized_url.find(normalized_current) != std::string::npos ||
|
||||
normalized_current.find(normalized_url) != std::string::npos) {
|
||||
main_doc = doc;
|
||||
break;
|
||||
}
|
||||
} else if (!main_doc) {
|
||||
// No override, use first non-debugger document
|
||||
screen = url;
|
||||
main_doc = doc;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
result["screen"] = screen;
|
||||
|
||||
// Elements - dump all documents
|
||||
|
||||
// Elements - dump the main document body (expected format for HierarchyReader)
|
||||
if (main_doc) {
|
||||
result["elements"] = ElementToJson(main_doc);
|
||||
} else {
|
||||
result["elements"] = json::object();
|
||||
}
|
||||
|
||||
// Also include all documents for more detailed inspection
|
||||
result["documents"] = json::array();
|
||||
for (int i = 0; i < m_context->GetNumDocuments(); i++) {
|
||||
auto* doc = m_context->GetDocument(i);
|
||||
@@ -108,24 +161,51 @@ json UIInspector::DumpHierarchy() const {
|
||||
json doc_json;
|
||||
doc_json["url"] = doc->GetSourceURL();
|
||||
doc_json["title"] = doc->GetTitle();
|
||||
|
||||
|
||||
// Dump document root (ElementDocument inherits from Element)
|
||||
doc_json["body"] = ElementToJson(doc);
|
||||
|
||||
|
||||
result["documents"].push_back(doc_json);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
bool UIInspector::SaveHierarchy(const std::string& path) const {
|
||||
json hierarchy = DumpHierarchy();
|
||||
|
||||
std::ofstream file(path);
|
||||
if (!file) return false;
|
||||
|
||||
file << hierarchy.dump(2);
|
||||
std::string content = hierarchy.dump(2);
|
||||
|
||||
// Use temporary file + rename for atomic writes
|
||||
std::string temp_path = path + ".tmp";
|
||||
|
||||
{
|
||||
std::ofstream file(temp_path, std::ios::binary | std::ios::trunc);
|
||||
if (!file) {
|
||||
return false;
|
||||
}
|
||||
file.write(content.c_str(), content.size());
|
||||
file.flush();
|
||||
if (!file.good()) {
|
||||
file.close();
|
||||
return false;
|
||||
}
|
||||
file.close();
|
||||
}
|
||||
|
||||
// Delete the old file first to avoid rename conflicts on Windows
|
||||
std::error_code ec;
|
||||
std::filesystem::remove(path, ec); // Ignore errors if file doesn't exist
|
||||
|
||||
// Rename temp file to final path
|
||||
std::filesystem::rename(temp_path, path, ec);
|
||||
if (ec) {
|
||||
// Fallback: copy and delete if rename fails
|
||||
std::filesystem::copy_file(temp_path, path,
|
||||
std::filesystem::copy_options::overwrite_existing, ec);
|
||||
std::filesystem::remove(temp_path, ec);
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
@@ -12,9 +12,12 @@ public:
|
||||
UIInspector(Rml::Context* context);
|
||||
~UIInspector() = default;
|
||||
|
||||
// Set current screen URL override (to handle stale document detection)
|
||||
void SetCurrentScreen(const std::string& screenUrl) { m_current_screen = screenUrl; }
|
||||
|
||||
// Dump entire UI hierarchy to JSON
|
||||
nlohmann::json DumpHierarchy() const;
|
||||
|
||||
|
||||
// Save hierarchy to file
|
||||
bool SaveHierarchy(const std::string& path) const;
|
||||
|
||||
@@ -42,6 +45,7 @@ private:
|
||||
Rml::Element* FindElementRecursive(Rml::Element* root, const std::string& selector) const;
|
||||
|
||||
Rml::Context* m_context;
|
||||
std::string m_current_screen; // Override for screen URL detection
|
||||
};
|
||||
|
||||
} // namespace mosis::testing
|
||||
|
||||
Reference in New Issue
Block a user