yaze 0.3.2
Link to the Past ROM Editor
 
Loading...
Searching...
No Matches
widget_state_capture.cc
Go to the documentation of this file.
2
3#include "absl/strings/str_format.h"
4#if defined(YAZE_ENABLE_IMGUI_TEST_ENGINE) && YAZE_ENABLE_IMGUI_TEST_ENGINE
5#include "imgui.h"
6#include "imgui_internal.h"
7#else
8#include "imgui/imgui.h"
9#endif
10#include <string>
11
12#if defined(YAZE_WITH_JSON)
13#include "nlohmann/json.hpp"
14#endif
15
16namespace yaze {
17namespace core {
18
19#if !defined(YAZE_WITH_JSON)
20namespace {
21
22std::string EscapeJsonString(const std::string& value) {
23 std::string escaped;
24 escaped.reserve(value.size() + 2);
25 escaped.push_back('"');
26
27 for (unsigned char c : value) {
28 switch (c) {
29 case '"':
30 escaped.append("\\\"");
31 break;
32 case '\\':
33 escaped.append("\\\\");
34 break;
35 case '\b':
36 escaped.append("\\b");
37 break;
38 case '\f':
39 escaped.append("\\f");
40 break;
41 case '\n':
42 escaped.append("\\n");
43 break;
44 case '\r':
45 escaped.append("\\r");
46 break;
47 case '\t':
48 escaped.append("\\t");
49 break;
50 default:
51 if (c <= 0x1F) {
52 escaped.append(absl::StrFormat("\\\\u%04X", static_cast<int>(c)));
53 } else {
54 escaped.push_back(static_cast<char>(c));
55 }
56 break;
57 }
58 }
59
60 escaped.push_back('"');
61 return escaped;
62}
63
64const char* BoolToJson(bool value) { return value ? "true" : "false"; }
65
66std::string FormatFloat(float value) {
67 // Match typical JSON formatting without trailing zeros when possible.
68 return absl::StrFormat("%.4f", value);
69}
70
71std::string FormatFloatCompact(float value) {
72 std::string formatted = FormatFloat(value);
73
74 // Trim trailing zeros while keeping at least one decimal place.
75 if (formatted.find('.') != std::string::npos) {
76 while (!formatted.empty() && formatted.back() == '0') {
77 formatted.pop_back();
78 }
79 if (!formatted.empty() && formatted.back() == '.') {
80 formatted.push_back('0');
81 }
82 }
83 return formatted;
84}
85
86} // namespace
87#endif // !defined(YAZE_WITH_JSON)
88
89std::string CaptureWidgetState() {
90 WidgetState state;
91
92#if defined(YAZE_ENABLE_IMGUI_TEST_ENGINE) && YAZE_ENABLE_IMGUI_TEST_ENGINE
93 // Check if ImGui context is available
94 ImGuiContext* ctx = ImGui::GetCurrentContext();
95 if (!ctx) {
96 return R"({"error": "ImGui context not available"})";
97 }
98
99 ImGuiIO& io = ImGui::GetIO();
100
101 // Capture frame information
102 state.frame_count = ImGui::GetFrameCount();
103 state.frame_rate = io.Framerate;
104
105 // Capture focused window
106 ImGuiWindow* current = ImGui::GetCurrentWindow();
107 if (current && !current->Hidden) {
108 state.focused_window = current->Name;
109 }
110
111 // Capture active widget (focused for input)
112 ImGuiID active_id = ImGui::GetActiveID();
113 if (active_id != 0) {
114 state.focused_widget = absl::StrFormat("0x%08X", active_id);
115 }
116
117 // Capture hovered widget
118 ImGuiID hovered_id = ImGui::GetHoveredID();
119 if (hovered_id != 0) {
120 state.hovered_widget = absl::StrFormat("0x%08X", hovered_id);
121 }
122
123 // Traverse visible windows
124 for (ImGuiWindow* window : ctx->Windows) {
125 if (window && window->Active && !window->Hidden) {
126 state.visible_windows.push_back(window->Name);
127 }
128 }
129
130 // Capture open popups
131 for (int i = 0; i < ctx->OpenPopupStack.Size; i++) {
132 ImGuiPopupData& popup = ctx->OpenPopupStack[i];
133 if (popup.Window && !popup.Window->Hidden) {
134 state.open_popups.push_back(popup.Window->Name);
135 }
136 }
137
138 // Capture navigation state
139 state.nav_id = ctx->NavId;
140 state.nav_active = ctx->NavWindow != nullptr;
141
142 // Capture mouse state
143 for (int i = 0; i < 5; i++) {
144 state.mouse_down[i] = io.MouseDown[i];
145 }
146 state.mouse_pos_x = io.MousePos.x;
147 state.mouse_pos_y = io.MousePos.y;
148
149 // Capture keyboard modifiers
150 state.ctrl_pressed = io.KeyCtrl;
151 state.shift_pressed = io.KeyShift;
152 state.alt_pressed = io.KeyAlt;
153
154#else
155 // When UI test engine / ImGui internals aren't available, provide a minimal
156 // payload so downstream systems still receive structured JSON. This keeps
157 // builds that exclude the UI test engine (e.g., Windows release) working.
158 return "{\"warning\": \"Widget state capture unavailable (UI test engine disabled)\"}";
159#endif
160
161 return SerializeWidgetStateToJson(state);
162}
163
164std::string SerializeWidgetStateToJson(const WidgetState& state) {
165#if defined(YAZE_WITH_JSON)
166 nlohmann::json j;
167
168 j["frame_count"] = state.frame_count;
169 j["frame_rate"] = state.frame_rate;
170 j["focused_window"] = state.focused_window;
171 j["focused_widget"] = state.focused_widget;
172 j["hovered_widget"] = state.hovered_widget;
173 j["visible_windows"] = state.visible_windows;
174 j["open_popups"] = state.open_popups;
175 j["navigation"] = {
176 {"nav_id", absl::StrFormat("0x%08X", state.nav_id)},
177 {"nav_active", state.nav_active}};
178
179 nlohmann::json mouse_buttons;
180 for (int i = 0; i < 5; ++i) {
181 mouse_buttons.push_back(state.mouse_down[i]);
182 }
183
184 j["input"] = {
185 {"mouse_buttons", mouse_buttons},
186 {"mouse_pos", {state.mouse_pos_x, state.mouse_pos_y}},
187 {"modifiers",
188 {{"ctrl", state.ctrl_pressed},
189 {"shift", state.shift_pressed},
190 {"alt", state.alt_pressed}}}};
191
192 return j.dump(2);
193#else
194 std::string json;
195 json.reserve(512);
196
197 json.append("{\n");
198 json.append(" \"frame_count\": ");
199 json.append(std::to_string(state.frame_count));
200 json.append(",\n");
201
202 json.append(" \"frame_rate\": ");
203 json.append(FormatFloatCompact(state.frame_rate));
204 json.append(",\n");
205
206 json.append(" \"focused_window\": ");
207 json.append(EscapeJsonString(state.focused_window));
208 json.append(",\n");
209
210 json.append(" \"focused_widget\": ");
211 json.append(EscapeJsonString(state.focused_widget));
212 json.append(",\n");
213
214 json.append(" \"hovered_widget\": ");
215 json.append(EscapeJsonString(state.hovered_widget));
216 json.append(",\n");
217
218 json.append(" \"visible_windows\": [");
219 for (size_t i = 0; i < state.visible_windows.size(); ++i) {
220 if (i > 0) {
221 json.append(", ");
222 }
223 json.append(EscapeJsonString(state.visible_windows[i]));
224 }
225 json.append("],\n");
226
227 json.append(" \"open_popups\": [");
228 for (size_t i = 0; i < state.open_popups.size(); ++i) {
229 if (i > 0) {
230 json.append(", ");
231 }
232 json.append(EscapeJsonString(state.open_popups[i]));
233 }
234 json.append("],\n");
235
236 json.append(" \"navigation\": {\n");
237 json.append(" \"nav_id\": ");
238 json.append(EscapeJsonString(absl::StrFormat("0x%08X", state.nav_id)));
239 json.append(",\n");
240 json.append(" \"nav_active\": ");
241 json.append(BoolToJson(state.nav_active));
242 json.append("\n },\n");
243
244 json.append(" \"input\": {\n");
245 json.append(" \"mouse_buttons\": [");
246 for (int i = 0; i < 5; ++i) {
247 if (i > 0) {
248 json.append(", ");
249 }
250 json.append(BoolToJson(state.mouse_down[i]));
251 }
252 json.append("],\n");
253
254 json.append(" \"mouse_pos\": [");
255 json.append(FormatFloatCompact(state.mouse_pos_x));
256 json.append(", ");
257 json.append(FormatFloatCompact(state.mouse_pos_y));
258 json.append("],\n");
259
260 json.append(" \"modifiers\": {\n");
261 json.append(" \"ctrl\": ");
262 json.append(BoolToJson(state.ctrl_pressed));
263 json.append(",\n");
264 json.append(" \"shift\": ");
265 json.append(BoolToJson(state.shift_pressed));
266 json.append(",\n");
267 json.append(" \"alt\": ");
268 json.append(BoolToJson(state.alt_pressed));
269 json.append("\n }\n");
270 json.append(" }\n");
271 json.append("}\n");
272
273 return json;
274#endif // defined(YAZE_WITH_JSON)
275}
276
277} // namespace core
278} // namespace yaze
std::string SerializeWidgetStateToJson(const WidgetState &state)
std::string CaptureWidgetState()
Main namespace for the application.
std::vector< std::string > visible_windows
std::vector< std::string > open_popups