imxc 0.5.4 → 0.6.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.
package/dist/emitter.js CHANGED
@@ -2,10 +2,15 @@ const INDENT = ' ';
2
2
  let currentCompName = '';
3
3
  let currentBoundProps = new Set();
4
4
  let allBoundProps = new Map();
5
+ let sourceMapEnabled = true;
5
6
  function emitLocComment(loc, tag, lines, indent) {
6
- if (loc) {
7
- lines.push(`${indent}// ${loc.file}:${loc.line} <${tag}>`);
7
+ if (!loc)
8
+ return;
9
+ if (sourceMapEnabled) {
10
+ const filename = loc.file.replace(/\\/g, '/');
11
+ lines.push(`#line ${loc.line} "${filename}"`);
8
12
  }
13
+ lines.push(`${indent}// ${loc.file}:${loc.line} <${tag}>`);
9
14
  }
10
15
  function cppType(t) {
11
16
  switch (t) {
@@ -14,12 +19,16 @@ function cppType(t) {
14
19
  case 'bool': return 'bool';
15
20
  case 'string': return 'std::string';
16
21
  case 'color': return 'std::array<float, 4>';
22
+ case 'int_array': return 'std::array<int, 4>';
17
23
  }
18
24
  }
19
25
  function cppPropType(t) {
20
26
  if (t === 'callback')
21
27
  return 'std::function<void()>';
22
- return cppType(t);
28
+ if (t === 'int' || t === 'float' || t === 'bool' || t === 'string' || t === 'color' || t === 'int_array') {
29
+ return cppType(t);
30
+ }
31
+ return t;
23
32
  }
24
33
  /**
25
34
  * Ensure a string expression is a const char*.
@@ -38,33 +47,50 @@ function asCharPtr(expr) {
38
47
  }
39
48
  /**
40
49
  * For directBind emitters: if the valueExpr references a bound prop (pointer),
41
- * return &*expr for bound props (C++ identity: &*ptr == ptr, and the *
42
- * blocks the post-processing regex lookbehind from dereferencing again).
50
+ * return the correct address expression for nested bound props.
43
51
  * Otherwise, emit &expr.
44
52
  */
45
53
  function emitDirectBindPtr(valueExpr) {
46
- const propName = valueExpr.startsWith('props.') ? valueExpr.slice(6).split('.')[0].split('[')[0] : '';
47
- if (currentBoundProps.has(propName)) {
48
- return `&*${valueExpr}`; // already a pointer; &* is identity, * blocks regex lookbehind
54
+ const boundExpr = emitBoundValueExpr(valueExpr);
55
+ if (boundExpr !== valueExpr) {
56
+ return `&${boundExpr}`;
49
57
  }
50
58
  return `&${valueExpr}`;
51
59
  }
60
+ function boundPropName(valueExpr) {
61
+ return valueExpr.startsWith('props.') ? valueExpr.slice(6).split('.')[0].split('[')[0] : '';
62
+ }
63
+ function emitBoundValueExpr(valueExpr) {
64
+ const propName = boundPropName(valueExpr);
65
+ if (!currentBoundProps.has(propName)) {
66
+ return valueExpr;
67
+ }
68
+ const prefix = `props.${propName}`;
69
+ const suffix = valueExpr.slice(prefix.length);
70
+ if (suffix.length === 0) {
71
+ return `(*props.${propName})`;
72
+ }
73
+ return `((*props.${propName})${suffix})`;
74
+ }
52
75
  function emitImVec4(arrayStr) {
53
76
  const parts = arrayStr.split(',').map(s => {
54
77
  const v = s.trim();
55
- return v.includes('.') ? `${v}f` : `${v}.0f`;
78
+ return /^-?\d+(\.\d+)?$/.test(v) ? (v.includes('.') ? `${v}f` : `${v}.0f`) : v;
56
79
  });
57
80
  return `ImVec4(${parts.join(', ')})`;
58
81
  }
59
82
  function emitImVec2(arrayStr) {
60
83
  const parts = arrayStr.split(',').map(s => {
61
84
  const v = s.trim();
62
- return v.includes('.') ? `${v}f` : `${v}.0f`;
85
+ return /^-?\d+(\.\d+)?$/.test(v) ? (v.includes('.') ? `${v}f` : `${v}.0f`) : v;
63
86
  });
64
87
  return `ImVec2(${parts.join(', ')})`;
65
88
  }
66
89
  function emitFloat(val) {
67
- return val.includes('.') ? `${val}F` : `${val}.0F`;
90
+ const trimmed = val.trim();
91
+ if (!/^-?\d+(\.\d+)?$/.test(trimmed))
92
+ return trimmed;
93
+ return trimmed.includes('.') ? `${trimmed}F` : `${trimmed}.0F`;
68
94
  }
69
95
  function findDockLayout(nodes) {
70
96
  for (const node of nodes) {
@@ -111,7 +137,7 @@ function emitDockSetupFunction(layout, compName, lines) {
111
137
  * Emit a .gen.h header for a component that has props.
112
138
  * Contains the props struct and function forward declaration.
113
139
  */
114
- export function emitComponentHeader(comp, sourceFile, boundProps) {
140
+ export function emitComponentHeader(comp, sourceFile, boundProps, sharedPropsType, resolvedBoundTypes) {
115
141
  const lines = [];
116
142
  if (sourceFile) {
117
143
  lines.push(`// Generated from ${sourceFile} by imxc`);
@@ -121,26 +147,37 @@ export function emitComponentHeader(comp, sourceFile, boundProps) {
121
147
  lines.push('#include <imx/renderer.h>');
122
148
  lines.push('#include <functional>');
123
149
  lines.push('#include <string>');
150
+ if (sharedPropsType) {
151
+ lines.push(`#include "${sharedPropsType}.h"`);
152
+ }
124
153
  lines.push('');
125
- // Props struct
126
- lines.push(`struct ${comp.name}Props {`);
127
- for (const p of comp.params) {
128
- if (boundProps && boundProps.has(p.name)) {
129
- lines.push(`${INDENT}${cppPropType(p.type)}* ${p.name} = nullptr;`);
130
- }
131
- else {
132
- lines.push(`${INDENT}${cppPropType(p.type)} ${p.name};`);
154
+ if (comp.params.length > 0) {
155
+ // Props struct
156
+ lines.push(`struct ${comp.name}Props {`);
157
+ for (const p of comp.params) {
158
+ if (boundProps && boundProps.has(p.name)) {
159
+ // Use resolved C++ type from parent interface if available (e.g. float instead of int)
160
+ const actualType = resolvedBoundTypes?.get(p.name) ?? cppPropType(p.type);
161
+ lines.push(`${INDENT}${actualType}* ${p.name} = nullptr;`);
162
+ }
163
+ else {
164
+ lines.push(`${INDENT}${cppPropType(p.type)} ${p.name};`);
165
+ }
133
166
  }
167
+ lines.push('};');
168
+ lines.push('');
169
+ lines.push(`void ${comp.name}_render(imx::RenderContext& ctx, ${comp.name}Props& props);`);
170
+ }
171
+ else {
172
+ // Propless component — just the function forward declaration
173
+ lines.push(`void ${comp.name}_render(imx::RenderContext& ctx);`);
134
174
  }
135
- lines.push('};');
136
- lines.push('');
137
- // Function forward declaration
138
- lines.push(`void ${comp.name}_render(imx::RenderContext& ctx, ${comp.name}Props& props);`);
139
175
  lines.push('');
140
176
  return lines.join('\n');
141
177
  }
142
- export function emitComponent(comp, imports, sourceFile, boundProps, boundPropsMap) {
178
+ export function emitComponent(comp, imports, sourceFile, boundProps, boundPropsMap, options) {
143
179
  const lines = [];
180
+ sourceMapEnabled = options?.sourceMap ?? true;
144
181
  // Reset counters for each component
145
182
  styleCounter = 0;
146
183
  customComponentCounter = 0;
@@ -149,8 +186,10 @@ export function emitComponent(comp, imports, sourceFile, boundProps, boundPropsM
149
186
  listBoxCounter = 0;
150
187
  nativeWidgetCounter = 0;
151
188
  plotCounter = 0;
189
+ widgetTempCounter = 0;
152
190
  dragDropSourceStack.length = 0;
153
191
  dragDropTargetStack.length = 0;
192
+ collapsingHeaderOnCloseStack.length = 0;
154
193
  currentCompName = comp.name;
155
194
  currentBoundProps = boundProps ?? new Set();
156
195
  allBoundProps = boundPropsMap ?? new Map();
@@ -191,6 +230,12 @@ export function emitComponent(comp, imports, sourceFile, boundProps, boundPropsM
191
230
  if (hasColorType) {
192
231
  lines.push('#include <array>');
193
232
  }
233
+ // Include imported component headers
234
+ if (imports && imports.length > 0) {
235
+ for (const imp of imports) {
236
+ lines.push(`#include "${imp.headerFile}"`);
237
+ }
238
+ }
194
239
  // Embed image includes
195
240
  const embedKeysProps = collectEmbedKeys(comp.body);
196
241
  for (const key of embedKeysProps) {
@@ -241,7 +286,9 @@ export function emitComponent(comp, imports, sourceFile, boundProps, boundPropsM
241
286
  ? `std::string(${slot.initialValue})`
242
287
  : slot.type === 'color'
243
288
  ? `std::array<float, 4>${slot.initialValue}`
244
- : slot.initialValue;
289
+ : slot.type === 'int_array'
290
+ ? `std::array<int, 4>${slot.initialValue}`
291
+ : slot.initialValue;
245
292
  lines.push(`${INDENT}auto ${slot.name} = ctx.use_state<${cppType(slot.type)}>(${initVal}, ${slot.index});`);
246
293
  }
247
294
  if (comp.stateSlots.length > 0) {
@@ -254,6 +301,8 @@ export function emitComponent(comp, imports, sourceFile, boundProps, boundPropsM
254
301
  for (let i = 0; i < lines.length; i++) {
255
302
  if (lines[i].trimStart().startsWith('//'))
256
303
  continue;
304
+ if (lines[i].trimStart().startsWith('#line'))
305
+ continue;
257
306
  for (const prop of currentBoundProps) {
258
307
  // Replace props.X reads with (*props.X) — but not &props.X or *props.X (already handled)
259
308
  const pattern = new RegExp(`(?<![&*])\\bprops\\.${prop}\\b`, 'g');
@@ -265,12 +314,37 @@ export function emitComponent(comp, imports, sourceFile, boundProps, boundPropsM
265
314
  lines.push('');
266
315
  return lines.join('\n');
267
316
  }
268
- export function emitRoot(rootName, stateCount, bufferCount, sourceFile, propsType, namedPropsType) {
317
+ export function emitRoot(rootName, stateCount, bufferCount, sourceFile, propsType, namedPropsType, fontDeclarations) {
269
318
  const lines = [];
319
+ const fonts = fontDeclarations ?? [];
270
320
  if (sourceFile) {
271
321
  lines.push(`// Generated from ${sourceFile} by imxc`);
272
322
  }
273
323
  lines.push('#include <imx/runtime.h>');
324
+ // Font embed includes and init function
325
+ if (fonts.length > 0) {
326
+ lines.push('#include <imx/renderer.h>');
327
+ for (const f of fonts) {
328
+ if (f.embed && f.embedKey) {
329
+ lines.push(`#include "${f.embedKey}.embed.h"`);
330
+ }
331
+ }
332
+ lines.push('');
333
+ lines.push('void _imx_load_fonts() {');
334
+ lines.push(`${INDENT}static bool done = false;`);
335
+ lines.push(`${INDENT}if (done) return;`);
336
+ lines.push(`${INDENT}done = true;`);
337
+ for (const f of fonts) {
338
+ if (f.embed && f.embedKey) {
339
+ lines.push(`${INDENT}imx::load_font_embedded("${f.name}", ${f.embedKey}_data, ${f.embedKey}_size, ${f.size});`);
340
+ }
341
+ else {
342
+ lines.push(`${INDENT}imx::load_font("${f.name}", "${f.src}", ${f.size});`);
343
+ }
344
+ }
345
+ lines.push('}');
346
+ lines.push('');
347
+ }
274
348
  if (propsType) {
275
349
  if (namedPropsType) {
276
350
  // Named interface type (e.g. AppState defined in user code).
@@ -295,6 +369,9 @@ export function emitRoot(rootName, stateCount, bufferCount, sourceFile, propsTyp
295
369
  lines.push(`void render_root(Runtime& runtime, ${propsType}& state) {`);
296
370
  }
297
371
  lines.push(`${INDENT}auto& ctx = runtime.begin_frame();`);
372
+ if (fonts.length > 0) {
373
+ lines.push(`${INDENT}_imx_load_fonts();`);
374
+ }
298
375
  lines.push(`${INDENT}ctx.begin_instance("${rootName}", 0, ${stateCount}, ${bufferCount});`);
299
376
  lines.push(`${INDENT}${rootName}_render(ctx, state);`);
300
377
  lines.push(`${INDENT}ctx.end_instance();`);
@@ -309,6 +386,9 @@ export function emitRoot(rootName, stateCount, bufferCount, sourceFile, propsTyp
309
386
  lines.push('namespace imx {');
310
387
  lines.push('void render_root(Runtime& runtime) {');
311
388
  lines.push(`${INDENT}auto& ctx = runtime.begin_frame();`);
389
+ if (fonts.length > 0) {
390
+ lines.push(`${INDENT}_imx_load_fonts();`);
391
+ }
312
392
  lines.push(`${INDENT}ctx.begin_instance("${rootName}", 0, ${stateCount}, ${bufferCount});`);
313
393
  lines.push(`${INDENT}${rootName}_render(ctx);`);
314
394
  lines.push(`${INDENT}ctx.end_instance();`);
@@ -339,6 +419,18 @@ function emitNode(node, lines, depth) {
339
419
  case 'button':
340
420
  emitButton(node, lines, indent, depth);
341
421
  break;
422
+ case 'small_button':
423
+ emitSmallButton(node, lines, indent);
424
+ break;
425
+ case 'arrow_button':
426
+ emitArrowButton(node, lines, indent);
427
+ break;
428
+ case 'invisible_button':
429
+ emitInvisibleButton(node, lines, indent);
430
+ break;
431
+ case 'image_button':
432
+ emitImageButton(node, lines, indent);
433
+ break;
342
434
  case 'text_input':
343
435
  emitTextInput(node, lines, indent);
344
436
  break;
@@ -348,6 +440,21 @@ function emitNode(node, lines, depth) {
348
440
  case 'separator':
349
441
  lines.push(`${indent}imx::renderer::separator();`);
350
442
  break;
443
+ case 'spacing':
444
+ emitSpacing(node, lines, indent);
445
+ break;
446
+ case 'dummy':
447
+ emitDummy(node, lines, indent);
448
+ break;
449
+ case 'same_line':
450
+ emitSameLine(node, lines, indent);
451
+ break;
452
+ case 'new_line':
453
+ emitNewLine(node, lines, indent);
454
+ break;
455
+ case 'cursor':
456
+ emitCursor(node, lines, indent);
457
+ break;
351
458
  case 'begin_popup':
352
459
  emitLocComment(node.loc, 'Popup', lines, indent);
353
460
  lines.push(`${indent}if (imx::renderer::begin_popup(${node.id})) {`);
@@ -369,6 +476,37 @@ function emitNode(node, lines, depth) {
369
476
  case 'menu_item':
370
477
  emitMenuItem(node, lines, indent, depth);
371
478
  break;
479
+ case 'begin_table':
480
+ emitBeginTable(node, lines, indent);
481
+ break;
482
+ case 'end_table':
483
+ lines.push(`${indent}imx::renderer::end_table();`);
484
+ lines.push(`${indent}}`);
485
+ break;
486
+ case 'begin_table_row':
487
+ emitBeginTableRow(node, lines, indent);
488
+ break;
489
+ case 'end_table_row':
490
+ lines.push(`${indent}imx::renderer::end_table_row();`);
491
+ break;
492
+ case 'begin_table_cell':
493
+ emitBeginTableCell(node, lines, indent);
494
+ break;
495
+ case 'end_table_cell':
496
+ lines.push(`${indent}imx::renderer::end_table_cell();`);
497
+ break;
498
+ case 'begin_tree_node':
499
+ emitBeginTreeNode(node, lines, indent);
500
+ break;
501
+ case 'end_tree_node':
502
+ emitEndTreeNode(node, lines, indent);
503
+ break;
504
+ case 'begin_collapsing_header':
505
+ emitBeginCollapsingHeader(node, lines, indent);
506
+ break;
507
+ case 'end_collapsing_header':
508
+ emitEndCollapsingHeader(node, lines, indent);
509
+ break;
372
510
  case 'custom_component':
373
511
  emitCustomComponent(node, lines, indent);
374
512
  break;
@@ -384,6 +522,35 @@ function emitNode(node, lines, depth) {
384
522
  case 'drag_int':
385
523
  emitDragInt(node, lines, indent);
386
524
  break;
525
+ case 'begin_combo': {
526
+ const n = node;
527
+ emitLocComment(n.loc, 'Combo (manual)', lines, indent);
528
+ const label = asCharPtr(n.label);
529
+ const preview = asCharPtr(n.preview);
530
+ const flagStr = n.flags.length > 0 ? n.flags.join(' | ') : '0';
531
+ if (n.width) {
532
+ lines.push(`${indent}ImGui::PushItemWidth(${n.width});`);
533
+ }
534
+ lines.push(`${indent}if (imx::renderer::begin_combo(${label}, ${preview}, ${flagStr})) {`);
535
+ break;
536
+ }
537
+ case 'end_combo':
538
+ lines.push(`${indent}imx::renderer::end_combo();`);
539
+ lines.push(`${indent}}`);
540
+ break;
541
+ case 'begin_list_box': {
542
+ const n = node;
543
+ emitLocComment(n.loc, 'ListBox (manual)', lines, indent);
544
+ const label = asCharPtr(n.label);
545
+ const w = n.width ?? '0.0f';
546
+ const h = n.height ?? '0.0f';
547
+ lines.push(`${indent}if (imx::renderer::begin_list_box(${label}, ${w}, ${h})) {`);
548
+ break;
549
+ }
550
+ case 'end_list_box':
551
+ lines.push(`${indent}imx::renderer::end_list_box();`);
552
+ lines.push(`${indent}}`);
553
+ break;
387
554
  case 'combo':
388
555
  emitCombo(node, lines, indent);
389
556
  break;
@@ -396,6 +563,9 @@ function emitNode(node, lines, depth) {
396
563
  case 'color_edit':
397
564
  emitColorEdit(node, lines, indent);
398
565
  break;
566
+ case 'color_edit3':
567
+ emitColorEdit3(node, lines, indent);
568
+ break;
399
569
  case 'list_box':
400
570
  emitListBox(node, lines, indent);
401
571
  break;
@@ -405,9 +575,16 @@ function emitNode(node, lines, depth) {
405
575
  case 'tooltip':
406
576
  emitTooltip(node, lines, indent);
407
577
  break;
578
+ case 'shortcut':
579
+ emitShortcut(node, lines, indent);
580
+ break;
408
581
  case 'bullet_text':
409
582
  emitBulletText(node, lines, indent);
410
583
  break;
584
+ case 'bullet':
585
+ emitLocComment(node.loc, 'Bullet', lines, indent);
586
+ lines.push(`${indent}imx::renderer::bullet();`);
587
+ break;
411
588
  case 'label_text':
412
589
  emitLabelText(node, lines, indent);
413
590
  break;
@@ -423,6 +600,9 @@ function emitNode(node, lines, depth) {
423
600
  case 'color_picker':
424
601
  emitColorPicker(node, lines, indent);
425
602
  break;
603
+ case 'color_picker3':
604
+ emitColorPicker3(node, lines, indent);
605
+ break;
426
606
  case 'plot_lines':
427
607
  emitPlotLines(node, lines, indent);
428
608
  break;
@@ -444,6 +624,62 @@ function emitNode(node, lines, depth) {
444
624
  case 'draw_text':
445
625
  emitDrawText(node, lines, indent);
446
626
  break;
627
+ case 'draw_bezier_cubic':
628
+ emitDrawBezierCubic(node, lines, indent);
629
+ break;
630
+ case 'draw_bezier_quadratic':
631
+ emitDrawBezierQuadratic(node, lines, indent);
632
+ break;
633
+ case 'draw_polyline':
634
+ emitDrawPolyline(node, lines, indent);
635
+ break;
636
+ case 'draw_convex_poly_filled':
637
+ emitDrawConvexPolyFilled(node, lines, indent);
638
+ break;
639
+ case 'draw_ngon':
640
+ emitDrawNgon(node, lines, indent);
641
+ break;
642
+ case 'draw_ngon_filled':
643
+ emitDrawNgonFilled(node, lines, indent);
644
+ break;
645
+ case 'draw_triangle':
646
+ emitDrawTriangle(node, lines, indent);
647
+ break;
648
+ case 'input_float_n':
649
+ emitVectorInput(node, 'input_float_n', 'float', lines, indent);
650
+ break;
651
+ case 'input_int_n':
652
+ emitVectorInput(node, 'input_int_n', 'int', lines, indent);
653
+ break;
654
+ case 'drag_float_n': {
655
+ const speed = ensureFloatLiteral(node.speed);
656
+ emitVectorInput(node, 'drag_float_n', 'float', lines, indent, `, ${speed}`);
657
+ break;
658
+ }
659
+ case 'drag_int_n': {
660
+ const speed = ensureFloatLiteral(node.speed);
661
+ emitVectorInput(node, 'drag_int_n', 'int', lines, indent, `, ${speed}`);
662
+ break;
663
+ }
664
+ case 'slider_float_n': {
665
+ const min = ensureFloatLiteral(node.min);
666
+ const max = ensureFloatLiteral(node.max);
667
+ emitVectorInput(node, 'slider_float_n', 'float', lines, indent, `, ${min}, ${max}`);
668
+ break;
669
+ }
670
+ case 'slider_int_n': {
671
+ emitVectorInput(node, 'slider_int_n', 'int', lines, indent, `, ${node.min}, ${node.max}`);
672
+ break;
673
+ }
674
+ case 'vslider_float':
675
+ emitVSliderFloat(node, lines, indent);
676
+ break;
677
+ case 'vslider_int':
678
+ emitVSliderInt(node, lines, indent);
679
+ break;
680
+ case 'slider_angle':
681
+ emitSliderAngle(node, lines, indent);
682
+ break;
447
683
  case 'native_widget':
448
684
  emitNativeWidget(node, lines, indent);
449
685
  break;
@@ -472,10 +708,13 @@ let comboCounter = 0;
472
708
  let listBoxCounter = 0;
473
709
  let nativeWidgetCounter = 0;
474
710
  let plotCounter = 0;
711
+ let widgetTempCounter = 0;
475
712
  const windowOpenStack = []; // tracks if begin_window used open prop
476
713
  const modalOnCloseStack = []; // tracks modal onClose expressions
714
+ const collapsingHeaderOnCloseStack = [];
477
715
  const dragDropSourceStack = [];
478
716
  const dragDropTargetStack = [];
717
+ const multiSelectCallbackStack = [];
479
718
  /**
480
719
  * Build a Style variable from a raw style expression string for self-closing components.
481
720
  * Handles JS-like object literals: { width: 300, height: 100 } -> imx::Style with assignments.
@@ -507,53 +746,118 @@ function splitStylePairs(inner) {
507
746
  pairs.push(last);
508
747
  return pairs;
509
748
  }
749
+ function assignStyleExpr(varName, styleExpr, indent, lines) {
750
+ const trimmed = styleExpr.trim();
751
+ if (!trimmed)
752
+ return;
753
+ if (!trimmed.startsWith('{') || !trimmed.endsWith('}')) {
754
+ lines.push(`${indent}${varName} = ${trimmed};`);
755
+ return;
756
+ }
757
+ const inner = trimmed.slice(1, -1).trim();
758
+ if (!inner)
759
+ return;
760
+ const pairs = splitStylePairs(inner);
761
+ for (const pair of pairs) {
762
+ const colonIdx = pair.indexOf(':');
763
+ if (colonIdx === -1)
764
+ continue;
765
+ const key = pair.substring(0, colonIdx).trim();
766
+ const val = pair.substring(colonIdx + 1).trim();
767
+ const cppKey = key === 'paddingHorizontal' ? 'padding_horizontal'
768
+ : key === 'paddingVertical' ? 'padding_vertical'
769
+ : key === 'minWidth' ? 'min_width'
770
+ : key === 'minHeight' ? 'min_height'
771
+ : key === 'backgroundColor' ? 'background_color'
772
+ : key === 'textColor' ? 'text_color'
773
+ : key === 'fontSize' ? 'font_size'
774
+ : key;
775
+ if (cppKey === 'background_color' || cppKey === 'text_color') {
776
+ const arrInner = val.trim().replace(/^\[/, '').replace(/\]$/, '');
777
+ const components = arrInner.split(',').map(c => {
778
+ const s = c.trim();
779
+ return /^-?\d+(\.\d+)?$/.test(s) ? (s.includes('.') ? `${s}f` : `${s}.0f`) : s;
780
+ });
781
+ lines.push(`${indent}${varName}.${cppKey} = ImVec4(${components.join(', ')});`);
782
+ }
783
+ else {
784
+ lines.push(`${indent}${varName}.${cppKey} = ${emitFloat(val)};`);
785
+ }
786
+ }
787
+ }
510
788
  function buildStyleVar(styleExpr, indent, lines) {
511
789
  if (!styleExpr)
512
790
  return null;
513
- // Check if it looks like an object literal: { key: value, ... }
514
- const trimmed = styleExpr.trim();
515
- if (trimmed.startsWith('{') && trimmed.endsWith('}')) {
516
- const inner = trimmed.slice(1, -1).trim();
517
- if (!inner)
518
- return null;
519
- const varName = `style_${styleCounter++}`;
520
- lines.push(`${indent}imx::Style ${varName};`);
521
- // Parse key: value pairs (bracket-aware to handle array values)
522
- const pairs = splitStylePairs(inner);
523
- for (const pair of pairs) {
524
- const colonIdx = pair.indexOf(':');
525
- if (colonIdx === -1)
526
- continue;
527
- const key = pair.substring(0, colonIdx).trim();
528
- const val = pair.substring(colonIdx + 1).trim();
529
- // Map camelCase to snake_case
530
- const cppKey = key === 'paddingHorizontal' ? 'padding_horizontal'
531
- : key === 'paddingVertical' ? 'padding_vertical'
532
- : key === 'minWidth' ? 'min_width'
533
- : key === 'minHeight' ? 'min_height'
534
- : key === 'backgroundColor' ? 'background_color'
535
- : key === 'textColor' ? 'text_color'
536
- : key === 'fontSize' ? 'font_size'
537
- : key;
538
- // ImVec4 fields (color arrays)
539
- if (cppKey === 'background_color' || cppKey === 'text_color') {
540
- // val is like [r, g, b, a] — convert to ImVec4(r, g, b, a)
541
- const arrInner = val.trim().replace(/^\[/, '').replace(/\]$/, '');
542
- const components = arrInner.split(',').map(c => {
543
- const s = c.trim();
544
- return s.includes('.') ? `${s}f` : `${s}.0f`;
545
- });
546
- lines.push(`${indent}${varName}.${cppKey} = ImVec4(${components.join(', ')});`);
547
- }
548
- else {
549
- const floatVal = val.includes('.') ? `${val}F` : `${val}.0F`;
550
- lines.push(`${indent}${varName}.${cppKey} = ${floatVal};`);
551
- }
552
- }
791
+ const varName = `style_${styleCounter++}`;
792
+ lines.push(`${indent}imx::Style ${varName};`);
793
+ assignStyleExpr(varName, styleExpr, indent, lines);
794
+ return varName;
795
+ }
796
+ function buildWidgetStyleVar(styleExpr, widthExpr, indent, lines) {
797
+ if (!styleExpr && !widthExpr)
798
+ return null;
799
+ const varName = `style_${styleCounter++}`;
800
+ lines.push(`${indent}imx::Style ${varName};`);
801
+ if (styleExpr) {
802
+ assignStyleExpr(varName, styleExpr, indent, lines);
803
+ }
804
+ if (widthExpr) {
805
+ lines.push(`${indent}${varName}.width = ${emitFloat(widthExpr)};`);
806
+ }
807
+ return varName;
808
+ }
809
+ function nextWidgetTemp(prefix) {
810
+ return `_${prefix}_${widgetTempCounter++}`;
811
+ }
812
+ function emitActionStatements(statements, lines, indent) {
813
+ if (!statements)
814
+ return;
815
+ for (const stmt of statements) {
816
+ lines.push(`${indent}${stmt}`);
817
+ }
818
+ }
819
+ function emitItemInteractionBefore(item, lines, indent) {
820
+ if (!item?.autoFocus)
821
+ return;
822
+ lines.push(`${indent}if (${item.autoFocus}) imx::renderer::request_keyboard_focus();`);
823
+ }
824
+ function emitItemInteractionAfter(item, lines, indent) {
825
+ if (!item)
826
+ return;
827
+ if (item.tooltip) {
828
+ lines.push(`${indent}imx::renderer::tooltip(${asCharPtr(item.tooltip)});`);
829
+ }
830
+ if (item.scrollToHere) {
831
+ lines.push(`${indent}if (${item.scrollToHere}) imx::renderer::item_scroll_to_here();`);
832
+ }
833
+ if (item.cursor) {
834
+ lines.push(`${indent}imx::renderer::item_cursor(${asCharPtr(item.cursor)});`);
835
+ }
836
+ const callbackChecks = [
837
+ [item.onHover, 'item_hovered'],
838
+ [item.onActive, 'item_active'],
839
+ [item.onFocused, 'item_focused'],
840
+ [item.onClicked, 'item_clicked'],
841
+ [item.onDoubleClicked, 'item_double_clicked'],
842
+ ];
843
+ for (const [statements, fn] of callbackChecks) {
844
+ if (!statements || statements.length === 0)
845
+ continue;
846
+ lines.push(`${indent}if (imx::renderer::${fn}()) {`);
847
+ emitActionStatements(statements, lines, indent + INDENT);
848
+ lines.push(`${indent}}`);
849
+ }
850
+ }
851
+ function emitBoolWidgetCall(callExpr, item, lines, indent, resultVar) {
852
+ emitItemInteractionBefore(item, lines, indent);
853
+ if (item || resultVar) {
854
+ const varName = resultVar ?? nextWidgetTemp('item');
855
+ lines.push(`${indent}bool ${varName} = ${callExpr};`);
856
+ emitItemInteractionAfter(item, lines, indent);
553
857
  return varName;
554
858
  }
555
- // Already a variable name or expression — return as-is
556
- return styleExpr;
859
+ lines.push(`${indent}${callExpr};`);
860
+ return null;
557
861
  }
558
862
  function buildStyleBlock(node, indent, lines) {
559
863
  // Check for style-related props (gap, padding, width, height, etc.)
@@ -582,12 +886,158 @@ function buildStyleBlock(node, indent, lines) {
582
886
  const varName = `style_${styleCounter++}`;
583
887
  lines.push(`${indent}imx::Style ${varName};`);
584
888
  for (const [key, val] of Object.entries(styleProps)) {
585
- // Ensure the value is a float literal (e.g., 8 -> 8.0F, 8.5 -> 8.5F)
586
- const floatVal = val.includes('.') ? `${val}F` : `${val}.0F`;
587
- lines.push(`${indent}${varName}.${key} = ${floatVal};`);
889
+ lines.push(`${indent}${varName}.${key} = ${emitFloat(val)};`);
588
890
  }
589
891
  return varName;
590
892
  }
893
+ function emitTableOptions(node, varName, indent, lines) {
894
+ lines.push(`${indent}imx::TableOptions ${varName};`);
895
+ if (node.sortable || node.onSortBody)
896
+ lines.push(`${indent}${varName}.sortable = ${node.sortable ?? 'true'};`);
897
+ if (node.hideable)
898
+ lines.push(`${indent}${varName}.hideable = ${node.hideable};`);
899
+ if (node.multiSortable)
900
+ lines.push(`${indent}${varName}.multi_sortable = ${node.multiSortable};`);
901
+ if (node.noClip)
902
+ lines.push(`${indent}${varName}.no_clip = ${node.noClip};`);
903
+ if (node.padOuterX)
904
+ lines.push(`${indent}${varName}.pad_outer_x = ${node.padOuterX};`);
905
+ if (node.scrollX)
906
+ lines.push(`${indent}${varName}.scroll_x = ${node.scrollX};`);
907
+ if (node.scrollY)
908
+ lines.push(`${indent}${varName}.scroll_y = ${node.scrollY};`);
909
+ if (node.noBorders)
910
+ lines.push(`${indent}${varName}.no_borders = ${node.noBorders};`);
911
+ if (node.noRowBg)
912
+ lines.push(`${indent}${varName}.no_row_bg = ${node.noRowBg};`);
913
+ }
914
+ function emitBeginTable(node, lines, indent) {
915
+ emitLocComment(node.loc, 'Table', lines, indent);
916
+ const colsVar = `table_cols_${styleCounter++}`;
917
+ const optsVar = `table_opts_${styleCounter++}`;
918
+ const style = node.style ? buildStyleVar(node.style, indent, lines) : null;
919
+ lines.push(`${indent}imx::TableColumn ${colsVar}[${node.columns.length}];`);
920
+ node.columns.forEach((column, index) => {
921
+ lines.push(`${indent}${colsVar}[${index}].label = ${asCharPtr(column.label)};`);
922
+ if (column.defaultHide)
923
+ lines.push(`${indent}if (${column.defaultHide}) ${colsVar}[${index}].flags |= ImGuiTableColumnFlags_DefaultHide;`);
924
+ if (column.preferSortAscending)
925
+ lines.push(`${indent}if (${column.preferSortAscending}) ${colsVar}[${index}].flags |= ImGuiTableColumnFlags_PreferSortAscending;`);
926
+ if (column.preferSortDescending)
927
+ lines.push(`${indent}if (${column.preferSortDescending}) ${colsVar}[${index}].flags |= ImGuiTableColumnFlags_PreferSortDescending;`);
928
+ if (column.noResize)
929
+ lines.push(`${indent}if (${column.noResize}) ${colsVar}[${index}].flags |= ImGuiTableColumnFlags_NoResize;`);
930
+ if (column.fixedWidth)
931
+ lines.push(`${indent}if (${column.fixedWidth}) ${colsVar}[${index}].flags |= ImGuiTableColumnFlags_WidthFixed;`);
932
+ });
933
+ emitTableOptions(node, optsVar, indent, lines);
934
+ const styleArg = style ?? '{}';
935
+ lines.push(`${indent}if (imx::renderer::begin_table("##table", ${colsVar}, ${node.columns.length}, ${styleArg}, ${optsVar})) {`);
936
+ if (node.onSortBody) {
937
+ const paramName = node.onSortParam ?? 'tableSortSpecs';
938
+ lines.push(`${indent}${INDENT}if (ImGuiTableSortSpecs* _imx_sort_specs = ImGui::TableGetSortSpecs()) {`);
939
+ lines.push(`${indent}${INDENT}${INDENT}if (_imx_sort_specs->SpecsDirty) {`);
940
+ lines.push(`${indent}${INDENT}${INDENT}${INDENT}ImGuiTableSortSpecs& ${paramName} = *_imx_sort_specs;`);
941
+ lines.push(`${indent}${INDENT}${INDENT}${INDENT}${node.onSortBody}`);
942
+ lines.push(`${indent}${INDENT}${INDENT}${INDENT}_imx_sort_specs->SpecsDirty = false;`);
943
+ lines.push(`${indent}${INDENT}${INDENT}}`);
944
+ lines.push(`${indent}${INDENT}}`);
945
+ }
946
+ }
947
+ function emitBeginTableRow(node, lines, indent) {
948
+ emitLocComment(node.loc, 'TableRow', lines, indent);
949
+ if (node.bgColor) {
950
+ lines.push(`${indent}imx::renderer::begin_table_row(${emitImVec4(node.bgColor)});`);
951
+ }
952
+ else {
953
+ lines.push(`${indent}imx::renderer::begin_table_row();`);
954
+ }
955
+ }
956
+ function emitBeginTableCell(node, lines, indent) {
957
+ emitLocComment(node.loc, 'TableCell', lines, indent);
958
+ const columnIndex = node.columnIndex ?? '-1';
959
+ if (node.bgColor) {
960
+ lines.push(`${indent}imx::renderer::begin_table_cell(${columnIndex}, ${emitImVec4(node.bgColor)});`);
961
+ }
962
+ else {
963
+ lines.push(`${indent}imx::renderer::begin_table_cell(${columnIndex});`);
964
+ }
965
+ }
966
+ function emitBeginTreeNode(node, lines, indent) {
967
+ emitLocComment(node.loc, 'TreeNode', lines, indent);
968
+ if (node.forceOpen) {
969
+ lines.push(`${indent}ImGui::SetNextItemOpen(${node.forceOpen}, ImGuiCond_Always);`);
970
+ }
971
+ else if (node.defaultOpen) {
972
+ lines.push(`${indent}ImGui::SetNextItemOpen(${node.defaultOpen}, ImGuiCond_Once);`);
973
+ }
974
+ const flagsVar = `tree_flags_${styleCounter++}`;
975
+ lines.push(`${indent}ImGuiTreeNodeFlags ${flagsVar} = 0;`);
976
+ if (node.defaultOpen)
977
+ lines.push(`${indent}if (${node.defaultOpen}) ${flagsVar} |= ImGuiTreeNodeFlags_DefaultOpen;`);
978
+ if (node.openOnArrow)
979
+ lines.push(`${indent}if (${node.openOnArrow}) ${flagsVar} |= ImGuiTreeNodeFlags_OpenOnArrow;`);
980
+ if (node.openOnDoubleClick)
981
+ lines.push(`${indent}if (${node.openOnDoubleClick}) ${flagsVar} |= ImGuiTreeNodeFlags_OpenOnDoubleClick;`);
982
+ if (node.leaf)
983
+ lines.push(`${indent}if (${node.leaf}) ${flagsVar} |= ImGuiTreeNodeFlags_Leaf;`);
984
+ if (node.bullet)
985
+ lines.push(`${indent}if (${node.bullet}) ${flagsVar} |= ImGuiTreeNodeFlags_Bullet;`);
986
+ if (node.noTreePushOnOpen)
987
+ lines.push(`${indent}if (${node.noTreePushOnOpen}) ${flagsVar} |= ImGuiTreeNodeFlags_NoTreePushOnOpen;`);
988
+ emitItemInteractionBefore(node.item, lines, indent);
989
+ const openVar = nextWidgetTemp('tree_open');
990
+ lines.push(`${indent}bool ${openVar} = imx::renderer::begin_tree_node(${asCharPtr(node.label)}, ${flagsVar});`);
991
+ emitItemInteractionAfter(node.item, lines, indent);
992
+ lines.push(`${indent}if (${openVar}) {`);
993
+ }
994
+ function emitEndTreeNode(node, lines, indent) {
995
+ lines.push(`${indent}imx::renderer::end_tree_node(${node.noTreePushOnOpen ?? 'false'});`);
996
+ lines.push(`${indent}}`);
997
+ }
998
+ function emitBeginCollapsingHeader(node, lines, indent) {
999
+ emitLocComment(node.loc, 'CollapsingHeader', lines, indent);
1000
+ if (node.forceOpen) {
1001
+ lines.push(`${indent}ImGui::SetNextItemOpen(${node.forceOpen}, ImGuiCond_Always);`);
1002
+ }
1003
+ else if (node.defaultOpen) {
1004
+ lines.push(`${indent}ImGui::SetNextItemOpen(${node.defaultOpen}, ImGuiCond_Once);`);
1005
+ }
1006
+ const flagsVar = `header_flags_${styleCounter++}`;
1007
+ lines.push(`${indent}ImGuiTreeNodeFlags ${flagsVar} = 0;`);
1008
+ if (node.defaultOpen)
1009
+ lines.push(`${indent}if (${node.defaultOpen}) ${flagsVar} |= ImGuiTreeNodeFlags_DefaultOpen;`);
1010
+ if (node.closable) {
1011
+ collapsingHeaderOnCloseStack.push(node.onCloseBody ?? null);
1012
+ lines.push(`${indent}{`);
1013
+ lines.push(`${indent}${INDENT}bool header_visible = true;`);
1014
+ lines.push(`${indent}${INDENT}bool* header_visible_ptr = ${node.closable} ? &header_visible : nullptr;`);
1015
+ emitItemInteractionBefore(node.item, lines, indent + INDENT);
1016
+ const openVar = nextWidgetTemp('header_open');
1017
+ lines.push(`${indent}${INDENT}bool ${openVar} = imx::renderer::begin_collapsing_header(${asCharPtr(node.label)}, ${flagsVar}, header_visible_ptr);`);
1018
+ emitItemInteractionAfter(node.item, lines, indent + INDENT);
1019
+ lines.push(`${indent}${INDENT}if (${openVar}) {`);
1020
+ }
1021
+ else {
1022
+ collapsingHeaderOnCloseStack.push(null);
1023
+ emitItemInteractionBefore(node.item, lines, indent);
1024
+ const openVar = nextWidgetTemp('header_open');
1025
+ lines.push(`${indent}bool ${openVar} = imx::renderer::begin_collapsing_header(${asCharPtr(node.label)}, ${flagsVar});`);
1026
+ emitItemInteractionAfter(node.item, lines, indent);
1027
+ lines.push(`${indent}if (${openVar}) {`);
1028
+ }
1029
+ }
1030
+ function emitEndCollapsingHeader(node, lines, indent) {
1031
+ lines.push(`${indent}imx::renderer::end_collapsing_header();`);
1032
+ lines.push(`${indent}}`);
1033
+ const onCloseBody = collapsingHeaderOnCloseStack.pop() ?? null;
1034
+ if (node.closable) {
1035
+ if (onCloseBody) {
1036
+ lines.push(`${indent}${INDENT}if (${node.closable} && !header_visible) { ${onCloseBody} }`);
1037
+ }
1038
+ lines.push(`${indent}}`);
1039
+ }
1040
+ }
591
1041
  function emitBeginContainer(node, lines, indent) {
592
1042
  emitLocComment(node.loc, node.tag, lines, indent);
593
1043
  switch (node.tag) {
@@ -606,21 +1056,89 @@ function emitBeginContainer(node, lines, indent) {
606
1056
  flagParts.push('ImGuiWindowFlags_NoDocking');
607
1057
  if (node.props['noScrollbar'] === 'true')
608
1058
  flagParts.push('ImGuiWindowFlags_NoScrollbar');
1059
+ if (node.props['noBackground'] === 'true')
1060
+ flagParts.push('ImGuiWindowFlags_NoBackground');
1061
+ if (node.props['alwaysAutoResize'] === 'true')
1062
+ flagParts.push('ImGuiWindowFlags_AlwaysAutoResize');
1063
+ if (node.props['noNavFocus'] === 'true')
1064
+ flagParts.push('ImGuiWindowFlags_NoNavFocus');
1065
+ if (node.props['noNav'] === 'true')
1066
+ flagParts.push('ImGuiWindowFlags_NoNav');
1067
+ if (node.props['noDecoration'] === 'true')
1068
+ flagParts.push('ImGuiWindowFlags_NoDecoration');
1069
+ if (node.props['noInputs'] === 'true')
1070
+ flagParts.push('ImGuiWindowFlags_NoInputs');
1071
+ if (node.props['noScrollWithMouse'] === 'true')
1072
+ flagParts.push('ImGuiWindowFlags_NoScrollWithMouse');
1073
+ if (node.props['horizontalScrollbar'] === 'true')
1074
+ flagParts.push('ImGuiWindowFlags_HorizontalScrollbar');
1075
+ if (node.props['alwaysVerticalScrollbar'] === 'true')
1076
+ flagParts.push('ImGuiWindowFlags_AlwaysVerticalScrollbar');
1077
+ if (node.props['alwaysHorizontalScrollbar'] === 'true')
1078
+ flagParts.push('ImGuiWindowFlags_AlwaysHorizontalScrollbar');
1079
+ if (node.props['hasMenuBar'] === 'true')
1080
+ flagParts.push('ImGuiWindowFlags_MenuBar');
609
1081
  const flags = flagParts.length > 0 ? flagParts.join(' | ') : '0';
1082
+ // Window positioning
1083
+ const xExpr = node.props['x'];
1084
+ const yExpr = node.props['y'];
1085
+ if (xExpr && yExpr) {
1086
+ const posCond = node.props['forcePosition'] === 'true' ? 'ImGuiCond_Always' : 'ImGuiCond_Once';
1087
+ lines.push(`${indent}ImGui::SetNextWindowPos(ImVec2(${xExpr}, ${yExpr}), ${posCond});`);
1088
+ }
1089
+ // Window sizing
1090
+ const wExpr = node.props['width'];
1091
+ const hExpr = node.props['height'];
1092
+ if (wExpr || hExpr) {
1093
+ const sizeCond = node.props['forceSize'] === 'true' ? 'ImGuiCond_Always' : 'ImGuiCond_Once';
1094
+ const sw = wExpr ?? '0.0f';
1095
+ const sh = hExpr ?? '0.0f';
1096
+ lines.push(`${indent}ImGui::SetNextWindowSize(ImVec2(${sw}, ${sh}), ${sizeCond});`);
1097
+ }
1098
+ // Window size constraints
1099
+ const minW = node.props['minWidth'];
1100
+ const minH = node.props['minHeight'];
1101
+ const maxW = node.props['maxWidth'];
1102
+ const maxH = node.props['maxHeight'];
1103
+ if (minW || minH || maxW || maxH) {
1104
+ const cminW = minW ?? '0.0f';
1105
+ const cminH = minH ?? '0.0f';
1106
+ const cmaxW = maxW ?? 'FLT_MAX';
1107
+ const cmaxH = maxH ?? 'FLT_MAX';
1108
+ lines.push(`${indent}ImGui::SetNextWindowSizeConstraints(ImVec2(${cminW}, ${cminH}), ImVec2(${cmaxW}, ${cmaxH}));`);
1109
+ }
1110
+ // Window background alpha
1111
+ const bgAlpha = node.props['bgAlpha'];
1112
+ if (bgAlpha) {
1113
+ lines.push(`${indent}ImGui::SetNextWindowBgAlpha(${bgAlpha});`);
1114
+ }
1115
+ // Viewport control
1116
+ if (node.props['noViewport'] === 'true') {
1117
+ lines.push(`${indent}ImGui::SetNextWindowViewport(ImGui::GetMainViewport()->ID);`);
1118
+ }
610
1119
  const openExpr = node.props['open'];
611
1120
  const onCloseExpr = node.props['onClose'];
612
1121
  if (openExpr) {
1122
+ const vpOnTop = node.props['viewportAlwaysOnTop'] === 'true';
613
1123
  windowOpenStack.push(true);
614
1124
  lines.push(`${indent}{`);
615
1125
  lines.push(`${indent} bool win_open = ${openExpr};`);
616
- lines.push(`${indent} imx::renderer::begin_window(${title}, ${flags}, &win_open);`);
1126
+ lines.push(`${indent} imx::renderer::begin_window(${title}, ${flags}, &win_open${vpOnTop ? ', true' : ''});`);
617
1127
  if (onCloseExpr) {
618
- lines.push(`${indent} if (!win_open) { ${onCloseExpr}; }`);
1128
+ const lambdaMatch = onCloseExpr.match(/^\[&\]\(\)\s*\{\s*(.*?)\s*\}$/);
1129
+ const onCloseBody = lambdaMatch ? lambdaMatch[1] : `${onCloseExpr}();`;
1130
+ lines.push(`${indent} if (!win_open) { ${onCloseBody} }`);
619
1131
  }
620
1132
  }
621
1133
  else {
1134
+ const vpOnTopElse = node.props['viewportAlwaysOnTop'] === 'true';
622
1135
  windowOpenStack.push(false);
623
- lines.push(`${indent}imx::renderer::begin_window(${title}, ${flags});`);
1136
+ if (vpOnTopElse) {
1137
+ lines.push(`${indent}imx::renderer::begin_window(${title}, ${flags}, nullptr, true);`);
1138
+ }
1139
+ else {
1140
+ lines.push(`${indent}imx::renderer::begin_window(${title}, ${flags});`);
1141
+ }
624
1142
  }
625
1143
  break;
626
1144
  }
@@ -654,6 +1172,15 @@ function emitBeginContainer(node, lines, indent) {
654
1172
  }
655
1173
  break;
656
1174
  }
1175
+ case 'Indent': {
1176
+ const width = node.props['width'] ? emitFloat(node.props['width']) : '0.0F';
1177
+ lines.push(`${indent}imx::renderer::begin_indent(${width});`);
1178
+ break;
1179
+ }
1180
+ case 'TextWrap': {
1181
+ lines.push(`${indent}imx::renderer::begin_text_wrap(${emitFloat(node.props['width'] ?? '0')});`);
1182
+ break;
1183
+ }
657
1184
  case 'DockSpace': {
658
1185
  const style = buildStyleBlock(node, indent, lines);
659
1186
  const hasMenuBar = node.props['hasMenuBar'] === 'true';
@@ -668,6 +1195,10 @@ function emitBeginContainer(node, lines, indent) {
668
1195
  }
669
1196
  break;
670
1197
  }
1198
+ case 'MainMenuBar': {
1199
+ lines.push(`${indent}if (imx::renderer::begin_main_menu_bar()) {`);
1200
+ break;
1201
+ }
671
1202
  case 'MenuBar': {
672
1203
  lines.push(`${indent}if (imx::renderer::begin_menu_bar()) {`);
673
1204
  break;
@@ -677,32 +1208,6 @@ function emitBeginContainer(node, lines, indent) {
677
1208
  lines.push(`${indent}if (imx::renderer::begin_menu(${label})) {`);
678
1209
  break;
679
1210
  }
680
- case 'Table': {
681
- const columnsRaw = node.props['columns'] ?? '';
682
- const columnNames = columnsRaw.split(',').map(s => s.trim()).filter(s => s.length > 0);
683
- const count = columnNames.length;
684
- const varName = `table_cols_${styleCounter++}`;
685
- const style = buildStyleVar(node.style, indent, lines);
686
- const scrollY = node.props['scrollY'] === 'true';
687
- const noBorders = node.props['noBorders'] === 'true';
688
- const noRowBg = node.props['noRowBg'] === 'true';
689
- lines.push(`${indent}const char* ${varName}[] = {${columnNames.join(', ')}};`);
690
- const styleArg = style ?? '{}';
691
- if (scrollY || noBorders || noRowBg) {
692
- lines.push(`${indent}if (imx::renderer::begin_table("##table", ${count}, ${varName}, ${styleArg}, ${scrollY}, ${noBorders}, ${noRowBg})) {`);
693
- }
694
- else if (style) {
695
- lines.push(`${indent}if (imx::renderer::begin_table("##table", ${count}, ${varName}, ${styleArg})) {`);
696
- }
697
- else {
698
- lines.push(`${indent}if (imx::renderer::begin_table("##table", ${count}, ${varName})) {`);
699
- }
700
- break;
701
- }
702
- case 'TableRow': {
703
- lines.push(`${indent}imx::renderer::begin_table_row();`);
704
- break;
705
- }
706
1211
  case 'TabBar': {
707
1212
  lines.push(`${indent}if (imx::renderer::begin_tab_bar()) {`);
708
1213
  break;
@@ -712,16 +1217,6 @@ function emitBeginContainer(node, lines, indent) {
712
1217
  lines.push(`${indent}if (imx::renderer::begin_tab_item(${label})) {`);
713
1218
  break;
714
1219
  }
715
- case 'TreeNode': {
716
- const label = asCharPtr(node.props['label'] ?? '""');
717
- lines.push(`${indent}if (imx::renderer::begin_tree_node(${label})) {`);
718
- break;
719
- }
720
- case 'CollapsingHeader': {
721
- const label = asCharPtr(node.props['label'] ?? '""');
722
- lines.push(`${indent}if (imx::renderer::begin_collapsing_header(${label})) {`);
723
- break;
724
- }
725
1220
  case 'Theme': {
726
1221
  const preset = asCharPtr(node.props['preset'] ?? '"dark"');
727
1222
  const varName = `theme_${styleCounter++}`;
@@ -755,6 +1250,25 @@ function emitBeginContainer(node, lines, indent) {
755
1250
  }
756
1251
  case 'Modal': {
757
1252
  const title = asCharPtr(node.props['title'] ?? '""');
1253
+ // Build modal flags
1254
+ const flagParts = [];
1255
+ if (node.props['noTitleBar'] === 'true')
1256
+ flagParts.push('ImGuiWindowFlags_NoTitleBar');
1257
+ if (node.props['noResize'] === 'true')
1258
+ flagParts.push('ImGuiWindowFlags_NoResize');
1259
+ if (node.props['noMove'] === 'true')
1260
+ flagParts.push('ImGuiWindowFlags_NoMove');
1261
+ if (node.props['noScrollbar'] === 'true')
1262
+ flagParts.push('ImGuiWindowFlags_NoScrollbar');
1263
+ if (node.props['noCollapse'] === 'true')
1264
+ flagParts.push('ImGuiWindowFlags_NoCollapse');
1265
+ if (node.props['alwaysAutoResize'] === 'true')
1266
+ flagParts.push('ImGuiWindowFlags_AlwaysAutoResize');
1267
+ if (node.props['noBackground'] === 'true')
1268
+ flagParts.push('ImGuiWindowFlags_NoBackground');
1269
+ if (node.props['horizontalScrollbar'] === 'true')
1270
+ flagParts.push('ImGuiWindowFlags_HorizontalScrollbar');
1271
+ const modalFlags = flagParts.length > 0 ? flagParts.join(' | ') : '0';
758
1272
  const openExpr = node.props['open'];
759
1273
  const onCloseExpr = node.props['onClose'];
760
1274
  if (openExpr) {
@@ -768,12 +1282,12 @@ function emitBeginContainer(node, lines, indent) {
768
1282
  modalOnCloseStack.push(onCloseBody);
769
1283
  lines.push(`${indent}{`);
770
1284
  lines.push(`${indent} bool modal_closed = false;`);
771
- lines.push(`${indent} if (imx::renderer::begin_modal(${title}, ${openExpr}, &modal_closed)) {`);
1285
+ lines.push(`${indent} if (imx::renderer::begin_modal(${title}, ${openExpr}, &modal_closed, ${modalFlags})) {`);
772
1286
  }
773
1287
  else {
774
1288
  windowOpenStack.push(false);
775
1289
  modalOnCloseStack.push(null);
776
- lines.push(`${indent}if (imx::renderer::begin_modal(${title}, true, nullptr)) {`);
1290
+ lines.push(`${indent}if (imx::renderer::begin_modal(${title}, true, nullptr, ${modalFlags})) {`);
777
1291
  }
778
1292
  break;
779
1293
  }
@@ -875,6 +1389,64 @@ function emitBeginContainer(node, lines, indent) {
875
1389
  }
876
1390
  break;
877
1391
  }
1392
+ case 'Font': {
1393
+ const name = asCharPtr(node.props['name'] ?? '""');
1394
+ lines.push(`${indent}imx::renderer::begin_font(${name});`);
1395
+ break;
1396
+ }
1397
+ case 'ContextMenu': {
1398
+ const idExpr = node.props['id'];
1399
+ const idArg = idExpr ? asCharPtr(idExpr) : 'nullptr';
1400
+ const mbExpr = node.props['mouseButton'];
1401
+ let mouseButtonArg = '1'; // default: right click
1402
+ if (mbExpr === '"left"')
1403
+ mouseButtonArg = '0';
1404
+ else if (mbExpr === '"middle"')
1405
+ mouseButtonArg = '2';
1406
+ if (node.props['target'] === '"window"') {
1407
+ lines.push(`${indent}if (imx::renderer::begin_context_menu_window(${idArg}, ${mouseButtonArg})) {`);
1408
+ }
1409
+ else {
1410
+ lines.push(`${indent}if (imx::renderer::begin_context_menu_item(${idArg}, ${mouseButtonArg})) {`);
1411
+ }
1412
+ break;
1413
+ }
1414
+ case 'MultiSelect': {
1415
+ const flagParts = [];
1416
+ if (node.props['singleSelect'] === 'true')
1417
+ flagParts.push('ImGuiMultiSelectFlags_SingleSelect');
1418
+ if (node.props['noSelectAll'] === 'true')
1419
+ flagParts.push('ImGuiMultiSelectFlags_NoSelectAll');
1420
+ if (node.props['noRangeSelect'] === 'true')
1421
+ flagParts.push('ImGuiMultiSelectFlags_NoRangeSelect');
1422
+ if (node.props['noAutoSelect'] === 'true')
1423
+ flagParts.push('ImGuiMultiSelectFlags_NoAutoSelect');
1424
+ if (node.props['noAutoClear'] === 'true')
1425
+ flagParts.push('ImGuiMultiSelectFlags_NoAutoClear');
1426
+ if (node.props['boxSelect'] === 'true')
1427
+ flagParts.push('ImGuiMultiSelectFlags_BoxSelect1d');
1428
+ if (node.props['boxSelect2d'] === 'true')
1429
+ flagParts.push('ImGuiMultiSelectFlags_BoxSelect2d');
1430
+ if (node.props['boxSelectNoScroll'] === 'true')
1431
+ flagParts.push('ImGuiMultiSelectFlags_BoxSelectNoScroll');
1432
+ if (node.props['clearOnClickVoid'] === 'true')
1433
+ flagParts.push('ImGuiMultiSelectFlags_ClearOnClickVoid');
1434
+ const flags = flagParts.length > 0 ? flagParts.join(' | ') : '0';
1435
+ const selSize = node.props['selectionSize'] ?? '-1';
1436
+ const itemCount = node.props['itemsCount'] ?? '-1';
1437
+ lines.push(`${indent}{`);
1438
+ lines.push(`${indent} auto* ms_io = imx::renderer::begin_multi_select(${flags}, ${selSize}, ${itemCount});`);
1439
+ const onChangeExpr = node.props['onSelectionChange'];
1440
+ // Also apply requests from BeginMultiSelect (ImGui protocol requires both)
1441
+ if (onChangeExpr) {
1442
+ const lambdaMatch = onChangeExpr.match(/^\[&\]\(\)\s*\{\s*(.*?)\(\s*\d+\s*\)\s*;?\s*\}$/);
1443
+ if (lambdaMatch) {
1444
+ lines.push(`${indent} ${lambdaMatch[1]}(ms_io);`);
1445
+ }
1446
+ }
1447
+ multiSelectCallbackStack.push(onChangeExpr ?? null);
1448
+ break;
1449
+ }
878
1450
  case 'DockLayout':
879
1451
  case 'DockSplit':
880
1452
  case 'DockPanel':
@@ -900,9 +1472,19 @@ function emitEndContainer(node, lines, indent) {
900
1472
  case 'View':
901
1473
  lines.push(`${indent}imx::renderer::end_view();`);
902
1474
  break;
1475
+ case 'Indent':
1476
+ lines.push(`${indent}imx::renderer::end_indent();`);
1477
+ break;
1478
+ case 'TextWrap':
1479
+ lines.push(`${indent}imx::renderer::end_text_wrap();`);
1480
+ break;
903
1481
  case 'DockSpace':
904
1482
  lines.push(`${indent}imx::renderer::end_dockspace();`);
905
1483
  break;
1484
+ case 'MainMenuBar':
1485
+ lines.push(`${indent}imx::renderer::end_main_menu_bar();`);
1486
+ lines.push(`${indent}}`);
1487
+ break;
906
1488
  case 'MenuBar':
907
1489
  lines.push(`${indent}imx::renderer::end_menu_bar();`);
908
1490
  lines.push(`${indent}}`);
@@ -911,13 +1493,6 @@ function emitEndContainer(node, lines, indent) {
911
1493
  lines.push(`${indent}imx::renderer::end_menu();`);
912
1494
  lines.push(`${indent}}`);
913
1495
  break;
914
- case 'Table':
915
- lines.push(`${indent}imx::renderer::end_table();`);
916
- lines.push(`${indent}}`);
917
- break;
918
- case 'TableRow':
919
- lines.push(`${indent}imx::renderer::end_table_row();`);
920
- break;
921
1496
  case 'TabBar':
922
1497
  lines.push(`${indent}imx::renderer::end_tab_bar();`);
923
1498
  lines.push(`${indent}}`);
@@ -926,14 +1501,6 @@ function emitEndContainer(node, lines, indent) {
926
1501
  lines.push(`${indent}imx::renderer::end_tab_item();`);
927
1502
  lines.push(`${indent}}`);
928
1503
  break;
929
- case 'TreeNode':
930
- lines.push(`${indent}imx::renderer::end_tree_node();`);
931
- lines.push(`${indent}}`);
932
- break;
933
- case 'CollapsingHeader':
934
- lines.push(`${indent}imx::renderer::end_collapsing_header();`);
935
- lines.push(`${indent}}`);
936
- break;
937
1504
  case 'Theme':
938
1505
  lines.push(`${indent}imx::renderer::end_theme();`);
939
1506
  break;
@@ -989,6 +1556,30 @@ function emitEndContainer(node, lines, indent) {
989
1556
  case 'Canvas':
990
1557
  lines.push(`${indent}imx::renderer::end_canvas();`);
991
1558
  break;
1559
+ case 'Font':
1560
+ lines.push(`${indent}imx::renderer::end_font();`);
1561
+ break;
1562
+ case 'ContextMenu':
1563
+ lines.push(`${indent}imx::renderer::end_context_menu();`);
1564
+ lines.push(`${indent}}`);
1565
+ break;
1566
+ case 'MultiSelect': {
1567
+ lines.push(`${indent} auto* ms_io_end = imx::renderer::end_multi_select();`);
1568
+ const onChangeExpr = multiSelectCallbackStack.pop() ?? null;
1569
+ if (onChangeExpr) {
1570
+ // Extract function call from lambda [&]() { fn(placeholder); }
1571
+ // Replace the placeholder argument with ms_io_end
1572
+ const lambdaMatch = onChangeExpr.match(/^\[&\]\(\)\s*\{\s*(.*?)\(\s*\d+\s*\)\s*;?\s*\}$/);
1573
+ if (lambdaMatch) {
1574
+ lines.push(`${indent} if (ms_io_end) { ${lambdaMatch[1]}(ms_io_end); }`);
1575
+ }
1576
+ else {
1577
+ lines.push(`${indent} if (ms_io_end) { ${onChangeExpr}; }`);
1578
+ }
1579
+ }
1580
+ lines.push(`${indent}}`);
1581
+ break;
1582
+ }
992
1583
  case 'DragDropTarget': {
993
1584
  const props = dragDropTargetStack.pop() ?? {};
994
1585
  const typeStr = asCharPtr(props['type'] ?? '""');
@@ -1018,26 +1609,96 @@ function emitEndContainer(node, lines, indent) {
1018
1609
  }
1019
1610
  function emitText(node, lines, indent) {
1020
1611
  emitLocComment(node.loc, 'Text', lines, indent);
1021
- if (node.args.length === 0) {
1022
- lines.push(`${indent}imx::renderer::text(${JSON.stringify(node.format)});`);
1612
+ const fmtStr = JSON.stringify(node.format);
1613
+ const argsStr = node.args.length > 0 ? ', ' + node.args.join(', ') : '';
1614
+ if (node.disabled) {
1615
+ // disabled takes priority — ImGui::TextDisabled has its own grayed style
1616
+ lines.push(`${indent}imx::renderer::text_disabled(${fmtStr}${argsStr});`);
1617
+ }
1618
+ else if (node.color && node.wrapped) {
1619
+ // color + wrapped: PushStyleColor + TextWrapped + PopStyleColor
1620
+ lines.push(`${indent}ImGui::PushStyleColor(ImGuiCol_Text, ImVec4(${node.color}));`);
1621
+ lines.push(`${indent}imx::renderer::text_wrapped(${fmtStr}${argsStr});`);
1622
+ lines.push(`${indent}ImGui::PopStyleColor();`);
1623
+ }
1624
+ else if (node.color) {
1625
+ // color only: inline ImGui::TextColored
1626
+ lines.push(`${indent}ImGui::TextColored(ImVec4(${node.color}), ${fmtStr}${argsStr});`);
1627
+ }
1628
+ else if (node.wrapped) {
1629
+ // wrapped only
1630
+ lines.push(`${indent}imx::renderer::text_wrapped(${fmtStr}${argsStr});`);
1023
1631
  }
1024
1632
  else {
1025
- const argsStr = node.args.join(', ');
1026
- lines.push(`${indent}imx::renderer::text(${JSON.stringify(node.format)}, ${argsStr});`);
1633
+ // plain text (current behavior)
1634
+ if (node.args.length === 0) {
1635
+ lines.push(`${indent}imx::renderer::text(${fmtStr});`);
1636
+ }
1637
+ else {
1638
+ lines.push(`${indent}imx::renderer::text(${fmtStr}${argsStr});`);
1639
+ }
1027
1640
  }
1028
1641
  }
1029
1642
  function emitButton(node, lines, indent, depth) {
1030
1643
  emitLocComment(node.loc, 'Button', lines, indent);
1031
1644
  const title = asCharPtr(node.title);
1032
1645
  const disabledArg = node.disabled ? ', {}, true' : '';
1033
- if (node.action.length === 0) {
1034
- lines.push(`${indent}imx::renderer::button(${title}${disabledArg});`);
1646
+ const pressedVar = node.action.length > 0 ? nextWidgetTemp('button_pressed') : undefined;
1647
+ const resultVar = emitBoolWidgetCall(`imx::renderer::button(${title}${disabledArg})`, node.item, lines, indent, pressedVar);
1648
+ if (node.action.length > 0 && resultVar) {
1649
+ lines.push(`${indent}if (${resultVar}) {`);
1650
+ emitActionStatements(node.action, lines, indent + INDENT);
1651
+ lines.push(`${indent}}`);
1035
1652
  }
1036
- else {
1037
- lines.push(`${indent}if (imx::renderer::button(${title}${disabledArg})) {`);
1038
- for (const stmt of node.action) {
1039
- lines.push(`${indent}${INDENT}${stmt}`);
1040
- }
1653
+ }
1654
+ function emitSmallButton(node, lines, indent) {
1655
+ emitLocComment(node.loc, 'SmallButton', lines, indent);
1656
+ const label = asCharPtr(node.label);
1657
+ const pressedVar = node.action.length > 0 ? nextWidgetTemp('small_button_pressed') : undefined;
1658
+ const resultVar = emitBoolWidgetCall(`imx::renderer::small_button(${label})`, node.item, lines, indent, pressedVar);
1659
+ if (node.action.length > 0 && resultVar) {
1660
+ lines.push(`${indent}if (${resultVar}) {`);
1661
+ emitActionStatements(node.action, lines, indent + INDENT);
1662
+ lines.push(`${indent}}`);
1663
+ }
1664
+ }
1665
+ function emitArrowButton(node, lines, indent) {
1666
+ emitLocComment(node.loc, 'ArrowButton', lines, indent);
1667
+ const id = asCharPtr(node.id);
1668
+ const dirMap = { '"left"': '0', '"right"': '1', '"up"': '2', '"down"': '3' };
1669
+ const dir = dirMap[node.direction] ?? '0';
1670
+ const pressedVar = node.action.length > 0 ? nextWidgetTemp('arrow_button_pressed') : undefined;
1671
+ const resultVar = emitBoolWidgetCall(`imx::renderer::arrow_button(${id}, ${dir})`, node.item, lines, indent, pressedVar);
1672
+ if (node.action.length > 0 && resultVar) {
1673
+ lines.push(`${indent}if (${resultVar}) {`);
1674
+ emitActionStatements(node.action, lines, indent + INDENT);
1675
+ lines.push(`${indent}}`);
1676
+ }
1677
+ }
1678
+ function emitInvisibleButton(node, lines, indent) {
1679
+ emitLocComment(node.loc, 'InvisibleButton', lines, indent);
1680
+ const id = asCharPtr(node.id);
1681
+ const width = emitFloat(node.width);
1682
+ const height = emitFloat(node.height);
1683
+ const pressedVar = node.action.length > 0 ? nextWidgetTemp('invisible_button_pressed') : undefined;
1684
+ const resultVar = emitBoolWidgetCall(`imx::renderer::invisible_button(${id}, ${width}, ${height})`, node.item, lines, indent, pressedVar);
1685
+ if (node.action.length > 0 && resultVar) {
1686
+ lines.push(`${indent}if (${resultVar}) {`);
1687
+ emitActionStatements(node.action, lines, indent + INDENT);
1688
+ lines.push(`${indent}}`);
1689
+ }
1690
+ }
1691
+ function emitImageButton(node, lines, indent) {
1692
+ emitLocComment(node.loc, 'ImageButton', lines, indent);
1693
+ const id = asCharPtr(node.id);
1694
+ const src = asCharPtr(node.src);
1695
+ const width = node.width ? emitFloat(node.width) : '0';
1696
+ const height = node.height ? emitFloat(node.height) : '0';
1697
+ const pressedVar = node.action.length > 0 ? nextWidgetTemp('image_button_pressed') : undefined;
1698
+ const resultVar = emitBoolWidgetCall(`imx::renderer::image_button(${id}, ${src}, ${width}, ${height})`, node.item, lines, indent, pressedVar);
1699
+ if (node.action.length > 0 && resultVar) {
1700
+ lines.push(`${indent}if (${resultVar}) {`);
1701
+ emitActionStatements(node.action, lines, indent + INDENT);
1041
1702
  lines.push(`${indent}}`);
1042
1703
  }
1043
1704
  }
@@ -1045,24 +1706,14 @@ function emitMenuItem(node, lines, indent, depth) {
1045
1706
  emitLocComment(node.loc, 'MenuItem', lines, indent);
1046
1707
  const label = asCharPtr(node.label);
1047
1708
  const shortcut = node.shortcut ? asCharPtr(node.shortcut) : undefined;
1048
- if (node.action.length === 0) {
1049
- if (shortcut) {
1050
- lines.push(`${indent}imx::renderer::menu_item(${label}, ${shortcut});`);
1051
- }
1052
- else {
1053
- lines.push(`${indent}imx::renderer::menu_item(${label});`);
1054
- }
1055
- }
1056
- else {
1057
- if (shortcut) {
1058
- lines.push(`${indent}if (imx::renderer::menu_item(${label}, ${shortcut})) {`);
1059
- }
1060
- else {
1061
- lines.push(`${indent}if (imx::renderer::menu_item(${label})) {`);
1062
- }
1063
- for (const stmt of node.action) {
1064
- lines.push(`${indent} ${stmt}`);
1065
- }
1709
+ const callExpr = shortcut
1710
+ ? `imx::renderer::menu_item(${label}, ${shortcut})`
1711
+ : `imx::renderer::menu_item(${label})`;
1712
+ const pressedVar = node.action.length > 0 ? nextWidgetTemp('menu_item_pressed') : undefined;
1713
+ const resultVar = emitBoolWidgetCall(callExpr, node.item, lines, indent, pressedVar);
1714
+ if (node.action.length > 0 && resultVar) {
1715
+ lines.push(`${indent}if (${resultVar}) {`);
1716
+ emitActionStatements(node.action, lines, indent + INDENT);
1066
1717
  lines.push(`${indent}}`);
1067
1718
  }
1068
1719
  }
@@ -1071,40 +1722,58 @@ function emitTextInput(node, lines, indent) {
1071
1722
  const label = asCharPtr(node.label && node.label !== '""' ? node.label : `"##textinput_${node.bufferIndex}"`);
1072
1723
  if (node.stateVar) {
1073
1724
  lines.push(`${indent}{`);
1074
- lines.push(`${indent}${INDENT}auto& buf = ctx.get_buffer(${node.bufferIndex});`);
1075
- lines.push(`${indent}${INDENT}buf.sync_from(${node.stateVar}.get());`);
1076
- lines.push(`${indent}${INDENT}if (imx::renderer::text_input(${label}, buf)) {`);
1077
- lines.push(`${indent}${INDENT}${INDENT}${node.stateVar}.set(buf.value());`);
1078
- lines.push(`${indent}${INDENT}}`);
1725
+ const innerIndent = indent + INDENT;
1726
+ const styleVar = buildWidgetStyleVar(node.style, node.width, innerIndent, lines);
1727
+ const styleArg = styleVar ? `, ${styleVar}` : '';
1728
+ lines.push(`${innerIndent}auto& buf = ctx.get_buffer(${node.bufferIndex});`);
1729
+ lines.push(`${innerIndent}buf.sync_from(${node.stateVar}.get());`);
1730
+ const changedVar = nextWidgetTemp('text_input_changed');
1731
+ const resultVar = emitBoolWidgetCall(`imx::renderer::text_input(${label}, buf${styleArg})`, node.item, lines, innerIndent, changedVar);
1732
+ lines.push(`${innerIndent}if (${resultVar}) {`);
1733
+ lines.push(`${innerIndent}${INDENT}${node.stateVar}.set(buf.value());`);
1734
+ lines.push(`${innerIndent}}`);
1079
1735
  lines.push(`${indent}}`);
1080
1736
  }
1081
1737
  else if (node.directBind && node.valueExpr) {
1082
- const propName = node.valueExpr.startsWith('props.') ? node.valueExpr.slice(6).split('.')[0].split('[')[0] : '';
1083
- const isBound = currentBoundProps.has(propName);
1084
- const readExpr = isBound ? `(*${node.valueExpr})` : node.valueExpr;
1085
- const writeExpr = isBound ? `(*${node.valueExpr})` : node.valueExpr;
1738
+ const readExpr = emitBoundValueExpr(node.valueExpr);
1739
+ const writeExpr = emitBoundValueExpr(node.valueExpr);
1086
1740
  lines.push(`${indent}{`);
1087
- lines.push(`${indent}${INDENT}auto& buf = ctx.get_buffer(${node.bufferIndex});`);
1088
- lines.push(`${indent}${INDENT}buf.sync_from(${readExpr});`);
1089
- lines.push(`${indent}${INDENT}if (imx::renderer::text_input(${label}, buf)) {`);
1090
- lines.push(`${indent}${INDENT}${INDENT}${writeExpr} = buf.value();`);
1091
- lines.push(`${indent}${INDENT}}`);
1741
+ const innerIndent = indent + INDENT;
1742
+ const styleVar = buildWidgetStyleVar(node.style, node.width, innerIndent, lines);
1743
+ const styleArg = styleVar ? `, ${styleVar}` : '';
1744
+ lines.push(`${innerIndent}auto& buf = ctx.get_buffer(${node.bufferIndex});`);
1745
+ lines.push(`${innerIndent}buf.sync_from(${readExpr});`);
1746
+ const changedVar = nextWidgetTemp('text_input_changed');
1747
+ const resultVar = emitBoolWidgetCall(`imx::renderer::text_input(${label}, buf${styleArg})`, node.item, lines, innerIndent, changedVar);
1748
+ lines.push(`${innerIndent}if (${resultVar}) {`);
1749
+ lines.push(`${innerIndent}${INDENT}${writeExpr} = buf.value();`);
1750
+ lines.push(`${innerIndent}}`);
1092
1751
  lines.push(`${indent}}`);
1093
1752
  }
1094
1753
  else if (node.valueExpr !== undefined) {
1095
1754
  lines.push(`${indent}{`);
1096
- lines.push(`${indent}${INDENT}auto& buf = ctx.get_buffer(${node.bufferIndex});`);
1097
- lines.push(`${indent}${INDENT}buf.sync_from(${node.valueExpr});`);
1098
- lines.push(`${indent}${INDENT}if (imx::renderer::text_input(${label}, buf)) {`);
1755
+ const innerIndent = indent + INDENT;
1756
+ const styleVar = buildWidgetStyleVar(node.style, node.width, innerIndent, lines);
1757
+ const styleArg = styleVar ? `, ${styleVar}` : '';
1758
+ lines.push(`${innerIndent}auto& buf = ctx.get_buffer(${node.bufferIndex});`);
1759
+ lines.push(`${innerIndent}buf.sync_from(${node.valueExpr});`);
1760
+ const changedVar = nextWidgetTemp('text_input_changed');
1761
+ const resultVar = emitBoolWidgetCall(`imx::renderer::text_input(${label}, buf${styleArg})`, node.item, lines, innerIndent, changedVar);
1762
+ lines.push(`${innerIndent}if (${resultVar}) {`);
1099
1763
  if (node.onChangeExpr) {
1100
- lines.push(`${indent}${INDENT}${INDENT}${node.onChangeExpr};`);
1764
+ lines.push(`${innerIndent}${INDENT}${node.onChangeExpr};`);
1101
1765
  }
1102
- lines.push(`${indent}${INDENT}}`);
1766
+ lines.push(`${innerIndent}}`);
1103
1767
  lines.push(`${indent}}`);
1104
1768
  }
1105
1769
  else {
1106
- lines.push(`${indent}auto& buf_${node.bufferIndex} = ctx.get_buffer(${node.bufferIndex});`);
1107
- lines.push(`${indent}imx::renderer::text_input(${label}, buf_${node.bufferIndex});`);
1770
+ lines.push(`${indent}{`);
1771
+ const innerIndent = indent + INDENT;
1772
+ const styleVar = buildWidgetStyleVar(node.style, node.width, innerIndent, lines);
1773
+ const styleArg = styleVar ? `, ${styleVar}` : '';
1774
+ lines.push(`${innerIndent}auto& buf_${node.bufferIndex} = ctx.get_buffer(${node.bufferIndex});`);
1775
+ emitBoolWidgetCall(`imx::renderer::text_input(${label}, buf_${node.bufferIndex}${styleArg})`, node.item, lines, innerIndent);
1776
+ lines.push(`${indent}}`);
1108
1777
  }
1109
1778
  }
1110
1779
  function emitCheckbox(node, lines, indent) {
@@ -1114,31 +1783,55 @@ function emitCheckbox(node, lines, indent) {
1114
1783
  if (node.stateVar) {
1115
1784
  // State-bound case
1116
1785
  lines.push(`${indent}{`);
1117
- lines.push(`${indent}${INDENT}bool val = ${node.stateVar}.get();`);
1118
- lines.push(`${indent}${INDENT}if (imx::renderer::checkbox(${label}, &val)) {`);
1119
- lines.push(`${indent}${INDENT}${INDENT}${node.stateVar}.set(val);`);
1120
- lines.push(`${indent}${INDENT}}`);
1786
+ const innerIndent = indent + INDENT;
1787
+ lines.push(`${innerIndent}bool val = ${node.stateVar}.get();`);
1788
+ const changedVar = nextWidgetTemp('checkbox_changed');
1789
+ const resultVar = emitBoolWidgetCall(`imx::renderer::checkbox(${label}, &val)`, node.item, lines, innerIndent, changedVar);
1790
+ lines.push(`${innerIndent}if (${resultVar}) {`);
1791
+ lines.push(`${innerIndent}${INDENT}${node.stateVar}.set(val);`);
1792
+ lines.push(`${innerIndent}}`);
1121
1793
  lines.push(`${indent}}`);
1122
1794
  }
1123
1795
  else if (node.directBind && node.valueExpr) {
1124
- // Direct pointer binding — no temp variable
1125
- lines.push(`${indent}imx::renderer::checkbox(${label}, ${emitDirectBindPtr(node.valueExpr)});`);
1796
+ emitBoolWidgetCall(`imx::renderer::checkbox(${label}, ${emitDirectBindPtr(node.valueExpr)})`, node.item, lines, indent);
1126
1797
  }
1127
1798
  else if (node.valueExpr !== undefined) {
1128
- // Props-bound / expression-bound case
1129
1799
  lines.push(`${indent}{`);
1130
- lines.push(`${indent}${INDENT}bool val = ${node.valueExpr};`);
1131
- lines.push(`${indent}${INDENT}if (imx::renderer::checkbox(${label}, &val)) {`);
1800
+ const innerIndent = indent + INDENT;
1801
+ lines.push(`${innerIndent}bool val = ${node.valueExpr};`);
1802
+ const changedVar = nextWidgetTemp('checkbox_changed');
1803
+ const resultVar = emitBoolWidgetCall(`imx::renderer::checkbox(${label}, &val)`, node.item, lines, innerIndent, changedVar);
1804
+ lines.push(`${innerIndent}if (${resultVar}) {`);
1132
1805
  if (node.onChangeExpr) {
1133
- lines.push(`${indent}${INDENT}${INDENT}${node.onChangeExpr};`);
1806
+ lines.push(`${innerIndent}${INDENT}${node.onChangeExpr};`);
1134
1807
  }
1135
- lines.push(`${indent}${INDENT}}`);
1808
+ lines.push(`${innerIndent}}`);
1136
1809
  lines.push(`${indent}}`);
1137
1810
  }
1138
1811
  else {
1139
- lines.push(`${indent}imx::renderer::checkbox(${label}, nullptr);`);
1812
+ emitBoolWidgetCall(`imx::renderer::checkbox(${label}, nullptr)`, node.item, lines, indent);
1140
1813
  }
1141
1814
  }
1815
+ function emitSpacing(node, lines, indent) {
1816
+ emitLocComment(node.loc, 'Spacing', lines, indent);
1817
+ lines.push(`${indent}imx::renderer::spacing();`);
1818
+ }
1819
+ function emitDummy(node, lines, indent) {
1820
+ emitLocComment(node.loc, 'Dummy', lines, indent);
1821
+ lines.push(`${indent}imx::renderer::dummy(${emitFloat(node.width)}, ${emitFloat(node.height)});`);
1822
+ }
1823
+ function emitSameLine(node, lines, indent) {
1824
+ emitLocComment(node.loc, 'SameLine', lines, indent);
1825
+ lines.push(`${indent}imx::renderer::same_line(${emitFloat(node.offset)}, ${emitFloat(node.spacing)});`);
1826
+ }
1827
+ function emitNewLine(node, lines, indent) {
1828
+ emitLocComment(node.loc, 'NewLine', lines, indent);
1829
+ lines.push(`${indent}imx::renderer::new_line();`);
1830
+ }
1831
+ function emitCursor(node, lines, indent) {
1832
+ emitLocComment(node.loc, 'Cursor', lines, indent);
1833
+ lines.push(`${indent}imx::renderer::set_cursor_pos(${emitFloat(node.x)}, ${emitFloat(node.y)});`);
1834
+ }
1142
1835
  function emitConditional(node, lines, indent, depth) {
1143
1836
  if (node.loc) {
1144
1837
  lines.push(`${indent}// ${node.loc.file}:${node.loc.line} conditional`);
@@ -1176,7 +1869,15 @@ function emitCustomComponent(node, lines, indent) {
1176
1869
  lines.push(`${indent}${INDENT}${node.name}Props p;`);
1177
1870
  for (const [k, v] of propsEntries) {
1178
1871
  if (childBound.has(k)) {
1179
- lines.push(`${indent}${INDENT}p.${k} = &${v};`);
1872
+ // Check if the value is already a pointer (bound prop of current component)
1873
+ const valPropName = v.startsWith('props.') ? v.slice(6).split('.')[0].split('[')[0] : '';
1874
+ if (currentBoundProps.has(valPropName)) {
1875
+ // Already a pointer — pass through directly. Use &* to block post-processing regex.
1876
+ lines.push(`${indent}${INDENT}p.${k} = &*${v};`);
1877
+ }
1878
+ else {
1879
+ lines.push(`${indent}${INDENT}p.${k} = &${v};`);
1880
+ }
1180
1881
  }
1181
1882
  else {
1182
1883
  lines.push(`${indent}${INDENT}p.${k} = ${v};`);
@@ -1208,11 +1909,10 @@ function emitNativeWidget(node, lines, indent) {
1208
1909
  lines.push(`${indent}}`);
1209
1910
  }
1210
1911
  function ensureFloatLiteral(val) {
1211
- // If already has 'f' suffix or '.', leave as-is
1212
- if (val.endsWith('f') || val.endsWith('F') || val.includes('.'))
1213
- return val;
1214
- // Append .0f to make it a float literal
1215
- return `${val}.0f`;
1912
+ const trimmed = val.trim();
1913
+ if (!/^-?\d+(\.\d+)?$/.test(trimmed))
1914
+ return trimmed;
1915
+ return trimmed.includes('.') ? `${trimmed}f` : `${trimmed}.0f`;
1216
1916
  }
1217
1917
  function emitSliderFloat(node, lines, indent) {
1218
1918
  emitLocComment(node.loc, 'SliderFloat', lines, indent);
@@ -1221,24 +1921,35 @@ function emitSliderFloat(node, lines, indent) {
1221
1921
  const max = ensureFloatLiteral(node.max);
1222
1922
  if (node.stateVar) {
1223
1923
  lines.push(`${indent}{`);
1224
- lines.push(`${indent}${INDENT}float val = ${node.stateVar}.get();`);
1225
- lines.push(`${indent}${INDENT}if (imx::renderer::slider_float(${label}, &val, ${min}, ${max})) {`);
1226
- lines.push(`${indent}${INDENT}${INDENT}${node.stateVar}.set(val);`);
1227
- lines.push(`${indent}${INDENT}}`);
1924
+ const innerIndent = indent + INDENT;
1925
+ const styleVar = buildWidgetStyleVar(node.style, node.width, innerIndent, lines);
1926
+ const styleArg = styleVar ? `, ${styleVar}` : '';
1927
+ lines.push(`${innerIndent}float val = ${node.stateVar}.get();`);
1928
+ const changedVar = nextWidgetTemp('slider_float_changed');
1929
+ const resultVar = emitBoolWidgetCall(`imx::renderer::slider_float(${label}, &val, ${min}, ${max}${styleArg})`, node.item, lines, innerIndent, changedVar);
1930
+ lines.push(`${innerIndent}if (${resultVar}) {`);
1931
+ lines.push(`${innerIndent}${INDENT}${node.stateVar}.set(val);`);
1932
+ lines.push(`${innerIndent}}`);
1228
1933
  lines.push(`${indent}}`);
1229
1934
  }
1230
1935
  else if (node.directBind && node.valueExpr) {
1231
- // Direct pointer binding
1232
- lines.push(`${indent}imx::renderer::slider_float(${label}, ${emitDirectBindPtr(node.valueExpr)}, ${min}, ${max});`);
1936
+ const styleVar = buildWidgetStyleVar(node.style, node.width, indent, lines);
1937
+ const styleArg = styleVar ? `, ${styleVar}` : '';
1938
+ emitBoolWidgetCall(`imx::renderer::slider_float(${label}, ${emitDirectBindPtr(node.valueExpr)}, ${min}, ${max}${styleArg})`, node.item, lines, indent);
1233
1939
  }
1234
1940
  else if (node.valueExpr !== undefined) {
1235
1941
  lines.push(`${indent}{`);
1236
- lines.push(`${indent}${INDENT}float val = ${node.valueExpr};`);
1237
- lines.push(`${indent}${INDENT}if (imx::renderer::slider_float(${label}, &val, ${min}, ${max})) {`);
1942
+ const innerIndent = indent + INDENT;
1943
+ const styleVar = buildWidgetStyleVar(node.style, node.width, innerIndent, lines);
1944
+ const styleArg = styleVar ? `, ${styleVar}` : '';
1945
+ lines.push(`${innerIndent}float val = ${node.valueExpr};`);
1946
+ const changedVar = nextWidgetTemp('slider_float_changed');
1947
+ const resultVar = emitBoolWidgetCall(`imx::renderer::slider_float(${label}, &val, ${min}, ${max}${styleArg})`, node.item, lines, innerIndent, changedVar);
1948
+ lines.push(`${innerIndent}if (${resultVar}) {`);
1238
1949
  if (node.onChangeExpr) {
1239
- lines.push(`${indent}${INDENT}${INDENT}${node.onChangeExpr};`);
1950
+ lines.push(`${innerIndent}${INDENT}${node.onChangeExpr};`);
1240
1951
  }
1241
- lines.push(`${indent}${INDENT}}`);
1952
+ lines.push(`${innerIndent}}`);
1242
1953
  lines.push(`${indent}}`);
1243
1954
  }
1244
1955
  }
@@ -1247,23 +1958,142 @@ function emitSliderInt(node, lines, indent) {
1247
1958
  const label = asCharPtr(node.label);
1248
1959
  if (node.stateVar) {
1249
1960
  lines.push(`${indent}{`);
1250
- lines.push(`${indent}${INDENT}int val = ${node.stateVar}.get();`);
1251
- lines.push(`${indent}${INDENT}if (imx::renderer::slider_int(${label}, &val, ${node.min}, ${node.max})) {`);
1252
- lines.push(`${indent}${INDENT}${INDENT}${node.stateVar}.set(val);`);
1253
- lines.push(`${indent}${INDENT}}`);
1961
+ const innerIndent = indent + INDENT;
1962
+ const styleVar = buildWidgetStyleVar(node.style, node.width, innerIndent, lines);
1963
+ const styleArg = styleVar ? `, ${styleVar}` : '';
1964
+ lines.push(`${innerIndent}int val = ${node.stateVar}.get();`);
1965
+ const changedVar = nextWidgetTemp('slider_int_changed');
1966
+ const resultVar = emitBoolWidgetCall(`imx::renderer::slider_int(${label}, &val, ${node.min}, ${node.max}${styleArg})`, node.item, lines, innerIndent, changedVar);
1967
+ lines.push(`${innerIndent}if (${resultVar}) {`);
1968
+ lines.push(`${innerIndent}${INDENT}${node.stateVar}.set(val);`);
1969
+ lines.push(`${innerIndent}}`);
1254
1970
  lines.push(`${indent}}`);
1255
1971
  }
1256
1972
  else if (node.directBind && node.valueExpr) {
1257
- lines.push(`${indent}imx::renderer::slider_int(${label}, ${emitDirectBindPtr(node.valueExpr)}, ${node.min}, ${node.max});`);
1973
+ const styleVar = buildWidgetStyleVar(node.style, node.width, indent, lines);
1974
+ const styleArg = styleVar ? `, ${styleVar}` : '';
1975
+ emitBoolWidgetCall(`imx::renderer::slider_int(${label}, ${emitDirectBindPtr(node.valueExpr)}, ${node.min}, ${node.max}${styleArg})`, node.item, lines, indent);
1258
1976
  }
1259
1977
  else if (node.valueExpr !== undefined) {
1260
1978
  lines.push(`${indent}{`);
1261
- lines.push(`${indent}${INDENT}int val = ${node.valueExpr};`);
1262
- lines.push(`${indent}${INDENT}if (imx::renderer::slider_int(${label}, &val, ${node.min}, ${node.max})) {`);
1979
+ const innerIndent = indent + INDENT;
1980
+ const styleVar = buildWidgetStyleVar(node.style, node.width, innerIndent, lines);
1981
+ const styleArg = styleVar ? `, ${styleVar}` : '';
1982
+ lines.push(`${innerIndent}int val = ${node.valueExpr};`);
1983
+ const changedVar = nextWidgetTemp('slider_int_changed');
1984
+ const resultVar = emitBoolWidgetCall(`imx::renderer::slider_int(${label}, &val, ${node.min}, ${node.max}${styleArg})`, node.item, lines, innerIndent, changedVar);
1985
+ lines.push(`${innerIndent}if (${resultVar}) {`);
1263
1986
  if (node.onChangeExpr) {
1264
- lines.push(`${indent}${INDENT}${INDENT}${node.onChangeExpr};`);
1987
+ lines.push(`${innerIndent}${INDENT}${node.onChangeExpr};`);
1265
1988
  }
1266
- lines.push(`${indent}${INDENT}}`);
1989
+ lines.push(`${innerIndent}}`);
1990
+ lines.push(`${indent}}`);
1991
+ }
1992
+ }
1993
+ function emitVSliderFloat(node, lines, indent) {
1994
+ emitLocComment(node.loc, 'VSliderFloat', lines, indent);
1995
+ const label = asCharPtr(node.label);
1996
+ const min = ensureFloatLiteral(node.min);
1997
+ const max = ensureFloatLiteral(node.max);
1998
+ const width = emitFloat(node.width);
1999
+ const height = emitFloat(node.height);
2000
+ if (node.stateVar) {
2001
+ lines.push(`${indent}{`);
2002
+ const innerIndent = indent + INDENT;
2003
+ lines.push(`${innerIndent}float val = ${node.stateVar}.get();`);
2004
+ const changedVar = nextWidgetTemp('vslider_float_changed');
2005
+ const resultVar = emitBoolWidgetCall(`imx::renderer::vslider_float(${label}, ${width}, ${height}, &val, ${min}, ${max})`, node.item, lines, innerIndent, changedVar);
2006
+ lines.push(`${innerIndent}if (${resultVar}) {`);
2007
+ lines.push(`${innerIndent}${INDENT}${node.stateVar}.set(val);`);
2008
+ lines.push(`${innerIndent}}`);
2009
+ lines.push(`${indent}}`);
2010
+ }
2011
+ else if (node.directBind && node.valueExpr) {
2012
+ emitBoolWidgetCall(`imx::renderer::vslider_float(${label}, ${width}, ${height}, ${emitDirectBindPtr(node.valueExpr)}, ${min}, ${max})`, node.item, lines, indent);
2013
+ }
2014
+ else if (node.valueExpr !== undefined) {
2015
+ lines.push(`${indent}{`);
2016
+ const innerIndent = indent + INDENT;
2017
+ lines.push(`${innerIndent}float val = ${node.valueExpr};`);
2018
+ const changedVar = nextWidgetTemp('vslider_float_changed');
2019
+ const resultVar = emitBoolWidgetCall(`imx::renderer::vslider_float(${label}, ${width}, ${height}, &val, ${min}, ${max})`, node.item, lines, innerIndent, changedVar);
2020
+ lines.push(`${innerIndent}if (${resultVar}) {`);
2021
+ if (node.onChangeExpr) {
2022
+ lines.push(`${innerIndent}${INDENT}${node.onChangeExpr};`);
2023
+ }
2024
+ lines.push(`${innerIndent}}`);
2025
+ lines.push(`${indent}}`);
2026
+ }
2027
+ }
2028
+ function emitVSliderInt(node, lines, indent) {
2029
+ emitLocComment(node.loc, 'VSliderInt', lines, indent);
2030
+ const label = asCharPtr(node.label);
2031
+ const width = emitFloat(node.width);
2032
+ const height = emitFloat(node.height);
2033
+ if (node.stateVar) {
2034
+ lines.push(`${indent}{`);
2035
+ const innerIndent = indent + INDENT;
2036
+ lines.push(`${innerIndent}int val = ${node.stateVar}.get();`);
2037
+ const changedVar = nextWidgetTemp('vslider_int_changed');
2038
+ const resultVar = emitBoolWidgetCall(`imx::renderer::vslider_int(${label}, ${width}, ${height}, &val, ${node.min}, ${node.max})`, node.item, lines, innerIndent, changedVar);
2039
+ lines.push(`${innerIndent}if (${resultVar}) {`);
2040
+ lines.push(`${innerIndent}${INDENT}${node.stateVar}.set(val);`);
2041
+ lines.push(`${innerIndent}}`);
2042
+ lines.push(`${indent}}`);
2043
+ }
2044
+ else if (node.directBind && node.valueExpr) {
2045
+ emitBoolWidgetCall(`imx::renderer::vslider_int(${label}, ${width}, ${height}, ${emitDirectBindPtr(node.valueExpr)}, ${node.min}, ${node.max})`, node.item, lines, indent);
2046
+ }
2047
+ else if (node.valueExpr !== undefined) {
2048
+ lines.push(`${indent}{`);
2049
+ const innerIndent = indent + INDENT;
2050
+ lines.push(`${innerIndent}int val = ${node.valueExpr};`);
2051
+ const changedVar = nextWidgetTemp('vslider_int_changed');
2052
+ const resultVar = emitBoolWidgetCall(`imx::renderer::vslider_int(${label}, ${width}, ${height}, &val, ${node.min}, ${node.max})`, node.item, lines, innerIndent, changedVar);
2053
+ lines.push(`${innerIndent}if (${resultVar}) {`);
2054
+ if (node.onChangeExpr) {
2055
+ lines.push(`${innerIndent}${INDENT}${node.onChangeExpr};`);
2056
+ }
2057
+ lines.push(`${innerIndent}}`);
2058
+ lines.push(`${indent}}`);
2059
+ }
2060
+ }
2061
+ function emitSliderAngle(node, lines, indent) {
2062
+ emitLocComment(node.loc, 'SliderAngle', lines, indent);
2063
+ const label = asCharPtr(node.label);
2064
+ const min = ensureFloatLiteral(node.min);
2065
+ const max = ensureFloatLiteral(node.max);
2066
+ if (node.stateVar) {
2067
+ lines.push(`${indent}{`);
2068
+ const innerIndent = indent + INDENT;
2069
+ const styleVar = buildWidgetStyleVar(node.style, node.width, innerIndent, lines);
2070
+ const styleArg = styleVar ? `, ${styleVar}` : '';
2071
+ lines.push(`${innerIndent}float val = ${node.stateVar}.get();`);
2072
+ const changedVar = nextWidgetTemp('slider_angle_changed');
2073
+ const resultVar = emitBoolWidgetCall(`imx::renderer::slider_angle(${label}, &val, ${min}, ${max}${styleArg})`, node.item, lines, innerIndent, changedVar);
2074
+ lines.push(`${innerIndent}if (${resultVar}) {`);
2075
+ lines.push(`${innerIndent}${INDENT}${node.stateVar}.set(val);`);
2076
+ lines.push(`${innerIndent}}`);
2077
+ lines.push(`${indent}}`);
2078
+ }
2079
+ else if (node.directBind && node.valueExpr) {
2080
+ const styleVar = buildWidgetStyleVar(node.style, node.width, indent, lines);
2081
+ const styleArg = styleVar ? `, ${styleVar}` : '';
2082
+ emitBoolWidgetCall(`imx::renderer::slider_angle(${label}, ${emitDirectBindPtr(node.valueExpr)}, ${min}, ${max}${styleArg})`, node.item, lines, indent);
2083
+ }
2084
+ else if (node.valueExpr !== undefined) {
2085
+ lines.push(`${indent}{`);
2086
+ const innerIndent = indent + INDENT;
2087
+ const styleVar = buildWidgetStyleVar(node.style, node.width, innerIndent, lines);
2088
+ const styleArg = styleVar ? `, ${styleVar}` : '';
2089
+ lines.push(`${innerIndent}float val = ${node.valueExpr};`);
2090
+ const changedVar = nextWidgetTemp('slider_angle_changed');
2091
+ const resultVar = emitBoolWidgetCall(`imx::renderer::slider_angle(${label}, &val, ${min}, ${max}${styleArg})`, node.item, lines, innerIndent, changedVar);
2092
+ lines.push(`${innerIndent}if (${resultVar}) {`);
2093
+ if (node.onChangeExpr) {
2094
+ lines.push(`${innerIndent}${INDENT}${node.onChangeExpr};`);
2095
+ }
2096
+ lines.push(`${innerIndent}}`);
1267
2097
  lines.push(`${indent}}`);
1268
2098
  }
1269
2099
  }
@@ -1273,23 +2103,35 @@ function emitDragFloat(node, lines, indent) {
1273
2103
  const speed = ensureFloatLiteral(node.speed);
1274
2104
  if (node.stateVar) {
1275
2105
  lines.push(`${indent}{`);
1276
- lines.push(`${indent}${INDENT}float val = ${node.stateVar}.get();`);
1277
- lines.push(`${indent}${INDENT}if (imx::renderer::drag_float(${label}, &val, ${speed})) {`);
1278
- lines.push(`${indent}${INDENT}${INDENT}${node.stateVar}.set(val);`);
1279
- lines.push(`${indent}${INDENT}}`);
2106
+ const innerIndent = indent + INDENT;
2107
+ const styleVar = buildWidgetStyleVar(node.style, node.width, innerIndent, lines);
2108
+ const styleArg = styleVar ? `, ${styleVar}` : '';
2109
+ lines.push(`${innerIndent}float val = ${node.stateVar}.get();`);
2110
+ const changedVar = nextWidgetTemp('drag_float_changed');
2111
+ const resultVar = emitBoolWidgetCall(`imx::renderer::drag_float(${label}, &val, ${speed}${styleArg})`, node.item, lines, innerIndent, changedVar);
2112
+ lines.push(`${innerIndent}if (${resultVar}) {`);
2113
+ lines.push(`${innerIndent}${INDENT}${node.stateVar}.set(val);`);
2114
+ lines.push(`${innerIndent}}`);
1280
2115
  lines.push(`${indent}}`);
1281
2116
  }
1282
2117
  else if (node.directBind && node.valueExpr) {
1283
- lines.push(`${indent}imx::renderer::drag_float(${label}, ${emitDirectBindPtr(node.valueExpr)}, ${speed});`);
2118
+ const styleVar = buildWidgetStyleVar(node.style, node.width, indent, lines);
2119
+ const styleArg = styleVar ? `, ${styleVar}` : '';
2120
+ emitBoolWidgetCall(`imx::renderer::drag_float(${label}, ${emitDirectBindPtr(node.valueExpr)}, ${speed}${styleArg})`, node.item, lines, indent);
1284
2121
  }
1285
2122
  else if (node.valueExpr !== undefined) {
1286
2123
  lines.push(`${indent}{`);
1287
- lines.push(`${indent}${INDENT}float val = ${node.valueExpr};`);
1288
- lines.push(`${indent}${INDENT}if (imx::renderer::drag_float(${label}, &val, ${speed})) {`);
2124
+ const innerIndent = indent + INDENT;
2125
+ const styleVar = buildWidgetStyleVar(node.style, node.width, innerIndent, lines);
2126
+ const styleArg = styleVar ? `, ${styleVar}` : '';
2127
+ lines.push(`${innerIndent}float val = ${node.valueExpr};`);
2128
+ const changedVar = nextWidgetTemp('drag_float_changed');
2129
+ const resultVar = emitBoolWidgetCall(`imx::renderer::drag_float(${label}, &val, ${speed}${styleArg})`, node.item, lines, innerIndent, changedVar);
2130
+ lines.push(`${innerIndent}if (${resultVar}) {`);
1289
2131
  if (node.onChangeExpr) {
1290
- lines.push(`${indent}${INDENT}${INDENT}${node.onChangeExpr};`);
2132
+ lines.push(`${innerIndent}${INDENT}${node.onChangeExpr};`);
1291
2133
  }
1292
- lines.push(`${indent}${INDENT}}`);
2134
+ lines.push(`${innerIndent}}`);
1293
2135
  lines.push(`${indent}}`);
1294
2136
  }
1295
2137
  }
@@ -1299,23 +2141,35 @@ function emitDragInt(node, lines, indent) {
1299
2141
  const speed = ensureFloatLiteral(node.speed);
1300
2142
  if (node.stateVar) {
1301
2143
  lines.push(`${indent}{`);
1302
- lines.push(`${indent}${INDENT}int val = ${node.stateVar}.get();`);
1303
- lines.push(`${indent}${INDENT}if (imx::renderer::drag_int(${label}, &val, ${speed})) {`);
1304
- lines.push(`${indent}${INDENT}${INDENT}${node.stateVar}.set(val);`);
1305
- lines.push(`${indent}${INDENT}}`);
2144
+ const innerIndent = indent + INDENT;
2145
+ const styleVar = buildWidgetStyleVar(node.style, node.width, innerIndent, lines);
2146
+ const styleArg = styleVar ? `, ${styleVar}` : '';
2147
+ lines.push(`${innerIndent}int val = ${node.stateVar}.get();`);
2148
+ const changedVar = nextWidgetTemp('drag_int_changed');
2149
+ const resultVar = emitBoolWidgetCall(`imx::renderer::drag_int(${label}, &val, ${speed}${styleArg})`, node.item, lines, innerIndent, changedVar);
2150
+ lines.push(`${innerIndent}if (${resultVar}) {`);
2151
+ lines.push(`${innerIndent}${INDENT}${node.stateVar}.set(val);`);
2152
+ lines.push(`${innerIndent}}`);
1306
2153
  lines.push(`${indent}}`);
1307
2154
  }
1308
2155
  else if (node.directBind && node.valueExpr) {
1309
- lines.push(`${indent}imx::renderer::drag_int(${label}, ${emitDirectBindPtr(node.valueExpr)}, ${speed});`);
2156
+ const styleVar = buildWidgetStyleVar(node.style, node.width, indent, lines);
2157
+ const styleArg = styleVar ? `, ${styleVar}` : '';
2158
+ emitBoolWidgetCall(`imx::renderer::drag_int(${label}, ${emitDirectBindPtr(node.valueExpr)}, ${speed}${styleArg})`, node.item, lines, indent);
1310
2159
  }
1311
2160
  else if (node.valueExpr !== undefined) {
1312
2161
  lines.push(`${indent}{`);
1313
- lines.push(`${indent}${INDENT}int val = ${node.valueExpr};`);
1314
- lines.push(`${indent}${INDENT}if (imx::renderer::drag_int(${label}, &val, ${speed})) {`);
2162
+ const innerIndent = indent + INDENT;
2163
+ const styleVar = buildWidgetStyleVar(node.style, node.width, innerIndent, lines);
2164
+ const styleArg = styleVar ? `, ${styleVar}` : '';
2165
+ lines.push(`${innerIndent}int val = ${node.valueExpr};`);
2166
+ const changedVar = nextWidgetTemp('drag_int_changed');
2167
+ const resultVar = emitBoolWidgetCall(`imx::renderer::drag_int(${label}, &val, ${speed}${styleArg})`, node.item, lines, innerIndent, changedVar);
2168
+ lines.push(`${innerIndent}if (${resultVar}) {`);
1315
2169
  if (node.onChangeExpr) {
1316
- lines.push(`${indent}${INDENT}${INDENT}${node.onChangeExpr};`);
2170
+ lines.push(`${innerIndent}${INDENT}${node.onChangeExpr};`);
1317
2171
  }
1318
- lines.push(`${indent}${INDENT}}`);
2172
+ lines.push(`${innerIndent}}`);
1319
2173
  lines.push(`${indent}}`);
1320
2174
  }
1321
2175
  }
@@ -1327,28 +2181,41 @@ function emitCombo(node, lines, indent) {
1327
2181
  const varName = `combo_items_${comboCounter++}`;
1328
2182
  if (node.stateVar) {
1329
2183
  lines.push(`${indent}{`);
1330
- lines.push(`${indent}${INDENT}const char* ${varName}[] = {${itemsList.join(', ')}};`);
1331
- lines.push(`${indent}${INDENT}int val = ${node.stateVar}.get();`);
1332
- lines.push(`${indent}${INDENT}if (imx::renderer::combo(${label}, &val, ${varName}, ${count})) {`);
1333
- lines.push(`${indent}${INDENT}${INDENT}${node.stateVar}.set(val);`);
1334
- lines.push(`${indent}${INDENT}}`);
2184
+ const innerIndent = indent + INDENT;
2185
+ const styleVar = buildWidgetStyleVar(node.style, node.width, innerIndent, lines);
2186
+ const styleArg = styleVar ? `, ${styleVar}` : '';
2187
+ lines.push(`${innerIndent}const char* ${varName}[] = {${itemsList.join(', ')}};`);
2188
+ lines.push(`${innerIndent}int val = ${node.stateVar}.get();`);
2189
+ const changedVar = nextWidgetTemp('combo_changed');
2190
+ const resultVar = emitBoolWidgetCall(`imx::renderer::combo(${label}, &val, ${varName}, ${count}${styleArg})`, node.item, lines, innerIndent, changedVar);
2191
+ lines.push(`${innerIndent}if (${resultVar}) {`);
2192
+ lines.push(`${innerIndent}${INDENT}${node.stateVar}.set(val);`);
2193
+ lines.push(`${innerIndent}}`);
1335
2194
  lines.push(`${indent}}`);
1336
2195
  }
1337
2196
  else if (node.directBind && node.valueExpr) {
1338
2197
  lines.push(`${indent}{`);
1339
- lines.push(`${indent}${INDENT}const char* ${varName}[] = {${itemsList.join(', ')}};`);
1340
- lines.push(`${indent}${INDENT}imx::renderer::combo(${label}, ${emitDirectBindPtr(node.valueExpr)}, ${varName}, ${count});`);
2198
+ const innerIndent = indent + INDENT;
2199
+ const styleVar = buildWidgetStyleVar(node.style, node.width, innerIndent, lines);
2200
+ const styleArg = styleVar ? `, ${styleVar}` : '';
2201
+ lines.push(`${innerIndent}const char* ${varName}[] = {${itemsList.join(', ')}};`);
2202
+ emitBoolWidgetCall(`imx::renderer::combo(${label}, ${emitDirectBindPtr(node.valueExpr)}, ${varName}, ${count}${styleArg})`, node.item, lines, innerIndent);
1341
2203
  lines.push(`${indent}}`);
1342
2204
  }
1343
2205
  else if (node.valueExpr !== undefined) {
1344
2206
  lines.push(`${indent}{`);
1345
- lines.push(`${indent}${INDENT}const char* ${varName}[] = {${itemsList.join(', ')}};`);
1346
- lines.push(`${indent}${INDENT}int val = ${node.valueExpr};`);
1347
- lines.push(`${indent}${INDENT}if (imx::renderer::combo(${label}, &val, ${varName}, ${count})) {`);
2207
+ const innerIndent = indent + INDENT;
2208
+ const styleVar = buildWidgetStyleVar(node.style, node.width, innerIndent, lines);
2209
+ const styleArg = styleVar ? `, ${styleVar}` : '';
2210
+ lines.push(`${innerIndent}const char* ${varName}[] = {${itemsList.join(', ')}};`);
2211
+ lines.push(`${innerIndent}int val = ${node.valueExpr};`);
2212
+ const changedVar = nextWidgetTemp('combo_changed');
2213
+ const resultVar = emitBoolWidgetCall(`imx::renderer::combo(${label}, &val, ${varName}, ${count}${styleArg})`, node.item, lines, innerIndent, changedVar);
2214
+ lines.push(`${innerIndent}if (${resultVar}) {`);
1348
2215
  if (node.onChangeExpr) {
1349
- lines.push(`${indent}${INDENT}${INDENT}${node.onChangeExpr};`);
2216
+ lines.push(`${innerIndent}${INDENT}${node.onChangeExpr};`);
1350
2217
  }
1351
- lines.push(`${indent}${INDENT}}`);
2218
+ lines.push(`${innerIndent}}`);
1352
2219
  lines.push(`${indent}}`);
1353
2220
  }
1354
2221
  }
@@ -1357,23 +2224,35 @@ function emitInputInt(node, lines, indent) {
1357
2224
  const label = asCharPtr(node.label);
1358
2225
  if (node.stateVar) {
1359
2226
  lines.push(`${indent}{`);
1360
- lines.push(`${indent}${INDENT}int val = ${node.stateVar}.get();`);
1361
- lines.push(`${indent}${INDENT}if (imx::renderer::input_int(${label}, &val)) {`);
1362
- lines.push(`${indent}${INDENT}${INDENT}${node.stateVar}.set(val);`);
1363
- lines.push(`${indent}${INDENT}}`);
2227
+ const innerIndent = indent + INDENT;
2228
+ const styleVar = buildWidgetStyleVar(node.style, node.width, innerIndent, lines);
2229
+ const styleArg = styleVar ? `, ${styleVar}` : '';
2230
+ lines.push(`${innerIndent}int val = ${node.stateVar}.get();`);
2231
+ const changedVar = nextWidgetTemp('input_int_changed');
2232
+ const resultVar = emitBoolWidgetCall(`imx::renderer::input_int(${label}, &val${styleArg})`, node.item, lines, innerIndent, changedVar);
2233
+ lines.push(`${innerIndent}if (${resultVar}) {`);
2234
+ lines.push(`${innerIndent}${INDENT}${node.stateVar}.set(val);`);
2235
+ lines.push(`${innerIndent}}`);
1364
2236
  lines.push(`${indent}}`);
1365
2237
  }
1366
2238
  else if (node.directBind && node.valueExpr) {
1367
- lines.push(`${indent}imx::renderer::input_int(${label}, ${emitDirectBindPtr(node.valueExpr)});`);
2239
+ const styleVar = buildWidgetStyleVar(node.style, node.width, indent, lines);
2240
+ const styleArg = styleVar ? `, ${styleVar}` : '';
2241
+ emitBoolWidgetCall(`imx::renderer::input_int(${label}, ${emitDirectBindPtr(node.valueExpr)}${styleArg})`, node.item, lines, indent);
1368
2242
  }
1369
2243
  else if (node.valueExpr !== undefined) {
1370
2244
  lines.push(`${indent}{`);
1371
- lines.push(`${indent}${INDENT}int val = ${node.valueExpr};`);
1372
- lines.push(`${indent}${INDENT}if (imx::renderer::input_int(${label}, &val)) {`);
2245
+ const innerIndent = indent + INDENT;
2246
+ const styleVar = buildWidgetStyleVar(node.style, node.width, innerIndent, lines);
2247
+ const styleArg = styleVar ? `, ${styleVar}` : '';
2248
+ lines.push(`${innerIndent}int val = ${node.valueExpr};`);
2249
+ const changedVar = nextWidgetTemp('input_int_changed');
2250
+ const resultVar = emitBoolWidgetCall(`imx::renderer::input_int(${label}, &val${styleArg})`, node.item, lines, innerIndent, changedVar);
2251
+ lines.push(`${innerIndent}if (${resultVar}) {`);
1373
2252
  if (node.onChangeExpr) {
1374
- lines.push(`${indent}${INDENT}${INDENT}${node.onChangeExpr};`);
2253
+ lines.push(`${innerIndent}${INDENT}${node.onChangeExpr};`);
1375
2254
  }
1376
- lines.push(`${indent}${INDENT}}`);
2255
+ lines.push(`${innerIndent}}`);
1377
2256
  lines.push(`${indent}}`);
1378
2257
  }
1379
2258
  }
@@ -1382,23 +2261,35 @@ function emitInputFloat(node, lines, indent) {
1382
2261
  const label = asCharPtr(node.label);
1383
2262
  if (node.stateVar) {
1384
2263
  lines.push(`${indent}{`);
1385
- lines.push(`${indent}${INDENT}float val = ${node.stateVar}.get();`);
1386
- lines.push(`${indent}${INDENT}if (imx::renderer::input_float(${label}, &val)) {`);
1387
- lines.push(`${indent}${INDENT}${INDENT}${node.stateVar}.set(val);`);
1388
- lines.push(`${indent}${INDENT}}`);
2264
+ const innerIndent = indent + INDENT;
2265
+ const styleVar = buildWidgetStyleVar(node.style, node.width, innerIndent, lines);
2266
+ const styleArg = styleVar ? `, ${styleVar}` : '';
2267
+ lines.push(`${innerIndent}float val = ${node.stateVar}.get();`);
2268
+ const changedVar = nextWidgetTemp('input_float_changed');
2269
+ const resultVar = emitBoolWidgetCall(`imx::renderer::input_float(${label}, &val${styleArg})`, node.item, lines, innerIndent, changedVar);
2270
+ lines.push(`${innerIndent}if (${resultVar}) {`);
2271
+ lines.push(`${innerIndent}${INDENT}${node.stateVar}.set(val);`);
2272
+ lines.push(`${innerIndent}}`);
1389
2273
  lines.push(`${indent}}`);
1390
2274
  }
1391
2275
  else if (node.directBind && node.valueExpr) {
1392
- lines.push(`${indent}imx::renderer::input_float(${label}, ${emitDirectBindPtr(node.valueExpr)});`);
2276
+ const styleVar = buildWidgetStyleVar(node.style, node.width, indent, lines);
2277
+ const styleArg = styleVar ? `, ${styleVar}` : '';
2278
+ emitBoolWidgetCall(`imx::renderer::input_float(${label}, ${emitDirectBindPtr(node.valueExpr)}${styleArg})`, node.item, lines, indent);
1393
2279
  }
1394
2280
  else if (node.valueExpr !== undefined) {
1395
2281
  lines.push(`${indent}{`);
1396
- lines.push(`${indent}${INDENT}float val = ${node.valueExpr};`);
1397
- lines.push(`${indent}${INDENT}if (imx::renderer::input_float(${label}, &val)) {`);
2282
+ const innerIndent = indent + INDENT;
2283
+ const styleVar = buildWidgetStyleVar(node.style, node.width, innerIndent, lines);
2284
+ const styleArg = styleVar ? `, ${styleVar}` : '';
2285
+ lines.push(`${innerIndent}float val = ${node.valueExpr};`);
2286
+ const changedVar = nextWidgetTemp('input_float_changed');
2287
+ const resultVar = emitBoolWidgetCall(`imx::renderer::input_float(${label}, &val${styleArg})`, node.item, lines, innerIndent, changedVar);
2288
+ lines.push(`${innerIndent}if (${resultVar}) {`);
1398
2289
  if (node.onChangeExpr) {
1399
- lines.push(`${indent}${INDENT}${INDENT}${node.onChangeExpr};`);
2290
+ lines.push(`${innerIndent}${INDENT}${node.onChangeExpr};`);
1400
2291
  }
1401
- lines.push(`${indent}${INDENT}}`);
2292
+ lines.push(`${innerIndent}}`);
1402
2293
  lines.push(`${indent}}`);
1403
2294
  }
1404
2295
  }
@@ -1407,26 +2298,74 @@ function emitColorEdit(node, lines, indent) {
1407
2298
  const label = asCharPtr(node.label);
1408
2299
  if (node.stateVar) {
1409
2300
  lines.push(`${indent}{`);
1410
- lines.push(`${indent}${INDENT}auto val = ${node.stateVar}.get();`);
1411
- lines.push(`${indent}${INDENT}if (imx::renderer::color_edit(${label}, val.data())) {`);
1412
- lines.push(`${indent}${INDENT}${INDENT}${node.stateVar}.set(val);`);
1413
- lines.push(`${indent}${INDENT}}`);
2301
+ const innerIndent = indent + INDENT;
2302
+ const styleVar = buildWidgetStyleVar(node.style, node.width, innerIndent, lines);
2303
+ const styleArg = styleVar ? `, ${styleVar}` : '';
2304
+ lines.push(`${innerIndent}auto val = ${node.stateVar}.get();`);
2305
+ const changedVar = nextWidgetTemp('color_edit_changed');
2306
+ const resultVar = emitBoolWidgetCall(`imx::renderer::color_edit(${label}, val.data()${styleArg})`, node.item, lines, innerIndent, changedVar);
2307
+ lines.push(`${innerIndent}if (${resultVar}) {`);
2308
+ lines.push(`${innerIndent}${INDENT}${node.stateVar}.set(val);`);
2309
+ lines.push(`${innerIndent}}`);
1414
2310
  lines.push(`${indent}}`);
1415
2311
  }
1416
2312
  else if (node.directBind && node.valueExpr) {
1417
- const propName = node.valueExpr.startsWith('props.') ? node.valueExpr.slice(6).split('.')[0].split('[')[0] : '';
1418
- const isBound = currentBoundProps.has(propName);
1419
- const dataExpr = isBound ? `(${node.valueExpr})->data()` : `${node.valueExpr}.data()`;
1420
- lines.push(`${indent}imx::renderer::color_edit(${label}, ${dataExpr});`);
2313
+ const dataExpr = `${emitBoundValueExpr(node.valueExpr)}.data()`;
2314
+ const styleVar = buildWidgetStyleVar(node.style, node.width, indent, lines);
2315
+ const styleArg = styleVar ? `, ${styleVar}` : '';
2316
+ emitBoolWidgetCall(`imx::renderer::color_edit(${label}, ${dataExpr}${styleArg})`, node.item, lines, indent);
1421
2317
  }
1422
2318
  else if (node.valueExpr !== undefined) {
1423
2319
  lines.push(`${indent}{`);
1424
- lines.push(`${indent}${INDENT}auto val = ${node.valueExpr};`);
1425
- lines.push(`${indent}${INDENT}if (imx::renderer::color_edit(${label}, val.data())) {`);
2320
+ const innerIndent = indent + INDENT;
2321
+ const styleVar = buildWidgetStyleVar(node.style, node.width, innerIndent, lines);
2322
+ const styleArg = styleVar ? `, ${styleVar}` : '';
2323
+ lines.push(`${innerIndent}auto val = ${node.valueExpr};`);
2324
+ const changedVar = nextWidgetTemp('color_edit_changed');
2325
+ const resultVar = emitBoolWidgetCall(`imx::renderer::color_edit(${label}, val.data()${styleArg})`, node.item, lines, innerIndent, changedVar);
2326
+ lines.push(`${innerIndent}if (${resultVar}) {`);
1426
2327
  if (node.onChangeExpr) {
1427
- lines.push(`${indent}${INDENT}${INDENT}${node.onChangeExpr};`);
2328
+ lines.push(`${innerIndent}${INDENT}${node.onChangeExpr};`);
1428
2329
  }
1429
- lines.push(`${indent}${INDENT}}`);
2330
+ lines.push(`${innerIndent}}`);
2331
+ lines.push(`${indent}}`);
2332
+ }
2333
+ }
2334
+ function emitColorEdit3(node, lines, indent) {
2335
+ emitLocComment(node.loc, 'ColorEdit3', lines, indent);
2336
+ const label = asCharPtr(node.label);
2337
+ if (node.stateVar) {
2338
+ lines.push(`${indent}{`);
2339
+ const innerIndent = indent + INDENT;
2340
+ const styleVar = buildWidgetStyleVar(node.style, node.width, innerIndent, lines);
2341
+ const styleArg = styleVar ? `, ${styleVar}` : '';
2342
+ lines.push(`${innerIndent}auto val = ${node.stateVar}.get();`);
2343
+ const changedVar = nextWidgetTemp('color_edit3_changed');
2344
+ const resultVar = emitBoolWidgetCall(`imx::renderer::color_edit3(${label}, val.data()${styleArg})`, node.item, lines, innerIndent, changedVar);
2345
+ lines.push(`${innerIndent}if (${resultVar}) {`);
2346
+ lines.push(`${innerIndent}${INDENT}${node.stateVar}.set(val);`);
2347
+ lines.push(`${innerIndent}}`);
2348
+ lines.push(`${indent}}`);
2349
+ }
2350
+ else if (node.directBind && node.valueExpr) {
2351
+ const dataExpr = `${emitBoundValueExpr(node.valueExpr)}.data()`;
2352
+ const styleVar = buildWidgetStyleVar(node.style, node.width, indent, lines);
2353
+ const styleArg = styleVar ? `, ${styleVar}` : '';
2354
+ emitBoolWidgetCall(`imx::renderer::color_edit3(${label}, ${dataExpr}${styleArg})`, node.item, lines, indent);
2355
+ }
2356
+ else if (node.valueExpr !== undefined) {
2357
+ lines.push(`${indent}{`);
2358
+ const innerIndent = indent + INDENT;
2359
+ const styleVar = buildWidgetStyleVar(node.style, node.width, innerIndent, lines);
2360
+ const styleArg = styleVar ? `, ${styleVar}` : '';
2361
+ lines.push(`${innerIndent}auto val = ${node.valueExpr};`);
2362
+ const changedVar = nextWidgetTemp('color_edit3_changed');
2363
+ const resultVar = emitBoolWidgetCall(`imx::renderer::color_edit3(${label}, val.data()${styleArg})`, node.item, lines, innerIndent, changedVar);
2364
+ lines.push(`${innerIndent}if (${resultVar}) {`);
2365
+ if (node.onChangeExpr) {
2366
+ lines.push(`${innerIndent}${INDENT}${node.onChangeExpr};`);
2367
+ }
2368
+ lines.push(`${innerIndent}}`);
1430
2369
  lines.push(`${indent}}`);
1431
2370
  }
1432
2371
  }
@@ -1438,28 +2377,41 @@ function emitListBox(node, lines, indent) {
1438
2377
  const varName = `listbox_items_${listBoxCounter++}`;
1439
2378
  if (node.stateVar) {
1440
2379
  lines.push(`${indent}{`);
1441
- lines.push(`${indent}${INDENT}const char* ${varName}[] = {${itemsList.join(', ')}};`);
1442
- lines.push(`${indent}${INDENT}int val = ${node.stateVar}.get();`);
1443
- lines.push(`${indent}${INDENT}if (imx::renderer::list_box(${label}, &val, ${varName}, ${count})) {`);
1444
- lines.push(`${indent}${INDENT}${INDENT}${node.stateVar}.set(val);`);
1445
- lines.push(`${indent}${INDENT}}`);
2380
+ const innerIndent = indent + INDENT;
2381
+ const styleVar = buildWidgetStyleVar(node.style, node.width, innerIndent, lines);
2382
+ const styleArg = styleVar ? `, ${styleVar}` : '';
2383
+ lines.push(`${innerIndent}const char* ${varName}[] = {${itemsList.join(', ')}};`);
2384
+ lines.push(`${innerIndent}int val = ${node.stateVar}.get();`);
2385
+ const changedVar = nextWidgetTemp('list_box_changed');
2386
+ const resultVar = emitBoolWidgetCall(`imx::renderer::list_box(${label}, &val, ${varName}, ${count}${styleArg})`, node.item, lines, innerIndent, changedVar);
2387
+ lines.push(`${innerIndent}if (${resultVar}) {`);
2388
+ lines.push(`${innerIndent}${INDENT}${node.stateVar}.set(val);`);
2389
+ lines.push(`${innerIndent}}`);
1446
2390
  lines.push(`${indent}}`);
1447
2391
  }
1448
2392
  else if (node.directBind && node.valueExpr) {
1449
2393
  lines.push(`${indent}{`);
1450
- lines.push(`${indent}${INDENT}const char* ${varName}[] = {${itemsList.join(', ')}};`);
1451
- lines.push(`${indent}${INDENT}imx::renderer::list_box(${label}, ${emitDirectBindPtr(node.valueExpr)}, ${varName}, ${count});`);
2394
+ const innerIndent = indent + INDENT;
2395
+ const styleVar = buildWidgetStyleVar(node.style, node.width, innerIndent, lines);
2396
+ const styleArg = styleVar ? `, ${styleVar}` : '';
2397
+ lines.push(`${innerIndent}const char* ${varName}[] = {${itemsList.join(', ')}};`);
2398
+ emitBoolWidgetCall(`imx::renderer::list_box(${label}, ${emitDirectBindPtr(node.valueExpr)}, ${varName}, ${count}${styleArg})`, node.item, lines, innerIndent);
1452
2399
  lines.push(`${indent}}`);
1453
2400
  }
1454
2401
  else if (node.valueExpr !== undefined) {
1455
2402
  lines.push(`${indent}{`);
1456
- lines.push(`${indent}${INDENT}const char* ${varName}[] = {${itemsList.join(', ')}};`);
1457
- lines.push(`${indent}${INDENT}int val = ${node.valueExpr};`);
1458
- lines.push(`${indent}${INDENT}if (imx::renderer::list_box(${label}, &val, ${varName}, ${count})) {`);
2403
+ const innerIndent = indent + INDENT;
2404
+ const styleVar = buildWidgetStyleVar(node.style, node.width, innerIndent, lines);
2405
+ const styleArg = styleVar ? `, ${styleVar}` : '';
2406
+ lines.push(`${innerIndent}const char* ${varName}[] = {${itemsList.join(', ')}};`);
2407
+ lines.push(`${innerIndent}int val = ${node.valueExpr};`);
2408
+ const changedVar = nextWidgetTemp('list_box_changed');
2409
+ const resultVar = emitBoolWidgetCall(`imx::renderer::list_box(${label}, &val, ${varName}, ${count}${styleArg})`, node.item, lines, innerIndent, changedVar);
2410
+ lines.push(`${innerIndent}if (${resultVar}) {`);
1459
2411
  if (node.onChangeExpr) {
1460
- lines.push(`${indent}${INDENT}${INDENT}${node.onChangeExpr};`);
2412
+ lines.push(`${innerIndent}${INDENT}${node.onChangeExpr};`);
1461
2413
  }
1462
- lines.push(`${indent}${INDENT}}`);
2414
+ lines.push(`${innerIndent}}`);
1463
2415
  lines.push(`${indent}}`);
1464
2416
  }
1465
2417
  }
@@ -1476,6 +2428,13 @@ function emitTooltip(node, lines, indent) {
1476
2428
  emitLocComment(node.loc, 'Tooltip', lines, indent);
1477
2429
  lines.push(`${indent}imx::renderer::tooltip(${node.text});`);
1478
2430
  }
2431
+ function emitShortcut(node, lines, indent) {
2432
+ emitLocComment(node.loc, 'Shortcut', lines, indent);
2433
+ const keys = asCharPtr(node.keys);
2434
+ lines.push(`${indent}if (imx::renderer::shortcut_pressed(${keys})) {`);
2435
+ emitActionStatements(node.action, lines, indent + INDENT);
2436
+ lines.push(`${indent}}`);
2437
+ }
1479
2438
  function emitBulletText(node, lines, indent) {
1480
2439
  emitLocComment(node.loc, 'BulletText', lines, indent);
1481
2440
  if (node.args.length === 0) {
@@ -1496,40 +2455,47 @@ function emitLabelText(node, lines, indent) {
1496
2455
  }
1497
2456
  function emitSelectable(node, lines, indent) {
1498
2457
  emitLocComment(node.loc, 'Selectable', lines, indent);
2458
+ if (node.selectionIndex) {
2459
+ lines.push(`${indent}imx::renderer::set_next_item_selection_data(${node.selectionIndex});`);
2460
+ }
1499
2461
  const label = asCharPtr(node.label);
1500
- if (node.action.length > 0) {
1501
- lines.push(`${indent}if (imx::renderer::selectable(${label}, ${node.selected})) {`);
1502
- for (const stmt of node.action) {
1503
- lines.push(`${indent}${INDENT}${stmt}`);
1504
- }
2462
+ const flagsArg = node.flags ? `, ${node.flags}` : '';
2463
+ const pressedVar = node.action.length > 0 ? nextWidgetTemp('selectable_pressed') : undefined;
2464
+ const resultVar = emitBoolWidgetCall(`imx::renderer::selectable(${label}, ${node.selected}${flagsArg})`, node.item, lines, indent, pressedVar);
2465
+ if (node.action.length > 0 && resultVar) {
2466
+ lines.push(`${indent}if (${resultVar}) {`);
2467
+ emitActionStatements(node.action, lines, indent + INDENT);
1505
2468
  lines.push(`${indent}}`);
1506
2469
  }
1507
- else {
1508
- lines.push(`${indent}imx::renderer::selectable(${label}, ${node.selected});`);
1509
- }
1510
2470
  }
1511
2471
  function emitRadio(node, lines, indent) {
1512
2472
  emitLocComment(node.loc, 'Radio', lines, indent);
1513
2473
  const label = asCharPtr(node.label);
1514
2474
  if (node.stateVar) {
1515
2475
  lines.push(`${indent}{`);
1516
- lines.push(`${indent}${INDENT}int val = ${node.stateVar}.get();`);
1517
- lines.push(`${indent}${INDENT}if (imx::renderer::radio(${label}, &val, ${node.index})) {`);
1518
- lines.push(`${indent}${INDENT}${INDENT}${node.stateVar}.set(val);`);
1519
- lines.push(`${indent}${INDENT}}`);
2476
+ const innerIndent = indent + INDENT;
2477
+ lines.push(`${innerIndent}int val = ${node.stateVar}.get();`);
2478
+ const changedVar = nextWidgetTemp('radio_changed');
2479
+ const resultVar = emitBoolWidgetCall(`imx::renderer::radio(${label}, &val, ${node.index})`, node.item, lines, innerIndent, changedVar);
2480
+ lines.push(`${innerIndent}if (${resultVar}) {`);
2481
+ lines.push(`${innerIndent}${INDENT}${node.stateVar}.set(val);`);
2482
+ lines.push(`${innerIndent}}`);
1520
2483
  lines.push(`${indent}}`);
1521
2484
  }
1522
2485
  else if (node.directBind && node.valueExpr) {
1523
- lines.push(`${indent}imx::renderer::radio(${label}, ${emitDirectBindPtr(node.valueExpr)}, ${node.index});`);
2486
+ emitBoolWidgetCall(`imx::renderer::radio(${label}, ${emitDirectBindPtr(node.valueExpr)}, ${node.index})`, node.item, lines, indent);
1524
2487
  }
1525
2488
  else if (node.valueExpr !== undefined) {
1526
2489
  lines.push(`${indent}{`);
1527
- lines.push(`${indent}${INDENT}int val = ${node.valueExpr};`);
1528
- lines.push(`${indent}${INDENT}if (imx::renderer::radio(${label}, &val, ${node.index})) {`);
2490
+ const innerIndent = indent + INDENT;
2491
+ lines.push(`${innerIndent}int val = ${node.valueExpr};`);
2492
+ const changedVar = nextWidgetTemp('radio_changed');
2493
+ const resultVar = emitBoolWidgetCall(`imx::renderer::radio(${label}, &val, ${node.index})`, node.item, lines, innerIndent, changedVar);
2494
+ lines.push(`${innerIndent}if (${resultVar}) {`);
1529
2495
  if (node.onChangeExpr) {
1530
- lines.push(`${indent}${INDENT}${INDENT}${node.onChangeExpr};`);
2496
+ lines.push(`${innerIndent}${INDENT}${node.onChangeExpr};`);
1531
2497
  }
1532
- lines.push(`${indent}${INDENT}}`);
2498
+ lines.push(`${innerIndent}}`);
1533
2499
  lines.push(`${indent}}`);
1534
2500
  }
1535
2501
  }
@@ -1537,17 +2503,40 @@ function emitInputTextMultiline(node, lines, indent) {
1537
2503
  emitLocComment(node.loc, 'InputTextMultiline', lines, indent);
1538
2504
  lines.push(`${indent}{`);
1539
2505
  const innerIndent = indent + INDENT;
1540
- const styleVar = buildStyleVar(node.style, innerIndent, lines);
2506
+ const styleVar = buildWidgetStyleVar(node.style, node.width, innerIndent, lines);
1541
2507
  lines.push(`${innerIndent}auto& buf = ctx.get_buffer(${node.bufferIndex});`);
1542
- if (node.stateVar) {
1543
- lines.push(`${innerIndent}buf.sync_from(${node.stateVar}.get());`);
1544
- }
1545
2508
  const styleArg = styleVar ? `, ${styleVar}` : '';
1546
- lines.push(`${innerIndent}if (imx::renderer::text_input_multiline(${node.label}, buf${styleArg})) {`);
1547
2509
  if (node.stateVar) {
2510
+ lines.push(`${innerIndent}buf.sync_from(${node.stateVar}.get());`);
2511
+ const changedVar = nextWidgetTemp('text_input_multiline_changed');
2512
+ const resultVar = emitBoolWidgetCall(`imx::renderer::text_input_multiline(${node.label}, buf${styleArg})`, node.item, lines, innerIndent, changedVar);
2513
+ lines.push(`${innerIndent}if (${resultVar}) {`);
1548
2514
  lines.push(`${innerIndent}${INDENT}${node.stateVar}.set(buf.value());`);
2515
+ lines.push(`${innerIndent}}`);
2516
+ }
2517
+ else if (node.directBind && node.valueExpr) {
2518
+ const readExpr = emitBoundValueExpr(node.valueExpr);
2519
+ const writeExpr = emitBoundValueExpr(node.valueExpr);
2520
+ lines.push(`${innerIndent}buf.sync_from(${readExpr});`);
2521
+ const changedVar = nextWidgetTemp('text_input_multiline_changed');
2522
+ const resultVar = emitBoolWidgetCall(`imx::renderer::text_input_multiline(${node.label}, buf${styleArg})`, node.item, lines, innerIndent, changedVar);
2523
+ lines.push(`${innerIndent}if (${resultVar}) {`);
2524
+ lines.push(`${innerIndent}${INDENT}${writeExpr} = buf.value();`);
2525
+ lines.push(`${innerIndent}}`);
2526
+ }
2527
+ else if (node.valueExpr !== undefined) {
2528
+ lines.push(`${innerIndent}buf.sync_from(${node.valueExpr});`);
2529
+ const changedVar = nextWidgetTemp('text_input_multiline_changed');
2530
+ const resultVar = emitBoolWidgetCall(`imx::renderer::text_input_multiline(${node.label}, buf${styleArg})`, node.item, lines, innerIndent, changedVar);
2531
+ lines.push(`${innerIndent}if (${resultVar}) {`);
2532
+ if (node.onChangeExpr) {
2533
+ lines.push(`${innerIndent}${INDENT}${node.onChangeExpr};`);
2534
+ }
2535
+ lines.push(`${innerIndent}}`);
2536
+ }
2537
+ else {
2538
+ emitBoolWidgetCall(`imx::renderer::text_input_multiline(${node.label}, buf${styleArg})`, node.item, lines, innerIndent);
1549
2539
  }
1550
- lines.push(`${innerIndent}}`);
1551
2540
  lines.push(`${indent}}`);
1552
2541
  }
1553
2542
  function emitColorPicker(node, lines, indent) {
@@ -1555,26 +2544,68 @@ function emitColorPicker(node, lines, indent) {
1555
2544
  const label = asCharPtr(node.label);
1556
2545
  if (node.stateVar) {
1557
2546
  lines.push(`${indent}{`);
1558
- lines.push(`${indent}${INDENT}auto val = ${node.stateVar}.get();`);
1559
- lines.push(`${indent}${INDENT}if (imx::renderer::color_picker(${label}, val.data())) {`);
1560
- lines.push(`${indent}${INDENT}${INDENT}${node.stateVar}.set(val);`);
1561
- lines.push(`${indent}${INDENT}}`);
2547
+ const innerIndent = indent + INDENT;
2548
+ lines.push(`${innerIndent}auto val = ${node.stateVar}.get();`);
2549
+ const changedVar = nextWidgetTemp('color_picker_changed');
2550
+ const resultVar = emitBoolWidgetCall(`imx::renderer::color_picker(${label}, val.data())`, node.item, lines, innerIndent, changedVar);
2551
+ lines.push(`${innerIndent}if (${resultVar}) {`);
2552
+ lines.push(`${innerIndent}${INDENT}${node.stateVar}.set(val);`);
2553
+ lines.push(`${innerIndent}}`);
1562
2554
  lines.push(`${indent}}`);
1563
2555
  }
1564
2556
  else if (node.directBind && node.valueExpr) {
1565
- const propName = node.valueExpr.startsWith('props.') ? node.valueExpr.slice(6).split('.')[0].split('[')[0] : '';
1566
- const isBound = currentBoundProps.has(propName);
1567
- const dataExpr = isBound ? `(${node.valueExpr})->data()` : `${node.valueExpr}.data()`;
1568
- lines.push(`${indent}imx::renderer::color_picker(${label}, ${dataExpr});`);
2557
+ const dataExpr = `${emitBoundValueExpr(node.valueExpr)}.data()`;
2558
+ emitBoolWidgetCall(`imx::renderer::color_picker(${label}, ${dataExpr})`, node.item, lines, indent);
1569
2559
  }
1570
2560
  else if (node.valueExpr !== undefined) {
1571
2561
  lines.push(`${indent}{`);
1572
- lines.push(`${indent}${INDENT}auto val = ${node.valueExpr};`);
1573
- lines.push(`${indent}${INDENT}if (imx::renderer::color_picker(${label}, val.data())) {`);
2562
+ const innerIndent = indent + INDENT;
2563
+ lines.push(`${innerIndent}auto val = ${node.valueExpr};`);
2564
+ const changedVar = nextWidgetTemp('color_picker_changed');
2565
+ const resultVar = emitBoolWidgetCall(`imx::renderer::color_picker(${label}, val.data())`, node.item, lines, innerIndent, changedVar);
2566
+ lines.push(`${innerIndent}if (${resultVar}) {`);
1574
2567
  if (node.onChangeExpr) {
1575
- lines.push(`${indent}${INDENT}${INDENT}${node.onChangeExpr};`);
2568
+ lines.push(`${innerIndent}${INDENT}${node.onChangeExpr};`);
1576
2569
  }
1577
- lines.push(`${indent}${INDENT}}`);
2570
+ lines.push(`${innerIndent}}`);
2571
+ lines.push(`${indent}}`);
2572
+ }
2573
+ }
2574
+ function emitColorPicker3(node, lines, indent) {
2575
+ emitLocComment(node.loc, 'ColorPicker3', lines, indent);
2576
+ const label = asCharPtr(node.label);
2577
+ if (node.stateVar) {
2578
+ lines.push(`${indent}{`);
2579
+ const innerIndent = indent + INDENT;
2580
+ const styleVar = buildWidgetStyleVar(node.style, node.width, innerIndent, lines);
2581
+ const styleArg = styleVar ? `, ${styleVar}` : '';
2582
+ lines.push(`${innerIndent}auto val = ${node.stateVar}.get();`);
2583
+ const changedVar = nextWidgetTemp('color_picker3_changed');
2584
+ const resultVar = emitBoolWidgetCall(`imx::renderer::color_picker3(${label}, val.data()${styleArg})`, node.item, lines, innerIndent, changedVar);
2585
+ lines.push(`${innerIndent}if (${resultVar}) {`);
2586
+ lines.push(`${innerIndent}${INDENT}${node.stateVar}.set(val);`);
2587
+ lines.push(`${innerIndent}}`);
2588
+ lines.push(`${indent}}`);
2589
+ }
2590
+ else if (node.directBind && node.valueExpr) {
2591
+ const dataExpr = `${emitBoundValueExpr(node.valueExpr)}.data()`;
2592
+ const styleVar = buildWidgetStyleVar(node.style, node.width, indent, lines);
2593
+ const styleArg = styleVar ? `, ${styleVar}` : '';
2594
+ emitBoolWidgetCall(`imx::renderer::color_picker3(${label}, ${dataExpr}${styleArg})`, node.item, lines, indent);
2595
+ }
2596
+ else if (node.valueExpr !== undefined) {
2597
+ lines.push(`${indent}{`);
2598
+ const innerIndent = indent + INDENT;
2599
+ const styleVar = buildWidgetStyleVar(node.style, node.width, innerIndent, lines);
2600
+ const styleArg = styleVar ? `, ${styleVar}` : '';
2601
+ lines.push(`${innerIndent}auto val = ${node.valueExpr};`);
2602
+ const changedVar = nextWidgetTemp('color_picker3_changed');
2603
+ const resultVar = emitBoolWidgetCall(`imx::renderer::color_picker3(${label}, val.data()${styleArg})`, node.item, lines, innerIndent, changedVar);
2604
+ lines.push(`${innerIndent}if (${resultVar}) {`);
2605
+ if (node.onChangeExpr) {
2606
+ lines.push(`${innerIndent}${INDENT}${node.onChangeExpr};`);
2607
+ }
2608
+ lines.push(`${innerIndent}}`);
1578
2609
  lines.push(`${indent}}`);
1579
2610
  }
1580
2611
  }
@@ -1672,6 +2703,107 @@ function emitDrawText(node, lines, indent) {
1672
2703
  const text = asCharPtr(node.text);
1673
2704
  lines.push(`${indent}imx::renderer::draw_text(${posParts.join(', ')}, ${color}, ${text});`);
1674
2705
  }
2706
+ function emitDrawBezierCubic(node, lines, indent) {
2707
+ const p1 = node.p1.split(',').map((s) => emitFloat(s.trim()));
2708
+ const p2 = node.p2.split(',').map((s) => emitFloat(s.trim()));
2709
+ const p3 = node.p3.split(',').map((s) => emitFloat(s.trim()));
2710
+ const p4 = node.p4.split(',').map((s) => emitFloat(s.trim()));
2711
+ const color = emitImVec4(node.color);
2712
+ const thickness = emitFloat(node.thickness);
2713
+ const segments = node.segments;
2714
+ lines.push(`${indent}imx::renderer::draw_bezier_cubic(${p1.join(', ')}, ${p2.join(', ')}, ${p3.join(', ')}, ${p4.join(', ')}, ${color}, ${thickness}, ${segments});`);
2715
+ }
2716
+ function emitDrawBezierQuadratic(node, lines, indent) {
2717
+ const p1 = node.p1.split(',').map((s) => emitFloat(s.trim()));
2718
+ const p2 = node.p2.split(',').map((s) => emitFloat(s.trim()));
2719
+ const p3 = node.p3.split(',').map((s) => emitFloat(s.trim()));
2720
+ const color = emitImVec4(node.color);
2721
+ const thickness = emitFloat(node.thickness);
2722
+ const segments = node.segments;
2723
+ lines.push(`${indent}imx::renderer::draw_bezier_quadratic(${p1.join(', ')}, ${p2.join(', ')}, ${p3.join(', ')}, ${color}, ${thickness}, ${segments});`);
2724
+ }
2725
+ function emitDrawPolyline(node, lines, indent) {
2726
+ const color = emitImVec4(node.color);
2727
+ const thickness = emitFloat(node.thickness);
2728
+ const closed = node.closed;
2729
+ lines.push(`${indent}{`);
2730
+ lines.push(`${indent}${INDENT}float _poly_pts[] = {${node.points}};`);
2731
+ lines.push(`${indent}${INDENT}imx::renderer::draw_polyline(_poly_pts, sizeof(_poly_pts) / (2 * sizeof(float)), ${color}, ${thickness}, ${closed});`);
2732
+ lines.push(`${indent}}`);
2733
+ }
2734
+ function emitDrawConvexPolyFilled(node, lines, indent) {
2735
+ const color = emitImVec4(node.color);
2736
+ lines.push(`${indent}{`);
2737
+ lines.push(`${indent}${INDENT}float _poly_pts[] = {${node.points}};`);
2738
+ lines.push(`${indent}${INDENT}imx::renderer::draw_convex_poly_filled(_poly_pts, sizeof(_poly_pts) / (2 * sizeof(float)), ${color});`);
2739
+ lines.push(`${indent}}`);
2740
+ }
2741
+ function emitDrawNgon(node, lines, indent) {
2742
+ const center = node.center.split(',').map((s) => emitFloat(s.trim()));
2743
+ const radius = emitFloat(node.radius);
2744
+ const color = emitImVec4(node.color);
2745
+ const numSegments = node.numSegments;
2746
+ const thickness = emitFloat(node.thickness);
2747
+ lines.push(`${indent}imx::renderer::draw_ngon(${center.join(', ')}, ${radius}, ${color}, ${numSegments}, ${thickness});`);
2748
+ }
2749
+ function emitDrawNgonFilled(node, lines, indent) {
2750
+ const center = node.center.split(',').map((s) => emitFloat(s.trim()));
2751
+ const radius = emitFloat(node.radius);
2752
+ const color = emitImVec4(node.color);
2753
+ const numSegments = node.numSegments;
2754
+ lines.push(`${indent}imx::renderer::draw_ngon_filled(${center.join(', ')}, ${radius}, ${color}, ${numSegments});`);
2755
+ }
2756
+ function emitDrawTriangle(node, lines, indent) {
2757
+ const p1 = node.p1.split(',').map((s) => emitFloat(s.trim()));
2758
+ const p2 = node.p2.split(',').map((s) => emitFloat(s.trim()));
2759
+ const p3 = node.p3.split(',').map((s) => emitFloat(s.trim()));
2760
+ const color = emitImVec4(node.color);
2761
+ const filled = node.filled;
2762
+ const thickness = emitFloat(node.thickness);
2763
+ lines.push(`${indent}imx::renderer::draw_triangle(${p1.join(', ')}, ${p2.join(', ')}, ${p3.join(', ')}, ${color}, ${filled}, ${thickness});`);
2764
+ }
2765
+ function emitVectorInput(node, rendererFn, cppType, lines, indent, extraArgs = '') {
2766
+ const label = asCharPtr(node.label);
2767
+ const count = node.count;
2768
+ if (node.stateVar) {
2769
+ lines.push(`${indent}{`);
2770
+ const innerIndent = indent + INDENT;
2771
+ const styleVar = buildWidgetStyleVar(node.style, node.width, innerIndent, lines);
2772
+ const styleArg = styleVar ? `, ${styleVar}` : '';
2773
+ lines.push(`${innerIndent}auto val = ${node.stateVar}.get();`);
2774
+ const changedVar = nextWidgetTemp(`${rendererFn}_changed`);
2775
+ const resultVar = emitBoolWidgetCall(`imx::renderer::${rendererFn}(${label}, val.data(), ${count}${extraArgs}${styleArg})`, node.item, lines, innerIndent, changedVar);
2776
+ lines.push(`${innerIndent}if (${resultVar}) {`);
2777
+ lines.push(`${innerIndent}${INDENT}${node.stateVar}.set(val);`);
2778
+ lines.push(`${innerIndent}}`);
2779
+ lines.push(`${indent}}`);
2780
+ }
2781
+ else if (node.directBind && node.valueExpr) {
2782
+ const styleVar = buildWidgetStyleVar(node.style, node.width, indent, lines);
2783
+ const styleArg = styleVar ? `, ${styleVar}` : '';
2784
+ emitBoolWidgetCall(`imx::renderer::${rendererFn}(${label}, ${node.valueExpr}, ${count}${extraArgs}${styleArg})`, node.item, lines, indent);
2785
+ }
2786
+ else if (node.valueExpr) {
2787
+ lines.push(`${indent}{`);
2788
+ const innerIndent = indent + INDENT;
2789
+ const styleVar = buildWidgetStyleVar(node.style, node.width, innerIndent, lines);
2790
+ const styleArg = styleVar ? `, ${styleVar}` : '';
2791
+ lines.push(`${innerIndent}${cppType} _vec_val[${count}];`);
2792
+ lines.push(`${innerIndent}auto& _vec_src = ${node.valueExpr};`);
2793
+ lines.push(`${innerIndent}for (int i = 0; i < ${count}; ++i) _vec_val[i] = _vec_src[i];`);
2794
+ const changedVar = nextWidgetTemp(`${rendererFn}_changed`);
2795
+ const resultVar = emitBoolWidgetCall(`imx::renderer::${rendererFn}(${label}, _vec_val, ${count}${extraArgs}${styleArg})`, node.item, lines, innerIndent, changedVar);
2796
+ lines.push(`${innerIndent}if (${resultVar}) {`);
2797
+ if (node.onChangeExpr) {
2798
+ lines.push(`${innerIndent}${INDENT}${node.onChangeExpr};`);
2799
+ }
2800
+ else {
2801
+ lines.push(`${innerIndent}${INDENT}for (int i = 0; i < ${count}; ++i) _vec_src[i] = _vec_val[i];`);
2802
+ }
2803
+ lines.push(`${innerIndent}}`);
2804
+ lines.push(`${indent}}`);
2805
+ }
2806
+ }
1675
2807
  function collectEmbedKeys(nodes) {
1676
2808
  const keys = [];
1677
2809
  for (const node of nodes) {