yaze 0.3.2
Link to the Past ROM Editor
 
Loading...
Searching...
No Matches
widget_discovery_service.cc
Go to the documentation of this file.
2
3#include <algorithm>
4#include <cstdint>
5#include <map>
6#include <string>
7#include <utility>
8
9#include "absl/strings/ascii.h"
10#include "absl/strings/match.h"
11#include "absl/strings/str_cat.h"
12#include "absl/time/clock.h"
13#include "absl/time/time.h"
14
15namespace yaze {
16namespace test {
17namespace {
18
20 int index = -1;
21 bool visible = false;
22};
23
24} // namespace
25
27 ImGuiTestContext* ctx, const DiscoverWidgetsRequest& request,
28 DiscoverWidgetsResponse* response) const {
29 if (!response) {
30 return;
31 }
32
33 response->clear_windows();
34 response->set_total_widgets(0);
35 response->set_generated_at_ms(absl::ToUnixMillis(absl::Now()));
36
37 const auto& registry = gui::WidgetIdRegistry::Instance().GetAllWidgets();
38 if (registry.empty()) {
39 return;
40 }
41
42 const std::string window_filter_lower =
43 absl::AsciiStrToLower(std::string(request.window_filter()));
44 const std::string path_prefix_lower =
45 absl::AsciiStrToLower(std::string(request.path_prefix()));
46 const bool include_invisible = request.include_invisible();
47 const bool include_disabled = request.include_disabled();
48
49 std::map<std::string, WindowEntry> window_lookup;
50 int total_widgets = 0;
51
52 for (const auto& [path, info] : registry) {
53 if (!MatchesType(info.type, request.type_filter())) {
54 continue;
55 }
56
57 if (!MatchesPathPrefix(path, path_prefix_lower)) {
58 continue;
59 }
60
61 const std::string window_name =
62 info.window_name.empty() ? ExtractWindowName(path) : info.window_name;
63 if (!MatchesWindow(window_name, window_filter_lower)) {
64 continue;
65 }
66
67 const std::string label =
68 info.label.empty() ? ExtractLabel(path) : info.label;
69
70 bool widget_enabled = info.enabled;
71 bool widget_visible = info.visible;
72
73#if defined(YAZE_ENABLE_IMGUI_TEST_ENGINE) && YAZE_ENABLE_IMGUI_TEST_ENGINE
74 bool has_item_info = false;
75 ImGuiTestItemInfo item_info;
76 if (ctx) {
77 item_info = ctx->ItemInfo(label.c_str(), ImGuiTestOpFlags_NoError);
78 if (item_info.ID != 0) {
79 has_item_info = true;
80 widget_visible = item_info.RectClipped.GetWidth() > 0.0f &&
81 item_info.RectClipped.GetHeight() > 0.0f;
82 widget_enabled = (item_info.ItemFlags & ImGuiItemFlags_Disabled) == 0;
83 }
84 }
85#else
86 (void)ctx;
87#endif
88
89 auto [it, inserted] = window_lookup.emplace(window_name, WindowEntry{});
90 WindowEntry& entry = it->second;
91 if (inserted) {
92 entry.visible = widget_visible;
93 } else {
94 entry.visible = entry.visible || widget_visible;
95 }
96
97 if (!include_invisible && !widget_visible) {
98 continue;
99 }
100 if (!include_disabled && !widget_enabled) {
101 continue;
102 }
103
104 if (entry.index == -1) {
105 DiscoveredWindow* window_proto = response->add_windows();
106 entry.index = response->windows_size() - 1;
107 window_proto->set_name(window_name);
108 window_proto->set_visible(entry.visible);
109 }
110
111 auto* window_proto = response->mutable_windows(entry.index);
112 window_proto->set_visible(entry.visible);
113
114 auto* widget_proto = window_proto->add_widgets();
115 widget_proto->set_path(path);
116 widget_proto->set_label(label);
117 widget_proto->set_type(info.type);
118 widget_proto->set_suggested_action(SuggestedAction(info.type, label));
119 widget_proto->set_visible(widget_visible);
120 widget_proto->set_enabled(widget_enabled);
121 widget_proto->set_widget_id(info.imgui_id);
122
123 if (!info.description.empty()) {
124 widget_proto->set_description(info.description);
125 }
126
127 if (info.bounds.valid) {
128 WidgetBounds* bounds = widget_proto->mutable_bounds();
129 bounds->set_min_x(info.bounds.min_x);
130 bounds->set_min_y(info.bounds.min_y);
131 bounds->set_max_x(info.bounds.max_x);
132 bounds->set_max_y(info.bounds.max_y);
133#if defined(YAZE_ENABLE_IMGUI_TEST_ENGINE) && YAZE_ENABLE_IMGUI_TEST_ENGINE
134 } else if (ctx && has_item_info) {
135 WidgetBounds* bounds = widget_proto->mutable_bounds();
136 bounds->set_min_x(item_info.RectFull.Min.x);
137 bounds->set_min_y(item_info.RectFull.Min.y);
138 bounds->set_max_x(item_info.RectFull.Max.x);
139 bounds->set_max_y(item_info.RectFull.Max.y);
140 } else {
141 (void)ctx;
142#else
143 } else {
144 (void)ctx;
145#endif
146 }
147
148 widget_proto->set_last_seen_frame(info.last_seen_frame);
149 int64_t last_seen_ms = 0;
150 if (info.last_seen_time != absl::Time()) {
151 last_seen_ms = absl::ToUnixMillis(info.last_seen_time);
152 }
153 widget_proto->set_last_seen_at_ms(last_seen_ms);
154 widget_proto->set_stale(info.stale_frame_count > 0);
155
156 ++total_widgets;
157 }
158
159 response->set_total_widgets(total_widgets);
160}
161
162bool WidgetDiscoveryService::MatchesWindow(absl::string_view window_name,
163 absl::string_view filter_lower) const {
164 if (filter_lower.empty()) {
165 return true;
166 }
167 std::string name_lower = absl::AsciiStrToLower(std::string(window_name));
168 return absl::StrContains(name_lower, filter_lower);
169}
170
172 absl::string_view prefix_lower) const {
173 if (prefix_lower.empty()) {
174 return true;
175 }
176 std::string path_lower = absl::AsciiStrToLower(std::string(path));
177 return absl::StartsWith(path_lower, prefix_lower);
178}
179
180bool WidgetDiscoveryService::MatchesType(absl::string_view type,
181 WidgetType filter) const {
182 if (filter == WIDGET_TYPE_UNSPECIFIED || filter == WIDGET_TYPE_ALL) {
183 return true;
184 }
185
186 std::string type_lower = absl::AsciiStrToLower(std::string(type));
187 switch (filter) {
188 case WIDGET_TYPE_BUTTON:
189 return type_lower == "button" || type_lower == "menuitem";
190 case WIDGET_TYPE_INPUT:
191 return type_lower == "input" || type_lower == "textbox" ||
192 type_lower == "inputtext";
193 case WIDGET_TYPE_MENU:
194 return type_lower == "menu" || type_lower == "menuitem";
195 case WIDGET_TYPE_TAB:
196 return type_lower == "tab";
197 case WIDGET_TYPE_CHECKBOX:
198 return type_lower == "checkbox";
199 case WIDGET_TYPE_SLIDER:
200 return type_lower == "slider" || type_lower == "drag";
201 case WIDGET_TYPE_CANVAS:
202 return type_lower == "canvas";
203 case WIDGET_TYPE_SELECTABLE:
204 return type_lower == "selectable";
205 case WIDGET_TYPE_OTHER:
206 return true;
207 default:
208 return true;
209 }
210}
211
213 absl::string_view path) const {
214 size_t slash = path.find('/');
215 if (slash == absl::string_view::npos) {
216 return std::string(path);
217 }
218 return std::string(path.substr(0, slash));
219}
220
221std::string WidgetDiscoveryService::ExtractLabel(absl::string_view path) const {
222 size_t colon = path.rfind(':');
223 if (colon == absl::string_view::npos) {
224 size_t slash = path.rfind('/');
225 if (slash == absl::string_view::npos) {
226 return std::string(path);
227 }
228 return std::string(path.substr(slash + 1));
229 }
230 return std::string(path.substr(colon + 1));
231}
232
233std::string WidgetDiscoveryService::SuggestedAction(absl::string_view type,
234 absl::string_view label) const {
235 std::string type_lower = absl::AsciiStrToLower(std::string(type));
236 if (type_lower == "button" || type_lower == "menuitem") {
237 return absl::StrCat("Click button:", label);
238 }
239 if (type_lower == "checkbox") {
240 return absl::StrCat("Toggle checkbox:", label);
241 }
242 if (type_lower == "slider" || type_lower == "drag") {
243 return absl::StrCat("Adjust slider:", label);
244 }
245 if (type_lower == "input" || type_lower == "textbox" ||
246 type_lower == "inputtext") {
247 return absl::StrCat("Type text into:", label);
248 }
249 if (type_lower == "canvas") {
250 return absl::StrCat("Interact with canvas:", label);
251 }
252 if (type_lower == "tab") {
253 return absl::StrCat("Switch to tab:", label);
254 }
255 return absl::StrCat("Interact with:", label);
256}
257
258} // namespace test
259} // namespace yaze
const std::unordered_map< std::string, WidgetInfo > & GetAllWidgets() const
static WidgetIdRegistry & Instance()
bool MatchesPathPrefix(absl::string_view path, absl::string_view prefix) const
void CollectWidgets(ImGuiTestContext *ctx, const DiscoverWidgetsRequest &request, DiscoverWidgetsResponse *response) const
bool MatchesType(absl::string_view type, WidgetType filter) const
std::string ExtractLabel(absl::string_view path) const
bool MatchesWindow(absl::string_view window_name, absl::string_view filter) const
std::string ExtractWindowName(absl::string_view path) const
std::string SuggestedAction(absl::string_view type, absl::string_view label) const
Main namespace for the application.