3#include "common/config.h"
5#if defined(EVENGINE_WEBGPU) && defined(__EMSCRIPTEN__)
10struct EditorHost::Impl {};
16EditorHost::EditorHost() =
default;
22 return "error: editor host unavailable on this platform";
27 return "error: editor host unavailable on this platform";
30 return "error: editor host unavailable on this platform";
36 return "error: editor host unavailable on this platform";
40 return "{\"x\":0,\"y\":0,\"width\":0,\"height\":0}";
43 return "error: editor host unavailable on this platform";
46 return "error: editor host unavailable on this platform";
49 return "error: editor host unavailable on this platform";
52 return "error: editor host unavailable on this platform";
56 return "error: editor host unavailable on this platform";
59 return "error: editor host unavailable on this platform";
62 return "{\"running\":false,\"windowOpen\":false,\"editors\":[],\"viewModels\":[]}";
85#include <Poco/Dynamic/Var.h>
86#include <Poco/Exception.h>
87#include <Poco/JSON/Array.h>
88#include <Poco/JSON/Object.h>
89#include <Poco/JSON/Parser.h>
90#include <Poco/JSON/Stringifier.h>
93#include <imgui_internal.h>
94#include <simplesquirrel/simplesquirrel.hpp>
114using Poco::Dynamic::Var;
115using Poco::JSON::Array;
116using Poco::JSON::Object;
117using Poco::JSON::Parser;
119std::string jsonStringify(
const Var&
v) {
120 std::ostringstream oss;
121 Poco::JSON::Stringifier::stringify(
v, oss, 0, 0);
125Object::Ptr parseObject(
const std::string& json) {
126 Poco::JSON::Parser parser;
127 return parser.parse(json).extract<Object::Ptr>();
130std::string strOf(Object::Ptr o,
const char*
key,
const std::string& def = {}) {
131 if (!o || !o->has(
key))
return def;
133 return o->get(
key).convert<std::string>();
139int intOf(Object::Ptr o,
const char*
key,
int def = 0) {
140 if (!o || !o->has(
key))
return def;
142 return o->get(
key).convert<
int>();
148float floatOf(Object::Ptr o,
const char*
key,
float def = 0.f) {
149 if (!o || !o->has(
key))
return def;
151 return static_cast<float>(o->get(
key).convert<
double>());
157bool boolOf(Object::Ptr o,
const char*
key,
bool def =
false) {
158 if (!o || !o->has(
key))
return def;
160 return o->get(
key).convert<
bool>();
166float varFloat(
const Var&
v,
float def = 0.f) {
168 return static_cast<float>(
v.convert<
double>());
174bool varBool(
const Var&
v,
bool def =
false) {
176 return v.convert<
bool>();
182std::string varString(
const Var&
v,
const std::string& def = {}) {
184 return v.convert<std::string>();
190Array::Ptr varToArray(
const Var&
v) {
192 return v.extract<Array::Ptr>();
198std::vector<float> varFloats(
const Var&
v) {
199 std::vector<float> out;
200 Array::Ptr arr = varToArray(
v);
201 if (!arr)
return out;
202 for (
size_t i = 0; i < arr->size(); ++i) {
205 static_cast<float>(arr->get(
static_cast<unsigned int>(i)).convert<
double>()));
214std::vector<float> widgetFloatInit(
const Var&
v) {
215 std::vector<float> init = varFloats(
v);
216 if (!init.empty())
return init;
218 init.push_back(
static_cast<float>(
v.convert<
double>()));
224std::vector<std::string> varStrings(
const Var&
v) {
225 std::vector<std::string> out;
226 Array::Ptr arr = varToArray(
v);
227 if (!arr)
return out;
228 for (
size_t i = 0; i < arr->size(); ++i)
229 out.push_back(varString(arr->get(
static_cast<unsigned int>(i))));
236 switch (sq_gettype(
vm,
index)) {
241 if (SQ_SUCCEEDED(sq_getbool(
vm,
index, &
b)))
return Var(
bool(
b));
246 if (SQ_SUCCEEDED(sq_getinteger(
vm,
index, &i)))
247 return Var(
static_cast<Poco::Int64
>(i));
252 if (SQ_SUCCEEDED(sq_getfloat(
vm,
index, &
f)))
253 return Var(
static_cast<double>(
f));
257 const SQChar*
s =
nullptr;
258 if (SQ_SUCCEEDED(sq_getstring(
vm,
index, &
s)) &&
s)
return Var(std::string(
s));
263 if (SQ_FAILED(sq_getstackobj(
vm,
index, &obj)))
return Var();
264 sq_pushobject(
vm, obj);
266 Array::Ptr arr =
new Array();
267 while (SQ_SUCCEEDED(sq_next(
vm, -2))) {
268 arr->add(stackVar(
vm, -1));
286 sq_pushbool(
vm,
v.convert<
bool>() ? SQTrue : SQFalse);
290 sq_pushfloat(
vm,
static_cast<SQFloat
>(
v.convert<
double>()));
294 const std::string
s =
v.convert<std::string>();
295 sq_pushstring(
vm,
s.c_str(),
static_cast<SQInteger
>(
s.size()));
301 arr =
v.extract<Array::Ptr>();
308 for (
size_t i = 0; i < arr->size(); ++i) {
309 pushVar(
vm, arr->get(
static_cast<unsigned int>(i)));
310 if (SQ_FAILED(sq_arrayappend(
vm, -2)))
break;
322 const SQInteger
top = sq_gettop(
vm);
327 static_cast<SQInteger
>(
source.size()),
331 return text.empty() ?
"compile failed" : (
"compile failed: " +
text);
335 rt->scriptModules().instantiateDependencies(
name);
336 }
catch (
const std::exception&
error) {
341 sq_pushroottable(
vm);
342 if (SQ_FAILED(sq_call(
vm, 1, SQFalse, SQTrue))) {
345 const SQChar* msg =
nullptr;
346 if (sq_gettype(
vm, -1) == OT_STRING) sq_getstring(
vm, -1, &msg);
351 if (
auto failure = rt->scriptModules().takeCompilationFailure())
text = *failure;
356 return "runtime failed: " +
text;
362void callRootFunc(
HSQUIRRELVM vm,
const std::string&
name,
const std::vector<Var>& args) {
363 const SQInteger
top = sq_gettop(
vm);
364 sq_pushroottable(
vm);
365 sq_pushstring(
vm,
name.c_str(),
static_cast<SQInteger
>(
name.size()));
366 if (SQ_FAILED(sq_get(
vm, -2))) {
370 if (sq_gettype(
vm, -1) != OT_CLOSURE) {
374 sq_pushroottable(
vm);
375 for (
const auto&
a : args) pushVar(
vm,
a);
376 if (SQ_FAILED(sq_call(
vm,
static_cast<SQInteger
>(args.size()) + 1, SQFalse, SQTrue)))
377 fprintf(stderr,
"eve.host: %s() failed\n",
name.c_str());
381bool callTableFunc(
HSQUIRRELVM vm,
const ssq::Table& tbl,
const std::string&
name,
382 const std::vector<Var>& args, std::string* err) {
383 if (
name.empty())
return true;
384 const SQInteger
top = sq_gettop(
vm);
386 ssq::Function
fn = tbl.findFunc(
name.c_str());
387 sq_pushobject(
vm,
fn.getRaw());
388 sq_pushobject(
vm, tbl.getRaw());
389 for (
const auto&
a : args) pushVar(
vm,
a);
390 if (SQ_FAILED(sq_call(
vm,
static_cast<SQInteger
>(args.size()) + 1, SQFalse, SQTrue))) {
391 if (err) *err =
"runtime failed: " +
name;
397 }
catch (
const ssq::NotFoundException&) {
400 }
catch (
const std::exception& e) {
401 if (err) *err = e.what();
408bool splitBindPath(
const std::string& bind, std::string& slot, std::string& nested) {
409 if (bind.rfind(
"vm.", 0) != 0)
return false;
410 std::string rest = bind.substr(3);
411 const size_t dot = rest.find(
'.');
412 if (dot == std::string::npos) {
415 }
else if (rest.find(
'.', dot + 1) == std::string::npos) {
416 slot = rest.substr(0, dot);
417 nested = rest.substr(dot + 1);
421 return !slot.empty();
424Var objectToVar(ssq::VM&
vm,
const ssq::Object& o) {
427 const SQInteger
top = sq_gettop(
v);
428 sq_pushobject(
v, o.getRaw());
429 Var out = stackVar(
v, -1);
434Var readVMVar(ssq::VM&
vm,
const ssq::Table& tbl,
const std::string& bind,
bool*
ok =
nullptr) {
435 std::string slot, nested;
437 if (!splitBindPath(bind, slot, nested))
return Var();
439 ssq::Object o = tbl.find(slot.c_str());
440 if (!nested.empty()) {
441 if (o.getType() != ssq::Type::TABLE)
return Var();
442 o = o.toTable().find(nested.c_str());
445 return objectToVar(
vm, o);
451void writeVMVar(ssq::VM&
vm,
const ssq::Table& tbl,
const std::string& bind,
const Var&
value) {
452 std::string slot, nested;
453 if (!splitBindPath(bind, slot, nested))
return;
455 const SQInteger
top = sq_gettop(
v);
456 if (nested.empty()) {
457 sq_pushobject(
v, tbl.getRaw());
458 sq_pushstring(
v, slot.c_str(),
static_cast<SQInteger
>(slot.size()));
460 sq_newslot(
v, -3,
false);
463 ssq::Table t2 = tbl.find(slot.c_str()).toTable();
464 sq_pushobject(
v, t2.getRaw());
465 sq_pushstring(
v, nested.c_str(),
static_cast<SQInteger
>(nested.size()));
467 sq_newslot(
v, -3,
false);
496void forEachWidget(Object::Ptr
parent,
const std::function<
void(Object::Ptr)>&
fn) {
498 Array::Ptr kids =
parent->has(
"children") ?
parent->getArray(
"children") :
nullptr;
500 for (
size_t i = 0; i < kids->size(); ++i) {
501 Object::Ptr
w = kids->getObject(
static_cast<unsigned>(i));
504 forEachWidget(
w,
fn);
508Object::Ptr findWidget(Object::Ptr
parent,
const std::string&
id) {
509 if (!
parent ||
id.empty())
return nullptr;
510 Array::Ptr kids =
parent->has(
"children") ?
parent->getArray(
"children") :
nullptr;
511 if (!kids)
return nullptr;
512 for (
size_t i = 0; i < kids->size(); ++i) {
513 Object::Ptr
w = kids->getObject(
static_cast<unsigned>(i));
515 if (strOf(
w,
"id") ==
id)
return w;
516 if (Object::Ptr sub = findWidget(
w,
id))
return sub;
521std::string widgetIdLabel(Object::Ptr
w) {
522 const std::string
id = strOf(
w,
"id");
523 const std::string
label = strOf(
w,
"label");
524 if (
label.empty())
return "##" +
id;
525 if (
id.empty())
return label;
530std::string vmFuncName(
const std::string&
path) {
531 if (
path.rfind(
"vm.", 0) == 0)
return path.substr(3);
535void recordRect(Editor& ed,
const std::string&
id) {
536 if (
id.empty())
return;
537 ed.rectMin[
id] = ImGui::GetItemRectMin();
538 ed.rectMax[
id] = ImGui::GetItemRectMax();
541void emitEvent(Editor& ed,
const std::string&
type,
const std::string&
widget,
const Var&
value) {
545void renderChildren(EditorHost::Impl& I, Editor& ed, Array::Ptr kids,
const std::string&
layout);
546void syncVMToView(EditorHost::Impl& I, Editor& ed);
553 ssq::VM*
vm =
nullptr;
565 std::map<std::string, ssq::Table>
vms;
579EditorHost::EditorHost() =
default;
583 if (running_)
return;
584 impl_ = std::make_unique<Impl>();
586 impl_->allowWindow = allowWindow;
587 allowWindow_ = allowWindow;
588 gameRoot_ =
gameRoot.empty() ? std::filesystem::current_path().string() :
gameRoot;
589 impl_->rootDir = gameRoot_;
595 if (!running_)
return;
599 exitRequested_ =
false;
603 if (!impl_)
return "error: host not started";
605 if (!I.allowWindow)
return "error: window creation disabled";
607 if (!I.win) I.win = ModuleManager::requireInstance<eve::window::Window>(
"Window");
608 if (!I.gfx) I.gfx = ModuleManager::requireInstance<eve::graphics::Graphics>(
"Graphics");
611 const bool alreadyOpen = I.win->isOpen() || I.win->getHandle() !=
nullptr;
613 std::fprintf(stderr,
"[editor-host] reuse existing window %dx%d\n",
614 I.win->getWidth(), I.win->getHeight());
615 if (!
title.empty()) I.win->setWindowTitle(
title);
622 std::fprintf(stderr,
"[editor-host] create window %dx%d\n",
623 static_cast<int>(
s.width),
static_cast<int>(
s.height));
624 if (!I.win->setWindowSettings(
s))
return "error: setWindowSettings failed";
625 I.win->setWindowTitle(
title.empty() ?
"EVEngine AI Host" :
title);
627 if (!I.ui) I.ui = ModuleManager::requireInstance<eve::ui::UI>(
"UI");
629 I.windowTitle =
title.empty() ? I.win->getWindowTitle() :
title;
634 if (I.gfx) I.gfx->setScreenReadbackEnabled(
true);
636 }
catch (
const std::exception& e) {
637 return std::string(
"error: ") + e.what();
642 if (!impl_)
return "ok";
644 if (I.windowOpen && I.win) {
649 if (I.ui) I.ui->shutdownBackend();
650 I.windowOpen =
false;
656 return impl_ && impl_->windowOpen;
660 if (!impl_ || !impl_->windowOpen)
return "{\"open\":false}";
661 Object::Ptr o =
new Object();
662 o->set(
"open",
true);
663 o->set(
"title", impl_->windowTitle);
665 o->set(
"width", impl_->win->getWidth());
666 o->set(
"height", impl_->win->getHeight());
668 return jsonStringify(Var(o));
672 if (!impl_)
return "error: host not started";
675 Object::Ptr
root = parseObject(json);
676 const std::string
id = strOf(
root,
"id");
677 if (
id.empty())
return "error: editor requires id";
680 ed.title = strOf(
root,
"title",
id);
681 ed.vmName = strOf(
root,
"vm");
683 ed.x =
static_cast<float>(intOf(
root,
"x", 0));
684 ed.y =
static_cast<float>(intOf(
root,
"y", 0));
685 ed.width =
static_cast<float>(intOf(
root,
"width", 0));
686 ed.height =
static_cast<float>(intOf(
root,
"height", 0));
687 ed.resizable = boolOf(
root,
"resizable",
true);
688 ed.collapsible = boolOf(
root,
"collapsible",
true);
689 ed.layout = strOf(
root,
"layout",
"vertical");
690 if (
root->has(
"theme")) ed.theme =
root->getObject(
"theme");
691 forEachWidget(
root, [&](Object::Ptr
w) {
692 const std::string wid = strOf(
w,
"id");
693 if (wid.empty())
return;
694 if (
w->has(
"value")) ed.values[wid] =
w->get(
"value");
696 I.editors[
id] = std::move(ed);
697 if (!I.windowOpen && I.allowWindow) {
703 if (!I.win) I.win = ModuleManager::requireInstance<eve::window::Window>(
"Window");
704 if (I.win->isOpen() || I.win->getHandle() !=
nullptr) {
705 w = I.win->getWidth();
706 h = I.win->getHeight();
710 std::string err =
openWindow(ed.title +
" - EVEngine AI Host",
w,
h);
711 if (err.rfind(
"error:", 0) == 0)
return err;
714 }
catch (
const std::exception& e) {
715 return std::string(
"error: ") + e.what();
720 if (!impl_)
return "error: host not started";
721 auto it = impl_->editors.find(
id);
722 if (it == impl_->editors.end())
return "error: editor not found: " +
id;
723 impl_->editors.erase(it);
728 if (!impl_)
return "{\"editors\":[]}";
729 Array::Ptr arr =
new Array();
730 for (
const auto& [k, ed] : impl_->editors) {
731 Object::Ptr o =
new Object();
733 o->set(
"title", ed.title);
734 o->set(
"vm", ed.vmName);
738 root->set(
"editors", arr);
739 return jsonStringify(Var(
root));
744Poco::JSON::Object::Ptr editorStateObject(
const Editor& ed) {
745 Object::Ptr o =
new Object();
747 o->set(
"title", ed.title);
748 o->set(
"vm", ed.vmName);
749 Object::Ptr vals =
new Object();
750 for (
const auto& [k,
v] : ed.
values) vals->set(k,
v);
751 o->set(
"values", vals);
752 Array::Ptr evs =
new Array();
753 for (
const auto& e : ed.
events) {
754 Object::Ptr eo =
new Object();
755 eo->set(
"editor", ed.id);
756 eo->set(
"widget", e.widget);
757 eo->set(
"type", e.type);
758 if (!e.value.isEmpty()) eo->set(
"value", e.value);
761 o->set(
"events", evs);
768 if (!impl_)
return "{\"editors\":[]}";
770 Array::Ptr arr =
new Array();
772 auto it = impl_->editors.find(
id);
773 if (it == impl_->editors.end())
return "error: editor not found: " +
id;
774 arr->add(editorStateObject(it->second));
776 for (
const auto& [k, ed] : impl_->editors) arr->add(editorStateObject(ed));
779 root->set(
"editors", arr);
780 return jsonStringify(Var(
root));
785 for (
auto& [k, ed] : impl_->editors) syncVMToView(*impl_, ed);
791 if (!I.vm || ed.vmName.empty())
return;
792 auto it = I.vms.find(ed.vmName);
793 if (it == I.vms.end())
return;
794 Object::Ptr
w = findWidget(ed.view, widgetId);
796 const std::string bind = strOf(
w,
"bind");
797 if (!bind.empty()) writeVMVar(*I.vm, it->second, bind,
value);
798 const std::string onChange = strOf(
w,
"onChange");
799 if (!onChange.empty()) {
801 callTableFunc(I.vm->getHandle(), it->second, vmFuncName(onChange),
802 {Var(widgetId),
value}, &err);
804 fprintf(stderr,
"eve.host: onChange %s: %s\n", onChange.c_str(), err.c_str());
808void setWidgetValue(EditorHost::Impl& I, Editor& ed,
const std::string& widgetId,
810 ed.values[widgetId] =
value;
811 emitEvent(ed,
"change", widgetId,
value);
812 pushViewToVM(I, ed, widgetId,
value);
815void resolveOneWay(EditorHost::Impl& I, Editor& ed, Object::Ptr
w) {
816 if (!I.vm || ed.vmName.empty())
return;
817 auto it = I.vms.find(ed.vmName);
818 if (it == I.vms.end())
return;
819 const char* oneWay[] = {
"bind:label",
"bind:visible",
"bind:enabled"};
820 for (
const char*
key : oneWay) {
821 const std::string bind = strOf(
w,
key);
822 if (bind.empty())
continue;
823 const Var
v = readVMVar(*I.vm, it->second, bind);
824 if (
v.isEmpty())
continue;
825 const std::string k(
key);
826 if (k ==
"bind:label")
w->set(
"label", varString(
v));
827 else if (k ==
"bind:visible")
w->set(
"visible", varBool(
v,
true));
828 else if (k ==
"bind:enabled")
w->set(
"enabled", varBool(
v,
true));
830 const std::string optBind = strOf(
w,
"bind:options");
831 if (!optBind.empty()) {
832 const Var
v = readVMVar(*I.vm, it->second, optBind);
834 Array::Ptr opts =
new Array();
835 for (
const auto&
s : varStrings(
v)) opts->add(
s);
836 w->set(
"options", opts);
841void syncVMToView(EditorHost::Impl& I, Editor& ed) {
842 if (!I.vm || ed.vmName.empty())
return;
843 auto it = I.vms.find(ed.vmName);
844 if (it == I.vms.end())
return;
845 forEachWidget(ed.view, [&](Object::Ptr
w) {
846 const std::string bind = strOf(w,
"bind");
847 const std::string id = strOf(w,
"id");
848 if (bind.empty() || id.empty()) return;
849 const Var v = readVMVar(*I.vm, it->second, bind);
850 if (v.isEmpty()) return;
851 auto cur = ed.values.find(id);
852 if (cur == ed.values.end() || jsonStringify(cur->second) != jsonStringify(v))
857void renderWidget(EditorHost::Impl& I, Editor& ed, Object::Ptr
w);
859void renderChildren(EditorHost::Impl& I, Editor& ed, Array::Ptr kids,
const std::string&
layout) {
861 for (
size_t i = 0; i < kids->size(); ++i) {
862 Object::Ptr
w = kids->getObject(
static_cast<unsigned>(i));
864 resolveOneWay(I, ed,
w);
865 renderWidget(I, ed,
w);
866 if (
layout ==
"horizontal" && i + 1 < kids->size()) ImGui::SameLine();
870void renderWidget(EditorHost::Impl& I, Editor& ed, Object::Ptr
w) {
872 if (!boolOf(
w,
"visible",
true))
return;
873 const std::string
type = strOf(
w,
"type",
"label");
874 const std::string
id = strOf(
w,
"id");
875 const std::string
label = strOf(
w,
"label");
876 const bool enabled = boolOf(
w,
"enabled",
true);
878 ImGui::PushStyleVar(ImGuiStyleVar_Alpha, ImGui::GetStyle().
Alpha * 0.5f);
879 ImGui::PushItemFlag(ImGuiItemFlags_Disabled,
true);
882 const auto cur = [&](
const char*
key,
const Var& def) -> Var {
884 auto it = ed.values.find(
id);
885 if (it != ed.values.end())
return it->second;
887 if (
w->has(
key))
return w->get(
key);
891 if (
type ==
"label" ||
type ==
"text") {
892 const std::string
text = strOf(
w,
"text",
label);
893 if (
type ==
"text") ImGui::TextWrapped(
"%s",
text.c_str());
894 else ImGui::TextUnformatted(
text.c_str());
896 }
else if (
type ==
"separator") {
898 }
else if (
type ==
"spacer") {
899 ImGui::Dummy(ImVec2(floatOf(
w,
"width", 0.f), floatOf(
w,
"height", 8.f)));
900 }
else if (
type ==
"progress") {
901 float v = std::clamp(varFloat(cur(
"value", 0.0), 0.f), 0.f, 1.f);
902 const float pw = floatOf(
w,
"width", 0.f);
903 ImGui::ProgressBar(
v, ImVec2(pw > 0.f ? pw : -FLT_MIN, 0.f),
906 }
else if (
type ==
"plot") {
907 const std::vector<float> data = varFloats(cur(
"data", Var()));
909 ImGui::PlotLines(widgetIdLabel(
w).c_str(), data.data(),
static_cast<int>(data.size()), 0,
910 nullptr, floatOf(
w,
"min", 0.f), floatOf(
w,
"max", 1.f),
911 ImVec2(floatOf(
w,
"width", 0.f), floatOf(
w,
"height", 80.f)));
914 }
else if (
type ==
"input") {
915 std::string buf = varString(cur(
"value", strOf(
w,
"value")));
916 const bool multi = boolOf(
w,
"multiline",
false);
919 ImVec2(floatOf(
w,
"width", 0.f),
920 floatOf(
w,
"height", 96.f))) ==
922 if (
enabled && changed && !
id.empty()) {
923 setWidgetValue(I, ed,
id, Var(buf));
926 }
else if (
type ==
"slider" ||
type ==
"slider2" ||
type ==
"slider3") {
927 const int n =
type ==
"slider" ? 1 : (
type ==
"slider2" ? 2 : 3);
928 float v[3] = {0.f, 0.f, 0.f};
929 const std::vector<float> init = widgetFloatInit(cur(
"value", Var()));
930 for (
int i = 0; i < n; ++i) v[i] = i < static_cast<int>(init.size()) ? init[i] : 0.f;
931 const float lo = floatOf(
w,
"min", 0.f), hi = floatOf(
w,
"max", 1.f);
932 const std::string fmt = strOf(
w,
"format",
"%.2f");
933 bool changed =
false;
937 else if (
n == 2) changed = ImGui::SliderFloat2(widgetIdLabel(
w).c_str(),
v, lo, hi, fmt.c_str());
938 else changed = ImGui::SliderFloat3(widgetIdLabel(
w).c_str(),
v, lo, hi, fmt.c_str());
939 if (
enabled && changed && !
id.empty()) {
940 Array::Ptr arr =
new Array();
941 for (
int i = 0; i <
n; ++i) arr->add(
static_cast<double>(
v[i]));
942 setWidgetValue(I, ed,
id, Var(arr));
945 }
else if (
type ==
"color") {
946 const bool alpha = boolOf(
w,
"alpha",
false);
947 const std::vector<float> init = varFloats(cur(
"value", Var()));
948 float c[4] = {1.f, 1.f, 1.f, 1.f};
949 for (
size_t i = 0; i < std::min<size_t>(init.size(), 4); ++i)
c[i] = init[i];
950 const bool changed = alpha ? ImGui::ColorEdit4(widgetIdLabel(
w).c_str(),
c)
951 :
ImGui::ColorEdit3(widgetIdLabel(
w).c_str(),
c);
952 if (
enabled && changed && !
id.empty()) {
953 Array::Ptr arr =
new Array();
954 const int n = alpha ? 4 : 3;
955 for (
int i = 0; i <
n; ++i) arr->add(
static_cast<double>(
c[i]));
956 setWidgetValue(I, ed,
id, Var(arr));
959 }
else if (
type ==
"checkbox") {
960 bool b = varBool(cur(
"value",
false), boolOf(
w,
"value",
false));
964 setWidgetValue(I, ed,
id, Var(
b));
966 }
else if (
type ==
"dropdown" ||
type ==
"listbox") {
967 const std::vector<std::string> opts = varStrings(cur(
"options", Var()));
968 std::vector<const char*> items;
969 for (
const auto&
s : opts) items.push_back(
s.c_str());
970 std::string sel = varString(cur(
"value", strOf(
w,
"value")));
972 for (
size_t i = 0; i < opts.size(); ++i)
973 if (opts[i] == sel)
idx =
static_cast<int>(i);
974 bool changed =
false;
975 if (
type ==
"dropdown")
979 changed = !opts.empty() && ImGui::ListBox(widgetIdLabel(
w).c_str(), &
idx, items.data(),
980 static_cast<int>(items.size()),
981 intOf(
w,
"heightItems", -1));
982 if (
enabled && changed && !
id.empty() &&
idx >= 0 &&
idx <
static_cast<int>(opts.size()))
983 setWidgetValue(I, ed,
id, Var(opts[
static_cast<size_t>(
idx)]));
985 }
else if (
type ==
"button") {
989 emitEvent(ed,
"click",
id, Var());
990 const std::string cmd = strOf(
w,
"command", strOf(
w,
"action"));
991 if (!cmd.empty() && !ed.vmName.empty() && I.vm) {
992 auto it = I.vms.find(ed.vmName);
993 if (it != I.vms.end()) {
995 callTableFunc(I.vm->getHandle(), it->second, vmFuncName(cmd),
996 {Var(ed.id), Var(
id)}, &err);
998 fprintf(stderr,
"eve.host: command %s: %s\n", cmd.c_str(), err.c_str());
1003 }
else if (
type ==
"tree") {
1004 const bool open = boolOf(
w,
"open",
true);
1005 if (ImGui::TreeNodeEx(widgetIdLabel(
w).c_str(),
1006 open ? ImGuiTreeNodeFlags_DefaultOpen : 0)) {
1007 renderChildren(I, ed,
1008 w->has(
"children") ?
w->getArray(
"children") : nullptr, ed.
layout);
1012 }
else if (
type ==
"group") {
1013 const bool border = boolOf(
w,
"border",
true);
1015 if (ImGui::BeginChild(widgetIdLabel(
w).c_str(),
1016 ImVec2(floatOf(
w,
"width", 0.f), floatOf(
w,
"height", 0.f)),
true)) {
1017 if (!
label.empty()) ImGui::TextUnformatted(
label.c_str());
1018 renderChildren(I, ed,
1019 w->has(
"children") ?
w->getArray(
"children") : nullptr,
1020 strOf(
w,
"layout",
"vertical"));
1024 ImGui::BeginGroup();
1025 if (!
label.empty()) ImGui::TextUnformatted(
label.c_str());
1026 renderChildren(I, ed,
1027 w->has(
"children") ?
w->getArray(
"children") : nullptr,
1028 strOf(
w,
"layout",
"vertical"));
1032 }
else if (
type ==
"tabs") {
1033 Array::Ptr tabs =
w->has(
"children") ?
w->getArray(
"children") :
nullptr;
1034 if (tabs && ImGui::BeginTabBar((
id.empty() ?
"tabs" :
id).c_str())) {
1035 for (
size_t i = 0; i < tabs->size(); ++i) {
1036 Object::Ptr tab = tabs->getObject(
static_cast<unsigned>(i));
1038 if (ImGui::BeginTabItem(strOf(tab,
"label",
"Tab").c_str())) {
1039 renderChildren(I, ed,
1040 tab->has(
"children") ? tab->getArray(
"children") : nullptr,
1041 strOf(tab,
"layout",
"vertical"));
1042 ImGui::EndTabItem();
1047 }
else if (
type ==
"tab") {
1048 renderChildren(I, ed,
w->has(
"children") ?
w->getArray(
"children") : nullptr,
1049 strOf(
w,
"layout",
"vertical"));
1050 }
else if (
type ==
"table") {
1051 const std::vector<std::string>
cols =
1052 w->has(
"columns") ? varStrings(
w->get(
"columns")) :
std::vector<
std::
string>();
1053 Array::Ptr
rows =
w->has(
"rows") ?
w->getArray(
"rows") :
nullptr;
1054 if (!
cols.empty() && ImGui::BeginTable((
id.empty() ?
"table" :
id).c_str(),
1056 ImGuiTableFlags_Borders | ImGuiTableFlags_RowBg)) {
1057 for (
const auto&
c :
cols)
ImGui::TableSetupColumn(
c.c_str());
1058 ImGui::TableHeadersRow();
1060 for (
size_t r = 0;
r <
rows->size(); ++
r) {
1061 ImGui::TableNextRow();
1062 Array::Ptr
cells =
rows->getArray(
static_cast<unsigned>(
r));
1063 if (!
cells)
continue;
1064 for (
size_t c = 0;
c <
cells->size(); ++
c) {
1065 ImGui::TableSetColumnIndex(
static_cast<int>(
c));
1066 ImGui::TextUnformatted(
1067 varString(
cells->get(
static_cast<unsigned int>(
c))).c_str());
1074 }
else if (
type ==
"viewport") {
1075 const float vw = floatOf(
w,
"width", 0.f);
1076 const float vh = floatOf(
w,
"height", 120.f);
1077 const std::vector<float> bg =
1078 w->has(
"bg") ? varFloats(
w->get(
"bg")) :
std::vector<float>();
1079 const ImVec2
pos = ImGui::GetCursorScreenPos();
1080 ImVec2
size(vw > 0.f ? vw :
ImGui::GetContentRegionAvail().
x, vh);
1081 ImU32 col = IM_COL32(10, 12, 16, 255);
1083 col = IM_COL32(
static_cast<int>(bg[0] * 255.f),
static_cast<int>(bg[1] * 255.f),
1084 static_cast<int>(bg[2] * 255.f), 255);
1085 ImGui::GetWindowDrawList()->AddRectFilled(
pos, ImVec2(
pos.x +
size.x,
pos.y +
size.y), col);
1086 if (ImGui::BeginChild(widgetIdLabel(
w).c_str(),
size,
true)) {
1087 if (!
label.empty()) ImGui::TextUnformatted(
label.c_str());
1092 ed.rectMin[
id] = ImGui::GetItemRectMin();
1093 ed.rectMax[
id] = ImGui::GetItemRectMax();
1097 const std::string
tip = strOf(
w,
"tooltip");
1098 if (!
tip.empty() && ImGui::IsItemHovered()) ImGui::SetTooltip(
"%s",
tip.c_str());
1100 ImGui::PopItemFlag();
1101 ImGui::PopStyleVar();
1105void renderEditor(EditorHost::Impl& I, Editor& ed) {
1106 if (ed.removed)
return;
1108 ImGui::SetNextWindowSize(ImVec2(ed.width, ed.height), ImGuiCond_FirstUseEver);
1109 if (ed.x != 0.f || ed.y != 0.f)
1110 ImGui::SetNextWindowPos(ImVec2(ed.x, ed.y), ImGuiCond_FirstUseEver);
1112 ImVec4 accent(0.35f, 0.60f, 1.00f, 1.f), bg(0.10f, 0.11f, 0.13f, 1.f);
1113 ImVec4 panel(0.14f, 0.15f, 0.18f, 1.f),
text(0.92f, 0.93f, 0.95f, 1.f);
1114 float radius = 4.f, fontScale = 1.f;
1116 if (ed.theme->has(
"accent")) {
1117 const auto c = varFloats(ed.theme->get(
"accent"));
1118 if (
c.size() >= 3) accent = ImVec4(
c[0],
c[1],
c[2], 1.f);
1120 if (ed.theme->has(
"bg")) {
1121 const auto c = varFloats(ed.theme->get(
"bg"));
1122 if (
c.size() >= 3) bg = ImVec4(
c[0],
c[1],
c[2], 1.f);
1124 if (ed.theme->has(
"panel")) {
1125 const auto c = varFloats(ed.theme->get(
"panel"));
1126 if (
c.size() >= 3) panel = ImVec4(
c[0],
c[1],
c[2], 1.f);
1128 if (ed.theme->has(
"text")) {
1129 const auto c = varFloats(ed.theme->get(
"text"));
1130 if (
c.size() >= 3)
text = ImVec4(
c[0],
c[1],
c[2], 1.f);
1132 radius = floatOf(ed.theme,
"radius", 4.f);
1133 fontScale = floatOf(ed.theme,
"fontScale", 1.f);
1134 if (strOf(ed.theme,
"preset") ==
"light") {
1135 bg = ImVec4(0.93f, 0.93f, 0.93f, 1.f);
1136 panel = ImVec4(0.84f, 0.84f, 0.86f, 1.f);
1137 text = ImVec4(0.10f, 0.10f, 0.12f, 1.f);
1141 ImGui::PushStyleColor(ImGuiCol_WindowBg, bg);
1142 ImGui::PushStyleColor(ImGuiCol_ChildBg, panel);
1143 ImGui::PushStyleColor(ImGuiCol_Text,
text);
1144 ImGui::PushStyleColor(ImGuiCol_Button, accent);
1145 ImGui::PushStyleColor(ImGuiCol_ButtonHovered,
1146 ImVec4(accent.x + 0.08f, accent.y + 0.08f, accent.z + 0.08f, 1.f));
1147 ImGui::PushStyleColor(ImGuiCol_ButtonActive,
1148 ImVec4(accent.x - 0.05f, accent.y - 0.05f, accent.z - 0.05f, 1.f));
1149 ImGui::PushStyleColor(ImGuiCol_Header, accent);
1150 ImGui::PushStyleColor(ImGuiCol_SliderGrab, accent);
1151 ImGui::PushStyleColor(ImGuiCol_CheckMark, accent);
1152 ImGui::PushStyleVar(ImGuiStyleVar_FrameRounding,
radius);
1153 ImGui::PushStyleVar(ImGuiStyleVar_GrabRounding,
radius);
1154 ImGui::PushStyleVar(ImGuiStyleVar_WindowRounding,
radius);
1157 ImGuiWindowFlags flags = ImGuiWindowFlags_NoSavedSettings;
1158 if (!ed.resizable) flags |= ImGuiWindowFlags_NoResize;
1159 if (!ed.collapsible) flags |= ImGuiWindowFlags_NoCollapse;
1160 if (ImGui::Begin((ed.title +
"##" + ed.id).c_str(), &open, flags)) {
1161 if (fontScale != 1.f) ImGui::SetWindowFontScale(fontScale);
1162 renderChildren(I, ed,
1163 (ed.view && ed.view->has(
"children")) ? ed.view->getArray(
"children")
1168 emitEvent(ed,
"closed",
"", Var());
1172 if (fontScale != 1.f) ImGui::SetWindowFontScale(1.f);
1173 ImGui::PopStyleVar(3);
1174 ImGui::PopStyleColor(9);
1180 if (!impl_ || !impl_->windowOpen || !impl_->ui)
return;
1182 for (
auto& [
id, ed] : I.editors) {
1183 syncVMToView(I, ed);
1184 renderEditor(I, ed);
1186 for (
auto it = I.editors.begin(); it != I.editors.end();) {
1187 if (it->second.removed) it = I.editors.erase(it);
1193 if (!impl_ || !impl_->windowOpen || !impl_->vm)
return;
1196 if (!I.event) I.event = ModuleManager::requireInstance<eve::platform_event::PlatformEvent>(
"PlatformEvent");
1197 if (!I.timer) I.timer = ModuleManager::requireInstance<eve::timer::Timer>(
"Timer");
1204 while (
auto m = I.event->pollOwned()) {
1205 const std::string
name =
m->name;
1206 if (
name ==
"quit") {
1214 const float dt = I.timer ? I.timer->step() : 0.f;
1215 callRootFunc(I.vm->getHandle(),
"eve_host_update", {Var(static_cast<double>(dt))});
1216 I.gfx->clearScreen();
1217 callRootFunc(I.vm->getHandle(),
"eve_host_render", {});
1219 I.ui->beginFrameAndRender();
1223 if (I.ui) I.ui->dispatchEvents();
1229Var parseEditorValue(
const std::string& jsonValue) {
1232 Poco::JSON::Parser
p;
1233 v =
p.parse(jsonValue);
1237 if (jsonValue ==
"true")
1239 else if (jsonValue ==
"false")
1241 else if (jsonValue ==
"null")
1244 char*
end =
nullptr;
1245 const double d = std::strtod(jsonValue.c_str(), &
end);
1246 if (
end &&
end != jsonValue.c_str() && *
end ==
'\0')
1258 const std::string& jsonValue) {
1259 if (!impl_)
return "error: host not started";
1260 auto it = impl_->editors.find(editorId);
1261 if (it == impl_->editors.end())
return "error: editor not found: " + editorId;
1262 if (widgetId.empty())
return "error: missing widget";
1263 const Var
v = parseEditorValue(jsonValue);
1264 setWidgetValue(*impl_, it->second, widgetId,
v);
1269 const std::string& jsonValue) {
1270 if (!impl_ || !impl_->vm)
return "error: host not started";
1271 auto it = impl_->editors.find(editorId);
1272 if (it == impl_->editors.end())
return "error: editor not found: " + editorId;
1273 if (widgetId.empty())
return "error: missing widget";
1274 const Var
v = parseEditorValue(jsonValue);
1275 it->second.values[widgetId] =
v;
1276 auto vmIt = impl_->vms.find(it->second.vmName);
1277 const auto widget = findWidget(it->second.view, widgetId);
1278 if (vmIt != impl_->vms.end() &&
widget) {
1279 const std::string bind = strOf(
widget,
"bind");
1280 if (!bind.empty()) writeVMVar(*impl_->vm, vmIt->second, bind,
v);
1286 if (!impl_)
return "[]";
1287 Array::Ptr arr =
new Array();
1288 auto drain = [&](Editor& ed) {
1289 for (
const auto& e : ed.events) {
1290 Object::Ptr eo =
new Object();
1291 eo->set(
"editor", ed.id);
1292 eo->set(
"widget", e.widget);
1293 eo->set(
"type", e.type);
1294 if (!e.value.isEmpty()) eo->set(
"value", e.value);
1299 if (!editorId.empty()) {
1300 auto it = impl_->editors.find(editorId);
1301 if (it == impl_->editors.end())
return "error: editor not found: " + editorId;
1304 for (
auto& [k, ed] : impl_->editors) drain(ed);
1306 return jsonStringify(Var(arr));
1310 if (!impl_)
return "{\"x\":0,\"y\":0,\"width\":0,\"height\":0}";
1311 auto it = impl_->editors.find(editorId);
1312 if (it == impl_->editors.end())
return "{\"x\":0,\"y\":0,\"width\":0,\"height\":0}";
1313 const Editor& ed = it->second;
1314 auto mn = ed.rectMin.find(widgetId), mx = ed.rectMax.find(widgetId);
1315 Object::Ptr o =
new Object();
1316 if (mn != ed.rectMin.end() && mx != ed.rectMax.end()) {
1317 o->set(
"x",
static_cast<double>(mn->second.x));
1318 o->set(
"y",
static_cast<double>(mn->second.y));
1319 o->set(
"width",
static_cast<double>(mx->second.x - mn->second.x));
1320 o->set(
"height",
static_cast<double>(mx->second.y - mn->second.y));
1325 o->set(
"height", 0);
1327 return jsonStringify(Var(o));
1331 const std::string& sourceName) {
1332 if (!impl_ || !impl_->vm)
return "error: host not started";
1333 if (
name.empty())
return "error: missing name";
1334 const std::string
uri = sourceName.empty() ? std::string(
"host_vm.nut") : sourceName;
1335 const std::string err = runSquirrel(impl_->vm->getHandle(),
source,
uri.c_str());
1336 if (!err.empty())
return "error: " + err;
1338 ssq::Table tbl = impl_->vm->find(
name.c_str()).toTable();
1339 impl_->vms[
name] = tbl;
1342 }
catch (
const std::exception& e) {
1343 return std::string(
"error: table '") +
name +
"' not found after run: " + e.what();
1348 if (!impl_)
return "error: host not started";
1349 if (
name.empty())
return "error: missing name";
1350 impl_->vms.erase(
name);
1351 impl_->vmSources.erase(
name);
1354 const SQInteger
top = sq_gettop(
v);
1355 sq_pushroottable(
v);
1356 sq_pushstring(
v,
name.c_str(),
static_cast<SQInteger
>(
name.size()));
1357 sq_deleteslot(
v, -2, SQFalse);
1365 if (!impl_)
return "error: host not started";
1366 auto it = impl_->editors.find(
id);
1367 if (it == impl_->editors.end())
return "error: editor not found: " +
id;
1370 std::filesystem::path
dir = std::filesystem::path(impl_->rootDir) /
"editors";
1371 std::filesystem::create_directories(
dir, ec);
1372 std::filesystem::path vp =
dir / (
id +
".editor.json");
1374 std::ofstream ofs(vp, std::ios::trunc);
1375 if (!ofs)
return "error: cannot write " + vp.string();
1376 ofs << jsonStringify(Var(it->second.view));
1378 if (!it->second.vmName.empty()) {
1379 auto vs = impl_->vmSources.find(it->second.vmName);
1380 if (vs != impl_->vmSources.end()) {
1381 std::filesystem::path sp =
dir / (
id +
".vm.nut");
1382 std::ofstream ofs2(sp, std::ios::trunc);
1383 if (!ofs2)
return "error: cannot write " + sp.string();
1388 }
catch (
const std::exception& e) {
1389 return std::string(
"error: ") + e.what();
1394 if (!impl_)
return "error: host not started";
1395 auto it = impl_->editors.find(
id);
1396 if (it == impl_->editors.end())
return "error: editor not found: " +
id;
1397 impl_->editors.erase(it);
1404 std::filesystem::path
dir = std::filesystem::path(impl_->rootDir) /
"editors";
1405 if (!std::filesystem::is_directory(
dir, ec))
return;
1406 std::vector<std::pair<std::string, std::string>>
pending;
1407 for (
auto& entry : std::filesystem::directory_iterator(
dir, ec)) {
1409 const std::filesystem::path
p = entry.path();
1410 const std::string
name =
p.filename().string();
1411 const std::string suffix =
".editor.json";
1412 if (
p.extension() !=
".json" ||
name.size() <= suffix.size() ||
1413 name.compare(
name.size() - suffix.size(), suffix.size(), suffix) != 0)
1415 const std::string
id =
name.substr(0,
name.size() - suffix.size());
1416 std::ifstream ifs(
p);
1417 std::string json((std::istreambuf_iterator<char>(ifs)), std::istreambuf_iterator<char>());
1418 if (!json.empty())
pending.emplace_back(
id, json);
1420 for (
const auto& [
id, json] :
pending) {
1422 Object::Ptr
root = parseObject(json);
1423 const std::string
vmName = strOf(
root,
"vm");
1425 std::ifstream ifs2(
dir / (
id +
".vm.nut"));
1426 std::string src((std::istreambuf_iterator<char>(ifs2)),
1427 std::istreambuf_iterator<char>());
1428 if (!src.empty())
registerVM(
vmName, src,
"game:/editors/" +
id +
".vm.nut");
1437 if (!impl_ || !impl_->vm)
return "error: host not started";
1438 if (
source.empty())
return "error: missing source";
1439 const std::string
uri = sourceName.empty() ? std::string(
"host_snippet.nut") : sourceName;
1440 const std::string err = runSquirrel(impl_->vm->getHandle(),
source,
uri.c_str());
1441 return err.empty() ?
"ok" : (
"error: " + err);
1445 if (!impl_ || !impl_->gfx)
return "error: no window";
1448 std::string
p =
path.empty() ?
"eve_host_capture.png" :
path;
1449 I.gfx->setScreenReadbackEnabled(
true);
1452 img = I.gfx->newImageData();
1456 if (I.inFrame)
throw;
1458 img = I.gfx->newImageData();
1460 if (!img)
return "error: readback returned no image";
1461 const int w = I.gfx->getPixelWidth();
1462 const int h = I.gfx->getPixelHeight();
1464 img->
encode(eve::image::ImageData::FormatHandler::ENCODED_PNG,
p.c_str(),
false);
1466 if (!png)
return "error: PNG encode failed";
1468 std::filesystem::create_directories(std::filesystem::path(
p).parent_path(), ec);
1469 std::ofstream out(
p, std::ios::binary | std::ios::trunc);
1472 return "error: cannot write " +
p;
1474 out.write(
static_cast<const char*
>(png->
getData()),
1475 static_cast<std::streamsize
>(png->
getSize()));
1477 const bool wrote = out.good();
1479 if (!wrote)
return "error: failed writing " +
p;
1480 Object::Ptr o =
new Object();
1483 o->set(
"height",
h);
1484 return jsonStringify(Var(o));
1485 }
catch (
const std::exception& e) {
1486 return std::string(
"error: ") + e.what();
1491 if (!impl_)
return "{\"running\":false,\"windowOpen\":false,\"editors\":[],\"viewModels\":[]}";
1492 Object::Ptr o =
new Object();
1493 o->set(
"running", running_);
1494 o->set(
"windowOpen", impl_->windowOpen);
1495 o->set(
"windowTitle", impl_->windowTitle);
1496 o->set(
"gameRoot", gameRoot_);
1497 Array::Ptr eds =
new Array();
1498 for (
const auto& [k, ed] : impl_->editors) {
1499 Object::Ptr e =
new Object();
1501 e->set(
"title", ed.title);
1504 o->set(
"editors", eds);
1505 Array::Ptr vms =
new Array();
1506 for (
const auto& [k,
v] : impl_->vms) vms->add(k);
1507 o->set(
"viewModels", vms);
1508 return jsonStringify(Var(o));
1513 ssq::Table eveTbl =
vm.find(
"eve").toTable();
1514 ssq::Table
host = eveTbl.addTable(
"host");
1516 host.addFunc(
"openWindow", [](std::string
t,
int w,
int h) {
1521 host.addFunc(
"applyEditor", [](std::string json) {
1524 host.addFunc(
"removeEditor", [](std::string
id) {
1527 host.addFunc(
"setValue", [](std::string editor, std::string
widget, std::string
value) {
1530 host.addFunc(
"events", [](std::string editor) {
1533 host.addFunc(
"registerVM", [](std::string
name, std::string
source, std::string
uri) {
1536 host.addFunc(
"unregisterVM", [](std::string
name) {
1539 host.addFunc(
"widgetRect", [](std::string editor, std::string
widget) {
1542 host.addFunc(
"capture", [](std::string
path) {
1545 host.addFunc(
"save", [](std::string
id) {
1548 host.addFunc(
"runScript", [](std::string
source, std::string
uri) {
1551 host.addFunc(
"reloadResource", [](std::string
path) {
1554 host.addFunc(
"hotReloadStatus", []() {
std::vector< QuestEvent > pending
struct SQVM * HSQUIRRELVM
std::map< std::string, Var > values
std::map< std::string, ImVec2 > rectMin
std::map< std::string, ImVec2 > rectMax
std::array< PixelCell, kPixelChunkSize *kPixelChunkSize > cells
const UnitySourceAsset & source
static Runtime * runtime()
Active runtime associated with the last expose() call, or nullptr. @ownership Borrowed; the embedding...
Data buffer paired with a filename (used for type identification).
size_t getSize() const
Byte length of the owned buffer.
void * getData() const
Pointer to the owned byte buffer.
Represents raw pixel data.
filesystem::FileData * encode(FormatHandler::EncodedFormat format, const char *filename, bool writefile) const
Encodes raw pixel data into a given format.
static SQRESULT compileBuffer(HSQUIRRELVM vm, const SQChar *source, SQInteger size, const SQChar *sourceName, SQBool raiseError)
Unified raw-VM entry used by REPL, debugger, MCP, and editor adapters.
High-resolution frame/elapsed timer backed by SDL_GetPerformanceCounter(). Script: timer <- eve....
Headless MCP editor host (MVVM).
std::string status() const
Status.
std::string consumeEvents(const std::string &editorId)
Read and clear pending interaction events for one editor ("" = all).
void requestExit()
Request exit.
std::string closeWindow()
Closes window.
std::string listEditors() const
List editors.
std::string unregisterVM(const std::string &name)
Unregisters vm.
EditorHost(const EditorHost &)=delete
void syncBindings()
Pull bound ViewModel values into widget state (per-frame + on read).
void renderImGui()
Draw editor ImGui windows; call between beginFrameAndRender/present.
bool isWindowOpen() const
True when window open.
void loadEditorsFromDisk()
Load editors/<id>.editor.json (+ matching .vm.nut) on host startup.
std::string setEditorValue(const std::string &editorId, const std::string &widgetId, const std::string &jsonValue)
Sets the editor value.
std::string capture(const std::string &path)
Capture.
std::string applyEditor(const std::string &json)
JSON object/string: {id,title,vm,x,y,width,height,theme,children[]}.
std::string unloadEditor(const std::string &id)
Remove an editor from the session (files stay on disk).
std::string windowState() const
Window state.
const std::string & gameRoot() const
Game root.
std::string editorState(const std::string &id) const
Full state JSON: editors + values + (non-destructive) events.
void frame()
Pump events + update/render hooks + present (no-op without window).
std::string removeEditor(const std::string &id)
Removes editor.
std::string hotReloadStatus() const
Watch count, reload counters and latest diagnostic as compact JSON.
static EditorHost & instance()
Instance.
std::string openWindow(const std::string &title, int width, int height)
Opens window.
std::string registerVM(const std::string &name, const std::string &source, const std::string &sourceName={})
Compile Squirrel source in the host VM, then register table name.
std::string widgetRect(const std::string &editorId, const std::string &widgetId) const
Last-frame widget rect JSON (for script drawing inside viewports).
std::string reloadResource(const std::string &path)
Reload mcp.nut, scripts under mcp, or editor VM and manifest files.
std::string restoreEditorValue(const std::string &editorId, const std::string &widgetId, const std::string &jsonValue)
Restore a widget and its binding without emitting events or onChange.
std::string saveEditor(const std::string &id)
Write editors/<id>.editor.json + editors/<id>.vm.nut under gameRoot.
~EditorHost()
Editor host.
void start(ssq::VM &vm, const std::string &gameRoot={}, bool allowWindow=true)
Attach to the host VM. gameRoot is the project dir for editors/.
void exposeScriptApi(ssq::VM &vm)
Expose eve.host table (registerVM/unregisterVM/widgetRect/...).
std::string runScript(const std::string &source, const std::string &sourceName={})
Compile and run Squirrel source in the host VM.
Declarative UI module (eve.UI).
Platform window interface (SDL implementation on desktop/mobile). Script: win <- eve....
double dot(const Vec2 &a, const Vec2 &b)
Dot.
std::string formatLastScriptError(HSQUIRRELVM vm)
Formats the last captured error for a VM without consuming it.
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 checkbox(const char *label, bool &value)
Shared immediate renderer for retained and EditorHost checkboxes.
enum EVENGINE_API_FOUNDATION Array
OT_ARRAY (editable via Runtime array APIs / ReflectedPropertyModel).
SettlementPipeline::Stage fn
eve::window::Window * win
eve::graphics::Graphics * gfx
std::map< std::string, ssq::Table > vms
std::map< std::string, Editor > editors
eve::timer::Timer * timer
std::map< std::string, std::string > vmSources
Display settings for window creation/resize. width/height == 0 selects the desktop display mode size.