载入中...
搜索中...
未找到
Inspector.cpp
浏览该文件的文档.
1#include "ui/Inspector.h"
2
3#include "common/Module.h"
4#include "ui/PropertyView.h"
5#include "ui/UIHost.h"
6
7#include <algorithm>
8#include <cstdlib>
9
10namespace eve::ui {
11namespace {
12
13constexpr const char* kInspectorHostName = "eve_inspector";
14
15std::string valueText(const ReflectedValue& value) {
16 switch (value.kind) {
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: {
22 return reflectedFloatString(value.floating);
23 }
24 case ReflectedValueKind::String:
25 return value.text;
26 default:
27 return {};
28 }
29}
30
31} // namespace
32
34 // The ECS host outlives this panel; drop its tree so the stored widget
35 // callbacks (which capture `this`) are released while we are still alive.
36 if (auto host = UIHost::resolve(host_)) host->get().setTree(window("", {}));
37}
38
39Runtime* Inspector::runtime() const {
41}
42
43const ssq::Object* Inspector::currentInstance() const {
44 if (selectedInstance_ < 0 ||
45 size_t(selectedInstance_) >= instances_.size())
46 return nullptr;
47 return &instances_[size_t(selectedInstance_)].object;
48}
49
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());
53}
54
56 classNames_.clear();
57 if (Runtime* rt = runtime()) {
58 rt->scanClasses(); // picks up dofile()/compilestring() defined classes
59 for (const ReflectedClass& cls : rt->reflectedClasses())
60 classNames_.push_back(cls.name);
61 }
62 // Drop a selection whose class vanished (e.g. script unloaded).
63 if (!selectedClass_.empty() &&
64 std::find(classNames_.begin(), classNames_.end(), selectedClass_) ==
65 classNames_.end()) {
66 selectedClass_.clear();
67 instances_.clear();
68 selectedInstance_ = -1;
69 }
70 if (selectedClass_.empty() && !classNames_.empty())
71 selectClass(classNames_.front());
72 rebuildPropertyModel();
73 rebuildHost();
74}
75
77 auto host = UIHost::resolve(host_);
78 if (!host) {
79 host_ = UIHost::createHost(kInspectorHostName);
80 host = UIHost::resolve(host_);
81 }
82 if (!host) return;
83 host->get().setVisible(true);
84 host->get().setLayer(100);
85 refresh();
86}
87
89 if (auto host = UIHost::resolve(host_)) host->get().setVisible(false);
90}
91
92bool Inspector::isOpen() const {
93 auto host = UIHost::resolve(host_);
94 return host && host->get().meta()->visible;
95}
96
97bool Inspector::selectClass(const std::string& name) {
98 Runtime* rt = runtime();
99 if (!rt ||
100 std::find(classNames_.begin(), classNames_.end(), name) == classNames_.end())
101 return false;
102 navStack_.clear();
103 selectedClass_ = name;
104 instances_.clear();
105 selectedInstance_ = -1;
106 try {
107 ssq::Object instance = rt->createInstance(name);
108 InstanceEntry entry;
109 entry.label = name + " #1";
110 entry.object = instance;
111 instances_.push_back(std::move(entry));
112 selectedInstance_ = 0;
113 } catch (...) {
114 // Keep the class selected; the panel shows "no instances".
115 }
116 rebuildPropertyModel();
117 rebuildHost();
118 return selectedInstance_ >= 0;
119}
120
121bool Inspector::inspectObject(const ssq::Object& object) {
122 Runtime* rt = runtime();
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) ==
127 classNames_.end())
128 return false;
129 navStack_.clear();
130 selectedClass_ = className;
131 instances_.clear();
132 InstanceEntry entry;
133 entry.label = className + " (live)";
134 entry.object = object;
135 instances_.push_back(std::move(entry));
136 selectedInstance_ = 0;
137 rebuildPropertyModel();
138 rebuildHost();
139 return true;
140}
141
143 Runtime* rt = runtime();
144 if (!rt || selectedClass_.empty()) return false;
145 try {
146 ssq::Object instance = rt->createInstance(selectedClass_);
147 InstanceEntry entry;
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();
153 rebuildHost();
154 return true;
155 } catch (...) {
156 return false;
157 }
158}
159
160void Inspector::setPickScene(std::function<ssq::Object()> pickScene) {
161 pickScene_ = std::move(pickScene);
162}
163
165 if (index < 0 || index >= int(instances_.size())) return false;
166 selectedInstance_ = index;
167 rebuildPropertyModel();
168 rebuildHost();
169 return true;
170}
171
172void Inspector::openNested(const std::string& className, const ssq::Object& object) {
173 if (object.getType() != ssq::Type::INSTANCE) return;
174 if (selectedInstance_ >= 0 &&
175 size_t(selectedInstance_) < instances_.size()) {
176 NestedEntry entry;
177 entry.className = selectedClass_;
178 entry.object = instances_[size_t(selectedInstance_)].object;
179 navStack_.push_back(std::move(entry));
180 }
181 selectedClass_ = className;
182 instances_.clear();
183 InstanceEntry current;
184 current.label = className + " (nested)";
185 current.object = object;
186 instances_.push_back(std::move(current));
187 selectedInstance_ = 0;
188 rebuildPropertyModel();
189 rebuildHost();
190}
191
193 if (navStack_.empty()) return;
194 NestedEntry previous = std::move(navStack_.back());
195 navStack_.pop_back();
196 selectedClass_ = previous.className;
197 instances_.clear();
198 InstanceEntry current;
199 current.label = previous.className + " #1";
200 current.object = previous.object;
201 instances_.push_back(std::move(current));
202 selectedInstance_ = 0;
203 rebuildPropertyModel();
204 rebuildHost();
205}
206
207void Inspector::rebuildPropertyModel() {
208 propertyModel_.reset();
209 Runtime* rt = runtime();
210 const ssq::Object* instance = currentInstance();
211 if (rt && instance)
212 propertyModel_ =
213 std::make_unique<property_access::ReflectedPropertyModel>(*rt, *instance);
214}
215
216PropertyViewOptions Inspector::propertyViewOptions() {
217 PropertyViewOptions options;
218 options.idPrefix = "prop_";
219 options.showAdvanced = true;
220 options.showEditorOnly = true;
221 options.groupCategories = false;
222 options.onStructureChange = [this]() { rebuildHost(); };
223 return options;
224}
225
226WidgetDesc Inspector::propertyWidget(const std::string& ownerClass,
227 const ReflectedMember& member,
228 const ReflectedValue& value,
229 const ssq::Object& /*instance*/) {
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();
235 if (!rt) return;
236 const ssq::Object* inst = currentInstance();
237 if (!inst) return;
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);
243 });
244 }
245 if (propertyModel_) return buildPropertyField(*propertyModel_, member.name, propertyViewOptions());
246 return text(member.name + " = " + valueText(value), "prop_" + member.name);
247}
248
250 std::vector<WidgetDesc> children;
251 if (classNames_.empty()) {
252 children.push_back(text(
253 "No reflected script classes. Run a script that defines classes, "
254 "then call ui.inspectRefresh().",
255 "hint"));
256 } else {
257 std::vector<WidgetDesc> toolbar = {
258 text("Class", "lbl_class"),
259 spacer("class_spacer"),
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()))
264 selectClass(classNames_[size_t(idx)]);
265 }),
266 };
267 if (pickScene_) {
268 toolbar.push_back(
269 button("Pick##inspector_pick", "inspector_pick",
270 [this]() {
271 if (!pickScene_) return;
272 const ssq::Object picked = pickScene_();
273 if (picked.getType() == ssq::Type::INSTANCE)
274 inspectObject(picked);
275 }));
276 }
277 if (!navStack_.empty()) {
278 toolbar.insert(toolbar.begin(),
279 button("<- Back##inspector_back", "inspector_back",
280 [this]() { back(); }));
281 }
282 children.push_back(row(
283 std::move(toolbar),
284 "classrow"));
285
286 std::vector<std::string> labels;
287 labels.reserve(instances_.size());
288 for (const InstanceEntry& entry : instances_) labels.push_back(entry.label);
289 children.push_back(row(
290 {
291 text("Instance", "lbl_instance"),
292 spacer("instance_spacer"),
293 combo("##instance", labels, selectedInstance_, "instance",
294 [this](float index) { selectInstance(static_cast<int>(index)); }),
295 button("+", "add_instance", [this]() { addInstance(); }),
296 },
297 "instancerow"));
298 children.push_back(separator("inspector_sep"));
299
300 Runtime* rt = runtime();
301 if (rt && selectedInstance_ >= 0 &&
302 size_t(selectedInstance_) < instances_.size()) {
303 const ssq::Object& instance =
304 instances_[size_t(selectedInstance_)].object;
305 // Inheritance chain: own class first, then bases (parent props are
306 // grouped under their owning class header).
307 std::vector<std::string> chain;
308 std::string current = selectedClass_;
309 while (!current.empty()) {
310 chain.push_back(current);
312 if (!cls || cls->base.empty()) break;
313 current = cls->base;
314 }
315 for (const std::string& className : chain) {
316 const ReflectedClass* cls = rt->reflectedClass(className);
317 if (!cls) continue;
318 std::vector<WidgetDesc> props;
319 for (const ReflectedMember& member : cls->members) {
320 if (member.method) continue;
321 props.push_back(propertyWidget(
322 className, member, rt->readProperty(instance, member.name),
323 instance));
324 }
325 const bool own = className == selectedClass_;
326 const std::string headerLabel =
327 className + (own ? "" : " (base)") + "##cls_" + className;
328 if (props.empty()) {
329 children.push_back(
330 text(className + " (no editable properties)", "cls_" + className));
331 } else {
332 children.push_back(collapsingHeader(
333 headerLabel, std::move(props), "cls_" + className, own));
334 }
335 }
336 } else if (rt) {
337 children.push_back(text("Instance creation failed for " + selectedClass_,
338 "instance_error"));
339 }
340 }
341 return window("Inspector", std::move(children), "root");
342}
343
344void Inspector::rebuildHost() {
345 auto host = UIHost::resolve(host_);
346 if (!host || !host->get().meta()->visible) return;
347 host->get().setTreeReconcile(build());
348}
349
351 auto host = UIHost::resolve(host_);
352 if (!host || !host->get().meta()->visible) return;
353 Runtime* rt = runtime();
354 if (!rt || selectedInstance_ < 0 ||
355 size_t(selectedInstance_) >= instances_.size())
356 return;
357 if (!propertyModel_) rebuildPropertyModel();
358 if (!propertyModel_) return;
359 propertyModel_->refresh();
360 syncPropertyView(host->get(), *propertyModel_, propertyViewOptions());
361}
362
363} // namespace eve::ui
double value
HSQOBJECT cls
Definition ECS.cpp:21
std::string text
std::string name
graphics::Canvas * previous
int idx
double current
const SquirrelValueOptions & options
Json object
int children
Definition TreeMesh.cpp:295
float(ui::Theme::* member)[4]
uint32_t index
static Runtime * runtime()
Active runtime associated with the last expose() call, or nullptr. @ownership Borrowed; the embedding...
Definition Module.cpp:292
EVENGINE_API_FOUNDATION public API.
Definition Runtime.h:189
ssq::Object createInstance(const std::string &name, const std::string &source={})
Creates a live instance of a script class (default constructor).
Definition Runtime.cpp:629
const ReflectedClass * reflectedClass(const std::string &name) const noexcept
Reflected class by name, or nullptr.
Definition Runtime.cpp:1092
std::string classNameOf(const ssq::Object &instance) const
Name of the script class of a live instance ("" when unknown).
Definition Runtime.cpp:1163
ReflectedValue readProperty(const ssq::Object &instance, const std::string &name) const
Reads one property of a live instance.
Definition Runtime.cpp:716
bool addInstance()
Creates another instance of the selected class and selects it.
~Inspector()
Inspector.
Definition Inspector.cpp:33
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.
Definition Inspector.cpp:55
bool isOpen() const
True while the inspector host is mounted and visible.
Definition Inspector.cpp:92
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().
Definition Inspector.h:61
void open()
Mounts (or updates) the inspector host on the UI ECS world.
Definition Inspector.cpp:76
void back()
Returns to the previously inspected instance.
bool selectClass(const std::string &name)
Selects a class, auto-creating its first instance.
Definition Inspector.cpp:97
WidgetDesc build()
Declarative tree of the current selection.
void close()
Hides the inspector host.
Definition Inspector.cpp:88
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.
Definition UIHost.cpp:48
static eve::OptionalRef< UIHost > resolve(UIHostHandle handle) noexcept
Resolves a UI host handle with ECS generation checking.
Definition UIHost.cpp:61
WidgetDesc collapsingHeader(std::string label, std::vector< WidgetDesc > children, std::string id, bool defaultOpen)
Collapsible header containing child widgets.
Definition Widget.cpp:623
WidgetDesc combo(std::string label, std::vector< std::string > options, int selected, std::string id, std::function< void(float)> onValue)
Combo.
Definition Widget.cpp:405
WidgetDesc window(std::string title, std::vector< WidgetDesc > children, std::string id)
Top-level window widget with a title bar.
Definition Widget.cpp:292
WidgetDesc spacer(std::string id, float grow)
Flexible empty space; default flexGrow=1 so it absorbs free space in a Flex parent.
Definition Widget.cpp:697
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.
Definition Widget.cpp:563
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.
Definition Widget.cpp:311
WidgetDesc row(std::vector< WidgetDesc > children, std::string id)
Horizontal elastic layout row.
Definition Widget.cpp:679
WidgetDesc separator(std::string id)
Horizontal separator line.
Definition Widget.cpp:345
std::string reflectedFloatString(double value)
Shortest round-trip float formatting (portable).
Definition Runtime.h:33
EVENGINE_API_FOUNDATION_INLINE public API.
Definition Runtime.h:172
std::string base
Definition Runtime.h:175
EVENGINE_API_FOUNDATION public API.
Definition Runtime.h:152
Declarative widget description (build once / on dirty → flatten into UIHost::Tree).
Definition Widget.h:15