22std::vector<UIEvent> g_pending;
23std::vector<UIClick> g_clicks;
24std::vector<UIChange> g_changes;
25UIBackend *g_backend =
nullptr;
27std::map<std::string, ViewportState> g_viewports;
29std::string viewportKey(UIHost *host,
const UINode &
n) {
30 const std::string hostName =
31 host && !host->meta()->name.empty() ? host->meta()->name :
"?";
32 return hostName +
"/" + (
n.id.empty() ?
"viewport" :
n.id);
35void pushPending(UIHost *host,
const UINode &
n,
const char *
kind, uint32_t handlerIndex,
36 bool toggleValue =
false,
float floatValue = 0.f, std::string textValue = {}) {
39 ev.hostName = host ? host->meta()->name :
"";
42 ev.handlerIndex = handlerIndex;
43 ev.toggleValue = toggleValue;
44 ev.floatValue = floatValue;
45 ev.textValue = std::move(textValue);
46 g_pending.push_back(std::move(ev));
49void walkNode(UIHost *host, UIHost::Tree *tree,
int index);
51void walkSiblings(UIHost *host, UIHost::Tree *tree,
int index) {
52 while (index >= 0 && index <
int(tree->nodes.size())) {
53 const int next = tree->nodes[size_t(index)].nextSibling;
54 walkNode(host, tree, index);
59void emitSpacer(
float main,
float cross,
bool row) {
61 if (cross < 0.f) cross = 0.f;
63 if (
main <= 0.f && cross <= 0.f) {
64 ImGui::Dummy(ImVec2(0.f, 0.f));
66 ImGui::Dummy(ImVec2(
main, cross > 0.f ? cross : 1.f));
69 if (
main <= 0.f && cross <= 0.f) {
70 ImGui::Dummy(ImVec2(0.f, 0.f));
72 ImGui::Dummy(ImVec2(cross > 0.f ? cross : 1.f,
main));
78void drawNinePatch(
void *handle,
const ImVec2 &
pos,
const ImVec2 &size,
const ImVec2 &uv0,
79 const ImVec2 &uv1,
float bL,
float bT,
float bR,
float bB,
int texW,
int texH,
81 ImDrawList *dl = ImGui::GetWindowDrawList();
82 const auto draw = [&](
float x0,
float y0,
float x1,
float y1,
float u0,
float v0,
float u1,
84 dl->AddImage(
static_cast<ImTextureID
>(handle), ImVec2(
pos.x + x0,
pos.y + y0),
85 ImVec2(
pos.x + x1,
pos.y + y1), ImVec2(u0, v0), ImVec2(u1, v1), tint);
87 if (bL <= 0.f && bT <= 0.f && bR <= 0.f && bB <= 0.f) {
88 draw(0.f, 0.f, size.x, size.y, uv0.x, uv0.y, uv1.x, uv1.y);
91 if (texW <= 0 || texH <= 0) {
92 draw(0.f, 0.f, size.x, size.y, uv0.x, uv0.y, uv1.x, uv1.y);
95 const float uPerPx = (uv1.x - uv0.x) /
float(texW);
96 const float vPerPx = (uv1.y - uv0.y) /
float(texH);
97 const float uL = uv0.x + std::min(bL, size.x * 0.5f) * uPerPx;
98 const float uR = uv1.x - std::min(bR, size.x * 0.5f) * uPerPx;
99 const float vT = uv0.y + std::min(bT, size.y * 0.5f) * vPerPx;
100 const float vB = uv1.y - std::min(bB, size.y * 0.5f) * vPerPx;
101 const float bl = std::min(bL, size.x * 0.5f);
102 const float br = std::min(bR, size.x * 0.5f);
103 const float bt = std::min(bT, size.y * 0.5f);
104 const float bb = std::min(bB, size.y * 0.5f);
106 draw(0.f, 0.f, bl, bt, uv0.x, uv0.y, uL, vT);
107 draw(size.x - br, 0.f, size.x, bt, uR, uv0.y, uv1.x, vT);
108 draw(0.f, size.y - bb, bl, size.y, uv0.x, vB, uL, uv1.y);
109 draw(size.x - br, size.y - bb, size.x, size.y, uR, vB, uv1.x, uv1.y);
111 draw(bl, 0.f, size.x - br, bt, uL, uv0.y, uR, vT);
112 draw(bl, size.y - bb, size.x - br, size.y, uL, vB, uR, uv1.y);
113 draw(0.f, bt, bl, size.y - bb, uv0.x, vT, uL, vB);
114 draw(size.x - br, bt, size.x, size.y - bb, uR, vT, uv1.x, vB);
116 draw(bl, bt, size.x - br, size.y - bb, uL, vT, uR, vB);
119void walkFlex(UIHost *host, UIHost::Tree *tree, UINode &
flex) {
121 const ImGuiStyle &style = ImGui::GetStyle();
122 const float gap =
flex.
gap >= 0.f ?
flex.
gap : (
row ? style.ItemSpacing.x : style.ItemSpacing.y);
126 const ImVec2 avail = ImGui::GetContentRegionAvail();
131 const float availMain = (
row ? flexW : flexH) - padMain;
132 const float availCross = (
row ? flexH : flexW) - padCross;
134 std::vector<int> kids;
135 std::vector<int> absKids;
136 std::vector<FlexItemSpec> specs;
137 for (
int c =
flex.firstChild;
c >= 0;
c = tree->nodes[size_t(
c)].nextSibling) {
138 if (
c >=
int(tree->nodes.size()))
break;
139 UINode &
child = tree->nodes[size_t(
c)];
142 absKids.push_back(
c);
166 const FlexResult res =
167 flexArrange(
row, gap, std::max(0.f, availMain), std::max(0.f, availCross),
169 const ImVec2 cursor = ImGui::GetCursorScreenPos();
172 for (
size_t i = 0; i < kids.size(); ++i) {
173 UINode &
child = tree->nodes[size_t(kids[i])];
174 const FlexRect &r = res.items[i];
175 ImGui::SetCursorScreenPos(ImVec2(origin.x + r.x, origin.y + r.y));
178 bool pushedWidth =
false;
181 if (
row || r.w > 0.f) {
182 ImGui::PushItemWidth(r.w > 0.f ? r.w : 1.f);
185 walkNode(host, tree, kids[i]);
188 if (pushedWidth) ImGui::PopItemWidth();
192 for (
int c : absKids) {
193 UINode &
child = tree->nodes[size_t(
c)];
204 ImGui::SetCursorScreenPos(ImVec2(origin.x +
x, origin.y +
y));
209 if (
w > 0.f) ImGui::PushItemWidth(
w);
210 walkNode(host, tree,
c);
213 if (
w > 0.f) ImGui::PopItemWidth();
217void walkNode(UIHost *host, UIHost::Tree *tree,
int index) {
218 if (index < 0 || index >=
int(tree->nodes.size()))
return;
219 UINode &
n = tree->nodes[size_t(index)];
220 if (!
n.visible)
return;
224 std::string
title =
n.text.empty() ?
"Window" :
n.text;
225 if (host && !host->meta()->name.empty())
title = host->meta()->name +
"/" +
title;
226 const bool modal = host && host->meta()->modal;
227 ImGuiWindowFlags flags = 0;
230 if (host && host->meta()->percentW > 0.f)
231 winW = host->meta()->percentW * ImGui::GetIO().DisplaySize.x;
232 else if (host && host->meta()->hasSize)
233 winW = host->meta()->sizeX;
234 if (host && host->meta()->percentH > 0.f)
235 winH = host->meta()->percentH * ImGui::GetIO().DisplaySize.y;
236 else if (host && host->meta()->hasSize)
237 winH = host->meta()->sizeY;
238 if (host && host->meta()->hasPos) {
239 const ImVec2 display = ImGui::GetIO().DisplaySize;
240 const float px = host->meta()->pivotX;
241 const float py = host->meta()->pivotY;
242 const float x = host->meta()->anchorX * display.x + host->meta()->posX -
px * winW;
243 const float y = host->meta()->anchorY * display.y + host->meta()->posY - py * winH;
244 ImGui::SetNextWindowPos(ImVec2(
x,
y), ImGuiCond_Always,
245 ImVec2(host->meta()->pivotX, host->meta()->pivotY));
246 flags |= ImGuiWindowFlags_NoMove;
247 if (winW <= 0.f && winH <= 0.f) flags |= ImGuiWindowFlags_AlwaysAutoResize;
249 if (winW > 0.f || winH > 0.f) {
250 ImGui::SetNextWindowSize(
251 ImVec2(winW > 0.f ? winW : -1.f, winH > 0.f ? winH : -1.f), ImGuiCond_Always);
252 }
else if (
n.measuredW > 0.f ||
n.measuredH > 0.f) {
254 ImGui::SetNextWindowContentSize(ImVec2(
n.measuredW,
n.measuredH));
256 if (host && host->meta()->overlay) {
257 flags |= ImGuiWindowFlags_NoTitleBar | ImGuiWindowFlags_NoResize |
258 ImGuiWindowFlags_NoScrollbar | ImGuiWindowFlags_NoCollapse |
259 ImGuiWindowFlags_AlwaysAutoResize;
260 ImGui::SetNextWindowBgAlpha(0.4f);
263 ImGui::OpenPopup(
title.c_str());
264 if (ImGui::BeginPopupModal(
title.c_str(),
nullptr,
265 ImGuiWindowFlags_AlwaysAutoResize | flags)) {
266 if (
n.firstChild >= 0) walkSiblings(host, tree,
n.firstChild);
270 ImGui::Begin(
title.c_str(),
nullptr, flags);
271 if (
n.firstChild >= 0) walkSiblings(host, tree,
n.firstChild);
277 if (
n.wrapWidth > 0.f) {
278 ImGui::PushTextWrapPos(ImGui::GetCursorPosX() +
n.wrapWidth);
279 ImGui::TextUnformatted(
n.text.c_str());
280 ImGui::PopTextWrapPos();
282 ImGui::TextUnformatted(
n.text.c_str());
286 const char *label =
n.text.empty() ?
"Button" :
n.text.c_str();
287 const bool sized =
n.sizeX > 0.f ||
n.sizeY > 0.f;
289 sized ? ImGui::Button(label, ImVec2(
n.sizeX > 0.f ?
n.sizeX : 0.f,
290 n.sizeY > 0.f ?
n.sizeY : 0.f))
292 if (clicked) pushPending(host,
n,
"click",
n.handlerClick);
300 if (
n.firstChild >= 0) walkSiblings(host, tree,
n.firstChild);
307 bool checked =
n.checked;
308 if (ImGui::Checkbox(
n.text.empty() ?
"Check" :
n.
text.c_str(), &checked)) {
310 pushPending(host,
n,
"toggle",
n.handlerToggle, checked);
316 const char *label =
n.text.empty() ?
"Slider" :
n.text.c_str();
317 if (ImGui::SliderFloat(label, &
v,
n.minValue,
n.maxValue)) {
319 pushPending(host,
n,
"value",
n.handlerValue,
false,
v);
324 const char *overlay =
n.text.empty() ? nullptr :
n.text.c_str();
325 ImGui::ProgressBar(
n.value, ImVec2(-1.f, 0.f), overlay);
329 const float w =
n.sizeX > 0.f ?
n.sizeX : 32.f;
330 const float h =
n.sizeY > 0.f ?
n.sizeY : 32.f;
331 const ImVec2
pos = ImGui::GetCursorScreenPos();
332 const ImVec2 size(
w,
h);
334 ImGui::ColorConvertFloat4ToU32(ImVec4(
n.tintR,
n.tintG,
n.tintB,
n.tintA));
335 if (
n.textureId != 0 && g_backend) {
336 void *handle = g_backend->textureHandle(
n.textureId);
338 g_backend->textureSize(
n.textureId, &tw, &th);
339 drawNinePatch(handle,
pos, size, ImVec2(
n.uv0x,
n.uv0y), ImVec2(
n.uv1x,
n.uv1y),
340 n.borderL,
n.borderT,
n.borderR,
n.borderB, tw, th, tint);
342 ImGui::GetWindowDrawList()->AddRectFilled(
pos, ImVec2(
pos.x +
w,
pos.y +
h), tint,
349 const float w =
n.sizeX > 0.f ?
n.sizeX : 32.f;
350 const float h =
n.sizeY > 0.f ?
n.sizeY : 32.f;
351 const ImVec2 size(
w,
h);
352 const std::string sid =
n.id.empty() ?
"imgbtn" :
n.id;
353 ImGui::PushID(sid.c_str());
354 bool clicked =
false;
355 if (
n.textureId != 0 && g_backend) {
356 void *handle = g_backend->textureHandle(
n.textureId);
358 clicked = ImGui::ImageButton(
static_cast<ImTextureID
>(handle), size,
359 ImVec2(
n.uv0x,
n.uv0y), ImVec2(
n.uv1x,
n.uv1y), -1,
360 ImVec4(0.f, 0.f, 0.f, 0.f),
361 ImVec4(
n.tintR,
n.tintG,
n.tintB,
n.tintA));
364 if (!clicked &&
n.textureId == 0) {
365 clicked = ImGui::InvisibleButton(sid.c_str(), size);
366 const ImVec2 pmin = ImGui::GetItemRectMin();
367 const ImVec2 pmax = ImGui::GetItemRectMax();
369 ImGui::ColorConvertFloat4ToU32(ImVec4(
n.tintR,
n.tintG,
n.tintB,
n.tintA));
370 if (ImGui::IsItemHovered()) col = ImGui::GetColorU32(ImGuiCol_ButtonHovered);
371 ImGui::GetWindowDrawList()->AddRectFilled(pmin, pmax, col,
n.cornerRadius);
374 if (clicked) pushPending(host,
n,
"click",
n.handlerClick);
379 std::vector<std::string> storage;
381 while (start <=
n.valueText.size()) {
382 const size_t end =
n.valueText.find(
'\n', start);
383 storage.push_back(
n.valueText.substr(
384 start, end == std::string::npos ? std::string::npos : end - start));
385 if (end == std::string::npos)
break;
388 std::vector<const char *> items;
389 items.reserve(storage.size());
390 for (
const auto &
s : storage) items.push_back(
s.c_str());
391 int idx = int(
n.value);
392 const char *label =
n.text.empty() ?
"Combo" :
n.text.c_str();
393 if (ImGui::Combo(label, &
idx, items.data(),
int(items.size()))) {
394 n.value = float(
idx);
395 pushPending(host,
n,
"value",
n.handlerValue,
false,
float(
idx));
401 std::memset(buf, 0,
sizeof(buf));
402 if (!
n.valueText.empty()) {
403 std::strncpy(buf,
n.valueText.c_str(),
sizeof(buf) - 1);
405 const char *label =
n.text.empty() ?
"Input" :
n.text.c_str();
406 if (ImGui::InputText(label, buf,
sizeof(buf))) {
408 pushPending(host,
n,
"text",
n.handlerText,
false, 0.f,
n.valueText);
413 ImGuiTreeNodeFlags flags =
n.open ? ImGuiTreeNodeFlags_DefaultOpen : 0;
414 const char *label =
n.text.empty() ?
"Section" :
n.text.c_str();
415 if (ImGui::CollapsingHeader(label, flags)) {
416 if (
n.firstChild >= 0) walkSiblings(host, tree,
n.firstChild);
421 ImVec2 size(
n.sizeX,
n.sizeY);
422 const char *cid =
n.id.empty() ?
"child" :
n.id.c_str();
423 if (ImGui::BeginChild(cid, size,
true)) {
424 if (
n.firstChild >= 0) walkSiblings(host, tree,
n.firstChild);
431 std::vector<int> kids;
432 for (
int c =
n.firstChild;
c >= 0;
c = tree->nodes[size_t(
c)].nextSibling) {
433 if (
c >=
int(tree->nodes.size()))
break;
434 if (tree->nodes[
size_t(
c)].visible) kids.push_back(
c);
436 const int total = int(kids.size());
437 const char *cid =
n.id.empty() ?
"scrolllist" :
n.id.c_str();
438 const ImVec2 size(
n.sizeX > 0.f ?
n.sizeX : 0.f,
439 n.sizeY > 0.f ?
n.sizeY : 120.f);
440 ImGui::BeginChild(cid, size,
true);
441 const float itemH =
n.itemHeight > 0.f ?
n.itemHeight : ImGui::GetFrameHeight();
442 const float contentW = ImGui::GetContentRegionAvail().x;
443 const float scrollY = ImGui::GetScrollY();
444 const float viewH = ImGui::GetWindowHeight();
445 if (total > 0 && itemH > 0.f) {
446 int first = int(scrollY / itemH);
447 if (first < 0) first = 0;
448 if (first > total) first = total;
449 int last = int(std::ceil((scrollY + viewH) / itemH));
450 if (last < first) last = first;
451 if (last > total) last = total;
452 for (
int i = first; i < last; ++i) {
453 UINode &
child = tree->nodes[size_t(kids[
size_t(i)])];
454 ImGui::SetCursorPos(ImVec2(0.f,
float(i) * itemH));
458 if (contentW > 0.f) ImGui::PushItemWidth(contentW);
459 walkNode(host, tree, kids[
size_t(i)]);
462 if (contentW > 0.f) ImGui::PopItemWidth();
464 ImGui::SetCursorPosY(
float(i) * itemH + itemH);
468 ImGui::SetCursorPosY(
float(total) * itemH);
473 const float w =
n.sizeX > 0.f ?
n.sizeX : ImGui::GetContentRegionAvail().x;
474 const float h =
n.sizeY > 0.f ?
n.sizeY : ImGui::GetContentRegionAvail().y;
475 const ImVec2 size(std::max(1.f,
w), std::max(1.f,
h));
476 const std::string key = viewportKey(host,
n);
479 const ImVec2
rectMin = ImGui::GetCursorScreenPos();
480 const std::string label =
"##vp" + (
n.id.empty() ? std::string(
"viewport") :
n.
id);
481 ImGui::InvisibleButton(label.c_str(), size);
482 const bool hovered = ImGui::IsItemHovered();
483 const bool active = ImGui::IsItemActive();
484 const ImVec2 mouse = ImGui::GetMousePos();
486 vs->hovered = hovered;
488 vs->mouseX = mouse.x -
rectMin.x;
489 vs->mouseY = mouse.y -
rectMin.y;
491 vs->dragDX = ImGui::GetIO().MouseDelta.x;
492 vs->dragDY = ImGui::GetIO().MouseDelta.y;
497 vs->wheel = hovered ? ImGui::GetIO().MouseWheel : 0.f;
500 if (vs && vs->textureId && g_backend) {
501 void *handle = g_backend->textureHandle(vs->textureId);
503 ImGui::SetCursorScreenPos(
rectMin);
504 ImGui::Image(
static_cast<ImTextureID
>(handle), size);
506 ImGui::GetWindowDrawList()->AddRectFilled(
508 IM_COL32(10, 12, 16, 255));
511 ImGui::GetWindowDrawList()->AddRectFilled(
517 walkFlex(host, tree,
n);
521 emitSpacer(
n.sizeX > 0.f ?
n.sizeX : 0.f,
n.sizeY > 0.f ?
n.sizeY : 0.f, true);
526void walk(UIHost *host, UIHost::Tree *tree,
int index) { walkSiblings(host, tree, index); }
539 if (key.empty() ||
w <= 0 ||
h <= 0)
return nullptr;
542 auto *gfx = eve::ModuleManager::getInstance<eve::graphics::Graphics>(
"Graphics");
543 if (!gfx)
return &vs;
550 if (vs.
canvas && g_backend)
557 auto it = g_viewports.find(hostName +
"/" + nodeId);
558 return it == g_viewports.end() ? nullptr : &it->second;
562 if (
name.empty())
return nullptr;
563 if (ecs::current()->getManager<UIHost>() ==
nullptr)
return nullptr;
564 auto view = ecs::View<UIHost, UIHost::Meta, UIHost::Tree>();
565 for (
auto it =
view.begin(); it !=
view.end(); ++it) {
566 auto [meta, tree] = *it;
568 if (meta->name ==
name)
return meta->entity;
574 if (ownerId == 0)
return nullptr;
575 if (ecs::current()->getManager<UIHost>() ==
nullptr)
return nullptr;
576 auto view = ecs::View<UIHost, UIHost::Meta, UIHost::Tree>();
577 for (
auto it =
view.begin(); it !=
view.end(); ++it) {
578 auto [meta, tree] = *it;
580 if (meta->ownerId == ownerId)
return meta->entity;
586 if (ecs::current()->getManager<UIHost>() ==
nullptr)
return;
595 std::vector<Item> items;
597 auto view = ecs::View<UIHost, UIHost::Meta, UIHost::Tree>();
598 for (
auto it =
view.begin(); it !=
view.end(); ++it) {
599 auto [meta, tree] = *it;
600 if (!meta->visible)
continue;
601 UIHost *host = meta->entity;
603 items.push_back(Item{host, meta, tree});
606 std::stable_sort(items.begin(), items.end(),
607 [](
const Item &
a,
const Item &
b) { return a.meta->layer < b.meta->layer; });
609 g_stats.hostCount = int(items.size());
610 g_stats.nodeCount = 0;
611 g_stats.measureMs = 0.0;
612 g_stats.walkMs = 0.0;
613 for (
auto &item : items) {
614 item.tree->dirty =
false;
616 const auto m0 = std::chrono::steady_clock::now();
618 const auto m1 = std::chrono::steady_clock::now();
619 g_stats.nodeCount += int(item.tree->nodes.size());
620 if (item.tree->root >= 0) walk(item.host, item.tree, item.tree->root);
621 const auto m2 = std::chrono::steady_clock::now();
623 std::chrono::duration<double, std::milli>(m1 - m0).count();
624 g_stats.walkMs += std::chrono::duration<double, std::milli>(m2 - m1).count();
629 auto events = std::move(g_pending);
632 if (!ev.host)
continue;
634 if (ev.kind ==
"click" && !ev.nodeId.empty()) {
636 c.
hostName = ev.hostName.empty() ? ev.host->meta()->name : ev.hostName;
637 c.nodeId = ev.nodeId;
638 g_clicks.push_back(std::move(
c));
641 if ((ev.kind ==
"toggle" || ev.kind ==
"value" || ev.kind ==
"text") && !ev.nodeId.empty()) {
643 ch.
hostName = ev.hostName.empty() ? ev.host->meta()->name : ev.hostName;
646 g_changes.push_back(std::move(ch));
649 if (ev.kind ==
"toggle" && ev.handlerIndex != 0) {
650 auto t = ev.host->tree();
651 size_t idx = size_t(ev.handlerIndex - 1);
652 if (idx < t->toggleHandlers.size() && t->toggleHandlers[
idx])
653 t->toggleHandlers[
idx](ev.toggleValue);
656 if (ev.kind ==
"value" && ev.handlerIndex != 0) {
657 auto t = ev.host->tree();
658 size_t idx = size_t(ev.handlerIndex - 1);
659 if (idx < t->valueHandlers.size() && t->valueHandlers[
idx])
660 t->valueHandlers[
idx](ev.floatValue);
663 if (ev.kind ==
"text" && ev.handlerIndex != 0) {
664 auto t = ev.host->tree();
665 size_t idx = size_t(ev.handlerIndex - 1);
666 if (idx < t->textHandlers.size() && t->textHandlers[
idx])
667 t->textHandlers[
idx](ev.textValue);
670 if (ev.handlerIndex == 0)
continue;
671 auto t = ev.host->tree();
672 size_t idx = size_t(ev.handlerIndex - 1);
673 if (
idx >= t->clickHandlers.size())
continue;
674 if (t->clickHandlers[
idx]) t->clickHandlers[
idx]();
679 if (g_clicks.empty())
return {};
680 UIClick c = std::move(g_clicks.front());
681 g_clicks.erase(g_clicks.begin());
682 if (
c.hostName.empty())
return c.nodeId;
683 return c.hostName +
"/" +
c.nodeId;
687 for (
auto it = g_clicks.begin(); it != g_clicks.end(); ++it) {
688 if (it->hostName != hostName)
continue;
689 std::string
id = std::move(it->nodeId);
697 if (g_changes.empty())
return {};
698 UIChange c = std::move(g_changes.front());
699 g_changes.erase(g_changes.begin());
700 if (
c.hostName.empty())
return c.nodeId;
701 return c.hostName +
"/" +
c.nodeId;
std::map< std::string, ImVec2 > rectMin
std::vector< HostEvent > events
virtual Texture * getTexture()=0
Sampleable color buffer; screen Canvas returns nullptr.
Platform / renderer backend for declarative UI. Concrete implementations live under ui/<backend>/ (e....
ECS mount point for one UI panel/screen. Subclass to attach UI to game entities, e....
static ViewportState * ensureViewport(const std::string &key, int w, int h)
static UIBackend * backend()
static const UIStats & stats()
static std::vector< UIClick > & clickQueue()
static void setBackend(UIBackend *backend)
static std::string consumeChange()
Pop next change as "name/node"; empty if none.
static std::vector< UIChange > & changeQueue()
static UIHost * findHost(const std::string &name)
Lookup by Meta.name across the UIHost View.
static void dispatchEvents()
static UIHost * findHostByOwner(uint32_t ownerId)
First host with Meta.ownerId == ownerId, or nullptr.
static std::string consumeClick()
Pop next click as "name/node"; empty if none.
static void render()
Walk all UIHost (+ subclasses) via ECS View.
static std::string consumeClickFor(const std::string &hostName)
Pop next click for a specific host; returns node id only (or "").
static std::vector< UIEvent > & pendingEvents()
static ViewportState * viewportState(const std::string &hostName, const std::string &nodeId)
int main(int argc, char **argv)
void applyThemeToImGui(const Theme &theme)
Push tokens into ImGui style. Metrics are multiplied by uiScale (default: themeUiScale()).
WidgetDesc text(std::string content, std::string id)
Static text label.
void measureTree(UIHost::Tree &tree)
WidgetDesc flex(FlexDirection direction, std::vector< WidgetDesc > children, std::string id)
Elastic layout container (row/column). Prefer row / column shorthands.
FlexResult flexArrange(bool row, float gap, float availMain, float availCross, FlexAlign containerAlign, FlexJustify justify, const std::vector< FlexItemSpec > &items)
WidgetDesc child(std::string id, std::vector< WidgetDesc > children, float width, float height)
Scrollable child region with an explicit size.
WidgetDesc row(std::vector< WidgetDesc > children, std::string id)
Horizontal elastic layout row.
Value/text/toggle change for script: "hostName/nodeId".
One script-facing click: "hostName/nodeId".
graphics::Canvas * canvas