#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 #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(c))); } else { escaped.push_back(static_cast(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