12#include "common/config.h"
18#include <simplesquirrel/simplesquirrel.hpp>
20#if !(defined(EVENGINE_WEBGPU) && defined(__EMSCRIPTEN__))
21#include <Poco/JSON/Array.h>
22#include <Poco/JSON/Object.h>
23#include <Poco/JSON/Parser.h>
24#include <Poco/JSON/Stringifier.h>
38void callScriptHandler(ssq::Function &
fn,
const std::string &
kind,
const UIEvent &ev) {
41 const SQInteger top = sq_gettop(
vm);
42 sq_pushobject(
vm,
fn.getRaw());
44 if (
kind ==
"click") {
45 if (SQ_FAILED(sq_call(
vm, 1, SQFalse, SQTrue))) {
50 sq_pushstring(
vm,
kind.c_str(), -1);
51 if (
kind ==
"toggle") sq_pushbool(
vm, ev.toggleValue ? SQTrue : SQFalse);
52 else if (
kind ==
"value") sq_pushfloat(
vm, ev.floatValue);
53 else sq_pushstring(
vm, ev.textValue.c_str(), -1);
54 if (SQ_FAILED(sq_call(
vm, 3, SQFalse, SQTrue))) {
62#if !(defined(EVENGINE_WEBGPU) && defined(__EMSCRIPTEN__))
64void nodeToJson(
const UIHost::Tree &tree,
const UINode &
n, Poco::JSON::Object &o);
65WidgetDesc descFromJson(
const Poco::JSON::Object &o);
69const char *kUIComponentScript = R
"SQ(
70eve.UIComponent <- class {
76 constructor(uiInstance = null) {
83 function setUI(uiInstance) { _ui = uiInstance }
85 function mountAs(name) {
92 function setState() { dirty = true }
93 function markDirty() { dirty = true }
95 // Override in subclass: call this.ui().beginWindow / text / button / end ...
99 if (_ui != null) return _ui
101 if (::ui != null) return ::ui
107 function updateIfDirty() {
108 if (!dirty) return false
112 local name = hostName
113 if (name == null || name == "") name = "default"
118 u.remountBuildAs(name)
124 function rebuild(force = false) {
127 return updateIfDirty()
130// Note: eve.Component is reserved for script ECS (see exposeECS). Use eve.UIComponent.
133void injectUIComponentClass(ssq::Table &eveTable) {
135 const SQInteger top = sq_gettop(
vm);
136 if (SQ_FAILED(sq_compilebuffer(
vm, kUIComponentScript,
137 static_cast<SQInteger
>(std::strlen(kUIComponentScript)),
138 "UIComponent.nut", SQTrue))) {
142 sq_pushroottable(
vm);
143 sq_call(
vm, 1, SQFalse, SQTrue);
158 if (backend_->isInitialized())
return true;
159 auto *win = eve::ModuleManager::getInstance<eve::window::Window>(
"Window");
160 auto *gfx = eve::ModuleManager::getInstance<eve::graphics::Graphics>(
"Graphics");
161 if (!win || !gfx)
return false;
163 if (!sdlWin)
return false;
164 auto *native =
static_cast<SDL_Window *
>(sdlWin->getHandle());
165 if (!native)
return false;
166 const bool ok = backend_->init(native, gfx);
172 if (backend_) backend_->shutdown();
176 if (backend_) backend_->processEvent(event);
184 if (inspector_ && inspector_->isOpen()) inspector_->sync();
185 if (databasePanel_ && databasePanel_->isOpen()) databasePanel_->sync();
186 backend_->newFrame();
190void UI::updateHostTweens() {
191 if (hostTweens_.empty())
return;
193 std::chrono::duration<double, std::milli>(std::chrono::steady_clock::now().time_since_epoch())
195 for (
auto &t : hostTweens_) {
196 if (!t.host)
continue;
197 auto m = t.host->meta();
198 const double elapsed = now - t.startMs;
199 if (t.durationMs <= 0.0 || elapsed >= t.durationMs) {
206 const float k = float(elapsed / t.durationMs);
207 const float ease = k * k * (3.f - 2.f * k);
209 m->posX = t.fromX + (t.toX - t.fromX) * ease;
210 m->posY = t.fromY + (t.toY - t.fromY) * ease;
212 hostTweens_.erase(std::remove_if(hostTweens_.begin(), hostTweens_.end(),
213 [](
const HostTween &t) {
return t.host ==
nullptr; }),
221 for (
const auto &ev :
events) {
222 if (!ev.host)
continue;
223 fireScriptHandlers(ev);
227void UI::fireScriptHandlers(
const UIEvent &ev) {
228 for (
const auto &
h : scriptHandlers_) {
230 if (
h.kind ==
"click" && ev.
kind ==
"click") {
231 ssq::Function
fn =
h.fn;
232 callScriptHandler(
fn,
"click", ev);
233 }
else if (
h.kind == ev.
kind &&
234 (ev.
kind ==
"toggle" || ev.
kind ==
"value" || ev.
kind ==
"text")) {
235 ssq::Function
fn =
h.fn;
236 callScriptHandler(
fn, ev.
kind, ev);
242 if (!selected_ ||
id.empty())
return;
243 scriptHandlers_.push_back(
244 ScriptHandler(selected_->
getName(),
id,
"click", std::move(
fn)));
248 if (!selected_ ||
id.empty())
return;
249 for (
const char *
kind : {
"toggle",
"value",
"text"}) {
250 scriptHandlers_.push_back(ScriptHandler(selected_->
getName(),
id,
kind,
fn));
255 return backend_ ? backend_->wantCaptureMouse() :
false;
259 return backend_ ? backend_->wantCaptureKeyboard() :
false;
270 if (!
h)
return false;
276 if (selected_) selected_->
setOwnerId(ownerId);
279UIHost *UI::ensureSelected(
const std::string &preferredName) {
280 if (selected_)
return selected_;
281 if (!preferredName.empty()) {
296 else h->setName(
name);
297 h->setTree(std::move(root));
304 selected_->
setTree(std::move(root));
307 return mountAs(
"default", std::move(root));
312 h->setTree(std::move(root));
318 h->setTreeReconcile(std::move(root));
328 hasBuiltRoot_ =
false;
332void UI::pushOpen(
WidgetDesc d) { openStack_.push_back(std::move(
d)); }
334WidgetDesc &UI::currentParent() {
335 if (openStack_.empty())
throw std::runtime_error(
"ui: widget outside beginWindow/beginGroup");
336 return openStack_.back();
340 if (openStack_.empty() && hasBuiltRoot_)
beginBuild();
362std::string toLowerCopy(std::string
s) {
364 if (
c >=
'A' &&
c <=
'Z')
c = char(
c -
'A' +
'a');
369FlexDirection parseFlexDirection(
const std::string &direction) {
370 const std::string
d = toLowerCopy(direction);
375FlexAlign parseFlexAlign(
const std::string &align) {
376 const std::string
a = toLowerCopy(align);
383FlexJustify parseFlexJustify(
const std::string &justify) {
384 const std::string j = toLowerCopy(justify);
387 if (j ==
"spacebetween" || j ==
"space-between" || j ==
"between")
395void UI::beginFlex(
const std::string &direction,
const std::string &
id,
float gap) {
398 pushOpen(std::move(
d));
406 if (openStack_.empty())
throw std::runtime_error(
"ui: end() without begin");
407 WidgetDesc finished = std::move(openStack_.back());
408 openStack_.pop_back();
409 if (openStack_.empty()) {
410 builtRoot_ = std::move(finished);
411 hasBuiltRoot_ =
true;
413 openStack_.back().children.push_back(std::move(finished));
417void UI::addText(
const std::string &content,
const std::string &
id) {
424 currentParent().
children.push_back(std::move(
d));
442 const std::string &
id) {
446void UI::addProgress(
float fraction,
const std::string &
id,
const std::string &overlay) {
462void UI::addCombo(
const std::string &label,
const std::string &options,
int selected,
463 const std::string &
id) {
464 std::vector<std::string> items;
466 while (start <= options.size()) {
467 const size_t end = options.find(
'\n', start);
468 items.push_back(options.substr(start,
end == std::string::npos ? std::string::npos
470 if (
end == std::string::npos)
break;
473 currentParent().
children.push_back(
combo(label, items, selected,
id));
486 if (
parent.children.empty())
return;
487 parent.children.back().flexGrow = grow;
492 if (
parent.children.empty())
return;
499 if (
parent.children.empty())
return;
500 parent.children.back().marginL = l;
501 parent.children.back().marginT = t;
502 parent.children.back().marginR = r;
503 parent.children.back().marginB =
b;
508 if (
parent.children.empty())
return;
509 parent.children.back().paddingL = l;
510 parent.children.back().paddingT = t;
511 parent.children.back().paddingR = r;
512 parent.children.back().paddingB =
b;
517 if (
parent.children.empty())
return;
518 parent.children.back().minSizeX =
w;
519 parent.children.back().minSizeY =
h;
524 if (
parent.children.empty())
return;
525 parent.children.back().maxSizeX =
w;
526 parent.children.back().maxSizeY =
h;
531 if (
parent.children.empty())
return;
532 parent.children.back().percentW =
w;
533 parent.children.back().percentH =
h;
538 if (
parent.children.empty())
return;
539 parent.children.back().absolute =
true;
540 parent.children.back().anchorX = anchorX;
541 parent.children.back().anchorY = anchorY;
542 parent.children.back().posX =
x;
543 parent.children.back().posY =
y;
549 parent.alignItems = parseFlexAlign(align);
555 parent.justifyContent = parseFlexJustify(justify);
560 std::string itemId =
id;
562 itemId =
parent.id +
"/" + std::to_string(
parent.children.size());
563 parent.children.push_back(
button(label, itemId).withKey(itemId));
566bool UI::buildComplete()
const {
return openStack_.empty() && hasBuiltRoot_; }
569 if (!buildComplete())
return false;
570 remount(std::move(builtRoot_));
571 hasBuiltRoot_ =
false;
577 if (!buildComplete())
return false;
579 hasBuiltRoot_ =
false;
585 if (!buildComplete())
return false;
588 h->setTreeReconcile(std::move(builtRoot_));
590 hasBuiltRoot_ =
false;
596 if (!selected_)
return false;
598 auto *existing = selected_->
findById(listId);
602 {std::move(listNode)},
"root"));
616 if (!selected_)
return;
637 if (!selected_)
return;
646void UI::setImageUv(
const std::string &
id,
float u0,
float v0,
float u1,
float v1) {
647 if (!selected_)
return;
657 if (!selected_)
return;
667 if (!selected_)
return;
668 if (
auto *
n = selected_->
findById(
id))
n->cornerRadius = radius;
672 if (!selected_)
return;
673 if (
auto *
n = selected_->
findById(
id))
n->textureId = textureId;
680 return backend_ ? backend_->registerTexture(tex) : 0;
684 if (!selected_)
return 0.f;
685 if (
auto *
n = selected_->
findById(
id))
return n->value;
690 if (!selected_)
return {};
691 if (
auto *
n = selected_->
findById(
id))
return n->valueText;
696 if (!selected_)
return false;
697 if (
auto *
n = selected_->
findById(
id))
return n->checked;
702 if (selected_) selected_->
setVisible(visible);
710 if (selected_) selected_->
setModal(modal);
714 if (selected_) selected_->meta()->overlay = overlay;
718 if (!selected_)
return;
719 auto m = selected_->meta();
728 if (!selected_)
return;
729 auto m = selected_->meta();
735 if (!selected_)
return;
736 auto m = selected_->meta();
743 if (!selected_)
return;
744 auto m = selected_->meta();
750 if (!selected_)
return;
751 auto m = selected_->meta();
754 t.fromX =
m->hasPos ?
m->posX : 0.f;
755 t.fromY =
m->hasPos ?
m->posY : 0.f;
759 std::chrono::duration<double, std::milli>(std::chrono::steady_clock::now().time_since_epoch())
761 t.durationMs = std::max(0.0,
double(durationMs));
763 hostTweens_.push_back(t);
790 if (backend_) backend_->setScale(
scale);
795 return backend_ ? backend_->getScale() : 1.f;
801 std::snprintf(buf,
sizeof(buf),
"hosts=%d nodes=%d measureMs=%.3f walkMs=%.3f",
s.hostCount,
802 s.nodeCount,
s.measureMs,
s.walkMs);
806#if !(defined(EVENGINE_WEBGPU) && defined(__EMSCRIPTEN__))
808 if (!selected_)
return "{}";
809 auto t = selected_->tree();
810 Poco::JSON::Object root;
811 root.set(
"host", selected_->
getName());
812 if (t->root >= 0) nodeToJson(*t, t->nodes[
size_t(t->root)], root);
813 std::ostringstream oss;
814 Poco::JSON::Stringifier::stringify(root, oss, 1);
819 if (!selected_ || json.empty())
return false;
821 Poco::JSON::Parser parser;
822 const Poco::Dynamic::Var result = parser.parse(json);
823 const Poco::JSON::Object::Ptr obj = result.extract<Poco::JSON::Object::Ptr>();
824 if (!obj)
return false;
826 selected_->
setTree(std::move(root));
839 if (!selected_ ||
id.empty())
return nullptr;
840 const std::string key = selected_->
getName() +
"/" +
id;
843 return vs ? vs->
canvas :
nullptr;
847 if (!selected_)
return false;
853 if (!selected_)
return false;
859 if (!selected_)
return 0.f;
865 if (!selected_)
return 0.f;
871 if (!selected_)
return 0.f;
877 if (!selected_)
return 0.f;
883 if (!selected_)
return 0.f;
888#if !(defined(EVENGINE_WEBGPU) && defined(__EMSCRIPTEN__))
891const char *nodeTypeName(
NodeType t) {
914NodeType nodeTypeFromName(
const std::string &
s) {
934void nodeToJson(
const UIHost::Tree &tree,
const UINode &
n, Poco::JSON::Object &o) {
935 o.set(
"type", nodeTypeName(
n.type));
936 if (!
n.id.empty()) o.set(
"id",
n.id);
937 if (!
n.key.empty()) o.set(
"key",
n.key);
938 if (!
n.text.empty()) o.set(
"text",
n.text);
939 if (!
n.valueText.empty()) o.set(
"valueText",
n.valueText);
940 if (!
n.visible) o.set(
"visible",
false);
941 if (
n.checked) o.set(
"checked",
true);
942 if (!
n.open) o.set(
"open",
false);
943 if (
n.value != 0.f) o.set(
"value",
n.value);
944 if (
n.minValue != 0.f) o.set(
"minValue",
n.minValue);
945 if (
n.maxValue != 1.f) o.set(
"maxValue",
n.maxValue);
946 if (
n.sizeX != 0.f) o.set(
"sizeX",
n.sizeX);
947 if (
n.sizeY != 0.f) o.set(
"sizeY",
n.sizeY);
948 if (
n.marginL != 0.f ||
n.marginT != 0.f ||
n.marginR != 0.f ||
n.marginB != 0.f)
949 o.set(
"margin", Poco::Dynamic::Array({
n.marginL,
n.marginT,
n.marginR,
n.marginB}));
950 if (
n.paddingL != 0.f ||
n.paddingT != 0.f ||
n.paddingR != 0.f ||
n.paddingB != 0.f)
951 o.set(
"padding", Poco::Dynamic::Array({
n.paddingL,
n.paddingT,
n.paddingR,
n.paddingB}));
952 if (
n.minSizeX != 0.f) o.set(
"minSizeX",
n.minSizeX);
953 if (
n.minSizeY != 0.f) o.set(
"minSizeY",
n.minSizeY);
954 if (
n.maxSizeX != 0.f) o.set(
"maxSizeX",
n.maxSizeX);
955 if (
n.maxSizeY != 0.f) o.set(
"maxSizeY",
n.maxSizeY);
956 if (
n.percentW != 0.f) o.set(
"percentW",
n.percentW);
957 if (
n.percentH != 0.f) o.set(
"percentH",
n.percentH);
959 o.set(
"absolute",
true);
960 o.set(
"anchorX",
n.anchorX);
961 o.set(
"anchorY",
n.anchorY);
962 o.set(
"posX",
n.posX);
963 o.set(
"posY",
n.posY);
965 if (
n.wrapWidth != 0.f) o.set(
"wrapWidth",
n.wrapWidth);
970 if (
n.gap >= 0.f) o.set(
"gap",
n.gap);
971 if (
n.flexGrow != 0.f) o.set(
"flexGrow",
n.flexGrow);
972 if (
n.tintR != 1.f ||
n.tintG != 1.f ||
n.tintB != 1.f ||
n.tintA != 1.f)
973 o.set(
"tint", Poco::Dynamic::Array({
n.tintR,
n.tintG,
n.tintB,
n.tintA}));
974 if (
n.borderL != 0.f ||
n.borderT != 0.f ||
n.borderR != 0.f ||
n.borderB != 0.f)
975 o.set(
"border", Poco::Dynamic::Array({
n.borderL,
n.borderT,
n.borderR,
n.borderB}));
976 if (
n.cornerRadius != 0.f) o.set(
"cornerRadius",
n.cornerRadius);
977 if (
n.uv0x != 0.f ||
n.uv0y != 0.f ||
n.uv1x != 1.f ||
n.uv1y != 1.f)
978 o.set(
"uv", Poco::Dynamic::Array({
n.uv0x,
n.uv0y,
n.uv1x,
n.uv1y}));
981 for (
int c =
n.firstChild;
c >= 0;
c = tree.nodes[size_t(
c)].nextSibling) {
982 Poco::JSON::Object
child;
983 nodeToJson(tree, tree.nodes[
size_t(
c)],
child);
989float fnum(
const Poco::Dynamic::Var &
v,
float def = 0.f) {
991 return float(
v.convert<
double>());
997void applyCommonFields(WidgetDesc &
d,
const Poco::JSON::Object &o) {
998 if (o.has(
"id"))
d.id = o.getValue<std::string>(
"id");
999 if (o.has(
"key"))
d.key = o.getValue<std::string>(
"key");
1000 if (o.has(
"text"))
d.text = o.getValue<std::string>(
"text");
1001 if (o.has(
"valueText"))
d.valueText = o.getValue<std::string>(
"valueText");
1002 if (o.has(
"visible"))
d.visible = o.getValue<
bool>(
"visible");
1003 if (o.has(
"checked"))
d.checked = o.getValue<
bool>(
"checked");
1004 if (o.has(
"open"))
d.open = o.getValue<
bool>(
"open");
1005 if (o.has(
"value"))
d.value = fnum(o.get(
"value"));
1006 if (o.has(
"minValue"))
d.minValue = fnum(o.get(
"minValue"));
1007 if (o.has(
"maxValue"))
d.maxValue = fnum(o.get(
"maxValue"));
1008 if (o.has(
"sizeX"))
d.sizeX = fnum(o.get(
"sizeX"));
1009 if (o.has(
"sizeY"))
d.sizeY = fnum(o.get(
"sizeY"));
1010 if (o.has(
"margin")) {
1011 const Poco::Dynamic::Array
a = o.get(
"margin").extract<Poco::Dynamic::Array>();
1012 if (
a.size() >= 4) {
1013 d.marginL = fnum(
a[0]);
1014 d.marginT = fnum(
a[1]);
1015 d.marginR = fnum(
a[2]);
1016 d.marginB = fnum(
a[3]);
1019 if (o.has(
"padding")) {
1020 const Poco::Dynamic::Array
a = o.get(
"padding").extract<Poco::Dynamic::Array>();
1021 if (
a.size() >= 4) {
1022 d.paddingL = fnum(
a[0]);
1023 d.paddingT = fnum(
a[1]);
1024 d.paddingR = fnum(
a[2]);
1025 d.paddingB = fnum(
a[3]);
1028 if (o.has(
"minSizeX"))
d.minSizeX = fnum(o.get(
"minSizeX"));
1029 if (o.has(
"minSizeY"))
d.minSizeY = fnum(o.get(
"minSizeY"));
1030 if (o.has(
"maxSizeX"))
d.maxSizeX = fnum(o.get(
"maxSizeX"));
1031 if (o.has(
"maxSizeY"))
d.maxSizeY = fnum(o.get(
"maxSizeY"));
1032 if (o.has(
"percentW"))
d.percentW = fnum(o.get(
"percentW"));
1033 if (o.has(
"percentH"))
d.percentH = fnum(o.get(
"percentH"));
1034 if (o.has(
"absolute"))
d.absolute = o.getValue<
bool>(
"absolute");
1035 if (o.has(
"anchorX"))
d.anchorX = fnum(o.get(
"anchorX"));
1036 if (o.has(
"anchorY"))
d.anchorY = fnum(o.get(
"anchorY"));
1037 if (o.has(
"posX"))
d.posX = fnum(o.get(
"posX"));
1038 if (o.has(
"posY"))
d.posY = fnum(o.get(
"posY"));
1039 if (o.has(
"wrapWidth"))
d.wrapWidth = fnum(o.get(
"wrapWidth"));
1040 if (o.has(
"flexDirection"))
d.flexDirection =
1043 if (o.has(
"alignItems"))
d.alignItems =
FlexAlign(
int(fnum(o.get(
"alignItems"))));
1044 if (o.has(
"justifyContent"))
1045 d.justifyContent =
FlexJustify(
int(fnum(o.get(
"justifyContent"))));
1046 if (o.has(
"gap"))
d.gap = fnum(o.get(
"gap"), -1.f);
1047 if (o.has(
"flexGrow"))
d.flexGrow = fnum(o.get(
"flexGrow"));
1048 if (o.has(
"tint")) {
1049 const Poco::Dynamic::Array
a = o.get(
"tint").extract<Poco::Dynamic::Array>();
1050 if (
a.size() >= 4) {
1051 d.tintR = fnum(
a[0], 1.f);
1052 d.tintG = fnum(
a[1], 1.f);
1053 d.tintB = fnum(
a[2], 1.f);
1054 d.tintA = fnum(
a[3], 1.f);
1057 if (o.has(
"border")) {
1058 const Poco::Dynamic::Array
a = o.get(
"border").extract<Poco::Dynamic::Array>();
1059 if (
a.size() >= 4) {
1060 d.borderL = fnum(
a[0]);
1061 d.borderT = fnum(
a[1]);
1062 d.borderR = fnum(
a[2]);
1063 d.borderB = fnum(
a[3]);
1066 if (o.has(
"cornerRadius"))
d.cornerRadius = fnum(o.get(
"cornerRadius"));
1068 const Poco::Dynamic::Array
a = o.get(
"uv").extract<Poco::Dynamic::Array>();
1069 if (
a.size() >= 4) {
1070 d.uv0x = fnum(
a[0]);
1071 d.uv0y = fnum(
a[1]);
1072 d.uv1x = fnum(
a[2], 1.f);
1073 d.uv1y = fnum(
a[3], 1.f);
1078WidgetDesc descFromJson(
const Poco::JSON::Object &o) {
1080 d.type = nodeTypeFromName(o.optValue<std::string>(
"type",
"text"));
1081 applyCommonFields(
d, o);
1082 if (o.has(
"children")) {
1083 const Poco::JSON::Array::Ptr
children = o.getArray(
"children");
1084 for (
size_t i = 0; i <
children->size(); ++i) {
1085 const Poco::JSON::Object::Ptr
child =
children->getObject(i);
1096 const std::string &buttonText) {
1101 if (!inspector_) inspector_ = std::make_unique<Inspector>();
1102 inspector_->setPickScene([
this]() {
return callPickHandler(); });
1104 return inspector_->isOpen();
1108 if (inspector_) inspector_->close();
1112 if (!inspector_) inspector_ = std::make_unique<Inspector>();
1113 inspector_->refresh();
1114 return inspector_->instanceCount() > 0;
1118 if (!inspector_) inspector_ = std::make_unique<Inspector>();
1119 inspector_->setPickScene([
this]() {
return callPickHandler(); });
1121 return inspector_->selectClass(
name);
1125 if (!inspector_) inspector_ = std::make_unique<Inspector>();
1126 inspector_->setPickScene([
this]() {
return callPickHandler(); });
1128 return inspector_->inspectObject(
object);
1133 if (!rt)
return false;
1135 const SQInteger top = sq_gettop(squirrel);
1136 sq_pushroottable(squirrel);
1137 sq_pushstring(squirrel,
"eve", -1);
1138 if (SQ_FAILED(sq_get(squirrel, -2)) ||
1139 sq_gettype(squirrel, -1) != OT_TABLE) {
1140 sq_settop(squirrel, top);
1143 sq_pushstring(squirrel,
"_inspectorPickHandler", -1);
1144 sq_pushobject(squirrel,
fn.getRaw());
1145 sq_newslot(squirrel, -3, SQFalse);
1146 sq_settop(squirrel, top);
1150ssq::Object UI::callPickHandler() {
1154 const SQInteger top = sq_gettop(squirrel);
1155 sq_pushroottable(squirrel);
1156 sq_pushstring(squirrel,
"eve", -1);
1157 if (SQ_FAILED(sq_get(squirrel, -2)) ||
1158 sq_gettype(squirrel, -1) != OT_TABLE) {
1159 sq_settop(squirrel, top);
1162 sq_pushstring(squirrel,
"_inspectorPickHandler", -1);
1163 if (SQ_FAILED(sq_get(squirrel, -2)) ||
1164 (sq_gettype(squirrel, -1) != OT_CLOSURE &&
1165 sq_gettype(squirrel, -1) != OT_NATIVECLOSURE)) {
1166 sq_settop(squirrel, top);
1169 sq_pushroottable(squirrel);
1170 if (SQ_FAILED(sq_call(squirrel, 1, SQTrue, SQTrue))) {
1171 sq_settop(squirrel, top);
1174 if (sq_gettype(squirrel, -1) != OT_INSTANCE) {
1175 sq_settop(squirrel, top);
1178 ssq::Object out(squirrel);
1179 sq_getstackobj(squirrel, -1, &out.getRaw());
1180 sq_addref(squirrel, &out.getRaw());
1181 sq_settop(squirrel, top);
1186 if (!inspector_) inspector_ = std::make_unique<Inspector>();
1187 inspector_->setPickScene([
this]() {
return callPickHandler(); });
1189 const ssq::Object picked = callPickHandler();
1190 if (picked.getType() != ssq::Type::INSTANCE)
return false;
1191 return inspector_->inspectObject(picked);
1195 return inspector_ && inspector_->addInstance();
1199 if (!databasePanel_) databasePanel_ = std::make_unique<DatabasePanel>();
1200 databasePanel_->open();
1201 return databasePanel_->isOpen();
1205 if (databasePanel_) databasePanel_->close();
1209 if (!databasePanel_) databasePanel_ = std::make_unique<DatabasePanel>();
1210 databasePanel_->refresh();
1211 return databasePanel_->isOpen();
1215 if (!databasePanel_) databasePanel_ = std::make_unique<DatabasePanel>();
1216 databasePanel_->refresh();
1217 return databasePanel_->selectClass(
name);
1221 if (!databasePanel_) databasePanel_ = std::make_unique<DatabasePanel>();
1222 databasePanel_->open();
1223 return databasePanel_->registerObject(
object, label);
1227 if (!databasePanel_) databasePanel_ = std::make_unique<DatabasePanel>();
1228 databasePanel_->open();
1229 return databasePanel_->createInstance();
1233 return databasePanel_ && databasePanel_->unregister(
id);
1237 if (!inspector_) inspector_ = std::make_unique<Inspector>();
1238 inspector_->setPickScene([
this]() {
return callPickHandler(); });
1240 if (!databasePanel_) databasePanel_ = std::make_unique<DatabasePanel>();
1241 databasePanel_->open();
1242 if (!scenePanel_) scenePanel_ = std::make_unique<ScenePanel>();
1243 scenePanel_->setPickHandler([
this](
const std::string &nodeId) {
1244 callScenePickHandler(nodeId);
1246 scenePanel_->open();
1247 if (!editorShell_) editorShell_ = std::make_unique<EditorShell>();
1248 editorShell_->open(inspector_->host(), databasePanel_->host(),
1249 scenePanel_->host());
1250 editorShell_->selectPanel(
"inspector");
1251 return editorShell_->isOpen();
1255 if (editorShell_) editorShell_->close();
1259 return editorShell_ && editorShell_->selectPanel(
name);
1263 if (!scenePanel_) scenePanel_ = std::make_unique<ScenePanel>();
1264 scenePanel_->setPickHandler([
this](
const std::string &nodeId) {
1265 callScenePickHandler(nodeId);
1267 scenePanel_->open();
1268 return scenePanel_->isOpen();
1272 if (scenePanel_) scenePanel_->close();
1276 return scenePanel_ && scenePanel_->selectNode(
id);
1281 if (!rt)
return false;
1283 const SQInteger top = sq_gettop(squirrel);
1284 sq_pushroottable(squirrel);
1285 sq_pushstring(squirrel,
"eve", -1);
1286 if (SQ_FAILED(sq_get(squirrel, -2)) ||
1287 sq_gettype(squirrel, -1) != OT_TABLE) {
1288 sq_settop(squirrel, top);
1291 sq_pushstring(squirrel,
"_scenePickHandler", -1);
1292 sq_pushobject(squirrel,
fn.getRaw());
1293 sq_newslot(squirrel, -3, SQFalse);
1294 sq_settop(squirrel, top);
1298void UI::callScenePickHandler(
const std::string &nodeId) {
1302 const SQInteger top = sq_gettop(squirrel);
1303 sq_pushroottable(squirrel);
1304 sq_pushstring(squirrel,
"eve", -1);
1305 if (SQ_FAILED(sq_get(squirrel, -2)) ||
1306 sq_gettype(squirrel, -1) != OT_TABLE) {
1307 sq_settop(squirrel, top);
1310 sq_pushstring(squirrel,
"_scenePickHandler", -1);
1311 if (SQ_FAILED(sq_get(squirrel, -2)) ||
1312 (sq_gettype(squirrel, -1) != OT_CLOSURE &&
1313 sq_gettype(squirrel, -1) != OT_NATIVECLOSURE)) {
1314 sq_settop(squirrel, top);
1317 sq_pushroottable(squirrel);
1318 sq_pushstring(squirrel, nodeId.c_str(), -1);
1319 sq_call(squirrel, 2, SQFalse, SQTrue);
1320 sq_settop(squirrel, top);
1323void UI::expose(ssq::Table &table) {
1324 auto cls = table.addClass(
name, UI::create,
false);
1326 injectUIComponentClass(table);
1329void UI::expose(ssq::Class &
cls) {
struct SQVM * HSQUIRRELVM
std::vector< HostEvent > events
#define Module_IMPL(ModuleName, newExpr)
SettlementPipeline::Stage fn
static Runtime * runtime()
Active runtime associated with the last expose() call, or nullptr.
virtual std::string getName() const =0
HSQUIRRELVM handle() const noexcept
Raw Squirrel VM handle; nullptr after shutdown.
GPU texture created via Graphics::newTexture. Owns GPU resources through an opaque backend handle.
ECS mount point for one UI panel/screen. Subclass to attach UI to game entities, e....
bool setTreeReconcile(WidgetDesc root)
Key-aware patch when structure matches; else full replace.
static UIHost * createHost(const std::string &name="")
void setTree(WidgetDesc root)
Full replace.
void setCheckedById(const std::string &id, bool checked)
void setValueById(const std::string &id, float value)
void setOwnerId(uint32_t id)
Attaches the host to an owner id (scene/UI ownership).
const std::string & getName()
void setVisible(bool v)
Host visibility / layer / modality.
void setModal(bool modal)
void setVisibleById(const std::string &id, bool visible)
UINode * findById(const std::string &id)
Looks up a node by id or reconciliation key.
void setValueTextById(const std::string &id, const std::string &value)
void setTextById(const std::string &id, const std::string &text)
Widget state updates by node id.
static ViewportState * ensureViewport(const std::string &key, int w, int h)
static const UIStats & stats()
static void setBackend(UIBackend *backend)
static std::string consumeChange()
Pop next change as "name/node"; empty if none.
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::vector< UIEvent > & pendingEvents()
static ViewportState * viewportState(const std::string &hostName, const std::string &nodeId)
Declarative UI module (eve.UI).
UIHost * findHost(const std::string &name) const
Finds a host by name, or nullptr.
bool inspectSelectClass(const std::string &name)
Selects a class in the inspector (creates its first instance).
void setTextWrap(const std::string &id, float width)
bool mountBuild()
Finishes the build pass and mounts the built tree.
void addCombo(const std::string &label, const std::string &options, int selected, const std::string &id="")
float viewportMouseX(const std::string &id)
bool wantCaptureKeyboard() const
True when the UI wants to capture keyboard input this frame.
uint64_t registerTexture(graphics::Texture *tex)
std::string consumeClick()
Returns the id of the clicked widget since the last frame (or "").
bool initBackend()
Creates the platform UI backend (ImGui); true on success.
void setHostPercent(float w, float h)
void setImageTextureId(const std::string &id, uint64_t textureId)
void setHostPos(float x, float y, float pivotX=0.f, float pivotY=0.f)
Positions the host window with a pivot (0..1 each axis).
std::string saveTreeJson() const
bool editorSelectPanel(const std::string &name)
Shows one docked panel ("inspector"/"database"; "" hides all).
bool inspectPickScene()
Calls the pick handler and inspects the returned object.
void dispatchEvents()
Dispatches queued widget callbacks (click/toggle/value/text).
void setItemPercent(float w, float h)
void bindOwner(uint32_t ownerId)
Binds the selected host to a UI/scene owner id.
void addText(const std::string &content, const std::string &id="")
Adds a text label to the current container.
void setImageCornerRadius(const std::string &id, float radius)
void processEvent(const SDL_Event *event)
Feeds an SDL event into the UI backend (before window/game handling).
void setHostSize(float w, float h)
bool inspectOpen()
Opens the auto-generated inspector (scans reflected classes).
bool sceneOpen()
Opens the scene panel (tree + selected node properties).
bool sceneSetPickHandler(ssq::Function fn)
Registers the script callback for the scene panel Pick button. The callback (stored as eve....
void editorClose()
Closes the editor shell (and its docked panels).
void shutdownBackend()
Destroys the platform UI backend.
float viewportDragDX(const std::string &id)
UIHost * findHostByOwner(uint32_t ownerId) const
Finds the host bound to an owner id, or nullptr.
float getValue(const std::string &id) const
UIHost * mount(WidgetDesc root)
Mounts the tree as an auto-named host and selects it.
void addSpacer(const std::string &id="", float grow=1.f)
Flexible empty space inside Flex (default grow=1).
void addImage(const std::string &id="", float width=0.f, float height=0.f)
bool viewportActive(const std::string &id)
void setNavKeyboard(bool enabled)
Enables/disables keyboard navigation support.
void setItemMaxSize(float w, float h)
bool setListItems(const std::string &listId, const std::vector< std::string > &items)
Replace children of a Group listId with buttons for each label (reconcile). Host must already exist a...
void setItemPadding(float l, float t, float r, float b)
void setItemFlexGrow(float grow)
Set flex item props on the most recently added child of the current open container....
void beginWindow(const std::string &title, const std::string &id="root")
Opens a window for the current build pass.
void end()
Closes the innermost open container.
bool remountBuildAs(const std::string &name)
Like mountBuildAs but reconciles by key when possible.
void sceneClose()
Closes the scene panel.
void beginFlex(const std::string &direction="row", const std::string &id="", float gap=-1.f)
Begin a Flex container.
void addInputText(const std::string &label, const std::string &value, const std::string &id="")
Adds an editable text field.
bool editorOpen()
Opens the menu bar and docks the inspector + database panels.
void addViewport(const std::string &id, float width=0.f, float height=0.f)
void addImageButton(const std::string &id, float width, float height)
std::string getStats() const
void beginGroup(const std::string &id="")
Opens a group container in the current build pass.
void onClick(const std::string &id, ssq::Function fn)
void beginCollapsing(const std::string &label, const std::string &id="", bool open=true)
Opens a collapsible header.
bool setTheme(const std::string &name)
Named preset: "dark" / "light" (case-insensitive). Returns false if unknown.
void setHostLayer(int layer)
void setItemAbsolute(float anchorX, float anchorY, float x=0.f, float y=0.f)
UIHost * remountAs(const std::string &name, WidgetDesc root)
Creates/replaces a named host (does not select it).
void beginFrameAndRender()
Builds and presents the current frame's UI.
bool select(const std::string &name)
Selects a named host; false when it does not exist.
float getScale() const
Current UI scale factor.
void setItemMargin(float l, float t, float r, float b)
bool viewportHovered(const std::string &id)
float viewportWheel(const std::string &id)
void setScale(float scale)
Global UI scale factor (default 1).
void beginColumn(const std::string &id="", float gap=-1.f)
Opens a column flex container.
void setNavGamepad(bool enabled)
void setItemSize(float width, float height)
Sets width/height on the most recently added child.
void setHostModal(bool modal)
Marks the host as a modal (blocks other hosts) / overlay.
void addCheckbox(const std::string &label, bool checked, const std::string &id="")
Adds a checkbox.
bool inspectRefresh()
Re-scans script classes; true when any class is reflected.
bool dbSelectClass(const std::string &name)
Selects the class shown in the grid.
bool isBackendReady() const
True once the backend exists.
void beginBuild()
Imperative builder: open a new build pass (see beginWindow etc.).
bool dbRefresh()
Re-scans reflected classes; true when any class exists.
void addSlider(const std::string &label, float value, float minV, float maxV, const std::string &id="")
Adds a slider.
void setFlexAlign(const std::string &align)
Set Flex container align/justify on the current open Flex (no-op otherwise).
void addProgress(float fraction, const std::string &id="", const std::string &overlay="")
Adds a progress bar.
bool getChecked(const std::string &id) const
uint64_t dbRegister(ssq::Object object, const std::string &label)
Registers a live script object in the database grid.
bool sceneSelectNode(const std::string &id)
Selects a scene node by id.
void setHostVisible(bool visible)
Host-level state.
graphics::Canvas * viewportCanvas(const std::string &id)
uint64_t dbCreateInstance()
Creates + registers an instance of the selected class.
void setImageUv(const std::string &id, float u0, float v0, float u1, float v1)
void addTextWrapped(const std::string &content, float width, const std::string &id="")
void setText(const std::string &id, const std::string &text)
Widget state setters/getters on the current host (by node id).
void beginList(const std::string &id)
Opens a list container (rows added with addListItem).
void addSeparator(const std::string &id="")
Adds a separator line.
void beginChild(const std::string &id, float width=0.f, float height=120.f)
Opens a sized child region.
void setHostAnchor(float x, float y)
void setImageNinePatch(const std::string &id, float l, float t, float r, float b)
void addSameLine(const std::string &id="")
Adds an inline-break spacer.
void setImageTint(const std::string &id, float r, float g, float b, float a=1.f)
void setThemeDark()
Applies the dark/light built-in theme.
void onChange(const std::string &id, ssq::Function fn)
UIHost * mountAs(const std::string &name, WidgetDesc root)
Creates/replaces a named host from a WidgetDesc tree and selects it.
bool inspectObject(ssq::Object object)
Inspects a caller-provided live script instance.
std::string consumeChange()
Returns the id of the changed widget since the last frame (or "").
bool dbUnregister(uint64_t id)
Removes an entry from the database grid.
bool inspectAddInstance()
Creates another instance of the selected inspector class.
void setItemMinSize(float w, float h)
void beginScrollList(const std::string &id="", float height=0.f, float itemHeight=0.f)
void animateHostPos(float x, float y, float durationMs)
void setHostOverlay(bool overlay)
void setValueText(const std::string &id, const std::string &value)
void mountSimple(const std::string &title, const std::string &labelText, const std::string &buttonText)
One-shot convenience: a window with a label and a button.
void beginRow(const std::string &id="", float gap=-1.f)
Opens a row flex container.
bool wantCaptureMouse() const
True when the UI wants to capture mouse input this frame.
void addButton(const std::string &label, const std::string &id="")
Adds a button to the current container.
bool mountBuildAs(const std::string &name)
Finishes the build pass and mounts as a named host.
float viewportDragDY(const std::string &id)
bool inspectSetPickHandler(ssq::Function fn)
Registers the script callback used by the inspector Pick button. The callback is stored on the script...
void setFlexJustify(const std::string &justify)
Sets Flex container justify on the current open Flex.
bool dbOpen()
Opens the database panel (class menu + editable instance grid).
std::string getValueText(const std::string &id) const
void setValue(const std::string &id, float value)
void addListItem(const std::string &label, const std::string &id="")
Append one list row button (call inside beginList).
void inspectClose()
Closes the inspector panel.
UIHost * remountReconcile(WidgetDesc root)
Remount with key reconcile (props-only when structure matches).
void setVisible(const std::string &id, bool visible)
std::string getTheme() const
Name of the active theme.
void dbClose()
Closes the database panel.
void setChecked(const std::string &id, bool checked)
bool loadTreeJson(const std::string &json)
UIHost * remount(WidgetDesc root)
Replaces the selected host's tree.
float viewportMouseY(const std::string &id)
WidgetDesc spacer(std::string id, float grow)
Flexible empty space; default flexGrow=1 so it absorbs free space in a Flex parent.
void registerEditorHostCapabilities()
WidgetDesc slider(std::string label, float value, float minV, float maxV, std::string id, std::function< void(float)> onValue)
Horizontal slider; fires onValue.
WidgetDesc text(std::string content, std::string id)
Static text label.
WidgetDesc scrollList(std::string id, std::vector< WidgetDesc > children, float height, float itemHeight)
WidgetDesc progress(float fraction, std::string id, std::string overlay)
Progress bar; fraction is clamped to [0,1].
FlexDirection
Main-axis direction for Flex containers.
WidgetDesc checkbox(std::string label, bool checked, std::string id, std::function< void(bool)> onToggle)
Checkbox with a label; fires onToggle.
std::unique_ptr< UIBackend > createImGuiBackend()
Default backend: Dear ImGui + SDL + Vulkan (see ui/imgui/).
WidgetDesc separator(std::string id)
Horizontal separator line.
bool setThemeByName(const std::string &name)
Apply a named preset ("dark" / "light"). Case-insensitive. Returns false if unknown.
NodeType
Widget node kinds understood by the UI renderer.
WidgetDesc combo(std::string label, std::vector< std::string > options, int selected, std::string id, std::function< void(int)> onValue)
WidgetDesc inputText(std::string label, std::string value, std::string id, std::function< void(const std::string &)> onChange)
Editable text field; fires onTextChange.
WidgetDesc listButtons(std::string listId, const std::vector< std::string > &items)
Default list: one Button per item, id = listId + "/" + index.
void setThemeUiScale(float scale)
Logical (point-space) UI scale. Default 1.0.
FlexJustify
Main-axis distribution of free space in a Flex container.
WidgetDesc flex(FlexDirection direction, std::vector< WidgetDesc > children, std::string id)
Elastic layout container (row/column). Prefer row / column shorthands.
WidgetDesc imageButton(std::string id, float width, float height, std::function< void()> onClick)
WidgetDesc window(std::string title, std::vector< WidgetDesc > children, std::string id)
Top-level window widget with a title bar.
WidgetDesc collapsingHeader(std::string label, std::vector< WidgetDesc > children, std::string id, bool defaultOpen)
Collapsible header containing child widgets.
WidgetDesc sameLine(std::string id)
Holds the next widget on the same line as the previous one.
WidgetDesc button(std::string label, std::string id, std::function< void()> onClick)
Clickable button; fires onClick.
const std::string & globalThemeName()
Current preset name: "dark", "light", or "custom".
FlexAlign
Cross-axis alignment of Flex children.
WidgetDesc viewport(std::string id, float width, float height)
WidgetDesc child(std::string id, std::vector< WidgetDesc > children, float width, float height)
Scrollable child region with an explicit size.
graphics::Canvas * canvas