载入中...
搜索中...
未找到
PropertyView.cpp
浏览该文件的文档.
1#include "ui/PropertyView.h"
2
3#include <algorithm>
4#include <cerrno>
5#include <charconv>
6#include <cstdint>
7#include <cstdlib>
8#include <string_view>
9#include <utility>
10#include <vector>
11
12namespace eve::ui {
13namespace {
14
15using eve::Value;
16using property_access::PropertyDescriptor;
19
20std::string sanitize(std::string value) {
21 for (char &character : value)
22 if (character == ' ' || character == '.' || character == '[' || character == ']' ||
23 character == '/')
24 character = '_';
25 return value;
26}
27
28std::string displayName(const PropertyDescriptor &property) {
29 if (!property.displayName.empty()) return property.displayName;
30 const std::size_t separator = property.path.find_last_of("./");
31 return separator == std::string::npos ? property.path : property.path.substr(separator + 1);
32}
33
34std::string valueText(const Value &value) {
35 if (const auto *text = value.getIf<std::string>()) return *text;
36 if (const auto *boolean = value.getIf<bool>()) return *boolean ? "true" : "false";
37 if (const auto *integer = value.getIf<std::int64_t>()) return std::to_string(*integer);
38 if (const auto *number = value.getIf<double>()) return std::to_string(*number);
39 if (const auto *array = value.getIf<Value::Array>())
40 return std::to_string(array->size()) + " items";
41 if (const auto *object = value.getIf<Value::Object>())
42 return std::to_string(object->size()) + " fields";
43 return "null";
44}
45
46double numericValue(const Value &value) {
47 if (const auto *number = value.getIf<double>()) return *number;
48 if (const auto *integer = value.getIf<std::int64_t>()) return static_cast<double>(*integer);
49 return 0.0;
50}
51
52bool parseNumber(const std::string &text, bool integer, Value &out) {
53 if (integer) {
54 std::int64_t parsed = 0;
55 const auto converted = std::from_chars(text.data(), text.data() + text.size(), parsed);
56 if (converted.ec != std::errc() || converted.ptr != text.data() + text.size()) return false;
57 out = Value(parsed);
58 return true;
59 }
60 char *end = nullptr;
61 errno = 0;
62 const double parsed = std::strtod(text.c_str(), &end);
63 if (text.empty() || errno == ERANGE || end != text.c_str() + text.size()) return false;
64 out = Value(parsed);
65 return true;
66}
67
68Value::Array componentArray(const Value &value, std::size_t count) {
69 Value::Array result(count, Value(0.0));
70 if (const auto *items = value.getIf<Value::Array>()) {
71 for (std::size_t index = 0; index < count && index < items->size(); ++index)
72 result[index] = Value(numericValue((*items)[index]));
73 }
74 return result;
75}
76
77AccessibilityRole accessibilityRole(PropertyKind kind, bool readOnly) {
78 if (readOnly) return AccessibilityRole::Text;
79 switch (kind) {
80 case PropertyKind::Bool: return AccessibilityRole::Checkbox;
81 case PropertyKind::Integer:
82 case PropertyKind::Number: return AccessibilityRole::Slider;
83 case PropertyKind::Action: return AccessibilityRole::Button;
84 case PropertyKind::Enum: return AccessibilityRole::List;
85 case PropertyKind::Color: return AccessibilityRole::Slider;
86 case PropertyKind::String:
87 case PropertyKind::AssetRef:
88 case PropertyKind::ObjectRef:
89 case PropertyKind::Auto: return AccessibilityRole::TextInput;
90 default: return AccessibilityRole::Region;
91 }
92}
93
94WidgetDesc scalarElementEditor(const std::string &elementId, const std::string &elementLabel,
95 const Value &element, std::function<void(Value)> writeElement) {
96 if (const auto *boolean = element.getIf<bool>()) {
97 return checkbox(elementLabel, *boolean, elementId,
98 [writeElement = std::move(writeElement)](bool next) { writeElement(Value(next)); });
99 }
100 if (element.getIf<std::int64_t>() || element.getIf<double>()) {
101 return inputText(elementLabel, valueText(element), elementId,
102 [writeElement = std::move(writeElement),
103 integer = element.getIf<std::int64_t>() != nullptr](const std::string &next) {
104 Value parsed;
105 if (parseNumber(next, integer, parsed)) writeElement(std::move(parsed));
106 });
107 }
108 if (element.getIf<std::string>() || element.isNull()) {
109 return inputText(elementLabel, valueText(element), elementId,
110 [writeElement = std::move(writeElement)](const std::string &next) {
111 writeElement(Value(next));
112 });
113 }
114 return text(elementLabel + " = " + valueText(element), elementId);
115}
116
117WidgetDesc makeCompositeEditor(property_access::IPropertyAccess &model, const PropertyViewOptions &options,
118 const PropertyDescriptor &property, const Value &value,
119 const std::vector<std::string> &labels) {
120 const std::string id = propertyWidgetId(options, property.path);
121 Value::Array items = componentArray(value, labels.size());
122 std::vector<WidgetDesc> children;
123 children.push_back(text(displayName(property), id + "_label"));
124 for (std::size_t index = 0; index < labels.size(); ++index) {
125 const std::string componentId = id + "_" + labels[index];
126 const std::string path = property.path;
127 const std::size_t component = index;
128 const bool useSlider = property.numeric.minimum && property.numeric.maximum;
129 if (useSlider) {
130 children.push_back(slider(
131 labels[index], static_cast<float>(numericValue(items[index])),
132 static_cast<float>(*property.numeric.minimum), static_cast<float>(*property.numeric.maximum),
133 componentId, [&model, path, component, count = labels.size()](float next) {
134 const std::optional<Value> current = model.read(path);
135 Value::Array updated =
136 componentArray(current ? *current : Value(Value::Array{}), count);
137 updated[component] = Value(static_cast<double>(next));
138 model.write(path, Value(std::move(updated)));
139 }));
140 } else {
141 children.push_back(inputText(
142 labels[index], valueText(items[index]), componentId,
143 [&model, path, component, count = labels.size()](const std::string &next) {
144 Value parsed;
145 if (!parseNumber(next, false, parsed)) return;
146 const std::optional<Value> current = model.read(path);
148 componentArray(current ? *current : Value(Value::Array{}), count);
149 updated[component] = std::move(parsed);
150 model.write(path, Value(std::move(updated)));
151 }));
152 }
153 }
154 return row(std::move(children), id);
155}
156
157WidgetDesc makeArrayEditor(property_access::IPropertyAccess &model, const PropertyViewOptions &options,
158 const PropertyDescriptor &property, const Value &value) {
159 const std::string id = propertyWidgetId(options, property.path);
160 Value::Array items;
161 if (const auto *array = value.getIf<Value::Array>()) items = *array;
162 std::vector<WidgetDesc> rows;
163 for (std::size_t index = 0; index < items.size(); ++index) {
164 const std::string elementId = id + "_" + std::to_string(index);
165 const std::string elementLabel = displayName(property) + "[" + std::to_string(index) + "]";
166 const std::string path = property.path;
167 const std::size_t elementIndex = index;
168 WidgetDesc cell = scalarElementEditor(
169 elementId, elementLabel, items[index],
170 [&model, path, elementIndex](Value next) {
171 const std::optional<Value> current = model.read(path);
173 if (const auto *array = current ? current->getIf<Value::Array>() : nullptr)
174 updated = *array;
175 if (elementIndex >= updated.size()) updated.resize(elementIndex + 1);
176 updated[elementIndex] = std::move(next);
177 model.write(path, Value(std::move(updated)));
178 });
179 const auto onStructure = options.onStructureChange;
180 rows.push_back(row(
181 {std::move(cell),
182 button("x##" + elementId + "_del", elementId + "_del",
183 [&model, path, elementIndex, onStructure]() {
184 const std::optional<Value> current = model.read(path);
186 if (const auto *array = current ? current->getIf<Value::Array>() : nullptr)
187 updated = *array;
188 if (elementIndex < updated.size())
189 updated.erase(updated.begin() + static_cast<std::ptrdiff_t>(elementIndex));
190 model.write(path, Value(std::move(updated)));
191 if (onStructure) onStructure();
192 })},
193 elementId + "_row"));
194 }
195 const std::string path = property.path;
196 const auto onStructure = options.onStructureChange;
197 rows.push_back(row(
198 {button("+##" + id + "_add", id + "_add",
199 [&model, path, onStructure]() {
200 const std::optional<Value> current = model.read(path);
202 if (const auto *array = current ? current->getIf<Value::Array>() : nullptr)
203 updated = *array;
204 updated.emplace_back(std::string{});
205 model.write(path, Value(std::move(updated)));
206 if (onStructure) onStructure();
207 })},
208 id + "_addrow"));
209 return collapsingHeader(displayName(property) + " (array[" + std::to_string(items.size()) + "])##" + id,
210 std::move(rows), id, false);
211}
212
213WidgetDesc makeMapEditor(property_access::IPropertyAccess &model, const PropertyViewOptions &options,
214 const PropertyDescriptor &property, const Value &value, bool allowMutate) {
215 const std::string id = propertyWidgetId(options, property.path);
217 if (const auto *object = value.getIf<Value::Object>()) fields = *object;
218 std::vector<WidgetDesc> rows;
219 for (const auto &[key, entry] : fields) {
220 const std::string elementId = id + "_" + sanitize(key);
221 const std::string path = property.path;
222 const std::string fieldKey = key;
223 WidgetDesc cell = scalarElementEditor(
224 elementId, key, entry, [&model, path, fieldKey](Value next) {
225 const std::optional<Value> current = model.read(path);
227 if (const auto *object = current ? current->getIf<Value::Object>() : nullptr)
228 updated = *object;
229 updated[fieldKey] = std::move(next);
230 model.write(path, Value(std::move(updated)));
231 });
232 if (allowMutate) {
233 const auto onStructure = options.onStructureChange;
234 rows.push_back(row(
235 {std::move(cell),
236 button("x##" + elementId + "_del", elementId + "_del",
237 [&model, path, fieldKey, onStructure]() {
238 const std::optional<Value> current = model.read(path);
240 if (const auto *object = current ? current->getIf<Value::Object>() : nullptr)
241 updated = *object;
242 updated.erase(fieldKey);
243 model.write(path, Value(std::move(updated)));
244 if (onStructure) onStructure();
245 })},
246 elementId + "_row"));
247 } else {
248 rows.push_back(std::move(cell));
249 }
250 }
251 if (allowMutate) {
252 const std::string path = property.path;
253 const auto onStructure = options.onStructureChange;
254 rows.push_back(row(
255 {button("+##" + id + "_add", id + "_add",
256 [&model, path, onStructure]() {
257 const std::optional<Value> current = model.read(path);
259 if (const auto *object = current ? current->getIf<Value::Object>() : nullptr)
260 updated = *object;
261 std::string key = "key" + std::to_string(updated.size());
262 std::size_t suffix = 0;
263 while (updated.contains(key))
264 key = "key" + std::to_string(updated.size() + (++suffix));
265 updated.emplace(key, Value(std::string{}));
266 model.write(path, Value(std::move(updated)));
267 if (onStructure) onStructure();
268 })},
269 id + "_addrow"));
270 }
271 const std::string kindLabel = property.kind == PropertyKind::Struct ? "struct" : "map";
272 return collapsingHeader(displayName(property) + " (" + kindLabel + ")##" + id, std::move(rows), id, false);
273}
274
275WidgetDesc makePropertyField(property_access::IPropertyAccess &model, const PropertyViewOptions &options,
276 const PropertyDescriptor &property, const Value &value) {
277 const std::string id = propertyWidgetId(options, property.path);
278 const std::string label = displayName(property);
279 const bool readOnly =
280 property_access::hasFlag(property.flags, PropertyFlag::ReadOnly) || property.kind == PropertyKind::ReadOnlyText;
281 WidgetDesc result;
282
283 if (readOnly) {
284 if (property.kind == PropertyKind::Array || property.kind == PropertyKind::Map ||
285 property.kind == PropertyKind::Struct)
286 result = collapsingHeader(label + ": " + valueText(value) + "##" + id,
287 {text(valueText(value), id + "_summary")}, id, false);
288 else
289 result = text(label + ": " + valueText(value), id);
290 } else {
291 switch (property.kind) {
292 case PropertyKind::Bool: {
293 const bool current = value.getIf<bool>() ? *value.getIf<bool>() : false;
294 const std::string path = property.path;
295 result = checkbox(label, current, id, [&model, path](bool next) {
296 model.write(path, Value(next));
297 });
298 break;
299 }
300 case PropertyKind::Integer:
301 case PropertyKind::Number: {
302 const bool integer = property.kind == PropertyKind::Integer;
303 const std::string path = property.path;
304 if (property.numeric.minimum && property.numeric.maximum) {
305 result = slider(label, static_cast<float>(numericValue(value)),
306 static_cast<float>(*property.numeric.minimum),
307 static_cast<float>(*property.numeric.maximum), id,
308 [&model, path, integer](float next) {
309 if (integer)
310 model.write(path, Value(static_cast<std::int64_t>(next)));
311 else
312 model.write(path, Value(static_cast<double>(next)));
313 });
314 } else {
315 result = inputText(label, valueText(value), id,
316 [&model, path, integer](const std::string &next) {
317 Value parsed;
318 if (parseNumber(next, integer, parsed))
319 model.write(path, std::move(parsed));
320 });
321 }
322 break;
323 }
324 case PropertyKind::Enum: {
325 const std::string selected = valueText(value);
326 int selectedIndex = 0;
327 for (std::size_t index = 0; index < property.choices.size(); ++index)
328 if (property.choices[index] == selected) selectedIndex = static_cast<int>(index);
329 const std::string path = property.path;
330 const std::vector<std::string> choices = property.choices;
331 result = combo(label, choices, selectedIndex, id,
332 [&model, path, choices](float next) {
333 const int index = static_cast<int>(next);
334 if (index >= 0 && static_cast<std::size_t>(index) < choices.size())
335 model.write(path, Value(choices[static_cast<std::size_t>(index)]));
336 });
337 break;
338 }
339 case PropertyKind::Action: {
340 const std::string path = property.path;
341 result = button(label, id, [&model, path]() { model.write(path, Value(true)); });
342 break;
343 }
344 case PropertyKind::String:
345 case PropertyKind::AssetRef:
346 case PropertyKind::ObjectRef:
347 case PropertyKind::Auto: {
348 const std::string path = property.path;
349 result = inputText(label, valueText(value), id,
350 [&model, path](const std::string &next) {
351 model.write(path, Value(next));
352 });
353 break;
354 }
355 case PropertyKind::Color:
356 result = makeCompositeEditor(model, options, property, value, {"r", "g", "b", "a"});
357 break;
358 case PropertyKind::Vec2:
359 result = makeCompositeEditor(model, options, property, value, {"x", "y"});
360 break;
361 case PropertyKind::Vec3:
362 result = makeCompositeEditor(model, options, property, value, {"x", "y", "z"});
363 break;
364 case PropertyKind::Vec4:
365 result = makeCompositeEditor(model, options, property, value, {"x", "y", "z", "w"});
366 break;
367 case PropertyKind::Array:
368 result = makeArrayEditor(model, options, property, value);
369 break;
370 case PropertyKind::Map:
371 result = makeMapEditor(model, options, property, value, true);
372 break;
373 case PropertyKind::Struct:
374 result = makeMapEditor(model, options, property, value, true);
375 break;
376 case PropertyKind::ReadOnlyText:
377 result = text(label + ": " + valueText(value), id);
378 break;
379 }
380 }
381 result.tooltip = property.description;
382 result.accessibilityRole = accessibilityRole(property.kind, readOnly);
383 result.accessibilityName = label;
384 result.accessibilityDescription = property.description;
385 if (readOnly) {
386 result.focusMode = FocusMode::None;
387 result.mouseFilter = MouseFilter::Ignore;
388 }
389 return result;
390}
391
392bool visible(const PropertyDescriptor &property, const PropertyViewOptions &options) {
393 if (!options.showAdvanced && property_access::hasFlag(property.flags, PropertyFlag::Advanced)) return false;
394 if (!options.showEditorOnly && property_access::hasFlag(property.flags, PropertyFlag::EditorOnly)) return false;
395 if (!options.showReadOnly && (property_access::hasFlag(property.flags, PropertyFlag::ReadOnly) ||
396 property.kind == PropertyKind::ReadOnlyText))
397 return false;
398 return true;
399}
400
401void syncComposite(UIHost &host, const std::string &id, const Value &value,
402 const std::vector<std::string> &labels, const PropertyDescriptor &property) {
403 Value::Array items = componentArray(value, labels.size());
404 for (std::size_t index = 0; index < labels.size(); ++index) {
405 const std::string componentId = id + "_" + labels[index];
406 if (property.numeric.minimum && property.numeric.maximum)
407 host.setValueById(componentId, static_cast<float>(numericValue(items[index])));
408 else
409 host.setValueTextById(componentId, valueText(items[index]));
410 }
411}
412
413} // namespace
414
415std::string propertyWidgetId(const PropertyViewOptions &options, const std::string &path) {
416 return options.idPrefix + sanitize(path);
417}
418
421 auto property = model.schema().find(path);
422 const std::optional<Value> value = model.read(path);
423 if (!property || !value)
424 return text(path + ": unavailable", propertyWidgetId(options, path))
425 .withFocusMode(FocusMode::None)
426 .withMouseFilter(MouseFilter::Ignore)
427 .withAccessibility(AccessibilityRole::Text, path, "Property is unavailable");
428 return makePropertyField(model, options, property->get(), *value);
429}
430
432 std::vector<WidgetDesc> children;
433 if (!options.title.empty()) children.push_back(sectionHeader(options.title, options.idPrefix + "title"));
434
435 std::string category;
436 for (const PropertyDescriptor &property : model.schema().properties) {
437 if (!visible(property, options)) continue;
438 const std::optional<Value> value = model.read(property.path);
439 if (!value) continue;
440 if (options.groupCategories && !property.category.empty() && property.category != category) {
441 category = property.category;
442 children.push_back(sectionHeader(category, options.idPrefix + "category/" + sanitize(category)));
443 }
444 children.push_back(makePropertyField(model, options, property, *value));
445 }
446 return column(std::move(children), options.idPrefix + "root");
447}
448
450 for (const PropertyDescriptor &property : model.schema().properties) {
451 const std::optional<Value> value = model.read(property.path);
452 if (!value) continue;
453 const std::string id = propertyWidgetId(options, property.path);
454 const bool readOnly = property_access::hasFlag(property.flags, PropertyFlag::ReadOnly) ||
455 property.kind == PropertyKind::ReadOnlyText;
456 if (readOnly) {
457 host.setTextById(id, displayName(property) + ": " + valueText(*value));
458 continue;
459 }
460 switch (property.kind) {
461 case PropertyKind::Bool:
462 if (const auto *boolean = value->getIf<bool>()) host.setCheckedById(id, *boolean);
463 break;
464 case PropertyKind::Enum: {
465 const std::string selected = valueText(*value);
466 const auto found = std::find(property.choices.begin(), property.choices.end(), selected);
467 host.setValueById(id, found == property.choices.end()
468 ? 0.f
469 : static_cast<float>(found - property.choices.begin()));
470 break;
471 }
472 case PropertyKind::Integer:
473 case PropertyKind::Number:
474 if (property.numeric.minimum && property.numeric.maximum)
475 host.setValueById(id, static_cast<float>(numericValue(*value)));
476 else
477 host.setValueTextById(id, valueText(*value));
478 break;
479 case PropertyKind::Color:
480 syncComposite(host, id, *value, {"r", "g", "b", "a"}, property);
481 break;
482 case PropertyKind::Vec2:
483 syncComposite(host, id, *value, {"x", "y"}, property);
484 break;
485 case PropertyKind::Vec3:
486 syncComposite(host, id, *value, {"x", "y", "z"}, property);
487 break;
488 case PropertyKind::Vec4:
489 syncComposite(host, id, *value, {"x", "y", "z", "w"}, property);
490 break;
491 case PropertyKind::Array:
492 if (const auto *items = value->getIf<Value::Array>()) {
493 for (std::size_t index = 0; index < items->size(); ++index) {
494 const std::string elementId = id + "_" + std::to_string(index);
495 if (const auto *boolean = (*items)[index].getIf<bool>())
496 host.setCheckedById(elementId, *boolean);
497 else
498 host.setValueTextById(elementId, valueText((*items)[index]));
499 }
500 }
501 break;
502 case PropertyKind::Map:
503 case PropertyKind::Struct:
504 if (const auto *fields = value->getIf<Value::Object>()) {
505 for (const auto &[key, entry] : *fields) {
506 const std::string elementId = id + "_" + sanitize(key);
507 if (const auto *boolean = entry.getIf<bool>())
508 host.setCheckedById(elementId, *boolean);
509 else
510 host.setValueTextById(elementId, valueText(entry));
511 }
512 }
513 break;
514 case PropertyKind::String:
515 case PropertyKind::AssetRef:
516 case PropertyKind::ObjectRef:
517 case PropertyKind::Auto:
518 if (const auto *textValue = value->getIf<std::string>())
519 host.setValueTextById(id, *textValue);
520 break;
521 case PropertyKind::Action:
522 case PropertyKind::ReadOnlyText:
523 break;
524 }
525 }
526}
527
529 : model_(model), options_(std::move(options)) {
530 observe();
531}
532
534 subscription_.dispose();
535 model_ = model;
536 observe();
537 markDirty();
538}
539
541 options_ = std::move(options);
542 markDirty();
543}
544
546 if (!model_) return column({}, options_.idPrefix + "root");
547 return buildPropertyView(*model_, options_);
548}
549
550void PropertyComponent::observe() {
551 if (!model_) return;
552 subscription_ = model_->subscribe([this](const property_access::PropertyChange &) { markDirty(); });
553}
554
555} // namespace eve::ui
double value
std::string label
int column
int rows
std::uint32_t key
std::string text
TokenKind kind
std::set< std::string > choices
std::array< std::uint64_t, kPixelChunkSize *kPixelChunkSize > updated
std::string path
Definition PlayHost.cpp:110
std::vector< std::string > fields
Definition PlayHost.cpp:111
std::string id
Definition PlayHost.cpp:108
glm::mat4 model
double number
bool found
double current
std::uint32_t count
std::string element
const SquirrelValueOptions & options
Cell cell
bool visible
Json object
int children
Definition TreeMesh.cpp:295
UIHostHandle host
const char * category
uint32_t index
void dispose() noexcept
Cancels this registration; safe to call repeatedly.
The canonical owning dynamic value used by data-facing protocols.
Definition Value.h:31
std::map< std::string, Value > Object
Definition Value.h:34
std::vector< Value > Array
Definition Value.h:33
Renderer- and transaction-independent MVVM property surface.
virtual Subscription subscribe(ChangeCallback callback)=0
Observe changes until the returned token is destroyed.
void markDirty()
Mark dirty.
Definition Component.h:36
property_access::IPropertyAccess * model() const
Return the currently bound property model, or null when unbound.
void bind(property_access::IPropertyAccess *model)
Replace the model and reconnect change observation.
void setOptions(PropertyViewOptions options)
Replace generation policy and mark the component dirty.
WidgetDesc build() override
Build the current schema-driven tree.
ECS mount point for one UI panel/screen. Subclass to attach UI to game entities, e....
Definition UIHost.h:243
std::variant< std::monostate, std::int64_t, double, std::string, bool > Value
Definition Database.h:26
constexpr HexDirection next(HexDirection d) noexcept
The next direction clockwise (NW wraps to NE).
Definition HexMetrics.h:76
constexpr bool hasFlag(PropertyFlag value, PropertyFlag flag)
True when flag.
PropertyFlag
Cross-host property visibility and editing flags.
PropertyKind
Semantic property kind used by renderer-independent presenters.
int64_t integer(const RuntimeTensor &v, size_t i=0)
Integer.
WidgetDesc collapsingHeader(std::string label, std::vector< WidgetDesc > children, std::string id, bool defaultOpen)
Collapsible header containing child widgets.
Definition Widget.cpp:623
WidgetDesc buildPropertyView(property_access::IPropertyAccess &model, const PropertyViewOptions &options)
Generate a declarative, two-way property view from any model schema.
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 slider(std::string label, float value, float minV, float maxV, std::string id, std::function< void(float)> onValue)
Horizontal slider; fires onValue.
Definition Widget.cpp:365
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
std::string propertyWidgetId(const PropertyViewOptions &options, const std::string &path)
Return the stable widget id generated for a property path.
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.
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 sectionHeader(std::string label, std::string id)
Non-collapsible editor section heading with an accent marker.
Definition Widget.cpp:522
WidgetDesc separator(std::string id)
Horizontal separator line.
Definition Widget.cpp:345
AccessibilityRole
Platform-neutral accessibility role exposed by controls.
Definition UIHost.h:107
enum EVENGINE_API_FOUNDATION Array
OT_ARRAY (editable via Runtime array APIs / ReflectedPropertyModel).
Definition Runtime.h:120
Immutable notification emitted after a model property changes.
Policy controlling schema-to-widget generation for a UI shell.
Declarative widget description (build once / on dirty → flatten into UIHost::Tree).
Definition Widget.h:15
WidgetDesc & withMouseFilter(MouseFilter v)
Selects whether pointer input stops, passes through, or ignores this widget.
Definition Widget.h:154
WidgetDesc & withAccessibility(AccessibilityRole role, std::string name, std::string description={})
Supplies the semantic role and labels exposed to accessibility clients.
Definition Widget.h:184
WidgetDesc & withFocusMode(FocusMode v)
Selects how the widget participates in keyboard focus navigation.
Definition Widget.h:149