6int appendNode(UIHost::Tree &tree, WidgetDesc &&desc) {
7 const int index = int(tree.nodes.size());
10 node.
id = std::move(desc.id);
12 node.text = std::move(desc.text);
13 node.valueText = std::move(desc.valueText);
15 node.checked = desc.checked;
16 node.open = desc.open;
17 node.value = desc.value;
18 node.minValue = desc.minValue;
19 node.maxValue = desc.maxValue;
20 node.sizeX = desc.sizeX;
21 node.sizeY = desc.sizeY;
22 node.marginL = desc.marginL;
23 node.marginT = desc.marginT;
24 node.marginR = desc.marginR;
25 node.marginB = desc.marginB;
26 node.paddingL = desc.paddingL;
27 node.paddingT = desc.paddingT;
28 node.paddingR = desc.paddingR;
29 node.paddingB = desc.paddingB;
30 node.minSizeX = desc.minSizeX;
31 node.minSizeY = desc.minSizeY;
32 node.maxSizeX = desc.maxSizeX;
33 node.maxSizeY = desc.maxSizeY;
34 node.percentW = desc.percentW;
35 node.percentH = desc.percentH;
36 node.anchorX = desc.anchorX;
37 node.anchorY = desc.anchorY;
38 node.posX = desc.posX;
39 node.posY = desc.posY;
40 node.absolute = desc.absolute;
41 node.textureId = desc.textureId;
42 node.tintR = desc.tintR;
43 node.tintG = desc.tintG;
44 node.tintB = desc.tintB;
45 node.tintA = desc.tintA;
46 node.uv0x = desc.uv0x;
47 node.uv0y = desc.uv0y;
48 node.uv1x = desc.uv1x;
49 node.uv1y = desc.uv1y;
50 node.borderL = desc.borderL;
51 node.borderT = desc.borderT;
52 node.borderR = desc.borderR;
53 node.borderB = desc.borderB;
54 node.cornerRadius = desc.cornerRadius;
55 node.wrapWidth = desc.wrapWidth;
56 node.itemHeight = desc.itemHeight;
57 node.flexDirection = desc.flexDirection;
58 node.alignItems = desc.alignItems;
59 node.justifyContent = desc.justifyContent;
61 node.flexGrow = desc.flexGrow;
63 node.nextSibling = -1;
64 node.handlerClick = 0;
65 node.handlerToggle = 0;
66 node.handlerValue = 0;
70 tree.clickHandlers.push_back(std::move(desc.onClick));
71 node.handlerClick =
static_cast<uint32_t
>(tree.clickHandlers.size());
74 tree.toggleHandlers.push_back(std::move(desc.onToggle));
75 node.handlerToggle =
static_cast<uint32_t
>(tree.toggleHandlers.size());
78 tree.valueHandlers.push_back(std::move(desc.onValue));
79 node.handlerValue =
static_cast<uint32_t
>(tree.valueHandlers.size());
81 if (desc.onTextChange) {
82 tree.textHandlers.push_back(std::move(desc.onTextChange));
83 node.handlerText =
static_cast<uint32_t
>(tree.textHandlers.size());
86 tree.nodes.push_back(std::move(node));
91 int childIndex = appendNode(tree, std::move(
child));
92 if (firstChild < 0) firstChild = childIndex;
93 if (prevChild >= 0) tree.nodes[size_t(prevChild)].nextSibling = childIndex;
94 prevChild = childIndex;
96 tree.nodes[size_t(index)].firstChild = firstChild;
100bool structureMatches(
const UIHost::Tree &tree,
int nodeIndex,
const WidgetDesc &desc) {
101 if (nodeIndex < 0 || nodeIndex >=
int(tree.nodes.size()))
return false;
102 const UINode &
n = tree.nodes[size_t(nodeIndex)];
103 if (
n.type != desc.type)
return false;
104 const std::string &dk = desc.reconcileKey();
105 if (!dk.empty() && !
n.key.empty() &&
n.key != dk)
return false;
107 std::vector<std::string> oldKeys;
108 for (
int c =
n.firstChild;
c >= 0;
c = tree.nodes[size_t(
c)].nextSibling) {
109 oldKeys.push_back(tree.nodes[
size_t(
c)].key);
111 if (oldKeys.size() != desc.children.size())
return false;
112 for (
size_t i = 0; i < desc.children.size(); ++i) {
113 const std::string &ck = desc.children[i].reconcileKey();
114 if (ck.empty() || oldKeys[i].empty()) {
115 if (!(ck.empty() && oldKeys[i].empty()))
return false;
116 }
else if (ck != oldKeys[i]) {
122 for (
const auto &ch : desc.
children) {
123 if (!structureMatches(tree,
child, ch))
return false;
124 child = tree.nodes[size_t(
child)].nextSibling;
129void patchProps(UIHost::Tree &tree,
int nodeIndex, WidgetDesc &&desc) {
130 UINode &
n = tree.nodes[size_t(nodeIndex)];
131 n.
text = std::move(desc.text);
132 n.valueText = std::move(desc.valueText);
133 n.visible = desc.visible;
134 n.checked = desc.checked;
136 n.value = desc.value;
137 n.minValue = desc.minValue;
138 n.maxValue = desc.maxValue;
139 n.sizeX = desc.sizeX;
140 n.sizeY = desc.sizeY;
141 n.marginL = desc.marginL;
142 n.marginT = desc.marginT;
143 n.marginR = desc.marginR;
144 n.marginB = desc.marginB;
145 n.paddingL = desc.paddingL;
146 n.paddingT = desc.paddingT;
147 n.paddingR = desc.paddingR;
148 n.paddingB = desc.paddingB;
149 n.minSizeX = desc.minSizeX;
150 n.minSizeY = desc.minSizeY;
151 n.maxSizeX = desc.maxSizeX;
152 n.maxSizeY = desc.maxSizeY;
153 n.percentW = desc.percentW;
154 n.percentH = desc.percentH;
155 n.anchorX = desc.anchorX;
156 n.anchorY = desc.anchorY;
159 n.absolute = desc.absolute;
160 n.textureId = desc.textureId;
161 n.tintR = desc.tintR;
162 n.tintG = desc.tintG;
163 n.tintB = desc.tintB;
164 n.tintA = desc.tintA;
169 n.borderL = desc.borderL;
170 n.borderT = desc.borderT;
171 n.borderR = desc.borderR;
172 n.borderB = desc.borderB;
173 n.cornerRadius = desc.cornerRadius;
174 n.wrapWidth = desc.wrapWidth;
175 n.itemHeight = desc.itemHeight;
176 n.flexDirection = desc.flexDirection;
177 n.alignItems = desc.alignItems;
178 n.justifyContent = desc.justifyContent;
180 n.flexGrow = desc.flexGrow;
181 if (!desc.id.empty())
n.id = desc.id;
184 if (
n.handlerClick != 0) {
185 tree.clickHandlers[size_t(
n.handlerClick - 1)] = std::move(desc.onClick);
187 tree.clickHandlers.push_back(std::move(desc.onClick));
188 n.handlerClick =
static_cast<uint32_t
>(tree.clickHandlers.size());
192 if (
n.handlerToggle != 0) {
193 tree.toggleHandlers[size_t(
n.handlerToggle - 1)] = std::move(desc.onToggle);
195 tree.toggleHandlers.push_back(std::move(desc.onToggle));
196 n.handlerToggle =
static_cast<uint32_t
>(tree.toggleHandlers.size());
200 if (
n.handlerValue != 0) {
201 tree.valueHandlers[size_t(
n.handlerValue - 1)] = std::move(desc.onValue);
203 tree.valueHandlers.push_back(std::move(desc.onValue));
204 n.handlerValue =
static_cast<uint32_t
>(tree.valueHandlers.size());
207 if (desc.onTextChange) {
208 if (
n.handlerText != 0) {
209 tree.textHandlers[size_t(
n.handlerText - 1)] = std::move(desc.onTextChange);
211 tree.textHandlers.push_back(std::move(desc.onTextChange));
212 n.handlerText =
static_cast<uint32_t
>(tree.textHandlers.size());
218 patchProps(tree,
child, std::move(ch));
219 child = tree.nodes[size_t(
child)].nextSibling;
228 d.id = std::move(
id);
230 d.text = std::move(
title);
238 d.id = std::move(
id);
240 d.text = std::move(content);
247 d.id = std::move(
id);
249 d.text = std::move(label);
250 d.onClick = std::move(onClick);
257 d.id = std::move(
id);
266 d.id = std::move(
id);
274 d.id = std::move(
id);
280 std::function<
void(
bool)> onToggle) {
283 d.id = std::move(
id);
285 d.text = std::move(label);
287 d.onToggle = std::move(onToggle);
292 std::function<
void(
float)> onValue) {
295 d.id = std::move(
id);
297 d.text = std::move(label);
301 d.onValue = std::move(onValue);
308 d.id = std::move(
id);
311 d.text = std::move(overlay);
316 std::string
id, std::function<
void(
int)> onValue) {
319 d.id = std::move(
id);
321 d.text = std::move(label);
322 d.value = float(std::max(0, selected));
323 for (
size_t i = 0; i < options.size(); ++i) {
324 if (i)
d.valueText +=
'\n';
325 d.valueText += options[i];
327 d.onValue = std::move(onValue);
334 d.id = std::move(
id);
338 d.onClick = std::move(onClick);
345 d.id = std::move(
id);
349 d.onClick = std::move(onClick);
356 d.id = std::move(
id);
364 std::function<
void(
const std::string &)> onChange) {
367 d.id = std::move(
id);
369 d.text = std::move(label);
370 d.valueText = std::move(
value);
371 d.onTextChange = std::move(onChange);
379 d.id = std::move(
id);
381 d.text = std::move(label);
382 d.open = defaultOpen;
390 d.id = std::move(
id);
402 d.id = std::move(
id);
405 d.itemHeight = itemHeight;
412 const std::string lid = listId;
414 [lid](
const std::string &label,
int i) {
415 return button(label, lid +
"/" + std::to_string(i))
416 .
withKey(lid +
"/" + std::to_string(i));
424 d.id = std::move(
id);
426 d.flexDirection = direction;
442 d.id = std::move(
id);
444 d.flexGrow = grow > 0.f ? grow : 1.f;
449 if (!cond)
return group({},
"__when_empty");
454 return cond ? std::move(ifTrue) : std::move(ifFalse);
458 const std::function<
WidgetDesc(
const std::string &,
int)> &itemFn) {
461 for (
int i = 0; i < int(items.size()); ++i) {
463 if (
row.
id.empty())
row.
id = listId +
"/" + std::to_string(i);
471 std::string lid = listId;
472 return list(std::move(listId), items, [lid](
const std::string &label,
int i) {
473 return button(label, lid +
"/" + std::to_string(i)).
withKey(lid +
"/" + std::to_string(i));
479 auto t = host->tree();
481 t->clickHandlers.clear();
482 t->toggleHandlers.clear();
483 t->valueHandlers.clear();
484 t->textHandlers.clear();
486 t->root = appendNode(*t, std::move(root));
491 if (!host)
return true;
492 auto t = host->tree();
493 if (t->root < 0 || t->nodes.empty() || !structureMatches(*t, t->root, root)) {
497 patchProps(*t, t->root, std::move(root));
ECS mount point for one UI panel/screen. Subclass to attach UI to game entities, e....
NodeDesc node(std::string id, std::vector< NodeDesc > children, std::string name)
WidgetDesc spacer(std::string id, float grow)
Flexible empty space; default flexGrow=1 so it absorbs free space in a Flex parent.
WidgetDesc slider(std::string label, float value, float minV, float maxV, std::string id, std::function< void(float)> onValue)
Horizontal slider; fires onValue.
void applyTree(UIHost *host, WidgetDesc root)
Full replace flatten.
WidgetDesc text(std::string content, std::string id)
Static text label.
WidgetDesc scrollList(std::string id, std::vector< WidgetDesc > children, float height, float itemHeight)
WidgetDesc progress(float fraction, std::string id, std::string overlay)
Progress bar; fraction is clamped to [0,1].
FlexDirection
Main-axis direction for Flex containers.
WidgetDesc checkbox(std::string label, bool checked, std::string id, std::function< void(bool)> onToggle)
Checkbox with a label; fires onToggle.
WidgetDesc separator(std::string id)
Horizontal separator line.
WidgetDesc whenElse(bool cond, WidgetDesc ifTrue, WidgetDesc ifFalse)
WidgetDesc when(bool cond, WidgetDesc child)
Conditional: include child only when cond is true (empty group otherwise).
WidgetDesc combo(std::string label, std::vector< std::string > options, int selected, std::string id, std::function< void(int)> onValue)
WidgetDesc inputText(std::string label, std::string value, std::string id, std::function< void(const std::string &)> onChange)
Editable text field; fires onTextChange.
WidgetDesc listButtons(std::string listId, const std::vector< std::string > &items)
Default list: one Button per item, id = listId + "/" + index.
WidgetDesc flex(FlexDirection direction, std::vector< WidgetDesc > children, std::string id)
Elastic layout container (row/column). Prefer row / column shorthands.
WidgetDesc imageButton(std::string id, float width, float height, std::function< void()> onClick)
WidgetDesc window(std::string title, std::vector< WidgetDesc > children, std::string id)
Top-level window widget with a title bar.
WidgetDesc virtualList(std::string listId, const std::vector< std::string > &items, float height, float itemHeight)
WidgetDesc collapsingHeader(std::string label, std::vector< WidgetDesc > children, std::string id, bool defaultOpen)
Collapsible header containing child widgets.
bool applyTreeReconcile(UIHost *host, WidgetDesc root)
Patch by key/id when structure (type + child keys) matches; otherwise full replace....
WidgetDesc sameLine(std::string id)
Holds the next widget on the same line as the previous one.
WidgetDesc button(std::string label, std::string id, std::function< void()> onClick)
Clickable button; fires onClick.
WidgetDesc column(std::vector< WidgetDesc > children, std::string id)
Vertical elastic layout column.
WidgetDesc viewport(std::string id, float width, float height)
WidgetDesc child(std::string id, std::vector< WidgetDesc > children, float width, float height)
Scrollable child region with an explicit size.
WidgetDesc list(std::string listId, const std::vector< std::string > &items, const std::function< WidgetDesc(const std::string &, int)> &itemFn)
Expand a string list into a Group of item widgets. itemFn(label, index) builds each row; keys default...
WidgetDesc row(std::vector< WidgetDesc > children, std::string id)
Horizontal elastic layout row.
std::string key
Reconciliation key; defaults to id when empty.