载入中...
搜索中...
未找到
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
9namespace eve::ui {
10namespace {
11
12constexpr const char* kDatabaseHostName = "eve_database";
13constexpr float kCellWidth = 160.f;
14constexpr float kRowHeight = 30.f;
15
16bool sameValue(const ReflectedValue& a, const ReflectedValue& b) {
17 if (a.kind != b.kind) return false;
18 switch (a.kind) {
19 case ReflectedValueKind::Bool: return a.boolean == b.boolean;
20 case ReflectedValueKind::Integer: return a.integer == b.integer;
21 case ReflectedValueKind::Float: return a.floating == b.floating;
22 case ReflectedValueKind::String: return a.text == b.text;
23 default: return true;
24 }
25}
26
27std::string valueText(const ReflectedValue& value) {
28 switch (value.kind) {
30 return value.boolean ? "true" : "false";
32 return std::to_string(value.integer);
34 return reflectedFloatString(value.floating);
35 }
37 return value.text;
38 default:
39 return {};
40 }
41}
42
44std::string cellLabel(const std::string& memberName, uint64_t entryId) {
45 return memberName + "##db_" + std::to_string(entryId) + "_" + memberName;
46}
47
48} // namespace
49
51 // The ECS host outlives this panel; drop its tree so the stored widget
52 // callbacks (which capture `this`) are released while we are still alive.
53 if (host_) host_->setTree(window("", {}));
54}
55
57 if (!host_) host_ = UIHost::createHost(kDatabaseHostName);
58 host_->setVisible(true);
59 host_->setLayer(90);
60 refresh();
61}
62
64 if (host_) host_->setVisible(false);
65}
66
68 return host_ && host_->meta()->visible;
69}
70
72 classNames_.clear();
73 if (Runtime* rt = ModuleManager::runtime()) {
74 rt->scanClasses();
75 for (const ReflectedClass& cls : rt->reflectedClasses())
76 classNames_.push_back(cls.name);
77 }
78 if (!selectedClass_.empty() &&
79 std::find(classNames_.begin(), classNames_.end(), selectedClass_) ==
80 classNames_.end()) {
81 selectedClass_.clear();
82 }
83 if (selectedClass_.empty() && !classNames_.empty())
84 selectedClass_ = classNames_.front();
85 rebuildCache();
86 rebuildHost();
87}
88
89bool DatabasePanel::selectClass(const std::string& name) {
90 if (std::find(classNames_.begin(), classNames_.end(), name) ==
91 classNames_.end())
92 return false;
93 selectedClass_ = name;
94 rebuildCache();
95 rebuildHost();
96 return true;
97}
98
100 if (selectedClass_.empty()) return 0;
101 const uint64_t id = ObjectRegistry::instance().create(selectedClass_);
102 if (id != 0) {
103 rebuildCache();
104 rebuildHost();
105 }
106 return id;
107}
108
109uint64_t DatabasePanel::registerObject(const ssq::Object& object,
110 const std::string& label) {
111 const uint64_t id =
112 ObjectRegistry::instance().registerObject(selectedClass_, object, label);
113 if (id != 0) {
114 rebuildCache();
115 rebuildHost();
116 }
117 return id;
118}
119
120bool DatabasePanel::unregister(uint64_t id) {
122 if (removed) {
123 rebuildCache();
124 rebuildHost();
125 }
126 return removed;
127}
128
129void DatabasePanel::rebuildCache() {
130 entries_ = ObjectRegistry::instance().entries(selectedClass_);
131 members_.clear();
132 if (const ReflectedClass* cls =
134 selectedClass_)
135 : nullptr) {
136 for (const ReflectedMember& member : cls->members) {
137 if (!member.method) members_.push_back(member);
138 }
139 }
140 cached_.assign(entries_.size(), std::vector<ReflectedValue>(members_.size()));
141 Runtime* rt = ModuleManager::runtime();
142 if (!rt) {
143 entries_.clear();
144 return;
145 }
146 for (size_t r = 0; r < entries_.size(); ++r) {
147 for (size_t c = 0; c < members_.size(); ++c)
148 cached_[r][c] = rt->readProperty(entries_[r].object, members_[c].name);
149 }
150}
151
152WidgetDesc DatabasePanel::cellWidget(const ObjectEntry& entry,
153 const ReflectedMember& member,
154 const ReflectedValue& value) {
155 const std::string id =
156 "cell_" + std::to_string(entry.id) + "_" + member.name;
157 const std::string label = cellLabel(member.name, entry.id);
158 const std::string editor = member.attrString("editor");
159 const std::vector<std::string> options = member.attrOptions("options");
160 WidgetDesc cell;
161 if (value.kind == ReflectedValueKind::Bool) {
162 cell = checkbox(label, value.asBool(), id,
163 [this, entryId = entry.id, name = member.name](bool v) {
164 ReflectedValue out;
165 out.kind = ReflectedValueKind::Bool;
166 out.boolean = v;
167 if (const ObjectEntry* e =
168 ObjectRegistry::instance().entry(entryId))
169 ModuleManager::runtime()->writeProperty(e->object, name,
170 out);
171 });
172 } else if (editor == "combo" && !options.empty()) {
173 int index = 0;
174 const std::string current =
175 value.kind == ReflectedValueKind::String ? value.text : valueText(value);
176 const auto it = std::find(options.begin(), options.end(), current);
177 if (it != options.end()) index = int(it - options.begin());
178 cell = combo(label, options, index, id,
179 [this, entryId = entry.id, name = member.name,
180 kind = value.kind, options](int i) {
181 if (i < 0 || i >= int(options.size())) return;
182 const ObjectEntry* e =
183 ObjectRegistry::instance().entry(entryId);
184 if (!e) return;
185 ReflectedValue out;
186 if (kind == ReflectedValueKind::Integer) {
187 out.kind = ReflectedValueKind::Integer;
188 out.integer =
189 std::strtoll(options[size_t(i)].c_str(), nullptr, 10);
190 } else if (kind == ReflectedValueKind::Float) {
191 out.kind = ReflectedValueKind::Float;
192 out.floating =
193 std::strtod(options[size_t(i)].c_str(), nullptr);
194 } else {
196 out.text = options[size_t(i)];
197 }
198 ModuleManager::runtime()->writeProperty(e->object, name, out);
199 });
200 } else if (value.kind == ReflectedValueKind::Array ||
205 std::string shown;
206 switch (value.kind) {
207 case ReflectedValueKind::Array: shown = "array"; break;
208 case ReflectedValueKind::Table: shown = "table"; break;
209 case ReflectedValueKind::Instance: shown = "instance"; break;
210 default: shown = "null"; break;
211 }
212 cell = text(member.name + " = " + shown, id);
213 } else {
214 cell = inputText(label, valueText(value), id,
215 [this, entryId = entry.id, name = member.name,
216 kind = value.kind](const std::string& text) {
217 const ObjectEntry* e =
218 ObjectRegistry::instance().entry(entryId);
219 if (!e) return;
220 ReflectedValue out;
221 out.kind = ReflectedValueKind::String;
222 out.text = text;
223 ModuleManager::runtime()->writeProperty(e->object, name,
224 out);
225 });
226 }
227 cell.withSize(kCellWidth, 0.f);
228 return cell;
229}
230
231WidgetDesc DatabasePanel::build() {
232 std::vector<WidgetDesc> children;
233 if (classNames_.empty()) {
234 children.push_back(text(
235 "No reflected script classes. Run a script that defines classes, "
236 "then call ui.dbRefresh().",
237 "db_hint"));
238 } else {
239 const auto it =
240 std::find(classNames_.begin(), classNames_.end(), selectedClass_);
241 const int classIndex = it == classNames_.end() ? 0 : int(it - classNames_.begin());
242 children.push_back(row(
243 {
244 text("Class", "db_lbl_class"),
245 spacer("db_class_spacer"),
246 combo("##db_class", classNames_, classIndex, "db_class",
247 [this](int index) {
248 if (index >= 0 && index < int(classNames_.size()))
249 selectClass(classNames_[size_t(index)]);
250 }),
251 button("+", "db_add", [this]() { createInstance(); }),
252 },
253 "db_toolbar"));
254 children.push_back(separator("db_sep"));
255
256 // Header row: one label per reflected member.
257 std::vector<WidgetDesc> header;
258 for (const ReflectedMember& member : members_)
259 header.push_back(text(member.name, "db_hdr_" + member.name).withSize(kCellWidth, 0.f));
260 children.push_back(row(std::move(header), "db_header"));
261
262 // Body: one row per instance, cells bound to the live objects.
263 std::vector<WidgetDesc> rows;
264 for (size_t r = 0; r < entries_.size(); ++r) {
265 const ObjectEntry& entry = entries_[r];
266 std::vector<WidgetDesc> cells;
267 for (size_t c = 0; c < members_.size(); ++c) {
269 entry.object, members_[c].name);
270 cells.push_back(cellWidget(entry, members_[c], value));
271 }
272 cells.push_back(
273 button("x##db_" + std::to_string(entry.id),
274 "db_del_" + std::to_string(entry.id),
275 [this, entryId = entry.id]() { unregister(entryId); }));
276 rows.push_back(row(std::move(cells), "db_row_" + std::to_string(entry.id)));
277 }
278 if (rows.empty()) {
279 children.push_back(text("No instances of " + selectedClass_ +
280 ". Press + to create one.",
281 "db_empty"));
282 } else {
283 children.push_back(scrollList("db_rows", std::move(rows), 0.f, kRowHeight));
284 }
285 }
286 return window("Database", std::move(children), "root");
287}
288
289void DatabasePanel::rebuildHost() {
290 if (!host_ || !host_->meta()->visible) return;
291 host_->setTreeReconcile(build());
292}
293
294void DatabasePanel::sync() {
295 if (!host_ || !host_->meta()->visible) return;
297 if (!rt || entries_.size() != cached_.size()) return;
298 for (size_t r = 0; r < entries_.size(); ++r) {
299 for (size_t c = 0; c < members_.size(); ++c) {
300 const ReflectedValue live =
301 rt->readProperty(entries_[r].object, members_[c].name);
302 if (sameValue(live, cached_[r][c])) continue;
303 cached_[r][c] = live;
304 const std::string id =
305 "cell_" + std::to_string(entries_[r].id) + "_" + members_[c].name;
306 const ReflectedMember& member = members_[c];
307 const std::string editor = member.attrString("editor");
308 const std::vector<std::string> options = member.attrOptions("options");
309 if (live.kind == ReflectedValueKind::Bool) {
310 host_->setCheckedById(id, live.asBool());
311 } else if (editor == "combo" && !options.empty()) {
312 const std::string current =
314 : valueText(live);
315 const auto it = std::find(options.begin(), options.end(), current);
316 host_->setValueById(id, it == options.end() ? 0.f
317 : float(it - options.begin()));
318 } else if (live.kind == ReflectedValueKind::Array ||
323 // read-only cell; nothing to patch
324 } else {
325 host_->setValueTextById(id, valueText(live));
326 }
327 }
328 }
329}
330
331} // namespace eve::ui
Tok kind
std::string value
HSQOBJECT cls
Definition ECS.cpp:21
bool removed
std::string id
uint32_t a
uint32_t b
uint32_t c
const char * name
Definition RockMesh.cpp:21
int v
int children
Definition TreeMesh.cpp:177
static Runtime * runtime()
Active runtime associated with the last expose() call, or nullptr.
Definition Module.cpp:68
bool writeProperty(const ssq::Object &instance, const std::string &name, const ReflectedValue &value) const
Writes one property of a live instance.
Definition Runtime.cpp:699
ReflectedValue readProperty(const ssq::Object &instance, const std::string &name) const
Reads one property of a live instance.
Definition Runtime.cpp:680
bool unregister(uint64_t id)
Removes an entry from the registry and refreshes the grid.
uint64_t createInstance()
Creates + registers an instance of the selected class.
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.
void open()
Mounts (or updates) the database host on the UI ECS world.
uint64_t registerObject(const ssq::Object &object, const std::string &label={})
Registers a live script object (auto-derives class when empty).
void close()
Hides the database host.
uint64_t registerObject(const std::string &className, const ssq::Object &object, const std::string &label={})
Registers an existing live script instance.
uint64_t create(const std::string &className)
Creates a class instance through the active Runtime and registers it.
bool unregister(uint64_t id)
Removes an entry by id (the object itself stays alive).
static ObjectRegistry & instance()
std::vector< ObjectEntry > entries(const std::string &className) const
Entries of a class, in registration order.
static UIHost * createHost(const std::string &name="")
Definition UIHost.cpp:11
void setTree(WidgetDesc root)
Full replace.
Definition UIHost.cpp:26
void setLayer(int layer)
Definition UIHost.h:209
void setVisible(bool v)
Host visibility / layer / modality.
Definition UIHost.h:208
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:439
WidgetDesc text(std::string content, std::string id)
Static text label.
Definition Widget.cpp:235
WidgetDesc scrollList(std::string id, std::vector< WidgetDesc > children, float height, float itemHeight)
Definition Widget.cpp:398
WidgetDesc checkbox(std::string label, bool checked, std::string id, std::function< void(bool)> onToggle)
Checkbox with a label; fires onToggle.
Definition Widget.cpp:279
WidgetDesc separator(std::string id)
Horizontal separator line.
Definition Widget.cpp:271
WidgetDesc combo(std::string label, std::vector< std::string > options, int selected, std::string id, std::function< void(int)> onValue)
Definition Widget.cpp:315
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:363
WidgetDesc window(std::string title, std::vector< WidgetDesc > children, std::string id)
Top-level window widget with a title bar.
Definition Widget.cpp:225
WidgetDesc button(std::string label, std::string id, std::function< void()> onClick)
Clickable button; fires onClick.
Definition Widget.cpp:244
WidgetDesc row(std::vector< WidgetDesc > children, std::string id)
Horizontal elastic layout row.
Definition Widget.cpp:431
std::string reflectedFloatString(double value)
Shortest round-trip float formatting (portable).
Definition Runtime.h:28
@ Array
OT_ARRAY (not yet editable).
@ Table
OT_TABLE (not yet editable).
@ Other
Any other slot kind.
@ None
Missing / null slot.
@ Instance
Nested script instance (not yet editable).
std::string attrString(const std::string &name, const std::string &def={}) const
Raw string attribute value; def when missing.
Definition Runtime.cpp:350
std::vector< std::string > attrOptions(const std::string &name) const
Comma-separated string attribute split into trimmed options.
Definition Runtime.cpp:356
Typed snapshot of one live instance property.
Definition Runtime.h:119
ReflectedValueKind kind
Definition Runtime.h:120
std::string text
Definition Runtime.h:124
bool asBool() const noexcept
Definition Runtime.h:128
One registered live script instance.
Declarative widget description (build once / on dirty → flatten into UIHost::Tree).
Definition Widget.h:13
WidgetDesc & withSize(float w, float h)
Definition Widget.h:113