3#include "common/config.h"
5#if defined(EVENGINE_WEBGPU) && defined(__EMSCRIPTEN__)
21#include <Poco/Dynamic/Var.h>
22#include <Poco/JSON/Array.h>
23#include <Poco/JSON/Object.h>
24#include <Poco/JSON/Stringifier.h>
74bool isClickable(
const UINode&
node) {
78 node.handlerClick != 0;
81const char* focusModeName(
FocusMode mode) {
129 std::ostringstream out;
130 Poco::JSON::Stringifier::stringify(
value, out, 0, 0);
134Poco::JSON::Object::Ptr nodeJson(
const UIHost::Tree& tree,
int index) {
135 Poco::JSON::Object::Ptr out(
new Poco::JSON::Object());
136 if (index < 0 || index >=
static_cast<int>(tree.nodes.size()))
return out;
138 const UINode&
node = tree.nodes[
static_cast<size_t>(
index)];
139 out->set(
"type", nodeTypeName(
node.type));
140 out->set(
"id",
node.id);
141 if (!
node.key.empty()) out->set(
"key",
node.key);
142 out->set(
"text",
node.text);
143 out->set(
"visible",
node.visible);
144 out->set(
"enabled",
node.enabled);
145 out->set(
"clickable", isClickable(
node));
146 out->set(
"focusMode", focusModeName(
node.focusMode));
147 out->set(
"focused",
node.focused);
148 out->set(
"tabIndex",
node.tabIndex);
149 out->set(
"mouseFilter", mouseFilterName(
node.mouseFilter));
150 out->set(
"theme", themePresetName(
node.themePreset));
151 if (!
node.focusPrevious.empty()) out->set(
"focusPrevious",
node.focusPrevious);
152 if (!
node.focusNext.empty()) out->set(
"focusNext",
node.focusNext);
153 if (!
node.focusLeft.empty()) out->set(
"focusLeft",
node.focusLeft);
154 if (!
node.focusRight.empty()) out->set(
"focusRight",
node.focusRight);
155 if (!
node.focusUp.empty()) out->set(
"focusUp",
node.focusUp);
156 if (!
node.focusDown.empty()) out->set(
"focusDown",
node.focusDown);
157 Poco::JSON::Object::Ptr accessibility(
new Poco::JSON::Object());
158 accessibility->set(
"role", accessibilityRoleName(
node.accessibilityRole));
159 accessibility->set(
"name",
node.accessibilityName.empty() ?
node.text
160 :
node.accessibilityName);
161 if (!
node.accessibilityDescription.empty())
162 accessibility->set(
"description",
node.accessibilityDescription);
163 out->set(
"accessibility", accessibility);
166 out->set(
"checked",
node.checked);
169 out->set(
"value",
node.value);
170 out->set(
"min",
node.minValue);
171 out->set(
"max",
node.maxValue);
174 Poco::JSON::Array::Ptr
color(
new Poco::JSON::Array());
175 color->add(
static_cast<double>(
node.tintR));
176 color->add(
static_cast<double>(
node.tintG));
177 color->add(
static_cast<double>(
node.tintB));
178 color->add(
static_cast<double>(
node.tintA));
179 out->set(
"color",
color);
183 out->set(
"valueText",
node.valueText);
184 if (!
node.tooltip.empty()) out->set(
"tooltip",
node.tooltip);
186 Poco::JSON::Array::Ptr
children(
new Poco::JSON::Array());
188 if (
child >=
static_cast<int>(tree.nodes.size()))
break;
195Poco::JSON::Object::Ptr hostJson(UIHost&
host) {
196 Poco::JSON::Object::Ptr out(
new Poco::JSON::Object());
197 const auto meta =
host.meta();
198 const auto tree =
host.tree();
199 out->set(
"name", meta->name);
200 out->set(
"visible", meta->visible);
201 out->set(
"layer", meta->layer);
202 out->set(
"modal", meta->modal);
203 out->set(
"overlay", meta->overlay);
204 out->set(
"nodeCount",
static_cast<int>(tree->nodes.size()));
205 if (tree->root >= 0) out->set(
"root", nodeJson(*tree, tree->root));
209std::vector<UIHostHandle> hosts() {
210 std::vector<UIHostHandle> result;
211 if (ecs::current()->getManager<UIHost>() ==
nullptr)
return result;
212 auto view = ecs::View<UIHost, UIHost::Meta, UIHost::Tree>();
213 for (
auto it =
view.begin(); it !=
view.end(); ++it) {
214 auto [meta, tree] = *it;
226std::vector<WidgetMatch> findWidgets(
const std::string& hostName,
const std::string& widgetId) {
227 std::vector<WidgetMatch> result;
230 if (!
host || (!hostName.empty() &&
host->get().getName() != hostName))
continue;
231 auto node =
host->get().findById(widgetId);
233 const auto tree =
host->get().tree();
234 result.push_back({
handle,
static_cast<int>(&
node->get() - tree->nodes.data())});
239std::string matchError(
const std::string&
host,
const std::string&
widget,
size_t matchCount) {
240 if (
widget.empty())
return "error: missing widget";
241 if (matchCount == 0) {
242 if (!
host.empty())
return "error: widget not found: " +
host +
"/" +
widget;
243 return "error: widget not found: " +
widget;
245 return "error: widget id is ambiguous; specify host: " +
widget;
250 std::string tree(
const std::string& hostName)
const override {
251 Poco::JSON::Array::Ptr items(
new Poco::JSON::Array());
254 if (!
host || (!hostName.empty() &&
host->get().getName() != hostName))
continue;
255 items->add(hostJson(
host->get()));
257 if (!hostName.empty() && items->size() == 0)
return "error: UI host not found: " + hostName;
259 Poco::JSON::Object::Ptr out(
new Poco::JSON::Object());
260 out->set(
"hostCount",
static_cast<int>(items->size()));
261 out->set(
"hosts", items);
262 return stringify(Poco::Dynamic::Var(out));
265 std::string get(
const std::string& hostName,
const std::string& widgetId)
const override {
266 const std::vector<WidgetMatch> matches = findWidgets(hostName, widgetId);
267 if (matches.size() != 1)
return matchError(hostName, widgetId, matches.size());
269 if (!
host)
return "error: UI host became stale";
270 Poco::JSON::Object::Ptr out = nodeJson(*
host->get().tree(), matches[0].nodeIndex);
271 out->set(
"host",
host->get().getName());
272 out->set(
"hostVisible",
host->get().meta()->visible);
273 return stringify(Poco::Dynamic::Var(out));
276 std::string click(
const std::string& hostName,
const std::string& widgetId)
override {
277 const std::vector<WidgetMatch> matches = findWidgets(hostName, widgetId);
278 if (matches.size() != 1)
return matchError(hostName, widgetId, matches.size());
280 if (!
host)
return "error: UI host became stale";
281 UIHost& resolvedHost =
host->get();
282 const auto tree = resolvedHost.tree();
283 if (matches[0].
nodeIndex < 0 || matches[0].
nodeIndex >=
static_cast<int>(tree->nodes.size()))
284 return "error: UI widget became stale";
285 UINode&
node = tree->nodes[
static_cast<std::size_t
>(matches[0].nodeIndex)];
286 if (!resolvedHost.meta()->visible || !
node.visible)
287 return "error: widget is not visible: " + resolvedHost.getName() +
"/" + widgetId;
288 if (!isClickable(
node))
return "error: widget is not clickable: " + resolvedHost.getName() +
"/" + widgetId;
291 event.host = matches[0].host;
292 event.hostName = resolvedHost.getName();
293 event.nodeId =
node.id;
294 event.nodeIndex = matches[0].nodeIndex;
295 event.kind =
"click";
296 event.handlerIndex =
node.handlerClick;
299 Poco::JSON::Object::Ptr out(
new Poco::JSON::Object());
300 out->set(
"queued",
true);
301 out->set(
"host", resolvedHost.getName());
302 out->set(
"widget", widgetId);
303 out->set(
"event",
"click");
304 return stringify(Poco::Dynamic::Var(out));
308 static int nodeIndex(
const UIHost::Tree& tree,
const UINode*
node) {
309 if (!
node || tree.nodes.empty())
return -1;
310 return static_cast<int>(
node - tree.nodes.data());
317 static UIAutomationImpl
impl;
318 eve::cap::provide<eve::IUIAutomation>(&
impl);
Semantic retained-UI automation provided by the ui module.
static eve::OptionalRef< UIHost > resolve(UIHostHandle handle) noexcept
Resolves a UI host handle with ECS generation checking.
static std::vector< UIEvent > & pendingEvents()
Pending events.
Result< std::string > stringify(const eve::Value &value)
Serialize the canonical owning Value as deterministic compact JSON.
MouseFilter
Pointer event participation and propagation policy.
WidgetDesc child(std::string id, std::vector< WidgetDesc > children, float width, float height)
Scrollable child region with an explicit size.
NodeType
Widget node kinds understood by the UI renderer.
FocusMode
Keyboard/gamepad focus policy for an interactive control.
ThemePreset
Built-in theme override inherited by a retained UI subtree.
ecs::EntityHandle UIHostHandle
AccessibilityRole
Platform-neutral accessibility role exposed by controls.
void registerUIAutomationCapabilities()
Registers ui automation capabilities.