Files
yaze/src/app/gui/widgets/widget_state_capture.cc
scawful 3b406ab671 feat: Implement ROM version management and proposal approval system
- Introduced `RomVersionManager` for managing ROM snapshots, including automatic backups, manual checkpoints, and corruption detection.
- Added `ProposalApprovalManager` to facilitate collaborative proposal submissions and voting, enhancing team workflows.
- Updated `CollaborationPanel` to integrate version management features, allowing users to track changes and manage proposals effectively.
- Enhanced documentation to reflect new functionalities and usage instructions for version management and collaboration features.
2025-10-04 22:33:06 -04:00

279 lines
7.4 KiB
C++

#include "app/gui/widgets/widget_state_capture.h"
#include "absl/strings/str_format.h"
#if defined(YAZE_ENABLE_IMGUI_TEST_ENGINE) && YAZE_ENABLE_IMGUI_TEST_ENGINE
#include "imgui.h"
#include "imgui_internal.h"
#else
#include "imgui/imgui.h"
#endif
#include <string>
#if defined(YAZE_WITH_JSON)
#include "nlohmann/json.hpp"
#endif
namespace yaze {
namespace core {
#if !defined(YAZE_WITH_JSON)
namespace {
std::string EscapeJsonString(const std::string& value) {
std::string escaped;
escaped.reserve(value.size() + 2);
escaped.push_back('"');
for (unsigned char c : value) {
switch (c) {
case '"':
escaped.append("\\\"");
break;
case '\\':
escaped.append("\\\\");
break;
case '\b':
escaped.append("\\b");
break;
case '\f':
escaped.append("\\f");
break;
case '\n':
escaped.append("\\n");
break;
case '\r':
escaped.append("\\r");
break;
case '\t':
escaped.append("\\t");
break;
default:
if (c <= 0x1F) {
escaped.append(absl::StrFormat("\\\\u%04X", static_cast<int>(c)));
} else {
escaped.push_back(static_cast<char>(c));
}
break;
}
}
escaped.push_back('"');
return escaped;
}
const char* BoolToJson(bool value) { return value ? "true" : "false"; }
std::string FormatFloat(float value) {
// Match typical JSON formatting without trailing zeros when possible.
return absl::StrFormat("%.4f", value);
}
std::string FormatFloatCompact(float value) {
std::string formatted = FormatFloat(value);
// Trim trailing zeros while keeping at least one decimal place.
if (formatted.find('.') != std::string::npos) {
while (!formatted.empty() && formatted.back() == '0') {
formatted.pop_back();
}
if (!formatted.empty() && formatted.back() == '.') {
formatted.push_back('0');
}
}
return formatted;
}
} // namespace
#endif // !defined(YAZE_WITH_JSON)
std::string CaptureWidgetState() {
WidgetState state;
#if defined(YAZE_ENABLE_IMGUI_TEST_ENGINE) && YAZE_ENABLE_IMGUI_TEST_ENGINE
// Check if ImGui context is available
ImGuiContext* ctx = ImGui::GetCurrentContext();
if (!ctx) {
return R"({"error": "ImGui context not available"})";
}
ImGuiIO& io = ImGui::GetIO();
// Capture frame information
state.frame_count = ImGui::GetFrameCount();
state.frame_rate = io.Framerate;
// Capture focused window
ImGuiWindow* current = ImGui::GetCurrentWindow();
if (current && !current->Hidden) {
state.focused_window = current->Name;
}
// Capture active widget (focused for input)
ImGuiID active_id = ImGui::GetActiveID();
if (active_id != 0) {
state.focused_widget = absl::StrFormat("0x%08X", active_id);
}
// Capture hovered widget
ImGuiID hovered_id = ImGui::GetHoveredID();
if (hovered_id != 0) {
state.hovered_widget = absl::StrFormat("0x%08X", hovered_id);
}
// Traverse visible windows
for (ImGuiWindow* window : ctx->Windows) {
if (window && window->Active && !window->Hidden) {
state.visible_windows.push_back(window->Name);
}
}
// Capture open popups
for (int i = 0; i < ctx->OpenPopupStack.Size; i++) {
ImGuiPopupData& popup = ctx->OpenPopupStack[i];
if (popup.Window && !popup.Window->Hidden) {
state.open_popups.push_back(popup.Window->Name);
}
}
// Capture navigation state
state.nav_id = ctx->NavId;
state.nav_active = ctx->NavWindow != nullptr;
// Capture mouse state
for (int i = 0; i < 5; i++) {
state.mouse_down[i] = io.MouseDown[i];
}
state.mouse_pos_x = io.MousePos.x;
state.mouse_pos_y = io.MousePos.y;
// Capture keyboard modifiers
state.ctrl_pressed = io.KeyCtrl;
state.shift_pressed = io.KeyShift;
state.alt_pressed = io.KeyAlt;
#else
// When UI test engine / ImGui internals aren't available, provide a minimal
// payload so downstream systems still receive structured JSON. This keeps
// builds that exclude the UI test engine (e.g., Windows release) working.
return "{\"warning\": \"Widget state capture unavailable (UI test engine disabled)\"}";
#endif
return SerializeWidgetStateToJson(state);
}
std::string SerializeWidgetStateToJson(const WidgetState& state) {
#if defined(YAZE_WITH_JSON)
nlohmann::json j;
j["frame_count"] = state.frame_count;
j["frame_rate"] = state.frame_rate;
j["focused_window"] = state.focused_window;
j["focused_widget"] = state.focused_widget;
j["hovered_widget"] = state.hovered_widget;
j["visible_windows"] = state.visible_windows;
j["open_popups"] = state.open_popups;
j["navigation"] = {
{"nav_id", absl::StrFormat("0x%08X", state.nav_id)},
{"nav_active", state.nav_active}};
nlohmann::json mouse_buttons;
for (int i = 0; i < 5; ++i) {
mouse_buttons.push_back(state.mouse_down[i]);
}
j["input"] = {
{"mouse_buttons", mouse_buttons},
{"mouse_pos", {state.mouse_pos_x, state.mouse_pos_y}},
{"modifiers",
{{"ctrl", state.ctrl_pressed},
{"shift", state.shift_pressed},
{"alt", state.alt_pressed}}}};
return j.dump(2);
#else
std::string json;
json.reserve(512);
json.append("{\n");
json.append(" \"frame_count\": ");
json.append(std::to_string(state.frame_count));
json.append(",\n");
json.append(" \"frame_rate\": ");
json.append(FormatFloatCompact(state.frame_rate));
json.append(",\n");
json.append(" \"focused_window\": ");
json.append(EscapeJsonString(state.focused_window));
json.append(",\n");
json.append(" \"focused_widget\": ");
json.append(EscapeJsonString(state.focused_widget));
json.append(",\n");
json.append(" \"hovered_widget\": ");
json.append(EscapeJsonString(state.hovered_widget));
json.append(",\n");
json.append(" \"visible_windows\": [");
for (size_t i = 0; i < state.visible_windows.size(); ++i) {
if (i > 0) {
json.append(", ");
}
json.append(EscapeJsonString(state.visible_windows[i]));
}
json.append("],\n");
json.append(" \"open_popups\": [");
for (size_t i = 0; i < state.open_popups.size(); ++i) {
if (i > 0) {
json.append(", ");
}
json.append(EscapeJsonString(state.open_popups[i]));
}
json.append("],\n");
json.append(" \"navigation\": {\n");
json.append(" \"nav_id\": ");
json.append(EscapeJsonString(absl::StrFormat("0x%08X", state.nav_id)));
json.append(",\n");
json.append(" \"nav_active\": ");
json.append(BoolToJson(state.nav_active));
json.append("\n },\n");
json.append(" \"input\": {\n");
json.append(" \"mouse_buttons\": [");
for (int i = 0; i < 5; ++i) {
if (i > 0) {
json.append(", ");
}
json.append(BoolToJson(state.mouse_down[i]));
}
json.append("],\n");
json.append(" \"mouse_pos\": [");
json.append(FormatFloatCompact(state.mouse_pos_x));
json.append(", ");
json.append(FormatFloatCompact(state.mouse_pos_y));
json.append("],\n");
json.append(" \"modifiers\": {\n");
json.append(" \"ctrl\": ");
json.append(BoolToJson(state.ctrl_pressed));
json.append(",\n");
json.append(" \"shift\": ");
json.append(BoolToJson(state.shift_pressed));
json.append(",\n");
json.append(" \"alt\": ");
json.append(BoolToJson(state.alt_pressed));
json.append("\n }\n");
json.append(" }\n");
json.append("}\n");
return json;
#endif // defined(YAZE_WITH_JSON)
}
} // namespace core
} // namespace yaze