17#include <imgui_internal.h>
32std::vector<UIEvent> g_pending;
33std::vector<UIClick> g_clicks;
34std::vector<UIChange> g_changes;
35std::vector<UIDrop> g_drops;
36std::vector<std::string> g_pendingFileDrops;
37UIBackend *g_backend =
nullptr;
39std::map<std::string, ViewportState> g_viewports;
41struct HostThemeScope {
47 explicit HostThemeScope(UIHost *
host) {
48 if (!
host || !
host->hasThemeOverride() || !
host->themeOverride())
return;
63 HostThemeScope(
const HostThemeScope &) =
delete;
64 HostThemeScope &operator=(
const HostThemeScope &) =
delete;
67struct ActiveDragPayload {
75std::optional<ActiveDragPayload> g_activeDrag;
76uint64_t g_nextDragSerial = 1;
78bool acceptsDropType(
const UINode &
node,
const std::string &
type) {
79 return node.dropTarget && (
node.acceptedDropType ==
"*" ||
node.acceptedDropType ==
type);
82void renderDragDrop(UIHost *
host,
const UINode &
node) {
86 const std::string hostName =
host->meta()->name;
87 if (
node.dragSource && !
node.dragPayloadType.empty() && ImGui::BeginDragDropSource()) {
88 if (!g_activeDrag || g_activeDrag->sourceHostName != hostName ||
89 g_activeDrag->sourceNodeId !=
node.id ||
90 g_activeDrag->payloadType !=
node.dragPayloadType ||
91 g_activeDrag->payloadText !=
node.dragPayloadText) {
92 g_activeDrag = ActiveDragPayload{g_nextDragSerial++, hostName,
node.id,
93 node.dragPayloadType,
node.dragPayloadText};
95 const uint64_t
serial = g_activeDrag->serial;
96 ImGui::SetDragDropPayload(
"EVE_UI_DND", &
serial,
sizeof(
serial), ImGuiCond_Once);
97 ImGui::TextUnformatted(
node.dragPayloadText.empty() ?
node.dragPayloadType.c_str()
98 :
node.dragPayloadText.c_str());
99 ImGui::EndDragDropSource();
102 if (
node.dropTarget && ImGui::BeginDragDropTarget()) {
103 const bool acceptsActive = g_activeDrag &&
104 acceptsDropType(
node, g_activeDrag->payloadType);
105 if (
const ImGuiPayload *
payload =
106 acceptsActive ? ImGui::AcceptDragDropPayload(
"EVE_UI_DND") : nullptr) {
107 if (
payload->IsDelivery() &&
payload->DataSize ==
int(
sizeof(uint64_t)) &&
109 const uint64_t
serial = *
static_cast<const uint64_t *
>(
payload->Data);
110 if (
serial == g_activeDrag->serial) {
112 g_activeDrag->sourceHostName,
113 g_activeDrag->sourceNodeId,
116 g_activeDrag->payloadType,
117 g_activeDrag->payloadText});
118 g_activeDrag.reset();
122 ImGui::EndDragDropTarget();
125 if (!g_pendingFileDrops.empty() && acceptsDropType(
node,
"file") &&
126 ImGui::IsItemHovered()) {
127 for (
auto &
path : g_pendingFileDrops) {
131 g_pendingFileDrops.clear();
135graphics::Camera3D *activeCamera() {
136 if (ecs::current()->getManager<graphics::Camera3D>() ==
nullptr)
return nullptr;
137 auto cameras = ecs::View<graphics::Camera3D, graphics::Camera3D::Data>();
138 for (
auto it = cameras.begin(); it != cameras.end(); ++it) {
140 if (data->active)
return data->entity;
145bool updateWorldAnchor(UIHost &
host, graphics::Camera3D *camera,
float width,
float height) {
155 const auto data =
camera->data();
156 const glm::vec3
eye(data->eyeX, data->eyeY, data->eyeZ);
157 const glm::vec3
target(data->targetX, data->targetY, data->targetZ);
158 const glm::vec3
up(data->upX, data->upY, data->upZ);
161 data->orthographic, glm::radians(data->fovYDeg), data->orthoHeight,
170 anchor->displacementX = 0.f;
171 anchor->displacementY = 0.f;
175std::string viewportKey(UIHost *
host,
const UINode &
n) {
176 const std::string hostName =
177 host && !
host->meta()->name.empty() ?
host->meta()->name :
"?";
178 return hostName +
"/" + (
n.id.empty() ?
"viewport" :
n.id);
181void pushPending(UIHost *
host,
const UINode &
n,
const char *
kind, uint32_t handlerIndex,
182 bool toggleValue =
false,
float floatValue = 0.f, std::string textValue = {}) {
186 ev.host =
host->handle();
187 ev.hostName =
host ?
host->meta()->name :
"";
190 auto tree =
host->tree();
191 if (!tree->nodes.empty()) ev.nodeIndex = int(&
n - tree->nodes.data());
194 ev.handlerIndex = handlerIndex;
195 ev.toggleValue = toggleValue;
196 ev.floatValue = floatValue;
197 ev.textValue = std::move(textValue);
198 g_pending.push_back(std::move(ev));
201std::string nodeLabel(
const UINode &
node,
const char *fallback) {
202 std::string
label =
node.text.empty() ? fallback :
node.text;
207bool privateUseGlyph(
const std::string &
text, ImWchar *out) {
208 if (
text.size() != 3)
return false;
209 const auto b0 =
static_cast<unsigned char>(
text[0]);
210 const auto b1 =
static_cast<unsigned char>(
text[1]);
211 const auto b2 =
static_cast<unsigned char>(
text[2]);
212 if ((b0 & 0xf0u) != 0xe0u || (b1 & 0xc0u) != 0x80u || (b2 & 0xc0u) != 0x80u)
214 const unsigned int codepoint =
215 ((b0 & 0x0fu) << 12u) | ((b1 & 0x3fu) << 6u) | (b2 & 0x3fu);
216 if (codepoint < 0xe000u || codepoint > 0xf8ffu)
return false;
217 if (out) *out =
static_cast<ImWchar
>(codepoint);
234 default:
return false;
238struct PaletteSwatch {
245void appendDefaultPaletteSwatches(std::vector<PaletteSwatch> &out) {
246 static const PaletteSwatch kDefaults[] = {
247 {0.00f, 0.00f, 0.00f, 1.f}, {0.20f, 0.20f, 0.22f, 1.f},
248 {0.45f, 0.45f, 0.48f, 1.f}, {0.92f, 0.92f, 0.94f, 1.f},
249 {0.82f, 0.18f, 0.18f, 1.f}, {0.90f, 0.45f, 0.12f, 1.f},
250 {0.92f, 0.78f, 0.16f, 1.f}, {0.22f, 0.68f, 0.28f, 1.f},
251 {0.14f, 0.70f, 0.72f, 1.f}, {0.18f, 0.38f, 0.86f, 1.f},
252 {0.48f, 0.28f, 0.82f, 1.f}, {0.82f, 0.22f, 0.62f, 1.f},
253 {0.55f, 0.35f, 0.22f, 1.f}, {0.95f, 0.55f, 0.70f, 1.f},
254 {0.42f, 0.52f, 0.22f, 1.f}, {0.12f, 0.18f, 0.32f, 1.f},
256 out.insert(out.end(), std::begin(kDefaults), std::end(kDefaults));
259bool parsePaletteSwatch(std::string
token, PaletteSwatch &out) {
265 if (
token.empty())
return false;
266 if (
token[0] ==
'#') {
268 if (
token.size() == 7 && std::sscanf(
token.c_str() + 1,
"%06x", &
value) == 1) {
269 out.r = float((
value >> 16) & 0xff) / 255.f;
270 out.g = float((
value >> 8) & 0xff) / 255.f;
271 out.b = float(
value & 0xff) / 255.f;
275 if (
token.size() == 9 && std::sscanf(
token.c_str() + 1,
"%08x", &
value) == 1) {
276 out.r = float((
value >> 24) & 0xff) / 255.f;
277 out.g = float((
value >> 16) & 0xff) / 255.f;
278 out.b = float((
value >> 8) & 0xff) / 255.f;
279 out.a = float(
value & 0xff) / 255.f;
284 float r = 0.f,
g = 0.f,
b = 0.f,
a = 1.f;
285 const int n = std::sscanf(
token.c_str(),
"%f,%f,%f,%f", &
r, &
g, &
b, &
a);
286 if (
n < 3)
return false;
290 if (
n >= 4) out.a =
a;
294std::vector<PaletteSwatch> paletteSwatches(
const UINode &
n) {
295 std::vector<PaletteSwatch> swatches;
296 const std::string &src =
n.valueText;
298 while (i < src.size()) {
299 size_t end = src.find_first_of(
";\n", i);
300 if (
end == std::string::npos)
end = src.size();
301 PaletteSwatch swatch;
302 if (parsePaletteSwatch(src.substr(i,
end - i), swatch)) swatches.push_back(swatch);
305 if (swatches.empty()) appendDefaultPaletteSwatches(swatches);
309void commitPaletteColor(UIHost *
host, UINode &
n,
const float col[4]) {
315 pushPending(
host,
n,
"value",
n.handlerValue,
false, col[0]);
318bool keyPressed(ImGuiKey
key) {
319 const int index = ImGui::GetKeyIndex(
key);
320 return index >= 0 && ImGui::IsKeyPressed(
index,
false);
323void routeExplicitFocusInput(UIHost *
host,
const UINode &
node) {
325 if (keyPressed(ImGuiKey_Tab)) {
326 const bool backwards = ImGui::GetIO().KeyShift;
327 const std::string &
target = backwards ?
node.focusPrevious :
node.focusNext;
332 if (ImGui::IsItemActive())
return;
333 if (!
node.focusLeft.empty() && keyPressed(ImGuiKey_LeftArrow))
335 else if (!
node.focusRight.empty() && keyPressed(ImGuiKey_RightArrow))
337 else if (!
node.focusUp.empty() && keyPressed(ImGuiKey_UpArrow))
339 else if (!
node.focusDown.empty() && keyPressed(ImGuiKey_DownArrow))
343void walkNode(UIHost *
host, UIHost::Tree *tree,
int index);
345void walkSiblings(UIHost *
host, UIHost::Tree *tree,
int index) {
346 while (
index >= 0 &&
index <
int(tree->nodes.size())) {
347 const int next = tree->nodes[size_t(
index)].nextSibling;
348 UINode &
node = tree->nodes[size_t(
index)];
353 next <
int(tree->nodes.size()) &&
355 const float oldY =
node.sizeY;
356 const float reserve = tree->nodes[size_t(next)].measuredH +
357 ImGui::GetStyle().ItemSpacing.y;
358 node.sizeY = std::max(1.f, ImGui::GetContentRegionAvail().
y - reserve);
368void walkPaneChild(UIHost *
host, UIHost::Tree *tree,
int index) {
369 if (index < 0 || index >=
int(tree->nodes.size()))
return;
371 const ImVec2 avail = ImGui::GetContentRegionAvail();
385bool hasDirectChildType(
const UIHost::Tree &tree,
const UINode &
parent,
NodeType type) {
387 child = tree.nodes[size_t(
child)].nextSibling) {
388 if (
child >=
int(tree.nodes.size()))
break;
389 if (tree.nodes[
size_t(
child)].type ==
type)
return true;
394void emitSpacer(
float main,
float cross,
bool row) {
396 if (cross < 0.f)
cross = 0.f;
398 if (
main <= 0.f && cross <= 0.f) {
399 ImGui::Dummy(ImVec2(0.f, 0.f));
401 ImGui::Dummy(ImVec2(
main, cross > 0.f ? cross : 1.f));
404 if (
main <= 0.f && cross <= 0.f) {
405 ImGui::Dummy(ImVec2(0.f, 0.f));
407 ImGui::Dummy(ImVec2(cross > 0.f ? cross : 1.f,
main));
413void drawNinePatch(
void *
handle,
const ImVec2 &
pos,
const ImVec2 &
size,
const ImVec2 &uv0,
414 const ImVec2 &uv1,
float bL,
float bT,
float bR,
float bB,
int texW,
int texH,
416 ImDrawList *dl = ImGui::GetWindowDrawList();
417 const auto draw = [&](
float x0,
float y0,
float x1,
float y1,
float u0,
float v0,
float u1,
419 dl->AddImage(
static_cast<ImTextureID
>(
handle), ImVec2(
pos.x + x0,
pos.y + y0),
420 ImVec2(
pos.x + x1,
pos.y + y1), ImVec2(u0, v0), ImVec2(u1, v1),
tint);
422 if (bL <= 0.f && bT <= 0.f && bR <= 0.f && bB <= 0.f) {
423 draw(0.f, 0.f,
size.x,
size.y, uv0.x, uv0.y, uv1.x, uv1.y);
426 if (texW <= 0 || texH <= 0) {
427 draw(0.f, 0.f,
size.x,
size.y, uv0.x, uv0.y, uv1.x, uv1.y);
430 const float uPerPx = (uv1.x - uv0.x) /
float(texW);
431 const float vPerPx = (uv1.y - uv0.y) /
float(texH);
432 const float uL = uv0.x + std::min(bL,
size.x * 0.5f) * uPerPx;
433 const float uR = uv1.x - std::min(bR,
size.x * 0.5f) * uPerPx;
434 const float vT = uv0.y + std::min(bT,
size.y * 0.5f) * vPerPx;
435 const float vB = uv1.y - std::min(bB,
size.y * 0.5f) * vPerPx;
436 const float bl = std::min(bL,
size.x * 0.5f);
437 const float br = std::min(bR,
size.x * 0.5f);
438 const float bt = std::min(bT,
size.y * 0.5f);
439 const float bb = std::min(bB,
size.y * 0.5f);
441 draw(0.f, 0.f, bl, bt, uv0.x, uv0.y, uL, vT);
442 draw(
size.x - br, 0.f,
size.x, bt, uR, uv0.y, uv1.x, vT);
443 draw(0.f,
size.y - bb, bl,
size.y, uv0.x, vB, uL, uv1.y);
446 draw(bl, 0.f,
size.x - br, bt, uL, uv0.y, uR, vT);
448 draw(0.f, bt, bl,
size.y - bb, uv0.x, vT, uL, vB);
455void queueNinePatch(uint64_t textureId,
const ImVec2 &
pos,
const ImVec2 &
size,
456 const ImVec2 &uv0,
const ImVec2 &uv1,
float bL,
float bT,
float bR,
457 float bB,
int texW,
int texH,
float r,
float g,
float b,
float a,
458 bool opaque =
false) {
459 if (!g_backend)
return;
460 const auto draw = [&](
float x0,
float y0,
float x1,
float y1,
float u0,
float v0,
461 float u1,
float v1) {
462 if (x1 <= x0 || y1 <= y0)
return;
463 g_backend->queueTextureDraw(textureId,
pos.x + x0,
pos.y + y0, x1 - x0, y1 - y0,
464 u0, v0, u1, v1,
r,
g,
b,
a, opaque);
466 if ((bL <= 0.f && bT <= 0.f && bR <= 0.f && bB <= 0.f) || texW <= 0 || texH <= 0) {
467 draw(0.f, 0.f,
size.x,
size.y, uv0.x, uv0.y, uv1.x, uv1.y);
470 const float bl = std::min(bL,
size.x * 0.5f);
471 const float br = std::min(bR,
size.x * 0.5f);
472 const float bt = std::min(bT,
size.y * 0.5f);
473 const float bb = std::min(bB,
size.y * 0.5f);
474 const float uPerPx = (uv1.x - uv0.x) /
float(texW);
475 const float vPerPx = (uv1.y - uv0.y) /
float(texH);
476 const float uL = uv0.x + bl * uPerPx;
477 const float uR = uv1.x - br * uPerPx;
478 const float vT = uv0.y + bt * vPerPx;
479 const float vB = uv1.y - bb * vPerPx;
480 draw(0.f, 0.f, bl, bt, uv0.x, uv0.y, uL, vT);
481 draw(
size.x - br, 0.f,
size.x, bt, uR, uv0.y, uv1.x, vT);
482 draw(0.f,
size.y - bb, bl,
size.y, uv0.x, vB, uL, uv1.y);
484 draw(bl, 0.f,
size.x - br, bt, uL, uv0.y, uR, vT);
486 draw(0.f, bt, bl,
size.y - bb, uv0.x, vT, uL, vB);
491void walkFlex(UIHost *
host, UIHost::Tree *tree, UINode &
flex) {
493 const ImGuiStyle &style = ImGui::GetStyle();
494 const float legacyGap =
497 : (
flex.rowGap >= 0.f ?
flex.rowGap : legacyGap);
499 : (
flex.columnGap >= 0.f ?
flex.columnGap : style.ItemSpacing.
x);
503 const ImVec2 avail = ImGui::GetContentRegionAvail();
504 const bool fillsContainer =
511 const float availMain = (
row ? flexW : flexH) - padMain;
512 const float availCross = (
row ? flexH : flexW) - padCross;
514 std::vector<int> kids;
515 std::vector<int> absKids;
516 std::vector<FlexItemSpec> specs;
517 for (
int c =
flex.firstChild;
c >= 0;
c = tree->nodes[size_t(
c)].nextSibling) {
518 if (
c >=
int(tree->nodes.size()))
break;
519 UINode &
child = tree->nodes[size_t(
c)];
522 absKids.push_back(
c);
550 const FlexResult res =
554 flex.layoutOverflowX = std::max(
555 0.f, res.contentW - std::max(0.f,
row ? availMain : availCross));
556 flex.layoutOverflowY = std::max(
557 0.f, res.contentH - std::max(0.f,
row ? availCross : availMain));
558 if (
row)
flex.layoutOverflowX = std::max(
flex.layoutOverflowX, res.overflowMain);
559 else flex.layoutOverflowY = std::max(
flex.layoutOverflowY, res.overflowMain);
562 ImGui::PushID(&
flex);
563 ImGui::BeginChild(
"layout-scroll", ImVec2(std::max(0.f, flexW), std::max(0.f, flexH)),
564 false, ImGuiWindowFlags_HorizontalScrollbar);
566 const ImVec2
cursor = ImGui::GetCursorScreenPos();
571 ImGui::Dummy(ImVec2(std::max(0.f, dummyW), std::max(0.f, dummyH)));
572 const ImVec2 flowEnd = ImGui::GetCursorScreenPos();
578 for (
size_t i = 0; i < kids.size(); ++i) {
579 UINode &
child = tree->nodes[size_t(kids[i])];
580 const FlexRect &
r = res.items[i];
581 ImGui::SetCursorScreenPos(ImVec2(
origin.x +
r.x,
origin.y +
r.y));
584 bool pushedWidth =
false;
587 if (
row ||
r.w > 0.f) {
588 ImGui::PushItemWidth(
r.w > 0.f ?
r.w : 1.f);
591 walkNode(
host, tree, kids[i]);
594 if (pushedWidth) ImGui::PopItemWidth();
598 for (
int c : absKids) {
599 UINode &
child = tree->nodes[size_t(
c)];
615 if (
w > 0.f) ImGui::PushItemWidth(
w);
616 walkNode(
host, tree,
c);
619 if (
w > 0.f) ImGui::PopItemWidth();
621 if (clipped) ImGui::PopClipRect();
622 ImGui::SetCursorScreenPos(flowEnd);
629void walkGrid(UIHost *
host, UIHost::Tree *tree, UINode &grid) {
630 const ImGuiStyle &style = ImGui::GetStyle();
637 const ImVec2 avail = ImGui::GetContentRegionAvail();
640 std::vector<int> kids;
641 std::vector<int> absKids;
642 std::vector<GridItemSpec> specs;
643 for (
int c =
grid.firstChild;
c >= 0;
c = tree->nodes[size_t(
c)].nextSibling) {
644 if (
c >=
int(tree->nodes.size()))
break;
645 UINode &
child = tree->nodes[size_t(
c)];
648 absKids.push_back(
c);
653 spec.basisW =
child.measuredW;
654 spec.basisH =
child.measuredH;
661 specs.push_back(spec);
663 const GridResult result =
667 grid.layoutOverflowX = result.overflowX;
668 grid.layoutOverflowY = std::max(0.f, naturalH - gridH);
671 ImGui::PushID(&grid);
672 ImGui::BeginChild(
"grid-scroll", ImVec2(std::max(0.f, gridW), std::max(0.f, gridH)),
673 false, ImGuiWindowFlags_HorizontalScrollbar);
675 const ImVec2
cursor = ImGui::GetCursorScreenPos();
676 ImGui::Dummy(ImVec2(std::max(0.f, gridW),
677 std::max(0.f, scrolling ? std::max(gridH, naturalH) : gridH)));
678 const ImVec2 flowEnd = ImGui::GetCursorScreenPos();
683 for (
size_t i = 0; i < kids.size(); ++i) {
684 UINode &
child = tree->nodes[size_t(kids[i])];
685 const FlexRect &rect = result.items[i];
686 ImGui::SetCursorScreenPos(ImVec2(
origin.x + rect.x,
origin.y + rect.y));
691 if (rect.w > 0.f) ImGui::PushItemWidth(rect.w);
692 walkNode(
host, tree, kids[i]);
695 if (rect.w > 0.f) ImGui::PopItemWidth();
698 for (
int c : absKids) {
699 UINode &
child = tree->nodes[size_t(
c)];
715 if (
w > 0.f) ImGui::PushItemWidth(
w);
716 walkNode(
host, tree,
c);
719 if (
w > 0.f) ImGui::PopItemWidth();
721 if (clipped) ImGui::PopClipRect();
722 ImGui::SetCursorScreenPos(flowEnd);
729void walkNode(UIHost *
host, UIHost::Tree *tree,
int index) {
730 if (index < 0 || index >=
int(tree->nodes.size()))
return;
731 UINode &
n = tree->nodes[size_t(
index)];
732 if (!
n.visible)
return;
745 int styleColorCount = 0;
746 int styleVarCount = 0;
747 if (
n.hasResolvedStyle) {
748 const StyleClass &style =
n.resolvedStyle;
749 const auto pushColor = [&](ImGuiCol slot,
const float color[4]) {
753 if (style.hasTextColor) pushColor(ImGuiCol_Text, style.textColor);
754 if (style.hasBackgroundColor) {
755 pushColor(ImGuiCol_Button, style.backgroundColor);
756 pushColor(ImGuiCol_FrameBg, style.backgroundColor);
757 pushColor(ImGuiCol_Header, style.backgroundColor);
758 pushColor(ImGuiCol_ChildBg, style.backgroundColor);
760 if (style.hasBorderColor) pushColor(ImGuiCol_Border, style.borderColor);
761 if (style.hasAccentColor) {
762 pushColor(ImGuiCol_ButtonHovered, style.accentColor);
763 pushColor(ImGuiCol_ButtonActive, style.accentColor);
764 pushColor(ImGuiCol_HeaderHovered, style.accentColor);
765 pushColor(ImGuiCol_HeaderActive, style.accentColor);
766 pushColor(ImGuiCol_CheckMark, style.accentColor);
767 pushColor(ImGuiCol_SliderGrab, style.accentColor);
768 pushColor(ImGuiCol_SliderGrabActive, style.accentColor);
771 if (style.hasPadding) {
772 ImGui::PushStyleVar(ImGuiStyleVar_FramePadding,
773 ImVec2(style.paddingX *
scale, style.paddingY *
scale));
776 if (style.hasRounding) {
777 ImGui::PushStyleVar(ImGuiStyleVar_FrameRounding, style.rounding *
scale);
780 if (style.hasAlpha) {
781 ImGui::PushStyleVar(ImGuiStyleVar_Alpha, ImGui::GetStyle().
Alpha * style.alpha);
785 if (
n.opacity < 0.9999f) {
786 ImGui::PushStyleVar(ImGuiStyleVar_Alpha,
787 ImGui::GetStyle().
Alpha * std::clamp(
n.opacity, 0.f, 1.f));
791 const bool interactive = isInteractive(
n.type);
792 const bool disableItem = interactive && !
n.enabled;
793 const bool disableNavigation = interactive &&
n.focusMode !=
FocusMode::All;
795 ImGui::PushItemFlag(ImGuiItemFlags_Disabled,
true);
796 ImGui::PushStyleVar(ImGuiStyleVar_Alpha, ImGui::GetStyle().
Alpha * 0.6f);
798 if (disableNavigation) ImGui::PushItemFlag(ImGuiItemFlags_NoNav,
true);
800 ImGui::SetKeyboardFocusHere();
804 const std::string visibleTitle =
n.text.empty() ?
"Window" :
n.text;
805 std::string
title = visibleTitle;
808 if (
host && !
host->meta()->name.empty())
809 title +=
"###" +
host->meta()->name +
"/" + visibleTitle;
810 const bool modal =
host &&
host->meta()->modal;
811 ImGuiWindowFlags flags = 0;
812 if (hasDirectChildType(*tree,
n,
NodeType::MenuBar)) flags |= ImGuiWindowFlags_MenuBar;
817 flags |= ImGuiWindowFlags_NoScrollbar | ImGuiWindowFlags_NoScrollWithMouse;
820 if (
host &&
host->meta()->percentW > 0.f)
821 winW =
host->meta()->percentW * ImGui::GetIO().DisplaySize.x;
822 else if (
host &&
host->meta()->hasSize)
823 winW =
host->meta()->sizeX;
824 if (
host &&
host->meta()->percentH > 0.f)
825 winH =
host->meta()->percentH * ImGui::GetIO().DisplaySize.y;
826 else if (
host &&
host->meta()->hasSize)
827 winH =
host->meta()->sizeY;
828 const auto worldAnchor =
host ?
host->worldAnchor() :
nullptr;
829 if (worldAnchor->enabled) {
830 ImGui::SetNextWindowPos(ImVec2(worldAnchor->screenX, worldAnchor->screenY),
832 ImVec2(
host->meta()->pivotX,
host->meta()->pivotY));
833 flags |= ImGuiWindowFlags_NoMove;
834 if (!
host->meta()->hasSize && winW <= 0.f && winH <= 0.f)
835 flags |= ImGuiWindowFlags_AlwaysAutoResize;
836 }
else if (
host &&
host->meta()->hasPos) {
837 const ImVec2 display = ImGui::GetIO().DisplaySize;
841 const float x =
host->meta()->anchorX * display.x +
host->meta()->posX;
842 const float y =
host->meta()->anchorY * display.y +
host->meta()->posY;
843 const bool drivePos =
host->meta()->lockPos ||
host->meta()->animDrivePos;
844 ImGui::SetNextWindowPos(ImVec2(
x,
y), drivePos ? ImGuiCond_Always : ImGuiCond_FirstUseEver,
845 ImVec2(
host->meta()->pivotX,
host->meta()->pivotY));
846 if (
host->meta()->lockPos) flags |= ImGuiWindowFlags_NoMove;
847 if (winW <= 0.f && winH <= 0.f) flags |= ImGuiWindowFlags_AlwaysAutoResize;
849 if (winW > 0.f || winH > 0.f) {
850 const bool driveSize =
host && (
host->meta()->lockSize ||
host->meta()->animDriveSize);
851 ImGui::SetNextWindowSize(ImVec2(winW > 0.f ? winW : -1.f, winH > 0.f ? winH : -1.f),
852 driveSize ? ImGuiCond_Always : ImGuiCond_FirstUseEver);
853 }
else if (
n.measuredW > 0.f ||
n.measuredH > 0.f) {
855 ImGui::SetNextWindowContentSize(ImVec2(
n.measuredW,
n.measuredH));
857 if (
host &&
host->meta()->overlay) {
858 flags |= ImGuiWindowFlags_NoTitleBar | ImGuiWindowFlags_NoResize |
859 ImGuiWindowFlags_NoScrollbar | ImGuiWindowFlags_NoCollapse;
860 if (!
host->meta()->hasSize &&
host->meta()->percentW <= 0.f &&
861 host->meta()->percentH <= 0.f)
862 flags |= ImGuiWindowFlags_AlwaysAutoResize;
863 ImGui::SetNextWindowBgAlpha(
host->meta()->overlayBgAlpha);
867 const bool transparentOverlay =
host &&
host->meta()->overlay &&
host->meta()->overlayBgAlpha == 0.f;
868 const bool flushOverlay =
host &&
host->meta()->overlay && (
host->meta()->overlayFlush || transparentOverlay);
869 if (flushOverlay) ImGui::PushStyleVar(ImGuiStyleVar_WindowPadding, ImVec2(0.f, 0.f));
870 if (transparentOverlay) ImGui::PushStyleVar(ImGuiStyleVar_WindowBorderSize, 0.f);
872 ImGui::OpenPopup(
title.c_str());
873 if (ImGui::BeginPopupModal(
title.c_str(),
nullptr,
874 ImGuiWindowFlags_AlwaysAutoResize | flags)) {
875 if (worldAnchor->enabled)
876 ImGui::SetWindowFontScale(worldAnchor->scale);
877 if (
n.firstChild >= 0) walkSiblings(
host, tree,
n.firstChild);
881 ImGui::Begin(
title.c_str(),
nullptr, flags);
882 if (worldAnchor->enabled)
883 ImGui::SetWindowFontScale(worldAnchor->scale);
884 if (
n.firstChild >= 0) walkSiblings(
host, tree,
n.firstChild);
887 if (transparentOverlay) ImGui::PopStyleVar();
888 if (flushOverlay) ImGui::PopStyleVar();
892 if (
n.wrapWidth > 0.f) {
893 ImGui::PushTextWrapPos(ImGui::GetCursorPosX() +
n.wrapWidth);
894 ImGui::TextUnformatted(
n.text.c_str());
895 ImGui::PopTextWrapPos();
897 ImGui::TextUnformatted(
n.text.c_str());
901 ImWchar iconCodepoint = 0;
902 const bool iconOnly = privateUseGlyph(
n.text, &iconCodepoint);
903 const std::string
label =
904 iconOnly ?
"##iconButton" + (
n.id.empty() ? std::string(
"button") :
n.
id)
905 : nodeLabel(
n,
"Button");
906 const bool sized =
n.sizeX > 0.f ||
n.sizeY > 0.f;
907 const float defaultSide = ImGui::GetFrameHeight();
908 const ImVec2 buttonSize(iconOnly ? (
n.sizeX > 0.f ?
n.sizeX : defaultSide)
909 : (
n.sizeX > 0.f ?
n.sizeX : 0.f),
910 iconOnly ? (
n.sizeY > 0.f ?
n.sizeY : defaultSide)
911 : (
n.sizeY > 0.f ?
n.sizeY : 0.f));
913 ImGui::PushStyleColor(ImGuiCol_Button,
914 ImGui::GetStyleColorVec4(ImGuiCol_ButtonActive));
915 ImGui::PushStyleColor(ImGuiCol_ButtonHovered,
916 ImGui::GetStyleColorVec4(ImGuiCol_ButtonActive));
919 (sized || iconOnly) ? buttonSize.x : 0.f,
920 (sized || iconOnly) ? buttonSize.
y : 0.f) ==
922 if (
n.checked) ImGui::PopStyleColor(2);
924 const ImVec2
rectMin = ImGui::GetItemRectMin();
925 const ImVec2
rectMax = ImGui::GetItemRectMax();
926 ImFont *font = ImGui::GetFont();
927 const ImFontGlyph *
glyph = font ? font->FindGlyph(iconCodepoint) :
nullptr;
929 const float fontSize = ImGui::GetFontSize();
930 const float fontScale = fontSize / font->FontSize;
931 const float glyphW = (
glyph->X1 -
glyph->X0) * fontScale;
932 const float glyphH = (
glyph->Y1 -
glyph->Y0) * fontScale;
933 const ImVec2 glyphOrigin(
936 ImGui::GetWindowDrawList()->AddText(font, fontSize, glyphOrigin,
937 ImGui::GetColorU32(ImGuiCol_Text),
941 if (clicked) pushPending(
host,
n,
"click",
n.handlerClick);
949 if (
n.firstChild >= 0) walkSiblings(
host, tree,
n.firstChild);
956 bool checked =
n.checked;
960 pushPending(
host,
n,
"toggle",
n.handlerToggle, checked);
966 const char *
label =
n.text.empty() ?
"Slider" :
n.text.c_str();
970 pushPending(
host,
n,
"value",
n.handlerValue,
false,
v);
975 const float swatch = ImGui::GetFrameHeight();
976 const float gap = 4.f;
978 float col[4] = {
n.tintR,
n.tintG,
n.tintB,
n.tintA};
979 const std::string
label =
n.text.empty() ?
"Color" :
n.text;
980 const std::string editId =
label +
"###palette-edit-" +
n.id;
982 ImGui::PushID(
n.id.empty() ?
"color-palette" :
n.
id.c_str());
983 ImGui::SetNextItemWidth(std::max(80.f,
n.measuredW > 0.f ?
n.measuredW : 220.f));
984 const ImGuiColorEditFlags flags =
985 ImGuiColorEditFlags_AlphaBar | ImGuiColorEditFlags_AlphaPreviewHalf |
986 ImGuiColorEditFlags_PickerHueBar | ImGuiColorEditFlags_DisplayRGB;
987 if (ImGui::ColorEdit4(editId.c_str(), col, flags)) commitPaletteColor(
host,
n, col);
988 const std::vector<PaletteSwatch> swatches = paletteSwatches(
n);
989 for (
size_t i = 0; i < swatches.size(); ++i) {
990 if (i %
static_cast<size_t>(
cols) != 0) ImGui::SameLine(0.f,
gap);
991 const PaletteSwatch &
s = swatches[i];
993 const bool selected = std::fabs(
n.tintR -
s.r) < 0.004f &&
994 std::fabs(
n.tintG -
s.g) < 0.004f &&
995 std::fabs(
n.tintB -
s.b) < 0.004f &&
996 std::fabs(
n.tintA -
s.a) < 0.004f;
997 ImGui::PushID(
static_cast<int>(i));
998 if (ImGui::ColorButton(
"##swatch",
color, ImGuiColorEditFlags_NoTooltip |
999 ImGuiColorEditFlags_AlphaPreview,
1000 ImVec2(swatch, swatch))) {
1001 float picked[4] = {
s.r,
s.g,
s.b,
s.a};
1002 commitPaletteColor(
host,
n, picked);
1005 const ImVec2 p0 = ImGui::GetItemRectMin();
1006 const ImVec2 p1 = ImGui::GetItemRectMax();
1007 ImGui::GetWindowDrawList()->AddRect(
1008 p0, p1, ImGui::GetColorU32(ImGuiCol_Text), 0.f, 0, 1.5f);
1017 const char *overlay =
n.text.empty() ? nullptr :
n.text.c_str();
1018 ImGui::ProgressBar(
n.value, ImVec2(
n.sizeX > 0.f ?
n.sizeX : -1.f,
n.sizeY > 0.f ?
n.sizeY : 0.f), overlay);
1022 const float w =
n.sizeX > 0.f ?
n.sizeX : 32.f;
1023 const float h =
n.sizeY > 0.f ?
n.sizeY : 32.f;
1024 const ImVec2
pos = ImGui::GetCursorScreenPos();
1027 ImGui::ColorConvertFloat4ToU32(ImVec4(
n.tintR,
n.tintG,
n.tintB,
n.tintA));
1028 if (
n.textureId != 0 && g_backend) {
1030 g_backend->textureSize(
n.textureId, &tw, &th);
1031 if (g_backend->usesQueuedTextureDraws()) {
1032 queueNinePatch(
n.textureId,
pos,
size, ImVec2(
n.uv0x,
n.uv0y),
1033 ImVec2(
n.uv1x,
n.uv1y),
n.borderL,
n.borderT,
n.borderR,
1034 n.borderB, tw, th,
n.tintR,
n.tintG,
n.tintB,
n.tintA);
1036 void *
handle = g_backend->textureHandle(
n.textureId);
1039 ImVec2(
n.uv1x,
n.uv1y),
n.borderL,
n.borderT,
1040 n.borderR,
n.borderB, tw, th,
tint);
1042 ImGui::GetWindowDrawList()->AddRectFilled(
1047 ImGui::GetWindowDrawList()->AddRectFilled(
pos, ImVec2(
pos.x +
w,
pos.y +
h),
tint,
1054 const float w =
n.sizeX > 0.f ?
n.sizeX : 32.f;
1055 const float h =
n.sizeY > 0.f ?
n.sizeY : 32.f;
1057 const std::string sid =
n.id.empty() ?
"imgbtn" :
n.id;
1058 ImGui::PushID(sid.c_str());
1059 bool clicked =
false;
1060 bool itemCreated =
false;
1061 if (
n.textureId != 0 && g_backend) {
1062 if (g_backend->usesQueuedTextureDraws()) {
1063 clicked = ImGui::InvisibleButton(sid.c_str(),
size);
1065 const ImVec2 pmin = ImGui::GetItemRectMin();
1066 const float hover = ImGui::IsItemHovered() ? 1.12f : 1.f;
1067 g_backend->queueTextureDraw(
n.textureId, pmin.x, pmin.y,
size.x,
size.y,
1068 n.uv0x,
n.uv0y,
n.uv1x,
n.uv1y,
1069 std::min(1.f,
n.tintR * hover),
1070 std::min(1.f,
n.tintG * hover),
1071 std::min(1.f,
n.tintB * hover),
n.tintA,
false);
1073 void *
handle = g_backend->textureHandle(
n.textureId);
1075 clicked = ImGui::ImageButton(
1076 static_cast<ImTextureID
>(
handle),
size, ImVec2(
n.uv0x,
n.uv0y),
1077 ImVec2(
n.uv1x,
n.uv1y), -1, ImVec4(0.f, 0.f, 0.f, 0.f),
1078 ImVec4(
n.tintR,
n.tintG,
n.tintB,
n.tintA));
1084 clicked = ImGui::InvisibleButton(sid.c_str(),
size);
1085 const ImVec2 pmin = ImGui::GetItemRectMin();
1086 const ImVec2 pmax = ImGui::GetItemRectMax();
1088 ImGui::ColorConvertFloat4ToU32(ImVec4(
n.tintR,
n.tintG,
n.tintB,
n.tintA));
1089 if (ImGui::IsItemHovered()) col = ImGui::GetColorU32(ImGuiCol_ButtonHovered);
1090 ImGui::GetWindowDrawList()->AddRectFilled(pmin, pmax, col,
n.cornerRadius);
1093 if (clicked) pushPending(
host,
n,
"click",
n.handlerClick);
1098 std::vector<std::string> storage;
1100 while (
start <=
n.valueText.size()) {
1101 const size_t end =
n.valueText.find(
'\n',
start);
1102 storage.push_back(
n.valueText.substr(
1104 if (
end == std::string::npos)
break;
1107 std::vector<const char *> items;
1108 items.reserve(storage.size());
1109 for (
const auto &
s : storage) items.push_back(
s.c_str());
1110 int idx = int(
n.value);
1111 const char *
label =
n.text.empty() ?
"Combo" :
n.text.c_str();
1113 n.value = float(
idx);
1114 pushPending(
host,
n,
"value",
n.handlerValue,
false,
float(
idx));
1119 const char *
label =
n.text.empty() ?
"Input" :
n.text.c_str();
1120 std::string edited =
n.valueText;
1122 n.valueText = std::move(edited);
1123 pushPending(
host,
n,
"text",
n.handlerText,
false, 0.f,
n.valueText);
1128 std::string edited =
n.valueText;
1129 const std::string
label =
"##search" + (
n.id.empty() ? std::string() :
"###" +
n.
id);
1130 const ImGuiStyle &style = ImGui::GetStyle();
1131 const std::string hint =
n.text.empty() ?
"Search" :
n.text;
1133 const ImVec2 iconSize = ImGui::CalcTextSize(
glyph);
1135 const float width =
n.sizeX > 0.f ?
n.sizeX : ImGui::GetContentRegionAvail().x;
1136 n.measuredW = std::max(1.f,
width);
1137 ImGui::PushItemWidth(std::max(1.f,
width));
1138 ImGui::PushStyleVar(ImGuiStyleVar_FramePadding,
1139 ImVec2(style.FramePadding.x + iconSize.x +
gap,
1140 style.FramePadding.y));
1143 n.valueText = std::move(edited);
1144 pushPending(
host,
n,
"text",
n.handlerText,
false, 0.f,
n.valueText);
1146 ImGui::PopStyleVar();
1147 ImGui::PopItemWidth();
1148 const ImVec2
rectMin = ImGui::GetItemRectMin();
1149 const ImVec2
rectMax = ImGui::GetItemRectMax();
1150 ImGui::GetWindowDrawList()->AddText(
1151 ImVec2(
rectMin.x + style.FramePadding.x,
1153 ImGui::GetColorU32(ImGuiCol_TextDisabled),
glyph);
1158 const float height = ImGui::GetFrameHeight();
1159 const float trackH = std::max(16.f,
height * 0.68f);
1160 const float trackW =
height * 1.7f;
1161 const float radius = trackH * 0.5f;
1162 const std::string
id =
"##switch" + (
n.id.empty() ? std::string() :
"###" +
n.
id);
1163 ImGui::BeginGroup();
1164 const ImVec2
pos = ImGui::GetCursorScreenPos();
1165 if (ImGui::InvisibleButton(
id.c_str(), ImVec2(trackW,
height))) {
1166 n.checked = !
n.checked;
1167 pushPending(
host,
n,
"toggle",
n.handlerToggle,
n.checked);
1169 const bool hovered = ImGui::IsItemHovered();
1170 const ImU32 bg = ImGui::GetColorU32(
1171 n.checked ? ImGuiCol_CheckMark
1172 : (hovered ? ImGuiCol_FrameBgHovered : ImGuiCol_FrameBg));
1173 const float y =
pos.y + (
height - trackH) * 0.5f;
1174 ImDrawList *
draw = ImGui::GetWindowDrawList();
1175 draw->AddRectFilled(ImVec2(
pos.x,
y), ImVec2(
pos.x + trackW,
y + trackH), bg,
radius);
1176 const float knobR =
radius - 2.f;
1178 draw->AddCircleFilled(ImVec2(knobX,
y +
radius), knobR,
1179 ImGui::GetColorU32(ImGuiCol_Text));
1180 if (!
n.text.empty()) {
1181 ImGui::SameLine(0.f, ImGui::GetStyle().ItemInnerSpacing.x);
1182 ImGui::AlignTextToFramePadding();
1183 ImGui::TextUnformatted(
n.text.c_str());
1189 const ImVec2 textSize = ImGui::CalcTextSize(
n.text.c_str());
1190 const ImVec2 padding(ImGui::GetStyle().FramePadding.x, 3.f);
1191 const ImVec2
pos = ImGui::GetCursorScreenPos();
1192 const ImVec2
size(textSize.x + padding.x * 2.f, textSize.y + padding.y * 2.f);
1193 ImDrawList *
draw = ImGui::GetWindowDrawList();
1194 const ImU32 bg = ImGui::ColorConvertFloat4ToU32(
1195 ImVec4(
n.tintR,
n.tintG,
n.tintB,
n.tintA));
1196 const ImU32
border = ImGui::ColorConvertFloat4ToU32(
1197 ImVec4(
n.tintR,
n.tintG,
n.tintB, std::min(1.f,
n.tintA + 0.35f)));
1200 draw->AddText(ImVec2(
pos.x + padding.x,
pos.y + padding.y),
1201 ImGui::GetColorU32(ImGuiCol_Text),
n.text.c_str());
1206 ImGuiTreeNodeFlags flags =
n.open ? ImGuiTreeNodeFlags_DefaultOpen : 0;
1207 const char *
label =
n.text.empty() ?
"Section" :
n.text.c_str();
1208 if (ImGui::CollapsingHeader(
label, flags)) {
1209 if (
n.firstChild >= 0) walkSiblings(
host, tree,
n.firstChild);
1214 ImVec2
size(
n.sizeX,
n.sizeY);
1215 const char *cid =
n.id.empty() ?
"child" :
n.id.c_str();
1216 if (ImGui::BeginChild(cid,
size,
true)) {
1217 if (
n.firstChild >= 0) walkSiblings(
host, tree,
n.firstChild);
1226 const float height =
n.sizeY > 0.f
1228 : std::max(ImGui::GetFrameHeight(),
1229 n.measuredH + padding.y * 2.f);
1230 const ImVec2
size(
n.sizeX > 0.f ?
n.sizeX : 0.f,
height);
1231 const std::string
id =
n.id.empty() ?
"card" :
n.id;
1232 ImGui::PushStyleVar(ImGuiStyleVar_WindowPadding, padding);
1233 ImGui::PushStyleColor(ImGuiCol_ChildBg, ImGui::GetStyleColorVec4(ImGuiCol_FrameBg));
1234 if (ImGui::BeginChild(
id.c_str(),
size,
true, ImGuiWindowFlags_AlwaysUseWindowPadding)) {
1235 if (
n.firstChild >= 0) walkSiblings(
host, tree,
n.firstChild);
1238 ImGui::PopStyleColor();
1239 ImGui::PopStyleVar();
1243 const float width =
n.sizeX > 0.f ?
n.sizeX : ImGui::GetContentRegionAvail().x;
1244 const float height =
n.sizeY > 0.f
1246 : std::max(
n.minSizeY,
1247 n.measuredH > 0.f ?
n.measuredH
1248 :
ImGui::GetFrameHeight());
1249 const ImVec2
pos = ImGui::GetCursorScreenPos();
1251 if (
n.textureId != 0 && g_backend) {
1253 g_backend->textureSize(
n.textureId, &tw, &th);
1254 if (g_backend->usesQueuedTextureDraws()) {
1255 queueNinePatch(
n.textureId,
pos,
size, ImVec2(
n.uv0x,
n.uv0y),
1256 ImVec2(
n.uv1x,
n.uv1y),
n.borderL,
n.borderT,
n.borderR,
1257 n.borderB, tw, th,
n.tintR,
n.tintG,
n.tintB,
n.tintA);
1258 }
else if (
void *
handle = g_backend->textureHandle(
n.textureId)) {
1259 const ImU32
tint = ImGui::ColorConvertFloat4ToU32(
1260 ImVec4(
n.tintR,
n.tintG,
n.tintB,
n.tintA));
1262 ImVec2(
n.uv1x,
n.uv1y),
n.borderL,
n.borderT,
n.borderR,
1263 n.borderB, tw, th,
tint);
1266 const std::string
id =
n.id.empty() ?
"ninePatchPanel" :
n.id;
1267 ImGui::PushStyleVar(ImGuiStyleVar_WindowPadding, ImVec2(0.f, 0.f));
1268 ImGui::PushStyleColor(ImGuiCol_ChildBg, ImVec4(0.f, 0.f, 0.f, 0.f));
1269 if (ImGui::BeginChild(
id.c_str(),
size,
false,
1270 ImGuiWindowFlags_AlwaysUseWindowPadding)) {
1271 ImGui::SetCursorPos(ImVec2(
n.paddingL,
n.paddingT));
1272 const ImVec2 contentSize(std::max(1.f,
size.x -
n.paddingL -
n.paddingR),
1273 std::max(1.f,
size.y -
n.paddingT -
n.paddingB));
1274 const std::string contentId =
id +
"##content";
1275 if (ImGui::BeginChild(contentId.c_str(), contentSize,
false)) {
1276 if (
n.firstChild >= 0) walkSiblings(
host, tree,
n.firstChild);
1281 ImGui::PopStyleColor();
1282 ImGui::PopStyleVar();
1287 const ImVec2
pos = ImGui::GetCursorScreenPos();
1288 const float height = ImGui::GetFrameHeight();
1289 const float totalHeight = sectionGap +
height;
1290 const float width =
n.sizeX > 0.f ?
n.sizeX : ImGui::GetContentRegionAvail().x;
1291 const float headerY =
pos.y + sectionGap;
1292 ImDrawList *
draw = ImGui::GetWindowDrawList();
1293 draw->AddRectFilled(ImVec2(
pos.x, headerY + 4.f),
1294 ImVec2(
pos.x + 3.f, headerY +
height - 4.f),
1295 ImGui::GetColorU32(ImGuiCol_CheckMark), 1.f);
1296 draw->AddText(ImVec2(
pos.x + 10.f,
1297 headerY + (
height - ImGui::GetFontSize()) * 0.5f),
1298 ImGui::GetColorU32(ImGuiCol_Text),
n.text.c_str());
1299 const float separatorY =
pos.y + totalHeight - 1.f;
1300 draw->AddLine(ImVec2(
pos.x, separatorY), ImVec2(
pos.x +
width, separatorY),
1301 ImGui::GetColorU32(ImGuiCol_Separator));
1302 ImGui::Dummy(ImVec2(
width, totalHeight));
1306 if (ImGui::BeginMenuBar()) {
1307 if (
n.firstChild >= 0) walkSiblings(
host, tree,
n.firstChild);
1308 ImGui::EndMenuBar();
1312 const std::string
label = nodeLabel(
n,
"Menu");
1313 if (ImGui::BeginMenu(
label.c_str())) {
1314 if (
n.firstChild >= 0) walkSiblings(
host, tree,
n.firstChild);
1320 const std::string
label = nodeLabel(
n,
"Command");
1321 const char *shortcut =
n.valueText.empty() ? nullptr :
n.valueText.c_str();
1322 if (ImGui::MenuItem(
label.c_str(), shortcut,
n.checked,
true))
1323 pushPending(
host,
n,
"click",
n.handlerClick);
1331 const float defaultHeight =
1333 const float height =
n.sizeY > 0.f ?
n.sizeY : defaultHeight;
1334 const ImVec2
size(
n.sizeX > 0.f ?
n.sizeX : 0.f,
height);
1335 const std::string
id =
n.id.empty() ? (
status ?
"statusbar" :
"toolbar") :
n.
id;
1336 ImGui::PushStyleVar(ImGuiStyleVar_WindowPadding,
1338 ImGui::PushStyleColor(ImGuiCol_ChildBg,
1339 ImGui::GetStyleColorVec4(
status ? ImGuiCol_WindowBg
1340 : ImGuiCol_MenuBarBg));
1341 if (ImGui::BeginChild(
id.c_str(),
size,
false,
1342 ImGuiWindowFlags_AlwaysUseWindowPadding |
1343 ImGuiWindowFlags_NoScrollbar)) {
1344 walkFlex(
host, tree,
n);
1347 ImGui::PopStyleColor();
1348 ImGui::PopStyleVar();
1354 const float cell =
n.itemHeight > 0.f ?
n.itemHeight :
layout.toolboxCellSize *
scale;
1356 const float outerWidth =
n.sizeX > 0.f ?
n.sizeX : ImGui::GetContentRegionAvail().x;
1357 const float contentWidth = std::max(1.f, outerWidth - padding.x * 2.f);
1358 const float spacing = ImGui::GetStyle().ItemSpacing.x;
1360 const int requested = int(
n.value) > 0 ? int(
n.value) :
capacity;
1363 for (
int childIndex =
n.firstChild; childIndex >= 0;
1364 childIndex = tree->nodes[size_t(childIndex)].nextSibling) {
1365 if (tree->nodes[
size_t(childIndex)].visible) ++
count;
1368 const float autoHeight = std::max(
1370 ImGui::GetStyle().ItemSpacing.y + padding.y * 2.f);
1371 const ImVec2
size(
n.sizeX > 0.f ?
n.sizeX : 0.f,
n.sizeY > 0.f ?
n.sizeY : autoHeight);
1372 const std::string
id =
n.id.empty() ?
"toolbox" :
n.id;
1373 ImGui::PushStyleVar(ImGuiStyleVar_WindowPadding, padding);
1374 ImGui::PushStyleColor(ImGuiCol_ChildBg, ImGui::GetStyleColorVec4(ImGuiCol_ChildBg));
1375 if (ImGui::BeginChild(
id.c_str(),
size,
false, ImGuiWindowFlags_AlwaysUseWindowPadding)) {
1376 int visibleIndex = 0;
1377 for (
int childIndex =
n.firstChild; childIndex >= 0;
1378 childIndex = tree->nodes[size_t(childIndex)].nextSibling) {
1379 UINode &
child = tree->nodes[size_t(childIndex)];
1385 walkNode(
host, tree, childIndex);
1389 if (visibleIndex %
columns != 0) ImGui::SameLine();
1393 ImGui::PopStyleColor();
1394 ImGui::PopStyleVar();
1401 n.sizeY > 0.f ?
n.sizeY : 0.f);
1402 const std::string
id =
n.id.empty() ?
"sidebar" :
n.id;
1403 ImGui::PushStyleVar(ImGuiStyleVar_WindowPadding,
1405 ImGui::PushStyleColor(ImGuiCol_ChildBg, ImGui::GetStyleColorVec4(ImGuiCol_ChildBg));
1406 if (ImGui::BeginChild(
id.c_str(),
size,
true, ImGuiWindowFlags_AlwaysUseWindowPadding)) {
1407 if (
n.firstChild >= 0) walkSiblings(
host, tree,
n.firstChild);
1410 ImGui::PopStyleColor();
1411 ImGui::PopStyleVar();
1416 const ImVec2 avail = ImGui::GetContentRegionAvail();
1417 const ImVec2
size(
n.sizeX > 0.f ?
n.sizeX : avail.
x,
n.sizeY > 0.f ?
n.sizeY : avail.
y);
1418 n.measuredW =
size.x;
1419 n.measuredH =
size.y;
1422 const float mainSize = std::max(1.f, (
row ?
size.x :
size.
y) - splitter);
1423 const float minPane = std::min(
layout.minPaneSize *
themeUiScale(), mainSize * 0.5f);
1424 const float ratioLo = std::max(
n.minValue, minPane / mainSize);
1425 const float ratioHi = std::min(
n.maxValue, 1.f - minPane / mainSize);
1426 const std::string
id =
n.id.empty() ?
"splitpane" :
n.id;
1427 ImGui::PushID(
id.c_str());
1428 const ImGuiID ratioStateId = ImGui::GetID(
"ratio");
1429 const float storedRatio = ImGui::GetStateStorage()->GetFloat(ratioStateId,
n.value);
1430 const float ratio = std::max(ratioLo, std::min(ratioHi, storedRatio));
1431 ImGui::GetStateStorage()->SetFloat(ratioStateId, ratio);
1433 const float firstMain = std::floor(mainSize * ratio);
1434 const float secondMain = std::max(1.f, mainSize - firstMain);
1435 const int first =
n.firstChild;
1436 const int second =
first >= 0 ? tree->nodes[size_t(
first)].nextSibling : -1;
1437 ImGui::BeginGroup();
1438 ImGui::PushStyleVar(ImGuiStyleVar_WindowPadding, ImVec2(0.f, 0.f));
1441 if (ImGui::BeginChild(
"first", paneSize,
false,
1442 ImGuiWindowFlags_NoScrollbar |
1443 ImGuiWindowFlags_NoScrollWithMouse))
1447 if (
row) ImGui::SameLine(0.f, 0.f);
1449 ImGui::InvisibleButton(
"splitter", handleSize);
1450 const bool hovered = ImGui::IsItemHovered();
1451 const bool active = ImGui::IsItemActive();
1453 ImGui::SetMouseCursor(
row ? ImGuiMouseCursor_ResizeEW : ImGuiMouseCursor_ResizeNS);
1454 ImGui::GetWindowDrawList()->AddRectFilled(
1455 ImGui::GetItemRectMin(), ImGui::GetItemRectMax(),
1456 ImGui::GetColorU32(
active ? ImGuiCol_CheckMark
1457 : (hovered ? ImGuiCol_SeparatorHovered
1458 : ImGuiCol_Separator)));
1460 const float delta =
row ? ImGui::GetIO().MouseDelta.x : ImGui::GetIO().MouseDelta.y;
1461 const float next = std::max(ratioLo, std::min(ratioHi, ratio + delta / mainSize));
1462 if (next != ratio) {
1464 ImGui::GetStateStorage()->SetFloat(ratioStateId, next);
1465 pushPending(
host,
n,
"value",
n.handlerValue,
false, next);
1468 if (
row) ImGui::SameLine(0.f, 0.f);
1470 const ImVec2 paneSize(
row ? secondMain :
size.
x,
row ?
size.
y : secondMain);
1471 if (ImGui::BeginChild(
"second", paneSize,
false,
1472 ImGuiWindowFlags_NoScrollbar |
1473 ImGuiWindowFlags_NoScrollWithMouse))
1477 ImGui::PopStyleVar();
1484 std::vector<int> kids;
1485 for (
int c =
n.firstChild;
c >= 0;
c = tree->nodes[size_t(
c)].nextSibling) {
1486 if (
c >=
int(tree->nodes.size()))
break;
1487 if (tree->nodes[
size_t(
c)].visible) kids.push_back(
c);
1489 const int total = int(kids.size());
1490 const char *cid =
n.id.empty() ?
"scrolllist" :
n.id.c_str();
1491 const ImVec2
size(
n.sizeX > 0.f ?
n.sizeX : 0.f,
1492 n.sizeY > 0.f ?
n.sizeY : 120.f);
1493 ImGui::BeginChild(cid,
size,
true);
1494 const float itemH =
n.itemHeight > 0.f ?
n.itemHeight : ImGui::GetFrameHeight();
1495 const float contentW = ImGui::GetContentRegionAvail().x;
1496 const float scrollY = ImGui::GetScrollY();
1497 const float viewH = ImGui::GetWindowHeight();
1498 if (total > 0 && itemH > 0.f) {
1499 int first = int(scrollY / itemH);
1502 int last = int(std::ceil((scrollY + viewH) / itemH));
1504 if (last > total) last = total;
1505 for (
int i =
first; i < last; ++i) {
1506 UINode &
child = tree->nodes[size_t(kids[
size_t(i)])];
1507 ImGui::SetCursorPos(ImVec2(0.f,
float(i) * itemH));
1511 if (contentW > 0.f) ImGui::PushItemWidth(contentW);
1512 walkNode(
host, tree, kids[
size_t(i)]);
1515 if (contentW > 0.f) ImGui::PopItemWidth();
1517 ImGui::SetCursorPosY(
float(i) * itemH + itemH);
1521 ImGui::SetCursorPosY(
float(total) * itemH);
1526 const float w =
n.sizeX > 0.f ?
n.sizeX : ImGui::GetContentRegionAvail().x;
1527 const float h =
n.sizeY > 0.f ?
n.sizeY : ImGui::GetContentRegionAvail().y;
1528 const ImVec2
size(std::max(1.f,
w), std::max(1.f,
h));
1529 const std::string
key = viewportKey(
host,
n);
1532 const ImVec2
rectMin = ImGui::GetCursorScreenPos();
1533 const std::string
label =
"##vp" + (
n.id.empty() ? std::string(
"viewport") :
n.
id);
1534 ImGui::InvisibleButton(
label.c_str(),
size);
1535 const bool hovered = ImGui::IsItemHovered();
1536 const bool active = ImGui::IsItemActive();
1537 const ImVec2 mouse = ImGui::GetMousePos();
1540 vs.hovered = hovered;
1542 vs.mouseX = mouse.x -
rectMin.x;
1543 vs.mouseY = mouse.y -
rectMin.y;
1545 vs.dragDX = ImGui::GetIO().MouseDelta.x;
1546 vs.dragDY = ImGui::GetIO().MouseDelta.y;
1551 vs.wheel = hovered ? ImGui::GetIO().MouseWheel : 0.f;
1553 if (vs.textureId && g_backend && g_backend->usesQueuedTextureDraws()) {
1554 g_backend->queueTextureDraw(vs.textureId,
rectMin.x,
rectMin.y,
size.x,
size.y, 0.f, 0.f, 1.f, 1.f, 1.f,
1555 1.f, 1.f, 1.f,
true);
1556 }
else if (vs.textureId && g_backend) {
1557 void *
handle = g_backend->textureHandle(vs.textureId);
1559 ImGui::SetCursorScreenPos(
rectMin);
1560 ImGui::Image(
static_cast<ImTextureID
>(
handle),
size);
1563 IM_COL32(10, 12, 16, 255));
1566 ImGui::GetWindowDrawList()->AddRectFilled(
1568 IM_COL32(10, 12, 16, 255));
1571 ImGui::GetWindowDrawList()->AddRectFilled(
1577 walkFlex(
host, tree,
n);
1580 walkGrid(
host, tree,
n);
1584 emitSpacer(
n.sizeX > 0.f ?
n.sizeX : 0.f,
n.sizeY > 0.f ?
n.sizeY : 0.f, true);
1589 n.focused = ImGui::IsItemFocused();
1590 routeExplicitFocusInput(
host,
n);
1591 n.focusRequested =
false;
1593 if (disableNavigation) ImGui::PopItemFlag();
1595 ImGui::PopStyleVar();
1596 ImGui::PopItemFlag();
1599 renderDragDrop(
host,
n);
1600 if (!
n.tooltip.empty() && ImGui::IsItemHovered(ImGuiHoveredFlags_None))
1601 ImGui::SetTooltip(
"%s",
n.tooltip.c_str());
1603 if (styleVarCount > 0) ImGui::PopStyleVar(styleVarCount);
1604 if (styleColorCount > 0) ImGui::PopStyleColor(styleColorCount);
1613void walk(UIHost *
host, UIHost::Tree *tree,
int index) { walkSiblings(
host, tree,
index); }
1623#if (defined(_WIN32) || defined(__APPLE__) || defined(__linux__)) && \
1624 !defined(__ANDROID__) && !defined(__EMSCRIPTEN__)
1633 g_pendingFileDrops.push_back(
path);
1637 if (g_drops.empty())
return std::nullopt;
1638 UIDrop result = std::move(g_drops.front());
1639 g_drops.erase(g_drops.begin());
1646 if (!g_backend)
return std::nullopt;
1647 return std::ref(*g_backend);
1653 if (
key.empty() ||
w <= 0 ||
h <= 0) {
1658 auto *gfx = eve::ModuleManager::getInstance<eve::graphics::Graphics>(
"Graphics");
1678 if (vs.
canvas && g_backend)
1681 return Return::success(std::ref(vs));
1685 const std::string &
nodeId) {
1686 auto it = g_viewports.find(hostName +
"/" +
nodeId);
1687 if (it == g_viewports.end())
return std::nullopt;
1688 return std::ref(it->second);
1692 if (
name.empty())
return {};
1693 if (ecs::current()->getManager<UIHost>() ==
nullptr)
return {};
1694 auto view = ecs::View<UIHost, UIHost::Meta, UIHost::Tree>();
1695 for (
auto it =
view.begin(); it !=
view.end(); ++it) {
1696 auto [meta, tree] = *it;
1704 if (ownerId == 0)
return {};
1705 if (ecs::current()->getManager<UIHost>() ==
nullptr)
return {};
1706 auto view = ecs::View<UIHost, UIHost::Meta, UIHost::Tree>();
1707 for (
auto it =
view.begin(); it !=
view.end(); ++it) {
1708 auto [meta, tree] = *it;
1710 if (meta->ownerId == ownerId &&
UIHost::resolve(meta->entity))
return meta->entity;
1717 if (ecs::current()->getManager<UIHost>() ==
nullptr)
return;
1727 std::vector<Item> items;
1730 const ImVec2 display = ImGui::GetIO().DisplaySize;
1731 auto view = ecs::View<UIHost, UIHost::Meta, UIHost::Tree>();
1732 for (
auto it =
view.begin(); it !=
view.end(); ++it) {
1733 auto [meta, tree] = *it;
1734 if (!meta->visible)
continue;
1736 if (!
host)
continue;
1737 if (!updateWorldAnchor(
host->get(),
camera, display.x, display.y))
continue;
1738 items.push_back(Item{&
host->get(), meta, tree,
true});
1741 std::stable_sort(items.begin(), items.end(),
1742 [](
const Item &
a,
const Item &
b) { return a.meta->layer < b.meta->layer; });
1744 g_stats.nodeCount = 0;
1745 g_stats.measureMs = 0.0;
1746 g_stats.walkMs = 0.0;
1747 for (
auto &item : items) {
1748 item.tree->dirty =
false;
1749 HostThemeScope
theme(item.host);
1750 const auto m0 = std::chrono::steady_clock::now();
1752 const auto m1 = std::chrono::steady_clock::now();
1753 g_stats.nodeCount += int(item.tree->nodes.size());
1754 g_stats.measureMs +=
1755 std::chrono::duration<double, std::milli>(m1 - m0).count();
1758 std::vector<WorldAnchorLayoutItem> anchorItems;
1759 anchorItems.reserve(items.size());
1760 const ImGuiStyle &style = ImGui::GetStyle();
1761 for (std::size_t i = 0; i < items.size(); ++i) {
1762 auto &item = items[i];
1763 auto anchor = item.host->worldAnchor();
1764 if (!
anchor->enabled)
continue;
1765 float width = item.meta->hasSize ? item.meta->sizeX : 0.f;
1766 float height = item.meta->hasSize ? item.meta->sizeY : 0.f;
1767 if (item.meta->percentW > 0.f)
width = item.meta->percentW * display.x;
1768 if (item.meta->percentH > 0.f)
height = item.meta->percentH * display.y;
1769 if (item.tree->root >= 0) {
1770 const auto &
root = item.tree->nodes[std::size_t(item.tree->root)];
1771 if (
width <= 0.f)
width =
root.measuredW + style.WindowPadding.x * 2.f;
1776 anchorItems.push_back({i,
1790 for (
const auto &resolved :
1792 auto &item = items[resolved.stableIndex];
1793 auto anchor = item.host->worldAnchor();
1794 anchor->screenX = resolved.screenX;
1795 anchor->screenY = resolved.screenY;
1796 anchor->displacementX = resolved.displacementX;
1797 anchor->displacementY = resolved.displacementY;
1798 item.render = resolved.render;
1802 g_stats.hostCount = int(std::count_if(items.begin(), items.end(),
1803 [](
const Item &item) { return item.render; }));
1804 for (
auto &item : items) {
1805 if (!item.render)
continue;
1806 HostThemeScope
theme(item.host);
1807 const auto w0 = std::chrono::steady_clock::now();
1808 if (item.tree->root >= 0) walk(item.host, item.tree, item.tree->root);
1809 const auto w1 = std::chrono::steady_clock::now();
1810 g_stats.walkMs += std::chrono::duration<double, std::milli>(w1 - w0).count();
1814 for (
auto &item : items) {
1815 item.meta->animDrivePos =
false;
1816 item.meta->animDriveSize =
false;
1821 auto events = std::move(g_pending);
1823 for (
auto &ev :
events) {
1825 if (!resolvedHost)
continue;
1828 if (ev.kind ==
"click" && !ev.nodeId.empty()) {
1830 c.
hostName = ev.hostName.empty() ?
host->meta()->name : ev.hostName;
1831 c.nodeId = ev.nodeId;
1832 g_clicks.push_back(std::move(
c));
1835 if ((ev.kind ==
"toggle" || ev.kind ==
"value" || ev.kind ==
"text") && !ev.nodeId.empty()) {
1837 ch.
hostName = ev.hostName.empty() ?
host->meta()->name : ev.hostName;
1840 g_changes.push_back(std::move(ch));
1843 if (ev.kind ==
"click") {
1844 auto tree =
host->tree();
1845 int index = ev.nodeIndex;
1846 if (index < 0 || index >=
int(tree->nodes.size()) ||
1847 tree->nodes[
size_t(
index)].id != ev.nodeId) {
1849 index =
target && !tree->nodes.empty() ? int(&
target->get() - tree->nodes.data()) : -1;
1853 while (
index >= 0 &&
index <
int(tree->nodes.size())) {
1855 if (!
node.visible || !
node.enabled)
break;
1858 const size_t handlerSlot = size_t(
handler - 1);
1859 if (handlerSlot < tree->clickHandlers.size() &&
1860 tree->clickHandlers[handlerSlot])
1861 tree->clickHandlers[handlerSlot]();
1870 if (ev.kind ==
"toggle" && ev.handlerIndex != 0) {
1871 auto t =
host->tree();
1872 size_t idx = size_t(ev.handlerIndex - 1);
1873 if (idx < t->toggleHandlers.size() &&
t->toggleHandlers[
idx])
1874 t->toggleHandlers[
idx](ev.toggleValue);
1877 if (ev.kind ==
"value" && ev.handlerIndex != 0) {
1878 auto t =
host->tree();
1879 size_t idx = size_t(ev.handlerIndex - 1);
1880 if (idx < t->valueHandlers.size() &&
t->valueHandlers[
idx])
1881 t->valueHandlers[
idx](ev.floatValue);
1884 if (ev.kind ==
"text" && ev.handlerIndex != 0) {
1885 auto t =
host->tree();
1886 size_t idx = size_t(ev.handlerIndex - 1);
1887 if (idx < t->textHandlers.size() &&
t->textHandlers[
idx])
1888 t->textHandlers[
idx](ev.textValue);
1895 if (g_clicks.empty())
return {};
1896 UIClick c = std::move(g_clicks.front());
1897 g_clicks.erase(g_clicks.begin());
1898 if (
c.hostName.empty())
return c.nodeId;
1899 return c.hostName +
"/" +
c.nodeId;
1903 for (
auto it = g_clicks.begin(); it != g_clicks.end(); ++it) {
1904 if (it->hostName != hostName)
continue;
1905 std::string
id = std::move(it->nodeId);
1913 if (g_changes.empty())
return {};
1914 UIChange c = std::move(g_changes.front());
1915 g_changes.erase(g_changes.begin());
1916 if (
c.hostName.empty())
return c.nodeId;
1917 return c.hostName +
"/" +
c.nodeId;
std::map< std::string, ImVec2 > rectMin
std::map< std::string, ImVec2 > rectMax
wgpu::PopErrorScopeStatus status
std::array< float, 3 > scale
#define EV_PROFILE_MODULE(module, name)
Profile the enclosing scope, tagged with a module for grouping.
ImGuiConfigFlags previousConfigFlags
std::string sourceHostName
static Diagnostic error(DiagnosticCode code, std::string message, std::string path={}, DiagnosticDetails details={}, std::string source={})
Construct an error diagnostic with the standard error severity.
Move-only operation result carrying either a value or Status.
static Status failure(StatusCode code, Diagnostic diagnostic)
Construct a failed status with one diagnostic.
EVENGINE_API_BACKENDS public API.
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 eve::OptionalRef< UIHost > resolve(UIHostHandle handle) noexcept
Resolves a UI host handle with ECS generation checking.
static std::vector< UIDrop > & dropQueue()
Returns the owning completed-drop queue.
static const UIStats & stats()
static void setBackend(UIBackend &backend)
Sets the borrowed backend used for the current UI/render lifetime.
static void clearBackend() noexcept
Clears the backend association before the backend is shut down.
static eve::OptionalRef< ViewportState > viewportState(const std::string &hostName, const std::string &nodeId)
Returns viewport state for a host/node key when it exists.
static void enqueuePlatformFileDrop(const std::string &path)
Copies an OS file-drop path into UI-owned pending state.
static std::vector< UIClick > & clickQueue()
static std::string consumeChange()
Pop next change as "name/node"; empty if none.
static eve::OptionalRef< UIBackend > backend()
Returns the configured backend for the current synchronous operation.
static UIHostHandle findHost(const std::string &name)
Lookup by Meta.name across the UIHost View.
static std::vector< UIChange > & changeQueue()
Change queue.
static void dispatchEvents()
Dispatches events.
static eve::ResultRef< ViewportState > ensureViewport(const std::string &key, int w, int h)
Creates or refreshes offscreen state for a viewport key.
static DragDropSupport dragDropSupport() noexcept
Returns retained drag-and-drop availability for this build target.
static std::string consumeClick()
Pop next click as "name/node"; empty if none.
static void render()
Walk all UIHost (+ subclasses) via ECS View.
static UIHostHandle findHostByOwner(uint32_t ownerId)
Find the first host with Meta.ownerId equal to ownerId, or null.
static std::optional< UIDrop > consumeDrop()
Pops the oldest completed drop, or returns empty when none exists.
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()
Pending events.
int main(int argc, char **argv)
glm::mat4 cameraProjectionVulkanRH_ZO(bool orthographic, float fovyRad, float orthoHeight, float aspect, float zNear, float zFar)
Camera projection shared by rendering, picking, and diagnostic passes.
constexpr HexDirection next(HexDirection d) noexcept
The next direction clockwise (NW wraps to NE).
double cross(const Vec2 &a, const Vec2 &b)
Cross.
ControlEdit slider(const char *label, float &value, float minimum, float maximum, const char *format="%.3f")
Shared immediate renderer for scalar sliders.
ControlEdit button(const char *label, float width=0.f, float height=0.f)
Shared immediate renderer for retained and EditorHost buttons.
ControlEdit combo(const char *label, int &selected, const std::vector< const char * > &items)
Shared immediate renderer for indexed combo boxes.
ControlEdit inputText(const char *label, std::string &value, bool multiline=false, ImVec2 multilineSize=ImVec2(0.f, 96.f))
UTF-8 owning text editor with automatic buffer growth.
ControlEdit inputTextWithHint(const char *label, const char *hint, std::string &value)
UTF-8 owning single-line editor with placeholder text and automatic growth.
ControlEdit checkbox(const char *label, bool &value)
Shared immediate renderer for retained and EditorHost checkboxes.
ControlEdit
Result of drawing one immediate control in the current frame.
const char * iconGlyph(Icon icon)
Returns the UTF-8 glyph for an icon, or an empty string for None.
DragDropSupport
Build-target availability of retained drag-and-drop.
GridResult gridArrange(int columns, float columnGap, float rowGap, float availWidth, const std::vector< GridItemSpec > &items)
Places source-ordered items into equal-width fixed columns.
void measureTree(UIHost::Tree &tree)
Measure tree.
std::vector< WorldAnchorLayoutResult > resolveWorldAnchorOverlaps(std::vector< WorldAnchorLayoutItem > items, float viewportWidth, float viewportHeight)
Resolves projected host overlap with bounded deterministic vertical displacement.
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.
FocusDirection
Logical focus movement understood by keyboard, gamepad and automation.
WidgetDesc grid(int columns, std::vector< WidgetDesc > children, std::string id)
Fixed-column grid; children are placed in source order.
WidgetDesc viewport(std::string id, float width, float height)
Viewport.
WidgetDesc flex(FlexDirection direction, std::vector< WidgetDesc > children, std::string id)
Elastic layout container (row/column). Prefer row / column shorthands.
ecs::EntityHandle UIHostHandle
WorldAnchorProjection projectWorldAnchor(const UIHost::WorldAnchor &anchor, const glm::mat4 &viewProjection, float cameraX, float cameraY, float cameraZ, float viewportWidth, float viewportHeight)
Projects one world-space UI anchor using a Vulkan RH-ZO view-projection matrix.
Theme & globalTheme()
Global theme.
WidgetDesc row(std::vector< WidgetDesc > children, std::string id)
Horizontal elastic layout row.
FlexResult flexArrange(bool row, float gap, float availMain, float availCross, FlexAlign containerAlign, FlexJustify justify, const std::vector< FlexItemSpec > &items, bool wrap, float crossGap)
Flex arrange.
void applyThemeToImGui(const Theme &theme)
Push tokens into ImGui style. Metrics are multiplied by uiScale (default: themeUiScale()).
float themeUiScale()
Theme ui scale.
static Theme dark()
Built-in presets with matching geometry; only colors differ.
Value/text/toggle change for script: "hostName/nodeId".
One script-facing click: "hostName/nodeId".
One completed desktop drop with no borrowed node or platform pointers.
Retained UI widget node (arena tree). Conceptual counterpart of eve::scene::SceneNode; built declarat...
ViewportState public API.
graphics::Canvas * canvas