12#include "absl/strings/ascii.h"
13#include "absl/strings/str_cat.h"
14#include "absl/strings/str_format.h"
15#include "absl/strings/str_join.h"
16#include "absl/strings/str_split.h"
17#include "absl/strings/strip.h"
18#include "absl/time/clock.h"
19#include "imgui/imgui_internal.h"
21#if defined(YAZE_WITH_JSON)
22#include "nlohmann/json.hpp"
35 ImGuiContext* ctx = ImGui::GetCurrentContext();
36 if (ctx && ctx->CurrentWindow && !ctx->Windows.empty()) {
37 ImGui::PushID(name.c_str());
55 const std::string& widget_name)
const {
60 return absl::StrCat(path, widget_type,
":", widget_name);
71 absl::MutexLock lock(&
mutex_);
72 ImGuiContext* ctx = ImGui::GetCurrentContext();
74 current_frame_ = ctx->FrameCount;
75 }
else if (current_frame_ >= 0) {
80 frame_time_ = absl::Now();
81 for (
auto& [_, info] : widgets_) {
82 info.seen_in_current_frame =
false;
87 absl::MutexLock lock(&
mutex_);
88 for (
auto& [_, info] : widgets_) {
89 if (!info.seen_in_current_frame) {
92 info.bounds.valid =
false;
93 info.stale_frame_count += 1;
95 info.seen_in_current_frame =
false;
96 info.stale_frame_count = 0;
105 size_t slash = path.find(
'/');
106 if (slash == absl::string_view::npos) {
107 return std::string(path);
109 return std::string(path.substr(0, slash));
113 size_t colon = path.rfind(
':');
114 if (colon == absl::string_view::npos) {
115 size_t slash = path.rfind(
'/');
116 if (slash == absl::string_view::npos) {
117 return std::string(path);
119 return std::string(path.substr(slash + 1));
121 return std::string(path.substr(colon + 1));
125 if (timestamp == absl::Time()) {
129 std::chrono::system_clock::time_point chrono_time =
130 absl::ToChronoTime(timestamp);
131 std::time_t time_value = std::chrono::system_clock::to_time_t(chrono_time);
135 if (gmtime_s(&tm_buffer, &time_value) != 0) {
139 if (gmtime_r(&time_value, &tm_buffer) ==
nullptr) {
145 if (std::snprintf(buffer,
sizeof(buffer),
"%04d-%02d-%02dT%02d:%02d:%02dZ",
146 tm_buffer.tm_year + 1900, tm_buffer.tm_mon + 1,
147 tm_buffer.tm_mday, tm_buffer.tm_hour, tm_buffer.tm_min,
148 tm_buffer.tm_sec) <= 0) {
152 return std::string(buffer);
157 bounds.
min_x = rect.Min.x;
158 bounds.
min_y = rect.Min.y;
159 bounds.
max_x = rect.Max.x;
160 bounds.
max_y = rect.Max.y;
168 size_t end = pos + 1;
169 while (end < text.size() &&
170 (
static_cast<unsigned char>(text[end]) & 0xC0) == 0x80) {
180bool GlobMatch(absl::string_view pattern, absl::string_view text) {
183 size_t star = absl::string_view::npos;
184 size_t star_text = 0;
185 while (t < text.size()) {
186 if (p < pattern.size() && pattern[p] ==
'*') {
189 }
else if (p < pattern.size() && pattern[p] ==
'?') {
192 }
else if (p < pattern.size() && pattern[p] == text[t]) {
195 }
else if (star != absl::string_view::npos) {
203 while (p < pattern.size() && pattern[p] ==
'*') {
206 return p == pattern.size();
212 return std::round(
static_cast<double>(value) * 10.0) / 10.0;
215#if defined(YAZE_WITH_JSON)
217nlohmann::ordered_json WidgetToJson(
const std::string& path,
219 nlohmann::ordered_json widget;
220 widget[
"path"] = path;
221 widget[
"type"] = info.
type;
223 widget[
"label"] = info.
label;
225 widget[
"visible"] = info.
visible;
226 widget[
"enabled"] = info.
enabled;
235 widget[
"bounds"] =
nullptr;
250 quoted.reserve(value.size() + 2);
251 quoted.push_back(
'"');
252 for (
unsigned char c : value) {
255 quoted.append(
"\\\"");
258 quoted.append(
"\\\\");
261 quoted.append(
"\\b");
264 quoted.append(
"\\f");
267 quoted.append(
"\\n");
270 quoted.append(
"\\r");
273 quoted.append(
"\\t");
277 quoted.append(absl::StrFormat(
"\\u%04X",
static_cast<int>(c)));
279 quoted.push_back(
static_cast<char>(c));
284 quoted.push_back(
'"');
290 if (!std::isfinite(value)) {
293 return absl::StrFormat(
"%.1f", value);
298 std::vector<std::string> fields;
301 fields.push_back(absl::StrCat(
"\"imgui_id\": ", info.
imgui_id));
306 absl::StrCat(
"\"visible\": ", info.
visible ?
"true" :
"false"));
308 absl::StrCat(
"\"enabled\": ", info.
enabled ?
"true" :
"false"));
310 fields.push_back(absl::StrCat(
316 fields.push_back(
"\"bounds\": null");
318 fields.push_back(absl::StrCat(
"\"last_seen_frame\": ", info.
last_seen_frame));
320 absl::StrCat(
"\"last_seen_at\": ",
322 fields.push_back(absl::StrCat(
"\"stale\": ",
328 return absl::StrCat(
" {\n ", absl::StrJoin(fields,
",\n "),
336 const std::string& type, ImGuiID imgui_id,
337 const std::string& description,
339 absl::MutexLock lock(&
mutex_);
346 if (metadata.
label.has_value()) {
351 if (info.
label.empty()) {
352 info.
label = ExtractLabelFromPath(full_path);
361 info.
window_name = ExtractWindowFromPath(full_path);
364 ImGuiContext* ctx = ImGui::GetCurrentContext();
365 absl::Time observed_at = absl::Now();
368 const ImGuiLastItemData& last = ctx->LastItemData;
369 if (metadata.
visible.has_value()) {
372 info.
visible = (last.StatusFlags & ImGuiItemStatusFlags_Visible) != 0;
375 if (metadata.
enabled.has_value()) {
378 info.
enabled = (last.ItemFlags & ImGuiItemFlags_Disabled) == 0;
381 if (metadata.
bounds.has_value()) {
384 info.
bounds = BoundsFromImGui(last.Rect);
391 if (metadata.
bounds.has_value()) {
396 if (current_frame_ >= 0) {
407 const std::string& pattern)
const {
408 absl::MutexLock lock(&
mutex_);
409 std::vector<std::string> matches;
411 for (
const auto& entry : widgets_) {
412 if (GlobMatch(pattern, entry.first)) {
413 matches.push_back(entry.first);
418 std::sort(matches.begin(), matches.end());
423 absl::MutexLock lock(&
mutex_);
424 auto it = widgets_.find(full_path);
425 if (it != widgets_.end()) {
426 return it->second.imgui_id;
432 const std::string& full_path)
const {
433 absl::MutexLock lock(&
mutex_);
434 auto it = widgets_.find(full_path);
435 if (it != widgets_.end()) {
441std::unordered_map<std::string, WidgetIdRegistry::WidgetInfo>
443 absl::MutexLock lock(&
mutex_);
448 absl::MutexLock lock(&
mutex_);
454 absl::MutexLock lock(&
mutex_);
455 std::ostringstream ss;
457 if (format ==
"json") {
459 using Entry = std::unordered_map<std::string, WidgetInfo>::value_type;
460 std::vector<const Entry*> entries;
461 entries.reserve(widgets_.size());
462 for (
const Entry& entry : widgets_) {
463 entries.push_back(&entry);
466 entries.begin(), entries.end(),
467 [](
const Entry* a,
const Entry* b) { return a->first < b->first; });
469#if defined(YAZE_WITH_JSON)
470 nlohmann::ordered_json widgets = nlohmann::ordered_json::array();
471 for (
const Entry* entry : entries) {
472 widgets.push_back(WidgetToJson(entry->first, entry->second));
474 nlohmann::ordered_json catalog;
475 catalog[
"widgets"] = std::move(widgets);
478 ss << catalog.dump(2,
' ',
false,
479 nlohmann::ordered_json::error_handler_t::replace)
482 std::vector<std::string> objects;
483 objects.reserve(entries.size());
484 for (
const Entry* entry : entries) {
485 objects.push_back(WidgetToJson(entry->first, entry->second));
487 if (objects.empty()) {
488 ss <<
"{\n \"widgets\": []\n}\n";
490 ss <<
"{\n \"widgets\": [\n"
491 << absl::StrJoin(objects,
",\n") <<
"\n ]\n}\n";
498 for (
const auto& [path, info] : widgets_) {
499 ss << absl::StrFormat(
" - path: \"%s\"\n", path);
500 ss << absl::StrFormat(
" type: %s\n", info.
type);
501 ss << absl::StrFormat(
" imgui_id: %u\n", info.
imgui_id);
502 ss << absl::StrFormat(
" label: \"%s\"\n", info.
label);
503 ss << absl::StrFormat(
" window: \"%s\"\n", info.
window_name);
504 ss << absl::StrFormat(
" visible: %s\n",
505 info.
visible ?
"true" :
"false");
506 ss << absl::StrFormat(
" enabled: %s\n",
507 info.
enabled ?
"true" :
"false");
510 ss << absl::StrFormat(
" min: [%0.1f, %0.1f]\n", info.
bounds.
min_x,
512 ss << absl::StrFormat(
" max: [%0.1f, %0.1f]\n", info.
bounds.
max_x,
516 std::string iso_timestamp = FormatTimestampUTC(info.
last_seen_time);
517 ss << absl::StrFormat(
" last_seen_at: %s\n", iso_timestamp);
518 ss << absl::StrFormat(
" stale: %s\n",
522 std::vector<std::string> segments = absl::StrSplit(path,
'/');
523 if (!segments.empty()) {
525 if (segments.size() > 0) {
526 ss << absl::StrFormat(
" editor: %s\n", segments[0]);
528 if (segments.size() > 1) {
529 ss << absl::StrFormat(
" tab: %s\n", segments[1]);
531 if (segments.size() > 2) {
532 ss << absl::StrFormat(
" section: %s\n", segments[2]);
537 ss << absl::StrFormat(
" description: %s\n", info.
description);
542 if (info.
type ==
"button") {
544 }
else if (info.
type ==
"input") {
546 }
else if (info.
type ==
"canvas") {
547 ss <<
"click, drag, scroll";
548 }
else if (info.
type ==
"checkbox") {
550 }
else if (info.
type ==
"slider") {
563 const std::string& format)
const {
565 std::ofstream file(output_file);
566 if (file.is_open()) {
573 size_t pos = label.find(
"##");
574 if (pos != absl::string_view::npos) {
575 label = label.substr(0, pos);
577 std::string sanitized = std::string(absl::StripAsciiWhitespace(label));
586 auto it = widgets_.begin();
587 while (it != widgets_.end()) {
589 it = widgets_.erase(it);
600 if (current_frame_ < 0) {