imxc 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,716 @@
1
+ const INDENT = ' ';
2
+ function cppType(t) {
3
+ switch (t) {
4
+ case 'int': return 'int';
5
+ case 'float': return 'float';
6
+ case 'bool': return 'bool';
7
+ case 'string': return 'std::string';
8
+ case 'color': return 'std::array<float, 4>';
9
+ }
10
+ }
11
+ function cppPropType(t) {
12
+ if (t === 'callback')
13
+ return 'std::function<void()>';
14
+ return cppType(t);
15
+ }
16
+ /**
17
+ * Ensure a string expression is a const char*.
18
+ * String literals (quoted) are already const char*.
19
+ * Expressions like props.title (std::string) need .c_str().
20
+ */
21
+ function asCharPtr(expr) {
22
+ // Already a string literal — "hello" is const char*
23
+ if (expr.startsWith('"'))
24
+ return expr;
25
+ // Already has .c_str()
26
+ if (expr.endsWith('.c_str()'))
27
+ return expr;
28
+ // Expression — assume std::string, add .c_str()
29
+ return `${expr}.c_str()`;
30
+ }
31
+ /**
32
+ * Emit a .gen.h header for a component that has props.
33
+ * Contains the props struct and function forward declaration.
34
+ */
35
+ export function emitComponentHeader(comp) {
36
+ const lines = [];
37
+ lines.push('#pragma once');
38
+ lines.push('#include <reimgui/runtime.h>');
39
+ lines.push('#include <reimgui/renderer.h>');
40
+ lines.push('#include <functional>');
41
+ lines.push('#include <string>');
42
+ lines.push('');
43
+ // Props struct
44
+ lines.push(`struct ${comp.name}Props {`);
45
+ for (const p of comp.params) {
46
+ lines.push(`${INDENT}${cppPropType(p.type)} ${p.name};`);
47
+ }
48
+ lines.push('};');
49
+ lines.push('');
50
+ // Function forward declaration
51
+ lines.push(`void ${comp.name}_render(reimgui::RenderContext& ctx, const ${comp.name}Props& props);`);
52
+ lines.push('');
53
+ return lines.join('\n');
54
+ }
55
+ export function emitComponent(comp, imports) {
56
+ const lines = [];
57
+ // Reset counters for each component
58
+ styleCounter = 0;
59
+ customComponentCounter = 0;
60
+ checkboxCounter = 0;
61
+ comboCounter = 0;
62
+ listBoxCounter = 0;
63
+ const hasProps = comp.params.length > 0;
64
+ const hasColorType = comp.stateSlots.some(s => s.type === 'color');
65
+ if (hasProps) {
66
+ // Component with props: include its own header instead of redeclaring struct
67
+ lines.push(`#include "${comp.name}.gen.h"`);
68
+ if (hasColorType) {
69
+ lines.push('#include <array>');
70
+ }
71
+ lines.push('');
72
+ }
73
+ else {
74
+ // No props: standard headers
75
+ lines.push('#include <reimgui/runtime.h>');
76
+ lines.push('#include <reimgui/renderer.h>');
77
+ if (hasColorType) {
78
+ lines.push('#include <array>');
79
+ }
80
+ // Include imported component headers
81
+ if (imports && imports.length > 0) {
82
+ for (const imp of imports) {
83
+ lines.push(`#include "${imp.headerFile}"`);
84
+ }
85
+ }
86
+ lines.push('');
87
+ }
88
+ // Function signature
89
+ const propsArg = hasProps ? `, const ${comp.name}Props& props` : '';
90
+ lines.push(`void ${comp.name}_render(reimgui::RenderContext& ctx${propsArg}) {`);
91
+ // State declarations
92
+ for (const slot of comp.stateSlots) {
93
+ const initVal = slot.type === 'string'
94
+ ? `std::string(${slot.initialValue})`
95
+ : slot.type === 'color'
96
+ ? `std::array<float, 4>${slot.initialValue}`
97
+ : slot.initialValue;
98
+ lines.push(`${INDENT}auto ${slot.name} = ctx.use_state<${cppType(slot.type)}>(${initVal}, ${slot.index});`);
99
+ }
100
+ if (comp.stateSlots.length > 0) {
101
+ lines.push('');
102
+ }
103
+ // Body IR nodes
104
+ emitNodes(comp.body, lines, 1);
105
+ lines.push('}');
106
+ lines.push('');
107
+ return lines.join('\n');
108
+ }
109
+ export function emitRoot(rootName, stateCount, bufferCount) {
110
+ const lines = [];
111
+ lines.push('#include <reimgui/runtime.h>');
112
+ lines.push('');
113
+ lines.push(`void ${rootName}_render(reimgui::RenderContext& ctx);`);
114
+ lines.push('');
115
+ lines.push('namespace reimgui {');
116
+ lines.push('void render_root(Runtime& runtime) {');
117
+ lines.push(`${INDENT}auto& ctx = runtime.begin_frame();`);
118
+ lines.push(`${INDENT}ctx.begin_instance("${rootName}", 0, ${stateCount}, ${bufferCount});`);
119
+ lines.push(`${INDENT}${rootName}_render(ctx);`);
120
+ lines.push(`${INDENT}ctx.end_instance();`);
121
+ lines.push(`${INDENT}runtime.end_frame();`);
122
+ lines.push('}');
123
+ lines.push('} // namespace reimgui');
124
+ lines.push('');
125
+ return lines.join('\n');
126
+ }
127
+ function emitNodes(nodes, lines, depth) {
128
+ for (const node of nodes) {
129
+ emitNode(node, lines, depth);
130
+ }
131
+ }
132
+ function emitNode(node, lines, depth) {
133
+ const indent = INDENT.repeat(depth);
134
+ switch (node.kind) {
135
+ case 'begin_container':
136
+ emitBeginContainer(node, lines, indent);
137
+ break;
138
+ case 'end_container':
139
+ emitEndContainer(node, lines, indent);
140
+ break;
141
+ case 'text':
142
+ emitText(node, lines, indent);
143
+ break;
144
+ case 'button':
145
+ emitButton(node, lines, indent, depth);
146
+ break;
147
+ case 'text_input':
148
+ emitTextInput(node, lines, indent);
149
+ break;
150
+ case 'checkbox':
151
+ emitCheckbox(node, lines, indent);
152
+ break;
153
+ case 'separator':
154
+ lines.push(`${indent}reimgui::renderer::separator();`);
155
+ break;
156
+ case 'begin_popup':
157
+ lines.push(`${indent}if (reimgui::renderer::begin_popup(${node.id})) {`);
158
+ break;
159
+ case 'end_popup':
160
+ lines.push(`${indent}reimgui::renderer::end_popup();`);
161
+ lines.push(`${indent}}`);
162
+ break;
163
+ case 'open_popup':
164
+ lines.push(`${indent}reimgui::renderer::open_popup(${node.id});`);
165
+ break;
166
+ case 'conditional':
167
+ emitConditional(node, lines, indent, depth);
168
+ break;
169
+ case 'list_map':
170
+ emitListMap(node, lines, indent, depth);
171
+ break;
172
+ case 'menu_item':
173
+ emitMenuItem(node, lines, indent, depth);
174
+ break;
175
+ case 'custom_component':
176
+ emitCustomComponent(node, lines, indent);
177
+ break;
178
+ case 'slider_float':
179
+ emitSliderFloat(node, lines, indent);
180
+ break;
181
+ case 'slider_int':
182
+ emitSliderInt(node, lines, indent);
183
+ break;
184
+ case 'drag_float':
185
+ emitDragFloat(node, lines, indent);
186
+ break;
187
+ case 'drag_int':
188
+ emitDragInt(node, lines, indent);
189
+ break;
190
+ case 'combo':
191
+ emitCombo(node, lines, indent);
192
+ break;
193
+ case 'input_int':
194
+ emitInputInt(node, lines, indent);
195
+ break;
196
+ case 'input_float':
197
+ emitInputFloat(node, lines, indent);
198
+ break;
199
+ case 'color_edit':
200
+ emitColorEdit(node, lines, indent);
201
+ break;
202
+ case 'list_box':
203
+ emitListBox(node, lines, indent);
204
+ break;
205
+ case 'progress_bar':
206
+ emitProgressBar(node, lines, indent);
207
+ break;
208
+ case 'tooltip':
209
+ emitTooltip(node, lines, indent);
210
+ break;
211
+ }
212
+ }
213
+ let styleCounter = 0;
214
+ let customComponentCounter = 0;
215
+ let checkboxCounter = 0;
216
+ let comboCounter = 0;
217
+ let listBoxCounter = 0;
218
+ function buildStyleBlock(node, indent, lines) {
219
+ // Check for style-related props (gap, padding, width, height, etc.)
220
+ const styleProps = {};
221
+ for (const [key, val] of Object.entries(node.props)) {
222
+ if (['gap', 'padding', 'paddingHorizontal', 'paddingVertical', 'width', 'height', 'minWidth', 'minHeight'].includes(key)) {
223
+ // Map camelCase to snake_case for C++ Style struct
224
+ const cppKey = key === 'paddingHorizontal' ? 'padding_horizontal'
225
+ : key === 'paddingVertical' ? 'padding_vertical'
226
+ : key === 'minWidth' ? 'min_width'
227
+ : key === 'minHeight' ? 'min_height'
228
+ : key;
229
+ styleProps[cppKey] = val;
230
+ }
231
+ }
232
+ // Also check node.style (explicit style prop)
233
+ const explicitStyle = node.style ?? node.props['style'];
234
+ if (explicitStyle) {
235
+ return explicitStyle;
236
+ }
237
+ if (Object.keys(styleProps).length === 0) {
238
+ return null;
239
+ }
240
+ // Generate MSVC-compatible style construction
241
+ const varName = `style_${styleCounter++}`;
242
+ lines.push(`${indent}reimgui::Style ${varName};`);
243
+ for (const [key, val] of Object.entries(styleProps)) {
244
+ // Ensure the value is a float literal (e.g., 8 -> 8.0F, 8.5 -> 8.5F)
245
+ const floatVal = val.includes('.') ? `${val}F` : `${val}.0F`;
246
+ lines.push(`${indent}${varName}.${key} = ${floatVal};`);
247
+ }
248
+ return varName;
249
+ }
250
+ function emitBeginContainer(node, lines, indent) {
251
+ switch (node.tag) {
252
+ case 'Window': {
253
+ const title = asCharPtr(node.props['title'] ?? '""');
254
+ lines.push(`${indent}reimgui::renderer::begin_window(${title});`);
255
+ break;
256
+ }
257
+ case 'Row': {
258
+ const style = buildStyleBlock(node, indent, lines);
259
+ if (style) {
260
+ lines.push(`${indent}reimgui::renderer::begin_row(${style});`);
261
+ }
262
+ else {
263
+ lines.push(`${indent}reimgui::renderer::begin_row();`);
264
+ }
265
+ break;
266
+ }
267
+ case 'Column': {
268
+ const style = buildStyleBlock(node, indent, lines);
269
+ if (style) {
270
+ lines.push(`${indent}reimgui::renderer::begin_column(${style});`);
271
+ }
272
+ else {
273
+ lines.push(`${indent}reimgui::renderer::begin_column();`);
274
+ }
275
+ break;
276
+ }
277
+ case 'View': {
278
+ const style = buildStyleBlock(node, indent, lines);
279
+ if (style) {
280
+ lines.push(`${indent}reimgui::renderer::begin_view(${style});`);
281
+ }
282
+ else {
283
+ lines.push(`${indent}reimgui::renderer::begin_view();`);
284
+ }
285
+ break;
286
+ }
287
+ case 'DockSpace': {
288
+ const style = buildStyleBlock(node, indent, lines);
289
+ if (style) {
290
+ lines.push(`${indent}reimgui::renderer::begin_dockspace(${style});`);
291
+ }
292
+ else {
293
+ lines.push(`${indent}reimgui::renderer::begin_dockspace();`);
294
+ }
295
+ break;
296
+ }
297
+ case 'MenuBar': {
298
+ lines.push(`${indent}if (reimgui::renderer::begin_menu_bar()) {`);
299
+ break;
300
+ }
301
+ case 'Menu': {
302
+ const label = asCharPtr(node.props['label'] ?? '""');
303
+ lines.push(`${indent}if (reimgui::renderer::begin_menu(${label})) {`);
304
+ break;
305
+ }
306
+ case 'Table': {
307
+ const columnsRaw = node.props['columns'] ?? '';
308
+ const columnNames = columnsRaw.split(',').map(s => s.trim()).filter(s => s.length > 0);
309
+ const count = columnNames.length;
310
+ const varName = `table_cols_${styleCounter++}`;
311
+ lines.push(`${indent}const char* ${varName}[] = {${columnNames.join(', ')}};`);
312
+ lines.push(`${indent}if (reimgui::renderer::begin_table("##table", ${count}, ${varName})) {`);
313
+ break;
314
+ }
315
+ case 'TableRow': {
316
+ lines.push(`${indent}reimgui::renderer::begin_table_row();`);
317
+ break;
318
+ }
319
+ case 'TabBar': {
320
+ lines.push(`${indent}if (reimgui::renderer::begin_tab_bar()) {`);
321
+ break;
322
+ }
323
+ case 'TabItem': {
324
+ const label = asCharPtr(node.props['label'] ?? '""');
325
+ lines.push(`${indent}if (reimgui::renderer::begin_tab_item(${label})) {`);
326
+ break;
327
+ }
328
+ case 'TreeNode': {
329
+ const label = asCharPtr(node.props['label'] ?? '""');
330
+ lines.push(`${indent}if (reimgui::renderer::begin_tree_node(${label})) {`);
331
+ break;
332
+ }
333
+ case 'CollapsingHeader': {
334
+ const label = asCharPtr(node.props['label'] ?? '""');
335
+ lines.push(`${indent}if (reimgui::renderer::begin_collapsing_header(${label})) {`);
336
+ break;
337
+ }
338
+ }
339
+ }
340
+ function emitEndContainer(node, lines, indent) {
341
+ switch (node.tag) {
342
+ case 'Window':
343
+ lines.push(`${indent}reimgui::renderer::end_window();`);
344
+ break;
345
+ case 'Row':
346
+ lines.push(`${indent}reimgui::renderer::end_row();`);
347
+ break;
348
+ case 'Column':
349
+ lines.push(`${indent}reimgui::renderer::end_column();`);
350
+ break;
351
+ case 'View':
352
+ lines.push(`${indent}reimgui::renderer::end_view();`);
353
+ break;
354
+ case 'DockSpace':
355
+ lines.push(`${indent}reimgui::renderer::end_dockspace();`);
356
+ break;
357
+ case 'MenuBar':
358
+ lines.push(`${indent}reimgui::renderer::end_menu_bar();`);
359
+ lines.push(`${indent}}`);
360
+ break;
361
+ case 'Menu':
362
+ lines.push(`${indent}reimgui::renderer::end_menu();`);
363
+ lines.push(`${indent}}`);
364
+ break;
365
+ case 'Table':
366
+ lines.push(`${indent}reimgui::renderer::end_table();`);
367
+ lines.push(`${indent}}`);
368
+ break;
369
+ case 'TableRow':
370
+ lines.push(`${indent}reimgui::renderer::end_table_row();`);
371
+ break;
372
+ case 'TabBar':
373
+ lines.push(`${indent}reimgui::renderer::end_tab_bar();`);
374
+ lines.push(`${indent}}`);
375
+ break;
376
+ case 'TabItem':
377
+ lines.push(`${indent}reimgui::renderer::end_tab_item();`);
378
+ lines.push(`${indent}}`);
379
+ break;
380
+ case 'TreeNode':
381
+ lines.push(`${indent}reimgui::renderer::end_tree_node();`);
382
+ lines.push(`${indent}}`);
383
+ break;
384
+ case 'CollapsingHeader':
385
+ lines.push(`${indent}reimgui::renderer::end_collapsing_header();`);
386
+ lines.push(`${indent}}`);
387
+ break;
388
+ }
389
+ }
390
+ function emitText(node, lines, indent) {
391
+ if (node.args.length === 0) {
392
+ lines.push(`${indent}reimgui::renderer::text(${JSON.stringify(node.format)});`);
393
+ }
394
+ else {
395
+ const argsStr = node.args.join(', ');
396
+ lines.push(`${indent}reimgui::renderer::text(${JSON.stringify(node.format)}, ${argsStr});`);
397
+ }
398
+ }
399
+ function emitButton(node, lines, indent, depth) {
400
+ const title = asCharPtr(node.title);
401
+ if (node.action.length === 0) {
402
+ lines.push(`${indent}reimgui::renderer::button(${title});`);
403
+ }
404
+ else {
405
+ lines.push(`${indent}if (reimgui::renderer::button(${title})) {`);
406
+ for (const stmt of node.action) {
407
+ lines.push(`${indent}${INDENT}${stmt}`);
408
+ }
409
+ lines.push(`${indent}}`);
410
+ }
411
+ }
412
+ function emitMenuItem(node, lines, indent, depth) {
413
+ const label = asCharPtr(node.label);
414
+ const shortcut = node.shortcut ? asCharPtr(node.shortcut) : undefined;
415
+ if (node.action.length === 0) {
416
+ if (shortcut) {
417
+ lines.push(`${indent}reimgui::renderer::menu_item(${label}, ${shortcut});`);
418
+ }
419
+ else {
420
+ lines.push(`${indent}reimgui::renderer::menu_item(${label});`);
421
+ }
422
+ }
423
+ else {
424
+ if (shortcut) {
425
+ lines.push(`${indent}if (reimgui::renderer::menu_item(${label}, ${shortcut})) {`);
426
+ }
427
+ else {
428
+ lines.push(`${indent}if (reimgui::renderer::menu_item(${label})) {`);
429
+ }
430
+ for (const stmt of node.action) {
431
+ lines.push(`${indent} ${stmt}`);
432
+ }
433
+ lines.push(`${indent}}`);
434
+ }
435
+ }
436
+ function emitTextInput(node, lines, indent) {
437
+ const label = asCharPtr(node.label && node.label !== '""' ? node.label : `"##textinput_${node.bufferIndex}"`);
438
+ if (node.stateVar) {
439
+ lines.push(`${indent}{`);
440
+ lines.push(`${indent}${INDENT}auto& buf = ctx.get_buffer(${node.bufferIndex});`);
441
+ lines.push(`${indent}${INDENT}buf.sync_from(${node.stateVar}.get());`);
442
+ lines.push(`${indent}${INDENT}if (reimgui::renderer::text_input(${label}, buf)) {`);
443
+ lines.push(`${indent}${INDENT}${INDENT}${node.stateVar}.set(buf.value());`);
444
+ lines.push(`${indent}${INDENT}}`);
445
+ lines.push(`${indent}}`);
446
+ }
447
+ else {
448
+ lines.push(`${indent}auto& buf_${node.bufferIndex} = ctx.get_buffer(${node.bufferIndex});`);
449
+ lines.push(`${indent}reimgui::renderer::text_input(${label}, buf_${node.bufferIndex});`);
450
+ }
451
+ }
452
+ function emitCheckbox(node, lines, indent) {
453
+ const label = asCharPtr(node.label && node.label !== '""' ? node.label : `"##checkbox_${checkboxCounter}"`);
454
+ checkboxCounter++;
455
+ if (node.stateVar) {
456
+ // State-bound case
457
+ lines.push(`${indent}{`);
458
+ lines.push(`${indent}${INDENT}bool val = ${node.stateVar}.get();`);
459
+ lines.push(`${indent}${INDENT}if (reimgui::renderer::checkbox(${label}, &val)) {`);
460
+ lines.push(`${indent}${INDENT}${INDENT}${node.stateVar}.set(val);`);
461
+ lines.push(`${indent}${INDENT}}`);
462
+ lines.push(`${indent}}`);
463
+ }
464
+ else if (node.valueExpr !== undefined) {
465
+ // Props-bound / expression-bound case
466
+ lines.push(`${indent}{`);
467
+ lines.push(`${indent}${INDENT}bool val = ${node.valueExpr};`);
468
+ lines.push(`${indent}${INDENT}if (reimgui::renderer::checkbox(${label}, &val)) {`);
469
+ if (node.onChangeExpr) {
470
+ lines.push(`${indent}${INDENT}${INDENT}${node.onChangeExpr};`);
471
+ }
472
+ lines.push(`${indent}${INDENT}}`);
473
+ lines.push(`${indent}}`);
474
+ }
475
+ else {
476
+ lines.push(`${indent}reimgui::renderer::checkbox(${label}, nullptr);`);
477
+ }
478
+ }
479
+ function emitConditional(node, lines, indent, depth) {
480
+ lines.push(`${indent}if (${node.condition}) {`);
481
+ emitNodes(node.body, lines, depth + 1);
482
+ if (node.elseBody && node.elseBody.length > 0) {
483
+ lines.push(`${indent}} else {`);
484
+ emitNodes(node.elseBody, lines, depth + 1);
485
+ }
486
+ lines.push(`${indent}}`);
487
+ }
488
+ function emitListMap(node, lines, indent, depth) {
489
+ lines.push(`${indent}for (size_t i = 0; i < ${node.array}.size(); i++) {`);
490
+ lines.push(`${indent}${INDENT}auto& ${node.itemVar} = ${node.array}[i];`);
491
+ lines.push(`${indent}${INDENT}ctx.begin_instance("${node.componentName}", i, ${node.stateCount}, ${node.bufferCount});`);
492
+ emitNodes(node.body, lines, depth + 2);
493
+ lines.push(`${indent}${INDENT}ctx.end_instance();`);
494
+ lines.push(`${indent}}`);
495
+ }
496
+ function emitCustomComponent(node, lines, indent) {
497
+ const instanceIndex = customComponentCounter++;
498
+ const propsEntries = Object.entries(node.props);
499
+ lines.push(`${indent}ctx.begin_instance("${node.name}", ${instanceIndex}, ${node.stateCount}, ${node.bufferCount});`);
500
+ if (propsEntries.length > 0) {
501
+ // MSVC-compatible: use variable-based prop assignment instead of designated initializers
502
+ lines.push(`${indent}{`);
503
+ lines.push(`${indent}${INDENT}${node.name}Props p;`);
504
+ for (const [k, v] of propsEntries) {
505
+ lines.push(`${indent}${INDENT}p.${k} = ${v};`);
506
+ }
507
+ lines.push(`${indent}${INDENT}${node.name}_render(ctx, p);`);
508
+ lines.push(`${indent}}`);
509
+ }
510
+ else {
511
+ lines.push(`${indent}${node.name}_render(ctx);`);
512
+ }
513
+ lines.push(`${indent}ctx.end_instance();`);
514
+ }
515
+ function ensureFloatLiteral(val) {
516
+ // If already has 'f' suffix or '.', leave as-is
517
+ if (val.endsWith('f') || val.endsWith('F') || val.includes('.'))
518
+ return val;
519
+ // Append .0f to make it a float literal
520
+ return `${val}.0f`;
521
+ }
522
+ function emitSliderFloat(node, lines, indent) {
523
+ const min = ensureFloatLiteral(node.min);
524
+ const max = ensureFloatLiteral(node.max);
525
+ if (node.stateVar) {
526
+ lines.push(`${indent}{`);
527
+ lines.push(`${indent}${INDENT}float val = ${node.stateVar}.get();`);
528
+ lines.push(`${indent}${INDENT}if (reimgui::renderer::slider_float(${node.label}, &val, ${min}, ${max})) {`);
529
+ lines.push(`${indent}${INDENT}${INDENT}${node.stateVar}.set(val);`);
530
+ lines.push(`${indent}${INDENT}}`);
531
+ lines.push(`${indent}}`);
532
+ }
533
+ else if (node.valueExpr !== undefined) {
534
+ lines.push(`${indent}{`);
535
+ lines.push(`${indent}${INDENT}float val = ${node.valueExpr};`);
536
+ lines.push(`${indent}${INDENT}if (reimgui::renderer::slider_float(${node.label}, &val, ${min}, ${max})) {`);
537
+ if (node.onChangeExpr) {
538
+ lines.push(`${indent}${INDENT}${INDENT}${node.onChangeExpr};`);
539
+ }
540
+ lines.push(`${indent}${INDENT}}`);
541
+ lines.push(`${indent}}`);
542
+ }
543
+ }
544
+ function emitSliderInt(node, lines, indent) {
545
+ if (node.stateVar) {
546
+ lines.push(`${indent}{`);
547
+ lines.push(`${indent}${INDENT}int val = ${node.stateVar}.get();`);
548
+ lines.push(`${indent}${INDENT}if (reimgui::renderer::slider_int(${node.label}, &val, ${node.min}, ${node.max})) {`);
549
+ lines.push(`${indent}${INDENT}${INDENT}${node.stateVar}.set(val);`);
550
+ lines.push(`${indent}${INDENT}}`);
551
+ lines.push(`${indent}}`);
552
+ }
553
+ else if (node.valueExpr !== undefined) {
554
+ lines.push(`${indent}{`);
555
+ lines.push(`${indent}${INDENT}int val = ${node.valueExpr};`);
556
+ lines.push(`${indent}${INDENT}if (reimgui::renderer::slider_int(${node.label}, &val, ${node.min}, ${node.max})) {`);
557
+ if (node.onChangeExpr) {
558
+ lines.push(`${indent}${INDENT}${INDENT}${node.onChangeExpr};`);
559
+ }
560
+ lines.push(`${indent}${INDENT}}`);
561
+ lines.push(`${indent}}`);
562
+ }
563
+ }
564
+ function emitDragFloat(node, lines, indent) {
565
+ const speed = ensureFloatLiteral(node.speed);
566
+ if (node.stateVar) {
567
+ lines.push(`${indent}{`);
568
+ lines.push(`${indent}${INDENT}float val = ${node.stateVar}.get();`);
569
+ lines.push(`${indent}${INDENT}if (reimgui::renderer::drag_float(${node.label}, &val, ${speed})) {`);
570
+ lines.push(`${indent}${INDENT}${INDENT}${node.stateVar}.set(val);`);
571
+ lines.push(`${indent}${INDENT}}`);
572
+ lines.push(`${indent}}`);
573
+ }
574
+ else if (node.valueExpr !== undefined) {
575
+ lines.push(`${indent}{`);
576
+ lines.push(`${indent}${INDENT}float val = ${node.valueExpr};`);
577
+ lines.push(`${indent}${INDENT}if (reimgui::renderer::drag_float(${node.label}, &val, ${speed})) {`);
578
+ if (node.onChangeExpr) {
579
+ lines.push(`${indent}${INDENT}${INDENT}${node.onChangeExpr};`);
580
+ }
581
+ lines.push(`${indent}${INDENT}}`);
582
+ lines.push(`${indent}}`);
583
+ }
584
+ }
585
+ function emitDragInt(node, lines, indent) {
586
+ const speed = ensureFloatLiteral(node.speed);
587
+ if (node.stateVar) {
588
+ lines.push(`${indent}{`);
589
+ lines.push(`${indent}${INDENT}int val = ${node.stateVar}.get();`);
590
+ lines.push(`${indent}${INDENT}if (reimgui::renderer::drag_int(${node.label}, &val, ${speed})) {`);
591
+ lines.push(`${indent}${INDENT}${INDENT}${node.stateVar}.set(val);`);
592
+ lines.push(`${indent}${INDENT}}`);
593
+ lines.push(`${indent}}`);
594
+ }
595
+ else if (node.valueExpr !== undefined) {
596
+ lines.push(`${indent}{`);
597
+ lines.push(`${indent}${INDENT}int val = ${node.valueExpr};`);
598
+ lines.push(`${indent}${INDENT}if (reimgui::renderer::drag_int(${node.label}, &val, ${speed})) {`);
599
+ if (node.onChangeExpr) {
600
+ lines.push(`${indent}${INDENT}${INDENT}${node.onChangeExpr};`);
601
+ }
602
+ lines.push(`${indent}${INDENT}}`);
603
+ lines.push(`${indent}}`);
604
+ }
605
+ }
606
+ function emitCombo(node, lines, indent) {
607
+ const itemsList = node.items.split(',').map(s => s.trim()).filter(s => s.length > 0);
608
+ const count = itemsList.length;
609
+ const varName = `combo_items_${comboCounter++}`;
610
+ if (node.stateVar) {
611
+ lines.push(`${indent}{`);
612
+ lines.push(`${indent}${INDENT}const char* ${varName}[] = {${itemsList.join(', ')}};`);
613
+ lines.push(`${indent}${INDENT}int val = ${node.stateVar}.get();`);
614
+ lines.push(`${indent}${INDENT}if (reimgui::renderer::combo(${node.label}, &val, ${varName}, ${count})) {`);
615
+ lines.push(`${indent}${INDENT}${INDENT}${node.stateVar}.set(val);`);
616
+ lines.push(`${indent}${INDENT}}`);
617
+ lines.push(`${indent}}`);
618
+ }
619
+ else if (node.valueExpr !== undefined) {
620
+ lines.push(`${indent}{`);
621
+ lines.push(`${indent}${INDENT}const char* ${varName}[] = {${itemsList.join(', ')}};`);
622
+ lines.push(`${indent}${INDENT}int val = ${node.valueExpr};`);
623
+ lines.push(`${indent}${INDENT}if (reimgui::renderer::combo(${node.label}, &val, ${varName}, ${count})) {`);
624
+ if (node.onChangeExpr) {
625
+ lines.push(`${indent}${INDENT}${INDENT}${node.onChangeExpr};`);
626
+ }
627
+ lines.push(`${indent}${INDENT}}`);
628
+ lines.push(`${indent}}`);
629
+ }
630
+ }
631
+ function emitInputInt(node, lines, indent) {
632
+ if (node.stateVar) {
633
+ lines.push(`${indent}{`);
634
+ lines.push(`${indent}${INDENT}int val = ${node.stateVar}.get();`);
635
+ lines.push(`${indent}${INDENT}if (reimgui::renderer::input_int(${node.label}, &val)) {`);
636
+ lines.push(`${indent}${INDENT}${INDENT}${node.stateVar}.set(val);`);
637
+ lines.push(`${indent}${INDENT}}`);
638
+ lines.push(`${indent}}`);
639
+ }
640
+ else if (node.valueExpr !== undefined) {
641
+ lines.push(`${indent}{`);
642
+ lines.push(`${indent}${INDENT}int val = ${node.valueExpr};`);
643
+ lines.push(`${indent}${INDENT}if (reimgui::renderer::input_int(${node.label}, &val)) {`);
644
+ if (node.onChangeExpr) {
645
+ lines.push(`${indent}${INDENT}${INDENT}${node.onChangeExpr};`);
646
+ }
647
+ lines.push(`${indent}${INDENT}}`);
648
+ lines.push(`${indent}}`);
649
+ }
650
+ }
651
+ function emitInputFloat(node, lines, indent) {
652
+ if (node.stateVar) {
653
+ lines.push(`${indent}{`);
654
+ lines.push(`${indent}${INDENT}float val = ${node.stateVar}.get();`);
655
+ lines.push(`${indent}${INDENT}if (reimgui::renderer::input_float(${node.label}, &val)) {`);
656
+ lines.push(`${indent}${INDENT}${INDENT}${node.stateVar}.set(val);`);
657
+ lines.push(`${indent}${INDENT}}`);
658
+ lines.push(`${indent}}`);
659
+ }
660
+ else if (node.valueExpr !== undefined) {
661
+ lines.push(`${indent}{`);
662
+ lines.push(`${indent}${INDENT}float val = ${node.valueExpr};`);
663
+ lines.push(`${indent}${INDENT}if (reimgui::renderer::input_float(${node.label}, &val)) {`);
664
+ if (node.onChangeExpr) {
665
+ lines.push(`${indent}${INDENT}${INDENT}${node.onChangeExpr};`);
666
+ }
667
+ lines.push(`${indent}${INDENT}}`);
668
+ lines.push(`${indent}}`);
669
+ }
670
+ }
671
+ function emitColorEdit(node, lines, indent) {
672
+ if (node.stateVar) {
673
+ lines.push(`${indent}{`);
674
+ lines.push(`${indent}${INDENT}auto val = ${node.stateVar}.get();`);
675
+ lines.push(`${indent}${INDENT}if (reimgui::renderer::color_edit(${node.label}, val.data())) {`);
676
+ lines.push(`${indent}${INDENT}${INDENT}${node.stateVar}.set(val);`);
677
+ lines.push(`${indent}${INDENT}}`);
678
+ lines.push(`${indent}}`);
679
+ }
680
+ }
681
+ function emitListBox(node, lines, indent) {
682
+ const itemsList = node.items.split(',').map(s => s.trim()).filter(s => s.length > 0);
683
+ const count = itemsList.length;
684
+ const varName = `listbox_items_${listBoxCounter++}`;
685
+ if (node.stateVar) {
686
+ lines.push(`${indent}{`);
687
+ lines.push(`${indent}${INDENT}const char* ${varName}[] = {${itemsList.join(', ')}};`);
688
+ lines.push(`${indent}${INDENT}int val = ${node.stateVar}.get();`);
689
+ lines.push(`${indent}${INDENT}if (reimgui::renderer::list_box(${node.label}, &val, ${varName}, ${count})) {`);
690
+ lines.push(`${indent}${INDENT}${INDENT}${node.stateVar}.set(val);`);
691
+ lines.push(`${indent}${INDENT}}`);
692
+ lines.push(`${indent}}`);
693
+ }
694
+ else if (node.valueExpr !== undefined) {
695
+ lines.push(`${indent}{`);
696
+ lines.push(`${indent}${INDENT}const char* ${varName}[] = {${itemsList.join(', ')}};`);
697
+ lines.push(`${indent}${INDENT}int val = ${node.valueExpr};`);
698
+ lines.push(`${indent}${INDENT}if (reimgui::renderer::list_box(${node.label}, &val, ${varName}, ${count})) {`);
699
+ if (node.onChangeExpr) {
700
+ lines.push(`${indent}${INDENT}${INDENT}${node.onChangeExpr};`);
701
+ }
702
+ lines.push(`${indent}${INDENT}}`);
703
+ lines.push(`${indent}}`);
704
+ }
705
+ }
706
+ function emitProgressBar(node, lines, indent) {
707
+ if (node.overlay) {
708
+ lines.push(`${indent}reimgui::renderer::progress_bar(${node.value}, ${node.overlay});`);
709
+ }
710
+ else {
711
+ lines.push(`${indent}reimgui::renderer::progress_bar(${node.value});`);
712
+ }
713
+ }
714
+ function emitTooltip(node, lines, indent) {
715
+ lines.push(`${indent}reimgui::renderer::tooltip(${node.text});`);
716
+ }