载入中...
搜索中...
未找到
DatabasePanel.cpp
浏览该文件的文档.
1#include "ui/DatabasePanel.h"
2
3#include "common/Module.h"
4#include "ui/UIHost.h"
5
6#include <algorithm>
7#include <cstdlib>
8#include <squirrel.h>
9
10namespace eve::ui {
11namespace {
12
13constexpr const char* kDatabaseHostName = "eve_database";
14constexpr float kCellWidth = 160.f;
15constexpr float kRowHeight = 30.f;
16
17bool sameValue(const ReflectedValue& a, const ReflectedValue& b) {
18 if (a.kind != b.kind) return false;
19 switch (a.kind) {
20 case ReflectedValueKind::Bool: return a.boolean == b.boolean;
21 case ReflectedValueKind::Integer: return a.integer == b.integer;
22 case ReflectedValueKind::Float: return a.floating == b.floating;
23 case ReflectedValueKind::String: return a.text == b.text;
24 default: return true;
25 }
26}
27
28std::string valueText(const ReflectedValue& value) {
29 switch (value.kind) {
30 case ReflectedValueKind::Bool:
31 return value.boolean ? "true" : "false";
32 case ReflectedValueKind::Integer:
33 return std::to_string(value.integer);
34 case ReflectedValueKind::Float: {
35 return reflectedFloatString(value.floating);
36 }
37 case ReflectedValueKind::String:
38 return value.text;
39 default:
40 return {};
41 }
42}
43
45std::string cellLabel(const std::string& memberName, ObjectHandle handle) {
46 return memberName + "##db_" + std::to_string(handle.packed()) + "_" + memberName;
47}
48
49} // namespace
50
52 // The ECS host outlives this panel; drop its tree so the stored widget
53 // callbacks (which capture `this`) are released while we are still alive.
54 if (auto host = UIHost::resolve(host_)) host->get().setTree(window("", {}));
56 for (auto& entry : entries_) sq_resetobject(&entry.object.getRaw());
57 }
58 entries_.clear();
59}
60
62 auto host = UIHost::resolve(host_);
63 if (!host) {
64 host_ = UIHost::createHost(kDatabaseHostName);
65 host = UIHost::resolve(host_);
66 }
67 if (!host) return;
68 host->get().setVisible(true);
69 host->get().setLayer(90);
70 refresh();
71}
72
74 if (auto host = UIHost::resolve(host_)) host->get().setVisible(false);
75}
76
78 auto host = UIHost::resolve(host_);
79 return host && host->get().meta()->visible;
80}
81
83 classNames_.clear();
84 if (Runtime* rt = ModuleManager::runtime()) {
85 rt->scanClasses();
86 for (const ReflectedClass& cls : rt->reflectedClasses())
87 classNames_.push_back(cls.name);
88 }
89 if (!selectedClass_.empty() &&
90 std::find(classNames_.begin(), classNames_.end(), selectedClass_) ==
91 classNames_.end()) {
92 selectedClass_.clear();
93 }
94 if (selectedClass_.empty() && !classNames_.empty())
95 selectedClass_ = classNames_.front();
96 rebuildCache();
97 rebuildHost();
98}
99
100bool DatabasePanel::selectClass(const std::string& name) {
101 if (std::find(classNames_.begin(), classNames_.end(), name) ==
102 classNames_.end())
103 return false;
104 selectedClass_ = name;
105 rebuildCache();
106 rebuildHost();
107 return true;
108}
109
111 if (selectedClass_.empty()) {
113 eve::DiagnosticCode::InvalidArgument, "database panel has no selected script class"));
114 }
115 auto result = ObjectRegistry::instance().create(selectedClass_);
116 if (!result) return eve::Result<ObjectHandle>::failure(result.status());
117 const ObjectHandle handle = result.value();
118 if (handle.isValid()) {
119 rebuildCache();
120 rebuildHost();
121 }
123}
124
125eve::Result<ObjectHandle> DatabasePanel::registerObject(const ssq::Object& object, const std::string& label) {
126 auto result = ObjectRegistry::instance().registerObject(selectedClass_, object, label);
127 if (!result) return eve::Result<ObjectHandle>::failure(result.status());
128 const ObjectHandle handle = result.value();
129 if (handle.isValid()) {
130 rebuildCache();
131 rebuildHost();
132 }
134}
135
138 if (!result) return eve::Result<void>::failure(result.status());
139 if (result.status().code() == eve::StatusCode::Applied) {
140 rebuildCache();
141 rebuildHost();
142 }
143 return result;
144}
145
146void DatabasePanel::rebuildCache() {
147 entries_ = ObjectRegistry::instance().entries(selectedClass_);
148 members_.clear();
149 if (const ReflectedClass* cls =
151 selectedClass_)
152 : nullptr) {
153 for (const ReflectedMember& member : cls->members) {
154 if (!member.method) members_.push_back(member);
155 }
156 }
157 cached_.assign(entries_.size(), std::vector<ReflectedValue>(members_.size()));
158 Runtime* rt = ModuleManager::runtime();
159 if (!rt) {
160 entries_.clear();
161 return;
162 }
163 for (size_t r = 0; r < entries_.size(); ++r) {
164 for (size_t c = 0; c < members_.size(); ++c)
165 cached_[r][c] = rt->readProperty(entries_[r].object, members_[c].name);
166 }
167}
168
169WidgetDesc DatabasePanel::cellWidget(const ObjectEntry& entry,
170 const ReflectedMember& member,
171 const ReflectedValue& value) {
172 const std::string id = "cell_" + std::to_string(entry.handle.packed()) + "_" + member.name;
173 const std::string label = cellLabel(member.name, entry.handle);
174 const std::string editor = member.attrString("editor");
175 const std::vector<std::string> options = member.attrOptions("options");
176 WidgetDesc cell;
177 if (value.kind == ReflectedValueKind::Bool) {
178 cell = checkbox(label, value.asBool(), id, [this, entryHandle = entry.handle, name = member.name](bool v) {
179 ReflectedValue out;
180 out.kind = ReflectedValueKind::Bool;
181 out.boolean = v;
182 if (const ObjectEntry* e = ObjectRegistry::instance().entry(entryHandle))
183 ModuleManager::runtime()->writeProperty(e->object, name, out);
184 });
185 } else if (editor == "combo" && !options.empty()) {
186 int index = 0;
187 const std::string current =
188 value.kind == ReflectedValueKind::String ? value.text : valueText(value);
189 const auto it = std::find(options.begin(), options.end(), current);
190 if (it != options.end()) index = int(it - options.begin());
191 cell = combo(label, options, index, id,
192 [this, entryHandle = entry.handle, name = member.name, kind = value.kind, options](float i) {
193 const int idx = static_cast<int>(i);
194 if (idx < 0 || idx >= int(options.size())) return;
195 const ObjectEntry* e = ObjectRegistry::instance().entry(entryHandle);
196 if (!e) return;
197 ReflectedValue out;
198 if (kind == ReflectedValueKind::Integer) {
199 out.kind = ReflectedValueKind::Integer;
200 out.integer =
201 std::strtoll(options[size_t(idx)].c_str(), nullptr, 10);
202 } else if (kind == ReflectedValueKind::Float) {
203 out.kind = ReflectedValueKind::Float;
204 out.floating =
205 std::strtod(options[size_t(idx)].c_str(), nullptr);
206 } else {
207 out.kind = ReflectedValueKind::String;
208 out.text = options[size_t(idx)];
209 }
210 ModuleManager::runtime()->writeProperty(e->object, name, out);
211 });
212 } else if (value.kind == ReflectedValueKind::Array ||
213 value.kind == ReflectedValueKind::Table ||
214 value.kind == ReflectedValueKind::Instance ||
215 value.kind == ReflectedValueKind::None ||
216 value.kind == ReflectedValueKind::Other) {
217 std::string shown;
218 switch (value.kind) {
219 case ReflectedValueKind::Array: shown = "array"; break;
220 case ReflectedValueKind::Table: shown = "table"; break;
221 case ReflectedValueKind::Instance: shown = "instance"; break;
222 default: shown = "null"; break;
223 }
224 cell = text(member.name + " = " + shown, id);
225 } else {
226 cell = inputText(
227 label, valueText(value), id,
228 [this, entryHandle = entry.handle, name = member.name, kind = value.kind](const std::string& text) {
229 const ObjectEntry* e = ObjectRegistry::instance().entry(entryHandle);
230 if (!e) return;
231 ReflectedValue out;
232 out.kind = ReflectedValueKind::String;
233 out.text = text;
234 ModuleManager::runtime()->writeProperty(e->object, name, out);
235 });
236 }
237 cell.withSize(kCellWidth, 0.f);
238 return cell;
239}
240
241WidgetDesc DatabasePanel::build() {
242 std::vector<WidgetDesc> children;
243 if (classNames_.empty()) {
244 children.push_back(text(
245 "No reflected script classes. Run a script that defines classes, "
246 "then call ui.dbRefresh().",
247 "db_hint"));
248 } else {
249 const auto it =
250 std::find(classNames_.begin(), classNames_.end(), selectedClass_);
251 const int classIndex = it == classNames_.end() ? 0 : int(it - classNames_.begin());
252 children.push_back(row(
253 {
254 text("Class", "db_lbl_class"),
255 spacer("db_class_spacer"),
256 combo("##db_class", classNames_, classIndex, "db_class",
257 [this](float index) {
258 const int idx = static_cast<int>(index);
259 if (idx >= 0 && idx < int(classNames_.size()))
260 selectClass(classNames_[size_t(idx)]);
261 }),
262 button("+", "db_add", [this]() { createInstance().ignore("create UI database row from button"); }),
263 },
264 "db_toolbar"));
265 children.push_back(separator("db_sep"));
266
267 // Header row: one label per reflected member.
268 std::vector<WidgetDesc> header;
269 for (const ReflectedMember& member : members_)
270 header.push_back(text(member.name, "db_hdr_" + member.name).withSize(kCellWidth, 0.f));
271 children.push_back(row(std::move(header), "db_header"));
272
273 // Body: one row per instance, cells bound to the live objects.
274 std::vector<WidgetDesc> rows;
275 for (size_t r = 0; r < entries_.size(); ++r) {
276 const ObjectEntry& entry = entries_[r];
277 std::vector<WidgetDesc> cells;
278 for (size_t c = 0; c < members_.size(); ++c) {
280 entry.object, members_[c].name);
281 cells.push_back(cellWidget(entry, members_[c], value));
282 }
283 cells.push_back(button(
284 "x##db_" + std::to_string(entry.handle.packed()), "db_del_" + std::to_string(entry.handle.packed()),
285 [this, entryHandle = entry.handle]() { unregister(entryHandle).ignore("delete UI database row"); }));
286 rows.push_back(row(std::move(cells), "db_row_" + std::to_string(entry.handle.packed())));
287 }
288 if (rows.empty()) {
289 children.push_back(text("No instances of " + selectedClass_ +
290 ". Press + to create one.",
291 "db_empty"));
292 } else {
293 children.push_back(scrollList("db_rows", std::move(rows), 0.f, kRowHeight));
294 }
295 }
296 return window("Database", std::move(children), "root");
297}
298
299void DatabasePanel::rebuildHost() {
300 auto host = UIHost::resolve(host_);
301 if (!host || !host->get().meta()->visible) return;
302 host->get().setTreeReconcile(build());
303}
304
305void DatabasePanel::sync() {
306 auto host = UIHost::resolve(host_);
307 if (!host || !host->get().meta()->visible) return;
309 if (!rt || entries_.size() != cached_.size()) return;
310 for (size_t r = 0; r < entries_.size(); ++r) {
311 for (size_t c = 0; c < members_.size(); ++c) {
312 const ReflectedValue live =
313 rt->readProperty(entries_[r].object, members_[c].name);
314 if (sameValue(live, cached_[r][c])) continue;
315 cached_[r][c] = live;
316 const std::string id = "cell_" + std::to_string(entries_[r].handle.packed()) + "_" + members_[c].name;
317 const ReflectedMember& member = members_[c];
318 const std::string editor = member.attrString("editor");
319 const std::vector<std::string> options = member.attrOptions("options");
320 if (live.kind == ReflectedValueKind::Bool) {
321 host->get().setCheckedById(id, live.asBool());
322 } else if (editor == "combo" && !options.empty()) {
323 const std::string current =
324 live.kind == ReflectedValueKind::String ? live.text
325 : valueText(live);
326 const auto it = std::find(options.begin(), options.end(), current);
327 host->get().setValueById(id, it == options.end() ? 0.f : float(it - options.begin()));
328 } else if (live.kind == ReflectedValueKind::Array ||
329 live.kind == ReflectedValueKind::Table ||
330 live.kind == ReflectedValueKind::Instance ||
331 live.kind == ReflectedValueKind::None ||
332 live.kind == ReflectedValueKind::Other) {
333 // read-only cell; nothing to patch
334 } else {
335 host->get().setValueTextById(id, valueText(live));
336 }
337 }
338 }
339}
340
341} // namespace eve::ui
double value
std::string label
HSQOBJECT cls
Definition ECS.cpp:21
int rows
double r
float v
std::int32_t c
std::string text
TokenKind kind
std::string name
MeleePoint3 b
Definition MeleeHit.cpp:41
MeleePoint3 a
Definition MeleeHit.cpp:40
int idx
std::array< PixelCell, kPixelChunkSize *kPixelChunkSize > cells
PrimitiveHandle handle
double current
const SquirrelValueOptions & options
Cell cell
int children
Definition TreeMesh.cpp:295
UIHostHandle host
float(ui::Theme::* member)[4]
std::map< std::string, std::span< const std::uint8_t > > members
uint32_t index
static Diagnostic error(DiagnosticCode code, std::string message, std::string path={}, DiagnosticDetails details={}, std::string source={})
Construct an error diagnostic with the standard error severity.
Definition Diagnostic.h:125
static Runtime * runtime()
Active runtime associated with the last expose() call, or nullptr. @ownership Borrowed; the embedding...
Definition Module.cpp:292
Move-only operation result carrying either a value or Status.
Definition Result.h:155
static Result success(T value)
Construct a successful result owning value.
Definition Result.h:164
static Result failure(Status status)
Construct a failed result from a structured status.
Definition Result.h:175
constexpr std::uint64_t packed() const noexcept
Encodes the two coordinates for a legacy integer boundary.
EVENGINE_API_FOUNDATION public API.
Definition Runtime.h:189
bool writeProperty(const ssq::Object &instance, const std::string &name, const ReflectedValue &value) const
Writes one property of a live instance.
Definition Runtime.cpp:732
ReflectedValue readProperty(const ssq::Object &instance, const std::string &name) const
Reads one property of a live instance.
Definition Runtime.cpp:716
eve::Result< ObjectHandle > registerObject(const ssq::Object &object, const std::string &label={})
Registers a live script object (auto-derives class when empty).
void refresh()
Re-scans reflected classes and refreshes the grid.
bool selectClass(const std::string &name)
Selects a class for the grid.
bool isOpen() const
True while the database host is mounted and visible.
UIHostHandle host() const noexcept
Returns the mounted database host handle, or an empty handle until open().
void open()
Mounts (or updates) the database host on the UI ECS world.
void close()
Hides the database host.
eve::Result< void > unregister(ObjectHandle handle)
Removes an entry from the registry and refreshes the grid.
~DatabasePanel()
Database panel.
eve::Result< ObjectHandle > createInstance()
Creates and registers an instance of the selected class.
eve::Result< ObjectHandle > create(const std::string &className)
Creates a class instance through the active Runtime and registers it.
static ObjectRegistry & instance()
Returns the process-local singleton owned by the UI module.
eve::Result< void > unregister(ObjectHandle handle)
Removes a live entry and advances its slot generation.
std::vector< ObjectEntry > entries(const std::string &className) const
Entries of a class, in registration order.
eve::Result< ObjectHandle > registerObject(const std::string &className, const ssq::Object &object, const std::string &label={})
Registers an existing live script instance.
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 checkbox(std::string label, bool checked, std::string id, std::function< void(bool)> onToggle)
Checkbox with a label; fires onToggle.
Definition Widget.cpp:353
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 inputText(std::string label, std::string value, std::string id, std::function< void(const std::string &)> onChange)
Editable text field; fires onTextChange.
Definition Widget.cpp:453
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
eve::RuntimeHandle< UiObjectHandleTag > ObjectHandle
Generation-qualified identity for one live UI database object.
WidgetDesc row(std::vector< WidgetDesc > children, std::string id)
Horizontal elastic layout row.
Definition Widget.cpp:679
WidgetDesc scrollList(std::string id, std::vector< WidgetDesc > children, float height, float itemHeight)
Scroll list.
Definition Widget.cpp:646
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
EVENGINE_API_FOUNDATION public API.
Definition Runtime.h:152
std::string attrString(const std::string &name, const std::string &def={}) const
Raw string attribute value; def when missing.
Definition Runtime.cpp:358
Typed snapshot of one live instance property.
Definition Runtime.h:132
ReflectedValueKind kind
Definition Runtime.h:133
std::string text
Definition Runtime.h:137
bool asBool() const noexcept
As bool.
Definition Runtime.h:142
One registered live script instance.
ssq::Object object
Rooted script instance owned by the registry while live.
ObjectHandle handle
Generation-qualified handle for the live registry slot.
Declarative widget description (build once / on dirty → flatten into UIHost::Tree).
Definition Widget.h:15