13constexpr const char* kInspectorHostName =
"eve_inspector";
15std::string valueText(
const ReflectedValue&
value) {
17 case ReflectedValueKind::Bool:
18 return value.boolean ?
"true" :
"false";
19 case ReflectedValueKind::Integer:
20 return std::to_string(
value.integer);
21 case ReflectedValueKind::Float: {
24 case ReflectedValueKind::String:
39Runtime* Inspector::runtime()
const {
43const ssq::Object* Inspector::currentInstance()
const {
44 if (selectedInstance_ < 0 ||
45 size_t(selectedInstance_) >= instances_.size())
47 return &instances_[size_t(selectedInstance_)].object;
50int Inspector::currentClassIndex()
const {
51 const auto it = std::find(classNames_.begin(), classNames_.end(), selectedClass_);
52 return it == classNames_.end() ? 0 : int(it - classNames_.begin());
60 classNames_.push_back(
cls.name);
63 if (!selectedClass_.empty() &&
64 std::find(classNames_.begin(), classNames_.end(), selectedClass_) ==
66 selectedClass_.clear();
68 selectedInstance_ = -1;
70 if (selectedClass_.empty() && !classNames_.empty())
72 rebuildPropertyModel();
83 host->get().setVisible(
true);
84 host->get().setLayer(100);
94 return host &&
host->get().meta()->visible;
100 std::find(classNames_.begin(), classNames_.end(),
name) == classNames_.end())
103 selectedClass_ =
name;
105 selectedInstance_ = -1;
109 entry.label =
name +
" #1";
110 entry.object = instance;
111 instances_.push_back(std::move(entry));
112 selectedInstance_ = 0;
116 rebuildPropertyModel();
118 return selectedInstance_ >= 0;
123 if (!rt ||
object.getType() != ssq::Type::INSTANCE)
return false;
124 const std::string className = rt->
classNameOf(
object);
125 if (className.empty() ||
126 std::find(classNames_.begin(), classNames_.end(), className) ==
130 selectedClass_ = className;
133 entry.label = className +
" (live)";
135 instances_.push_back(std::move(entry));
136 selectedInstance_ = 0;
137 rebuildPropertyModel();
144 if (!rt || selectedClass_.empty())
return false;
148 entry.label = selectedClass_ +
" #" + std::to_string(instances_.size() + 1);
149 entry.object = instance;
150 instances_.push_back(std::move(entry));
151 selectedInstance_ = int(instances_.size()) - 1;
152 rebuildPropertyModel();
161 pickScene_ = std::move(pickScene);
165 if (index < 0 || index >=
int(instances_.size()))
return false;
166 selectedInstance_ =
index;
167 rebuildPropertyModel();
173 if (
object.getType() != ssq::Type::INSTANCE)
return;
174 if (selectedInstance_ >= 0 &&
175 size_t(selectedInstance_) < instances_.size()) {
177 entry.className = selectedClass_;
178 entry.object = instances_[size_t(selectedInstance_)].object;
179 navStack_.push_back(std::move(entry));
181 selectedClass_ = className;
184 current.label = className +
" (nested)";
186 instances_.push_back(std::move(
current));
187 selectedInstance_ = 0;
188 rebuildPropertyModel();
193 if (navStack_.empty())
return;
194 NestedEntry
previous = std::move(navStack_.back());
195 navStack_.pop_back();
196 selectedClass_ =
previous.className;
201 instances_.push_back(std::move(
current));
202 selectedInstance_ = 0;
203 rebuildPropertyModel();
207void Inspector::rebuildPropertyModel() {
208 propertyModel_.reset();
210 const ssq::Object* instance = currentInstance();
213 std::make_unique<property_access::ReflectedPropertyModel>(*rt, *instance);
216PropertyViewOptions Inspector::propertyViewOptions() {
221 options.groupCategories =
false;
222 options.onStructureChange = [
this]() { rebuildHost(); };
226WidgetDesc Inspector::propertyWidget(
const std::string& ownerClass,
227 const ReflectedMember&
member,
228 const ReflectedValue&
value,
229 const ssq::Object& ) {
230 if (
value.kind == ReflectedValueKind::Instance) {
231 const std::string openId =
"open_" + ownerClass +
"_" +
member.name;
232 return button(
"open " +
member.name +
"##" + openId, openId,
233 [
this, memberName =
member.name]() {
234 Runtime* rt = runtime();
236 const ssq::Object* inst = currentInstance();
238 const ssq::Object nested = rt->readObjectProperty(*inst, memberName);
239 if (nested.getType() != ssq::Type::INSTANCE) return;
240 const std::string nestedClass = rt->classNameOf(nested);
241 if (nestedClass.empty()) return;
242 openNested(nestedClass, nested);
251 if (classNames_.empty()) {
253 "No reflected script classes. Run a script that defines classes, "
254 "then call ui.inspectRefresh().",
257 std::vector<WidgetDesc>
toolbar = {
258 text(
"Class",
"lbl_class"),
260 combo(
"##class", classNames_, currentClassIndex(),
"class",
261 [
this](
float index) {
262 const int idx =
static_cast<int>(
index);
263 if (
idx >= 0 &&
idx <
int(classNames_.size()))
269 button(
"Pick##inspector_pick",
"inspector_pick",
271 if (!pickScene_)
return;
272 const ssq::Object picked = pickScene_();
273 if (picked.getType() == ssq::Type::INSTANCE)
277 if (!navStack_.empty()) {
279 button(
"<- Back##inspector_back",
"inspector_back",
280 [
this]() {
back(); }));
286 std::vector<std::string> labels;
287 labels.reserve(instances_.size());
288 for (
const InstanceEntry& entry : instances_) labels.push_back(entry.label);
291 text(
"Instance",
"lbl_instance"),
292 spacer(
"instance_spacer"),
293 combo(
"##instance", labels, selectedInstance_,
"instance",
301 if (rt && selectedInstance_ >= 0 &&
302 size_t(selectedInstance_) < instances_.size()) {
303 const ssq::Object& instance =
304 instances_[size_t(selectedInstance_)].object;
307 std::vector<std::string> chain;
308 std::string
current = selectedClass_;
312 if (!
cls ||
cls->base.empty())
break;
315 for (
const std::string& className : chain) {
318 std::vector<WidgetDesc> props;
320 if (
member.method)
continue;
321 props.push_back(propertyWidget(
325 const bool own = className == selectedClass_;
326 const std::string headerLabel =
327 className + (own ?
"" :
" (base)") +
"##cls_" + className;
330 text(className +
" (no editable properties)",
"cls_" + className));
333 headerLabel, std::move(props),
"cls_" + className, own));
337 children.push_back(
text(
"Instance creation failed for " + selectedClass_,
344void Inspector::rebuildHost() {
346 if (!
host || !
host->get().meta()->visible)
return;
352 if (!
host || !
host->get().meta()->visible)
return;
354 if (!rt || selectedInstance_ < 0 ||
355 size_t(selectedInstance_) >= instances_.size())
357 if (!propertyModel_) rebuildPropertyModel();
358 if (!propertyModel_)
return;
359 propertyModel_->refresh();
graphics::Canvas * previous
const SquirrelValueOptions & options
float(ui::Theme::* member)[4]
static Runtime * runtime()
Active runtime associated with the last expose() call, or nullptr. @ownership Borrowed; the embedding...
EVENGINE_API_FOUNDATION public API.
ssq::Object createInstance(const std::string &name, const std::string &source={})
Creates a live instance of a script class (default constructor).
const ReflectedClass * reflectedClass(const std::string &name) const noexcept
Reflected class by name, or nullptr.
std::string classNameOf(const ssq::Object &instance) const
Name of the script class of a live instance ("" when unknown).
ReflectedValue readProperty(const ssq::Object &instance, const std::string &name) const
Reads one property of a live instance.
bool addInstance()
Creates another instance of the selected class and selects it.
void setPickScene(std::function< ssq::Object()> pickScene)
Registers the scene-pick source used by the Pick button.
void openNested(const std::string &className, const ssq::Object &object)
Navigates into a nested script instance (reference editing).
bool inspectObject(const ssq::Object &object)
Inspects a caller-provided live script instance (the game model).
void refresh()
Re-scans the active Runtime's reflected classes.
bool isOpen() const
True while the inspector host is mounted and visible.
void sync()
Pulls model → view for the selected instance (per-frame).
UIHostHandle host() const noexcept
Returns the mounted inspector host handle, or an empty handle until open().
void open()
Mounts (or updates) the inspector host on the UI ECS world.
void back()
Returns to the previously inspected instance.
bool selectClass(const std::string &name)
Selects a class, auto-creating its first instance.
WidgetDesc build()
Declarative tree of the current selection.
void close()
Hides the inspector host.
bool selectInstance(int index)
Selects an instance by index; false when out of range.
static UIHostHandle createHost(const std::string &name="")
Creates an ECS-owned UI host.
static eve::OptionalRef< UIHost > resolve(UIHostHandle handle) noexcept
Resolves a UI host handle with ECS generation checking.
WidgetDesc collapsingHeader(std::string label, std::vector< WidgetDesc > children, std::string id, bool defaultOpen)
Collapsible header containing child widgets.
WidgetDesc combo(std::string label, std::vector< std::string > options, int selected, std::string id, std::function< void(float)> onValue)
Combo.
WidgetDesc window(std::string title, std::vector< WidgetDesc > children, std::string id)
Top-level window widget with a title bar.
WidgetDesc spacer(std::string id, float grow)
Flexible empty space; default flexGrow=1 so it absorbs free space in a Flex parent.
WidgetDesc buildPropertyField(property_access::IPropertyAccess &model, const std::string &path, const PropertyViewOptions &options)
Generate one field from a model property, or a diagnostic text row.
WidgetDesc toolbar(std::vector< WidgetDesc > children, std::string id)
Horizontal editor command strip; Spacer children absorb free width.
void syncPropertyView(UIHost &host, const property_access::IPropertyAccess &model, const PropertyViewOptions &options)
Pull current model values into an existing compatible UI tree.
WidgetDesc button(std::string label, std::string id, std::function< void()> onClick)
Clickable button; fires onClick.
WidgetDesc row(std::vector< WidgetDesc > children, std::string id)
Horizontal elastic layout row.
WidgetDesc separator(std::string id)
Horizontal separator line.
std::string reflectedFloatString(double value)
Shortest round-trip float formatting (portable).
EVENGINE_API_FOUNDATION_INLINE public API.
EVENGINE_API_FOUNDATION public API.