imxc 0.5.4 → 0.6.1

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/lowering.js CHANGED
@@ -64,6 +64,7 @@ export function lowerComponent(parsed, validation, externalInterfaces) {
64
64
  setterMap,
65
65
  propsParam,
66
66
  propsFieldTypes,
67
+ externalInterfaces,
67
68
  bufferIndex: 0,
68
69
  mapCounter: 0,
69
70
  sourceFile: parsed.sourceFile,
@@ -86,19 +87,27 @@ export function lowerComponent(parsed, validation, externalInterfaces) {
86
87
  body,
87
88
  };
88
89
  }
90
+ function normalizePropTypeText(typeText) {
91
+ const trimmed = typeText.trim().replace(/\s*\|\s*undefined$/, '');
92
+ if (trimmed === 'number')
93
+ return 'int';
94
+ if (trimmed === 'boolean')
95
+ return 'bool';
96
+ if (trimmed === 'string')
97
+ return 'string';
98
+ // Array types: boolean[] -> bool*, number[] -> float*
99
+ if (trimmed === 'boolean[]')
100
+ return 'bool*';
101
+ if (trimmed === 'number[]')
102
+ return 'float*';
103
+ return trimmed;
104
+ }
89
105
  function inferPropType(member) {
90
106
  if (!member.type)
91
107
  return 'string';
92
108
  if (ts.isFunctionTypeNode(member.type))
93
109
  return 'callback';
94
- const text = member.type.getText();
95
- if (text === 'number')
96
- return 'int';
97
- if (text === 'boolean')
98
- return 'bool';
99
- if (text === 'string')
100
- return 'string';
101
- return 'string';
110
+ return normalizePropTypeText(member.type.getText());
102
111
  }
103
112
  /**
104
113
  * Scan the source file for an interface declaration matching `interfaceName`
@@ -119,26 +128,16 @@ function extractInterfaceFields(interfaceName, sourceFile, fieldTypes) {
119
128
  fieldTypes.set(fieldName, 'callback');
120
129
  continue;
121
130
  }
122
- const typeText = member.type.getText();
123
- if (typeText === 'number' || typeText === 'number | undefined') {
124
- fieldTypes.set(fieldName, 'float');
125
- }
126
- else if (typeText === 'boolean') {
127
- fieldTypes.set(fieldName, 'bool');
128
- }
129
- else if (typeText === 'string') {
130
- fieldTypes.set(fieldName, 'string');
131
- }
132
- else {
133
- // Array types, nested interfaces, etc. treated as opaque
134
- fieldTypes.set(fieldName, 'string');
135
- }
131
+ fieldTypes.set(fieldName, normalizePropTypeText(member.type.getText()));
136
132
  }
137
133
  }
138
134
  return;
139
135
  }
140
136
  }
141
137
  }
138
+ function isIntegerArrayLiteral(expr) {
139
+ return expr.elements.every(e => ts.isNumericLiteral(e) && !e.getText().includes('.'));
140
+ }
142
141
  function inferTypeFromExpr(expr) {
143
142
  if (ts.isNumericLiteral(expr)) {
144
143
  // Use getText() to check original source text, since TS normalizes 5.0 to "5" in .text
@@ -150,7 +149,10 @@ function inferTypeFromExpr(expr) {
150
149
  if (expr.kind === ts.SyntaxKind.TrueKeyword || expr.kind === ts.SyntaxKind.FalseKeyword)
151
150
  return 'bool';
152
151
  if (ts.isArrayLiteralExpression(expr))
153
- return 'color';
152
+ return isIntegerArrayLiteral(expr) ? 'int_array' : 'color';
153
+ // AsExpression (e.g. [1.0, 0.0] as [number, number]): infer from inner expression
154
+ if (ts.isAsExpression(expr))
155
+ return inferTypeFromExpr(expr.expression);
154
156
  // Default to int
155
157
  return 'int';
156
158
  }
@@ -165,11 +167,16 @@ function exprToLiteral(expr) {
165
167
  return 'true';
166
168
  if (expr.kind === ts.SyntaxKind.FalseKeyword)
167
169
  return 'false';
170
+ // AsExpression (e.g. [1.0, 0.0] as [number, number]): strip the type cast
171
+ if (ts.isAsExpression(expr))
172
+ return exprToLiteral(expr.expression);
168
173
  if (ts.isArrayLiteralExpression(expr)) {
169
174
  const elements = expr.elements.map(e => {
170
175
  const text = e.getText();
171
- // Ensure float suffix for color array elements
172
176
  if (ts.isNumericLiteral(e)) {
177
+ if (isIntegerArrayLiteral(expr)) {
178
+ return text;
179
+ }
173
180
  return text.includes('.') ? `${text}f` : `${text}.0f`;
174
181
  }
175
182
  return text;
@@ -302,6 +309,10 @@ export function exprToCpp(node, ctx) {
302
309
  if (ts.isArrayLiteralExpression(node)) {
303
310
  return node.elements.map(e => exprToCpp(e, ctx)).join(', ');
304
311
  }
312
+ // AsExpression (e.g. expr as Type): strip the type cast, emit inner expression
313
+ if (ts.isAsExpression(node)) {
314
+ return exprToCpp(node.expression, ctx);
315
+ }
305
316
  // Fallback: use text representation
306
317
  return node.getText();
307
318
  }
@@ -338,6 +349,31 @@ function extractActionStatements(expr, ctx) {
338
349
  }
339
350
  return [code + ';'];
340
351
  }
352
+ function lowerItemInteraction(attrs, rawAttrs, ctx) {
353
+ const item = {};
354
+ if (attrs['autoFocus'])
355
+ item.autoFocus = attrs['autoFocus'];
356
+ if (attrs['tooltip'])
357
+ item.tooltip = attrs['tooltip'];
358
+ if (attrs['scrollToHere'])
359
+ item.scrollToHere = attrs['scrollToHere'];
360
+ if (attrs['cursor'])
361
+ item.cursor = attrs['cursor'];
362
+ const callbackAttrs = [
363
+ ['onHover', 'onHover'],
364
+ ['onActive', 'onActive'],
365
+ ['onFocused', 'onFocused'],
366
+ ['onClicked', 'onClicked'],
367
+ ['onDoubleClicked', 'onDoubleClicked'],
368
+ ];
369
+ for (const [field, attrName] of callbackAttrs) {
370
+ const expr = rawAttrs.get(attrName);
371
+ if (expr) {
372
+ item[field] = extractActionStatements(expr, ctx);
373
+ }
374
+ }
375
+ return Object.keys(item).length > 0 ? item : undefined;
376
+ }
341
377
  /**
342
378
  * Lower a JSX expression (possibly wrapped in parenthesized expr) into IR nodes.
343
379
  */
@@ -409,11 +445,104 @@ function lowerJsxElement(node, body, ctx) {
409
445
  if (isHostComponent(name)) {
410
446
  const def = HOST_COMPONENTS[name];
411
447
  const attrs = getAttributes(node.openingElement.attributes, ctx);
448
+ const rawAttrs = getRawAttributes(node.openingElement.attributes);
449
+ if (name === 'Table') {
450
+ lowerTableElement(node, body, ctx, attrs, rawAttrs, getLoc(node, ctx));
451
+ return;
452
+ }
453
+ if (name === 'TableRow') {
454
+ body.push({ kind: 'begin_table_row', bgColor: attrs['bgColor'], loc: getLoc(node, ctx) });
455
+ for (const child of node.children) {
456
+ lowerJsxChild(child, body, ctx);
457
+ }
458
+ body.push({ kind: 'end_table_row' });
459
+ return;
460
+ }
461
+ if (name === 'TableCell') {
462
+ body.push({ kind: 'begin_table_cell', columnIndex: attrs['columnIndex'], bgColor: attrs['bgColor'], loc: getLoc(node, ctx) });
463
+ for (const child of node.children) {
464
+ lowerJsxChild(child, body, ctx);
465
+ }
466
+ body.push({ kind: 'end_table_cell' });
467
+ return;
468
+ }
469
+ if (name === 'TreeNode') {
470
+ body.push({
471
+ kind: 'begin_tree_node',
472
+ label: attrs['label'] ?? '""',
473
+ defaultOpen: attrs['defaultOpen'],
474
+ forceOpen: attrs['forceOpen'],
475
+ openOnArrow: attrs['openOnArrow'],
476
+ openOnDoubleClick: attrs['openOnDoubleClick'],
477
+ leaf: attrs['leaf'],
478
+ bullet: attrs['bullet'],
479
+ noTreePushOnOpen: attrs['noTreePushOnOpen'],
480
+ item: lowerItemInteraction(attrs, rawAttrs, ctx),
481
+ loc: getLoc(node, ctx),
482
+ });
483
+ for (const child of node.children) {
484
+ lowerJsxChild(child, body, ctx);
485
+ }
486
+ body.push({ kind: 'end_tree_node', noTreePushOnOpen: attrs['noTreePushOnOpen'] });
487
+ return;
488
+ }
489
+ if (name === 'CollapsingHeader') {
490
+ const onClose = rawAttrs.get('onClose');
491
+ const closeInfo = onClose ? lowerParameterizedCallback(onClose, ctx, 'onCloseHeader') : undefined;
492
+ body.push({
493
+ kind: 'begin_collapsing_header',
494
+ label: attrs['label'] ?? '""',
495
+ defaultOpen: attrs['defaultOpen'],
496
+ forceOpen: attrs['forceOpen'],
497
+ closable: attrs['closable'],
498
+ onCloseBody: closeInfo?.bodyCode,
499
+ item: lowerItemInteraction(attrs, rawAttrs, ctx),
500
+ loc: getLoc(node, ctx),
501
+ });
502
+ for (const child of node.children) {
503
+ lowerJsxChild(child, body, ctx);
504
+ }
505
+ body.push({ kind: 'end_collapsing_header', closable: attrs['closable'] });
506
+ return;
507
+ }
508
+ if (name === 'Combo') {
509
+ // Manual combo mode — has children, no items prop
510
+ const label = attrs['label'] ?? '""';
511
+ const preview = attrs['preview'] ?? '""';
512
+ const flagNames = [];
513
+ if (attrs['noArrowButton'] === 'true')
514
+ flagNames.push('ImGuiComboFlags_NoArrowButton');
515
+ if (attrs['noPreview'] === 'true')
516
+ flagNames.push('ImGuiComboFlags_NoPreview');
517
+ if (attrs['heightSmall'] === 'true')
518
+ flagNames.push('ImGuiComboFlags_HeightSmall');
519
+ if (attrs['heightLarge'] === 'true')
520
+ flagNames.push('ImGuiComboFlags_HeightLarge');
521
+ if (attrs['heightRegular'] === 'true')
522
+ flagNames.push('ImGuiComboFlags_HeightRegular');
523
+ const item = lowerItemInteraction(attrs, rawAttrs, ctx);
524
+ body.push({ kind: 'begin_combo', label, preview, flags: flagNames, width: attrs['width'], style: attrs['style'], item, loc: getLoc(node, ctx) });
525
+ for (const child of node.children) {
526
+ lowerJsxChild(child, body, ctx);
527
+ }
528
+ body.push({ kind: 'end_combo' });
529
+ return;
530
+ }
531
+ if (name === 'ListBox') {
532
+ // Manual ListBox mode — has children, no items prop
533
+ const label = attrs['label'] ?? '""';
534
+ const item = lowerItemInteraction(attrs, rawAttrs, ctx);
535
+ body.push({ kind: 'begin_list_box', label, width: attrs['width'], height: attrs['height'], style: attrs['style'], item, loc: getLoc(node, ctx) });
536
+ for (const child of node.children) {
537
+ lowerJsxChild(child, body, ctx);
538
+ }
539
+ body.push({ kind: 'end_list_box' });
540
+ return;
541
+ }
412
542
  if (def.isContainer) {
413
543
  const containerTag = name;
414
544
  // Special handling for DragDropTarget — lower onDrop callback with type info
415
545
  if (name === 'DragDropTarget') {
416
- const rawAttrs = getRawAttributes(node.openingElement.attributes);
417
546
  const props = {};
418
547
  for (const [attrName, expr] of rawAttrs) {
419
548
  if (attrName === 'onDrop' && expr && (ts.isArrowFunction(expr) || ts.isFunctionExpression(expr))) {
@@ -452,8 +581,8 @@ function lowerJsxElement(node, body, ctx) {
452
581
  body.push({ kind: 'end_container', tag: containerTag });
453
582
  return;
454
583
  }
455
- // DockSpace: detect if any child is a MenuBar
456
- if (containerTag === 'DockSpace') {
584
+ // DockSpace / Window: detect if any child is a MenuBar
585
+ if (containerTag === 'DockSpace' || containerTag === 'Window') {
457
586
  const hasMenuBar = node.children.some(c => ts.isJsxElement(c) && ts.isIdentifier(c.openingElement.tagName) && c.openingElement.tagName.text === 'MenuBar');
458
587
  if (hasMenuBar)
459
588
  attrs['hasMenuBar'] = 'true';
@@ -505,6 +634,52 @@ function lowerJsxSelfClosing(node, body, ctx) {
505
634
  const rawAttrs = getRawAttributes(node.attributes);
506
635
  const loc = getLoc(node, ctx);
507
636
  switch (name) {
637
+ case 'Table': {
638
+ lowerTableSelfClosing(body, ctx, attrs, rawAttrs, loc);
639
+ break;
640
+ }
641
+ case 'TableRow':
642
+ body.push({ kind: 'begin_table_row', bgColor: attrs['bgColor'], loc });
643
+ body.push({ kind: 'end_table_row' });
644
+ break;
645
+ case 'TableCell':
646
+ body.push({ kind: 'begin_table_cell', columnIndex: attrs['columnIndex'], bgColor: attrs['bgColor'], loc });
647
+ body.push({ kind: 'end_table_cell' });
648
+ break;
649
+ case 'TreeNode':
650
+ body.push({
651
+ kind: 'begin_tree_node',
652
+ label: attrs['label'] ?? '""',
653
+ defaultOpen: attrs['defaultOpen'],
654
+ forceOpen: attrs['forceOpen'],
655
+ openOnArrow: attrs['openOnArrow'],
656
+ openOnDoubleClick: attrs['openOnDoubleClick'],
657
+ leaf: attrs['leaf'],
658
+ bullet: attrs['bullet'],
659
+ noTreePushOnOpen: attrs['noTreePushOnOpen'],
660
+ item: lowerItemInteraction(attrs, rawAttrs, ctx),
661
+ loc,
662
+ });
663
+ body.push({ kind: 'end_tree_node', noTreePushOnOpen: attrs['noTreePushOnOpen'] });
664
+ break;
665
+ case 'CollapsingHeader': {
666
+ const closeInfo = rawAttrs.get('onClose') ? lowerParameterizedCallback(rawAttrs.get('onClose'), ctx, 'onCloseHeader') : undefined;
667
+ body.push({
668
+ kind: 'begin_collapsing_header',
669
+ label: attrs['label'] ?? '""',
670
+ defaultOpen: attrs['defaultOpen'],
671
+ forceOpen: attrs['forceOpen'],
672
+ closable: attrs['closable'],
673
+ onCloseBody: closeInfo?.bodyCode,
674
+ item: lowerItemInteraction(attrs, rawAttrs, ctx),
675
+ loc,
676
+ });
677
+ body.push({ kind: 'end_collapsing_header', closable: attrs['closable'] });
678
+ break;
679
+ }
680
+ case 'Shortcut':
681
+ lowerShortcut(attrs, rawAttrs, body, ctx, loc);
682
+ break;
508
683
  case 'Button':
509
684
  lowerButton(attrs, rawAttrs, body, ctx, loc);
510
685
  break;
@@ -541,22 +716,62 @@ function lowerJsxSelfClosing(node, body, ctx) {
541
716
  case 'ColorEdit':
542
717
  lowerColorEdit(attrs, rawAttrs, body, ctx, loc);
543
718
  break;
719
+ case 'ColorEdit3':
720
+ lowerColorEdit3(attrs, rawAttrs, body, ctx, loc);
721
+ break;
544
722
  case 'ListBox':
545
723
  lowerListBox(attrs, rawAttrs, body, ctx, loc);
546
724
  break;
547
725
  case 'ProgressBar':
548
726
  lowerProgressBar(attrs, rawAttrs, body, ctx, loc);
549
727
  break;
728
+ case 'Spacing':
729
+ body.push({ kind: 'spacing', loc });
730
+ break;
731
+ case 'Dummy':
732
+ body.push({
733
+ kind: 'dummy',
734
+ width: attrs['width'] ?? '0',
735
+ height: attrs['height'] ?? '0',
736
+ loc,
737
+ });
738
+ break;
739
+ case 'SameLine':
740
+ body.push({
741
+ kind: 'same_line',
742
+ offset: attrs['offset'] ?? '0',
743
+ spacing: attrs['spacing'] ?? '-1',
744
+ loc,
745
+ });
746
+ break;
747
+ case 'NewLine':
748
+ body.push({ kind: 'new_line', loc });
749
+ break;
750
+ case 'Cursor':
751
+ body.push({
752
+ kind: 'cursor',
753
+ x: attrs['x'] ?? '0',
754
+ y: attrs['y'] ?? '0',
755
+ loc,
756
+ });
757
+ break;
550
758
  case 'Tooltip':
551
759
  lowerTooltip(attrs, body, ctx, loc);
552
760
  break;
553
761
  case 'Separator':
554
762
  body.push({ kind: 'separator', loc });
555
763
  break;
556
- case 'Text':
557
- // Self-closing <Text /> - empty text
558
- body.push({ kind: 'text', format: '', args: [], loc });
764
+ case 'Bullet':
765
+ body.push({ kind: 'bullet', loc });
766
+ break;
767
+ case 'Text': {
768
+ // Self-closing <Text /> — may have disabled/wrapped/color props
769
+ const disabled = attrs['disabled'] === 'true';
770
+ const wrapped = attrs['wrapped'] === 'true';
771
+ const color = attrs['color'];
772
+ body.push({ kind: 'text', format: '', args: [], color, disabled: disabled || undefined, wrapped: wrapped || undefined, loc });
559
773
  break;
774
+ }
560
775
  case 'BulletText':
561
776
  // Self-closing <BulletText /> - empty bullet
562
777
  body.push({ kind: 'bullet_text', format: '', args: [], loc });
@@ -576,6 +791,9 @@ function lowerJsxSelfClosing(node, body, ctx) {
576
791
  case 'ColorPicker':
577
792
  lowerColorPicker(attrs, rawAttrs, body, ctx, loc);
578
793
  break;
794
+ case 'ColorPicker3':
795
+ lowerColorPicker3(attrs, rawAttrs, body, ctx, loc);
796
+ break;
579
797
  case 'PlotLines':
580
798
  lowerPlotLines(attrs, body, ctx, loc);
581
799
  break;
@@ -619,6 +837,143 @@ function lowerJsxSelfClosing(node, body, ctx) {
619
837
  body.push({ kind: 'draw_text', pos, text, color, loc });
620
838
  break;
621
839
  }
840
+ case 'DrawBezierCubic': {
841
+ const p1 = attrs['p1'] ?? '0, 0';
842
+ const p2 = attrs['p2'] ?? '0, 0';
843
+ const p3 = attrs['p3'] ?? '0, 0';
844
+ const p4 = attrs['p4'] ?? '0, 0';
845
+ const color = attrs['color'] ?? '1, 1, 1, 1';
846
+ const thickness = attrs['thickness'] ?? '1.0';
847
+ const segments = attrs['segments'] ?? '0';
848
+ body.push({ kind: 'draw_bezier_cubic', p1, p2, p3, p4, color, thickness, segments, loc });
849
+ break;
850
+ }
851
+ case 'DrawBezierQuadratic': {
852
+ const p1 = attrs['p1'] ?? '0, 0';
853
+ const p2 = attrs['p2'] ?? '0, 0';
854
+ const p3 = attrs['p3'] ?? '0, 0';
855
+ const color = attrs['color'] ?? '1, 1, 1, 1';
856
+ const thickness = attrs['thickness'] ?? '1.0';
857
+ const segments = attrs['segments'] ?? '0';
858
+ body.push({ kind: 'draw_bezier_quadratic', p1, p2, p3, color, thickness, segments, loc });
859
+ break;
860
+ }
861
+ case 'DrawPolyline': {
862
+ const points = attrs['points'] ?? '';
863
+ const color = attrs['color'] ?? '1, 1, 1, 1';
864
+ const thickness = attrs['thickness'] ?? '1.0';
865
+ const closed = attrs['closed'] ?? 'false';
866
+ body.push({ kind: 'draw_polyline', points, color, thickness, closed, loc });
867
+ break;
868
+ }
869
+ case 'DrawConvexPolyFilled': {
870
+ const points = attrs['points'] ?? '';
871
+ const color = attrs['color'] ?? '1, 1, 1, 1';
872
+ body.push({ kind: 'draw_convex_poly_filled', points, color, loc });
873
+ break;
874
+ }
875
+ case 'DrawNgon': {
876
+ const center = attrs['center'] ?? '0, 0';
877
+ const radius = attrs['radius'] ?? '0';
878
+ const color = attrs['color'] ?? '1, 1, 1, 1';
879
+ const numSegments = attrs['numSegments'] ?? '6';
880
+ const thickness = attrs['thickness'] ?? '1.0';
881
+ body.push({ kind: 'draw_ngon', center, radius, color, numSegments, thickness, loc });
882
+ break;
883
+ }
884
+ case 'DrawNgonFilled': {
885
+ const center = attrs['center'] ?? '0, 0';
886
+ const radius = attrs['radius'] ?? '0';
887
+ const color = attrs['color'] ?? '1, 1, 1, 1';
888
+ const numSegments = attrs['numSegments'] ?? '6';
889
+ body.push({ kind: 'draw_ngon_filled', center, radius, color, numSegments, loc });
890
+ break;
891
+ }
892
+ case 'DrawTriangle': {
893
+ const p1 = attrs['p1'] ?? '0, 0';
894
+ const p2 = attrs['p2'] ?? '0, 0';
895
+ const p3 = attrs['p3'] ?? '0, 0';
896
+ const color = attrs['color'] ?? '1, 1, 1, 1';
897
+ const filled = attrs['filled'] ?? 'false';
898
+ const thickness = attrs['thickness'] ?? '1.0';
899
+ body.push({ kind: 'draw_triangle', p1, p2, p3, color, filled, thickness, loc });
900
+ break;
901
+ }
902
+ case 'InputFloat2':
903
+ lowerVectorInput('input_float_n', 2, attrs, rawAttrs, body, ctx, loc);
904
+ break;
905
+ case 'InputFloat3':
906
+ lowerVectorInput('input_float_n', 3, attrs, rawAttrs, body, ctx, loc);
907
+ break;
908
+ case 'InputFloat4':
909
+ lowerVectorInput('input_float_n', 4, attrs, rawAttrs, body, ctx, loc);
910
+ break;
911
+ case 'InputInt2':
912
+ lowerVectorInput('input_int_n', 2, attrs, rawAttrs, body, ctx, loc);
913
+ break;
914
+ case 'InputInt3':
915
+ lowerVectorInput('input_int_n', 3, attrs, rawAttrs, body, ctx, loc);
916
+ break;
917
+ case 'InputInt4':
918
+ lowerVectorInput('input_int_n', 4, attrs, rawAttrs, body, ctx, loc);
919
+ break;
920
+ case 'DragFloat2':
921
+ lowerVectorInput('drag_float_n', 2, attrs, rawAttrs, body, ctx, loc);
922
+ break;
923
+ case 'DragFloat3':
924
+ lowerVectorInput('drag_float_n', 3, attrs, rawAttrs, body, ctx, loc);
925
+ break;
926
+ case 'DragFloat4':
927
+ lowerVectorInput('drag_float_n', 4, attrs, rawAttrs, body, ctx, loc);
928
+ break;
929
+ case 'DragInt2':
930
+ lowerVectorInput('drag_int_n', 2, attrs, rawAttrs, body, ctx, loc);
931
+ break;
932
+ case 'DragInt3':
933
+ lowerVectorInput('drag_int_n', 3, attrs, rawAttrs, body, ctx, loc);
934
+ break;
935
+ case 'DragInt4':
936
+ lowerVectorInput('drag_int_n', 4, attrs, rawAttrs, body, ctx, loc);
937
+ break;
938
+ case 'SliderFloat2':
939
+ lowerVectorInput('slider_float_n', 2, attrs, rawAttrs, body, ctx, loc);
940
+ break;
941
+ case 'SliderFloat3':
942
+ lowerVectorInput('slider_float_n', 3, attrs, rawAttrs, body, ctx, loc);
943
+ break;
944
+ case 'SliderFloat4':
945
+ lowerVectorInput('slider_float_n', 4, attrs, rawAttrs, body, ctx, loc);
946
+ break;
947
+ case 'SliderInt2':
948
+ lowerVectorInput('slider_int_n', 2, attrs, rawAttrs, body, ctx, loc);
949
+ break;
950
+ case 'SliderInt3':
951
+ lowerVectorInput('slider_int_n', 3, attrs, rawAttrs, body, ctx, loc);
952
+ break;
953
+ case 'SliderInt4':
954
+ lowerVectorInput('slider_int_n', 4, attrs, rawAttrs, body, ctx, loc);
955
+ break;
956
+ case 'VSliderFloat':
957
+ lowerVSliderFloat(attrs, rawAttrs, body, ctx, loc);
958
+ break;
959
+ case 'VSliderInt':
960
+ lowerVSliderInt(attrs, rawAttrs, body, ctx, loc);
961
+ break;
962
+ case 'SliderAngle':
963
+ lowerSliderAngle(attrs, rawAttrs, body, ctx, loc);
964
+ break;
965
+ case 'SmallButton':
966
+ lowerSmallButton(attrs, rawAttrs, body, ctx, loc);
967
+ break;
968
+ case 'ArrowButton':
969
+ lowerArrowButton(attrs, rawAttrs, body, ctx, loc);
970
+ break;
971
+ case 'InvisibleButton':
972
+ lowerInvisibleButton(attrs, rawAttrs, body, ctx, loc);
973
+ break;
974
+ case 'ImageButton':
975
+ lowerImageButton(attrs, rawAttrs, body, ctx, loc);
976
+ break;
622
977
  default:
623
978
  // Container self-closing (e.g., <Window title="X"/>)
624
979
  if (HOST_COMPONENTS[name]?.isContainer) {
@@ -638,14 +993,63 @@ function lowerButton(attrs, rawAttrs, body, ctx, loc) {
638
993
  }
639
994
  const disabled = attrs['disabled'] === 'true' ? true : undefined;
640
995
  const style = attrs['style'];
641
- body.push({ kind: 'button', title, action, disabled, style, loc });
996
+ const item = lowerItemInteraction(attrs, rawAttrs, ctx);
997
+ body.push({ kind: 'button', title, action, disabled, style, item, loc });
998
+ }
999
+ function lowerSmallButton(attrs, rawAttrs, body, ctx, loc) {
1000
+ const label = attrs['label'] ?? '""';
1001
+ const onPressExpr = rawAttrs.get('onPress');
1002
+ let action = [];
1003
+ if (onPressExpr) {
1004
+ action = extractActionStatements(onPressExpr, ctx);
1005
+ }
1006
+ const item = lowerItemInteraction(attrs, rawAttrs, ctx);
1007
+ body.push({ kind: 'small_button', label, action, item, loc });
1008
+ }
1009
+ function lowerArrowButton(attrs, rawAttrs, body, ctx, loc) {
1010
+ const id = attrs['id'] ?? '""';
1011
+ const direction = attrs['direction'] ?? '"left"';
1012
+ const onPressExpr = rawAttrs.get('onPress');
1013
+ let action = [];
1014
+ if (onPressExpr) {
1015
+ action = extractActionStatements(onPressExpr, ctx);
1016
+ }
1017
+ const item = lowerItemInteraction(attrs, rawAttrs, ctx);
1018
+ body.push({ kind: 'arrow_button', id, direction, action, item, loc });
1019
+ }
1020
+ function lowerInvisibleButton(attrs, rawAttrs, body, ctx, loc) {
1021
+ const id = attrs['id'] ?? '""';
1022
+ const width = attrs['width'] ?? '0';
1023
+ const height = attrs['height'] ?? '0';
1024
+ const onPressExpr = rawAttrs.get('onPress');
1025
+ let action = [];
1026
+ if (onPressExpr) {
1027
+ action = extractActionStatements(onPressExpr, ctx);
1028
+ }
1029
+ const item = lowerItemInteraction(attrs, rawAttrs, ctx);
1030
+ body.push({ kind: 'invisible_button', id, width, height, action, item, loc });
1031
+ }
1032
+ function lowerImageButton(attrs, rawAttrs, body, ctx, loc) {
1033
+ const id = attrs['id'] ?? '""';
1034
+ const src = attrs['src'] ?? '""';
1035
+ const width = attrs['width'];
1036
+ const height = attrs['height'];
1037
+ const onPressExpr = rawAttrs.get('onPress');
1038
+ let action = [];
1039
+ if (onPressExpr) {
1040
+ action = extractActionStatements(onPressExpr, ctx);
1041
+ }
1042
+ const item = lowerItemInteraction(attrs, rawAttrs, ctx);
1043
+ body.push({ kind: 'image_button', id, src, width, height, action, item, loc });
642
1044
  }
643
1045
  function lowerTextInput(attrs, rawAttrs, body, ctx, loc) {
644
1046
  const label = attrs['label'] ?? '""';
645
1047
  const bufferIndex = ctx.bufferIndex++;
1048
+ const width = attrs['width'];
646
1049
  const style = attrs['style'];
647
1050
  const { stateVar, valueExpr, onChangeExpr, directBind } = lowerValueOnChange(rawAttrs, ctx);
648
- body.push({ kind: 'text_input', label, bufferIndex, stateVar: stateVar, valueExpr, onChangeExpr, directBind, style, loc });
1051
+ const item = lowerItemInteraction(attrs, rawAttrs, ctx);
1052
+ body.push({ kind: 'text_input', label, bufferIndex, stateVar: stateVar, valueExpr, onChangeExpr, directBind, width, style, item, loc });
649
1053
  }
650
1054
  function lowerCheckbox(attrs, rawAttrs, body, ctx, loc) {
651
1055
  const label = attrs['label'] ?? '""';
@@ -688,7 +1092,8 @@ function lowerCheckbox(attrs, rawAttrs, body, ctx, loc) {
688
1092
  }
689
1093
  }
690
1094
  const style = attrs['style'];
691
- body.push({ kind: 'checkbox', label, stateVar, valueExpr: valueExprStr, onChangeExpr: onChangeExprStr, directBind, style, loc });
1095
+ const item = lowerItemInteraction(attrs, rawAttrs, ctx);
1096
+ body.push({ kind: 'checkbox', label, stateVar, valueExpr: valueExprStr, onChangeExpr: onChangeExprStr, directBind, style, item, loc });
692
1097
  }
693
1098
  function lowerMenuItem(attrs, rawAttrs, body, ctx, loc) {
694
1099
  const label = attrs['label'] ?? '""';
@@ -698,11 +1103,26 @@ function lowerMenuItem(attrs, rawAttrs, body, ctx, loc) {
698
1103
  if (onPressExpr) {
699
1104
  action = extractActionStatements(onPressExpr, ctx);
700
1105
  }
701
- body.push({ kind: 'menu_item', label, shortcut, action, loc });
1106
+ const item = lowerItemInteraction(attrs, rawAttrs, ctx);
1107
+ body.push({ kind: 'menu_item', label, shortcut, action, item, loc });
1108
+ }
1109
+ function lowerShortcut(attrs, rawAttrs, body, ctx, loc) {
1110
+ const keys = attrs['keys'] ?? '""';
1111
+ const onPressExpr = rawAttrs.get('onPress');
1112
+ let action = [];
1113
+ if (onPressExpr) {
1114
+ action = extractActionStatements(onPressExpr, ctx);
1115
+ }
1116
+ body.push({ kind: 'shortcut', keys, action, loc });
702
1117
  }
703
1118
  function lowerTextElement(node, body, ctx, loc) {
704
1119
  let format = '';
705
1120
  const args = [];
1121
+ // Extract Text props
1122
+ const attrs = getAttributes(node.openingElement.attributes, ctx);
1123
+ const color = attrs['color'];
1124
+ const disabled = attrs['disabled'] === 'true';
1125
+ const wrapped = attrs['wrapped'] === 'true';
706
1126
  for (const child of node.children) {
707
1127
  if (ts.isJsxText(child)) {
708
1128
  // Collapse whitespace (newlines, tabs, runs of spaces) into single spaces,
@@ -759,7 +1179,7 @@ function lowerTextElement(node, body, ctx, loc) {
759
1179
  }
760
1180
  }
761
1181
  }
762
- body.push({ kind: 'text', format, args, loc });
1182
+ body.push({ kind: 'text', format, args, color, disabled: disabled || undefined, wrapped: wrapped || undefined, loc });
763
1183
  }
764
1184
  /**
765
1185
  * Check if an expression will produce a const char* in C++ (not std::string).
@@ -781,6 +1201,8 @@ function inferExprType(expr, ctx) {
781
1201
  return 'string';
782
1202
  if (expr.kind === ts.SyntaxKind.TrueKeyword || expr.kind === ts.SyntaxKind.FalseKeyword)
783
1203
  return 'bool';
1204
+ if (ts.isArrayLiteralExpression(expr))
1205
+ return isIntegerArrayLiteral(expr) ? 'int_array' : 'color';
784
1206
  // Identifier: check state var type
785
1207
  if (ts.isIdentifier(expr)) {
786
1208
  const slot = ctx.stateVars.get(expr.text);
@@ -796,8 +1218,30 @@ function inferExprType(expr, ctx) {
796
1218
  // If accessing a direct field of the props param, look up its type
797
1219
  if (ctx.propsParam && ts.isIdentifier(expr.expression) && expr.expression.text === ctx.propsParam) {
798
1220
  const ft = ctx.propsFieldTypes.get(prop);
799
- if (ft && ft !== 'callback')
800
- return ft;
1221
+ if (ft && ft !== 'callback') {
1222
+ if (ft === 'int' || ft === 'float' || ft === 'bool' || ft === 'string' || ft === 'color' || ft === 'int_array') {
1223
+ return ft;
1224
+ }
1225
+ return 'string';
1226
+ }
1227
+ }
1228
+ // Nested access: props.data.field — resolve through external interfaces
1229
+ if (ctx.propsParam && ctx.externalInterfaces && ts.isPropertyAccessExpression(expr.expression)) {
1230
+ const mid = expr.expression;
1231
+ if (ts.isIdentifier(mid.expression) && mid.expression.text === ctx.propsParam) {
1232
+ const midType = ctx.propsFieldTypes.get(mid.name.text);
1233
+ if (midType && midType !== 'callback') {
1234
+ const iface = ctx.externalInterfaces.get(midType);
1235
+ if (iface) {
1236
+ const fieldType = iface.get(prop);
1237
+ if (fieldType && fieldType !== 'callback') {
1238
+ if (fieldType === 'int' || fieldType === 'float' || fieldType === 'bool' || fieldType === 'string' || fieldType === 'color' || fieldType === 'int_array') {
1239
+ return fieldType;
1240
+ }
1241
+ }
1242
+ }
1243
+ }
1244
+ }
801
1245
  }
802
1246
  return 'string';
803
1247
  }
@@ -881,6 +1325,113 @@ function lowerCustomComponent(name, attributes, body, ctx, loc) {
881
1325
  loc,
882
1326
  });
883
1327
  }
1328
+ function lowerParameterizedCallback(expr, ctx, defaultParamName) {
1329
+ if (ts.isArrowFunction(expr) || ts.isFunctionExpression(expr)) {
1330
+ const param = expr.parameters[0];
1331
+ const paramName = param && ts.isIdentifier(param.name) ? param.name.text : defaultParamName;
1332
+ const bodyCode = ts.isBlock(expr.body)
1333
+ ? expr.body.statements.map(s => stmtToCpp(s, ctx)).join(' ')
1334
+ : `${exprToCpp(expr.body, ctx)};`;
1335
+ return { paramName, bodyCode };
1336
+ }
1337
+ const callbackExpr = exprToCpp(expr, ctx);
1338
+ const bodyCode = (ts.isIdentifier(expr) || ts.isPropertyAccessExpression(expr))
1339
+ ? `${callbackExpr}(${defaultParamName});`
1340
+ : `${callbackExpr};`;
1341
+ return { paramName: defaultParamName, bodyCode };
1342
+ }
1343
+ function lowerTableColumns(expr, ctx) {
1344
+ if (!expr || !ts.isArrayLiteralExpression(expr)) {
1345
+ return [];
1346
+ }
1347
+ const columns = [];
1348
+ for (const element of expr.elements) {
1349
+ if (ts.isStringLiteral(element) || ts.isNoSubstitutionTemplateLiteral(element)) {
1350
+ columns.push({ label: JSON.stringify(element.text) });
1351
+ continue;
1352
+ }
1353
+ if (ts.isObjectLiteralExpression(element)) {
1354
+ const column = { label: '""' };
1355
+ for (const prop of element.properties) {
1356
+ if (!ts.isPropertyAssignment(prop) || !ts.isIdentifier(prop.name))
1357
+ continue;
1358
+ const propName = prop.name.text;
1359
+ const value = exprToCpp(prop.initializer, ctx);
1360
+ switch (propName) {
1361
+ case 'label':
1362
+ column.label = value;
1363
+ break;
1364
+ case 'defaultHide':
1365
+ column.defaultHide = value;
1366
+ break;
1367
+ case 'preferSortAscending':
1368
+ column.preferSortAscending = value;
1369
+ break;
1370
+ case 'preferSortDescending':
1371
+ column.preferSortDescending = value;
1372
+ break;
1373
+ case 'noResize':
1374
+ column.noResize = value;
1375
+ break;
1376
+ case 'fixedWidth':
1377
+ column.fixedWidth = value;
1378
+ break;
1379
+ }
1380
+ }
1381
+ columns.push(column);
1382
+ continue;
1383
+ }
1384
+ columns.push({ label: exprToCpp(element, ctx) });
1385
+ }
1386
+ return columns;
1387
+ }
1388
+ function lowerTableSelfClosing(body, ctx, attrs, rawAttrs, loc) {
1389
+ const onSort = rawAttrs.get('onSort') ?? null;
1390
+ const sortInfo = onSort ? lowerParameterizedCallback(onSort, ctx, 'tableSortSpecs') : undefined;
1391
+ body.push({
1392
+ kind: 'begin_table',
1393
+ columns: lowerTableColumns(rawAttrs.get('columns') ?? null, ctx),
1394
+ sortable: attrs['sortable'],
1395
+ hideable: attrs['hideable'],
1396
+ multiSortable: attrs['multiSortable'],
1397
+ noClip: attrs['noClip'],
1398
+ padOuterX: attrs['padOuterX'],
1399
+ scrollX: attrs['scrollX'],
1400
+ scrollY: attrs['scrollY'],
1401
+ noBorders: attrs['noBorders'],
1402
+ noRowBg: attrs['noRowBg'],
1403
+ onSortBody: sortInfo?.bodyCode,
1404
+ onSortParam: sortInfo?.paramName,
1405
+ style: attrs['style'],
1406
+ loc,
1407
+ });
1408
+ body.push({ kind: 'end_table' });
1409
+ }
1410
+ function lowerTableElement(node, body, ctx, attrs, rawAttrs, loc) {
1411
+ const onSort = rawAttrs.get('onSort') ?? null;
1412
+ const sortInfo = onSort ? lowerParameterizedCallback(onSort, ctx, 'tableSortSpecs') : undefined;
1413
+ body.push({
1414
+ kind: 'begin_table',
1415
+ columns: lowerTableColumns(rawAttrs.get('columns') ?? null, ctx),
1416
+ sortable: attrs['sortable'],
1417
+ hideable: attrs['hideable'],
1418
+ multiSortable: attrs['multiSortable'],
1419
+ noClip: attrs['noClip'],
1420
+ padOuterX: attrs['padOuterX'],
1421
+ scrollX: attrs['scrollX'],
1422
+ scrollY: attrs['scrollY'],
1423
+ noBorders: attrs['noBorders'],
1424
+ noRowBg: attrs['noRowBg'],
1425
+ onSortBody: sortInfo?.bodyCode,
1426
+ onSortParam: sortInfo?.paramName,
1427
+ style: attrs['style'],
1428
+ loc,
1429
+ });
1430
+ for (const child of node.children) {
1431
+ lowerJsxChild(child, body, ctx);
1432
+ }
1433
+ body.push({ kind: 'end_table' });
1434
+ }
884
1435
  function lowerNativeWidget(name, attributes, body, ctx, loc) {
885
1436
  const props = {};
886
1437
  const callbackProps = {};
@@ -1032,31 +1583,68 @@ function lowerSelectable(attrs, rawAttrs, body, ctx, loc) {
1032
1583
  action = extractActionStatements(onSelectExpr, ctx);
1033
1584
  }
1034
1585
  const style = attrs['style'];
1035
- body.push({ kind: 'selectable', label, selected, action, style, loc });
1586
+ const selectionIndex = attrs['selectionIndex'];
1587
+ const flagParts = [];
1588
+ if (attrs['spanAllColumns'] === 'true')
1589
+ flagParts.push('ImGuiSelectableFlags_SpanAllColumns');
1590
+ if (attrs['allowDoubleClick'] === 'true')
1591
+ flagParts.push('ImGuiSelectableFlags_AllowDoubleClick');
1592
+ if (attrs['dontClosePopups'] === 'true')
1593
+ flagParts.push('ImGuiSelectableFlags_DontClosePopups');
1594
+ const flags = flagParts.length > 0 ? flagParts.join(' | ') : undefined;
1595
+ const item = lowerItemInteraction(attrs, rawAttrs, ctx);
1596
+ body.push({ kind: 'selectable', label, selected, action, selectionIndex, flags, style, item, loc });
1036
1597
  }
1037
1598
  function lowerRadio(attrs, rawAttrs, body, ctx, loc) {
1038
1599
  const label = attrs['label'] ?? '""';
1039
1600
  const index = attrs['index'] ?? '0';
1040
1601
  const style = attrs['style'];
1041
1602
  const { stateVar, valueExpr, onChangeExpr, directBind } = lowerValueOnChange(rawAttrs, ctx);
1042
- body.push({ kind: 'radio', label, stateVar, valueExpr, onChangeExpr, directBind, index, style, loc });
1603
+ const item = lowerItemInteraction(attrs, rawAttrs, ctx);
1604
+ body.push({ kind: 'radio', label, stateVar, valueExpr, onChangeExpr, directBind, index, style, item, loc });
1043
1605
  }
1044
1606
  function lowerInputTextMultiline(attrs, rawAttrs, body, ctx, loc) {
1045
1607
  const label = attrs['label'] ?? '""';
1046
1608
  const bufferIndex = ctx.bufferIndex++;
1609
+ const width = attrs['width'];
1610
+ const style = attrs['style'];
1611
+ const { stateVar, valueExpr, onChangeExpr, directBind } = lowerValueOnChange(rawAttrs, ctx);
1612
+ const item = lowerItemInteraction(attrs, rawAttrs, ctx);
1613
+ body.push({ kind: 'input_text_multiline', label, bufferIndex, stateVar, valueExpr, onChangeExpr, directBind, width, style, item, loc });
1614
+ }
1615
+ function lowerColorPicker(attrs, rawAttrs, body, ctx, loc) {
1616
+ const label = attrs['label'] ?? '""';
1617
+ const style = attrs['style'];
1047
1618
  let stateVar = '';
1048
- const valueExpr = rawAttrs.get('value');
1049
- if (valueExpr && ts.isIdentifier(valueExpr)) {
1050
- const varName = valueExpr.text;
1051
- if (ctx.stateVars.has(varName)) {
1052
- stateVar = varName;
1619
+ let valueExpr;
1620
+ let onChangeExpr;
1621
+ let directBind;
1622
+ const valueRaw = rawAttrs.get('value');
1623
+ if (valueRaw && ts.isIdentifier(valueRaw) && ctx.stateVars.has(valueRaw.text)) {
1624
+ stateVar = valueRaw.text;
1625
+ }
1626
+ else if (valueRaw) {
1627
+ valueExpr = exprToCpp(valueRaw, ctx);
1628
+ const onChangeRaw = rawAttrs.get('onChange');
1629
+ if (onChangeRaw) {
1630
+ onChangeExpr = exprToCpp(onChangeRaw, ctx);
1631
+ if (onChangeExpr.startsWith('[')) {
1632
+ onChangeExpr = `(${onChangeExpr})()`;
1633
+ }
1634
+ else if (!onChangeExpr.endsWith(')')) {
1635
+ onChangeExpr = `${onChangeExpr}()`;
1636
+ }
1637
+ }
1638
+ else if (valueRaw && ts.isPropertyAccessExpression(valueRaw)) {
1639
+ directBind = true;
1053
1640
  }
1054
1641
  }
1055
- const style = attrs['style'];
1056
- body.push({ kind: 'input_text_multiline', label, bufferIndex, stateVar, style, loc });
1642
+ const item = lowerItemInteraction(attrs, rawAttrs, ctx);
1643
+ body.push({ kind: 'color_picker', label, stateVar, valueExpr, onChangeExpr, directBind, style, item, loc });
1057
1644
  }
1058
- function lowerColorPicker(attrs, rawAttrs, body, ctx, loc) {
1645
+ function lowerColorPicker3(attrs, rawAttrs, body, ctx, loc) {
1059
1646
  const label = attrs['label'] ?? '""';
1647
+ const width = attrs['width'];
1060
1648
  const style = attrs['style'];
1061
1649
  let stateVar = '';
1062
1650
  let valueExpr;
@@ -1082,7 +1670,8 @@ function lowerColorPicker(attrs, rawAttrs, body, ctx, loc) {
1082
1670
  directBind = true;
1083
1671
  }
1084
1672
  }
1085
- body.push({ kind: 'color_picker', label, stateVar, valueExpr, onChangeExpr, directBind, style, loc });
1673
+ const item = lowerItemInteraction(attrs, rawAttrs, ctx);
1674
+ body.push({ kind: 'color_picker3', label, stateVar, valueExpr, onChangeExpr, directBind, width, style, item, loc });
1086
1675
  }
1087
1676
  function lowerPlotLines(attrs, body, ctx, loc) {
1088
1677
  const label = attrs['label'] ?? '""';
@@ -1150,6 +1739,49 @@ function getRawAttributes(attributes) {
1150
1739
  }
1151
1740
  return result;
1152
1741
  }
1742
+ function lowerVectorInput(family, count, attrs, rawAttrs, body, ctx, loc) {
1743
+ const label = attrs['label'] ?? '""';
1744
+ const width = attrs['width'];
1745
+ const style = attrs['style'];
1746
+ const valueRaw = rawAttrs.get('value');
1747
+ let stateVar = '';
1748
+ let valueExpr;
1749
+ let directBind;
1750
+ let onChangeExpr;
1751
+ if (valueRaw && ts.isIdentifier(valueRaw) && ctx.stateVars.has(valueRaw.text)) {
1752
+ stateVar = valueRaw.text;
1753
+ }
1754
+ else if (valueRaw) {
1755
+ valueExpr = exprToCpp(valueRaw, ctx);
1756
+ const onChangeRaw = rawAttrs.get('onChange');
1757
+ if (onChangeRaw) {
1758
+ onChangeExpr = exprToCpp(onChangeRaw, ctx);
1759
+ if (onChangeExpr.startsWith('[')) {
1760
+ onChangeExpr = `(${onChangeExpr})()`;
1761
+ }
1762
+ else if (!onChangeExpr.endsWith(')')) {
1763
+ onChangeExpr = `${onChangeExpr}()`;
1764
+ }
1765
+ }
1766
+ else if (ts.isPropertyAccessExpression(valueRaw)) {
1767
+ directBind = true;
1768
+ }
1769
+ }
1770
+ const item = lowerItemInteraction(attrs, rawAttrs, ctx);
1771
+ const base = { kind: family, label, count, stateVar, valueExpr, directBind, onChangeExpr, width, style, item, loc };
1772
+ if (family === 'drag_float_n' || family === 'drag_int_n') {
1773
+ base.speed = attrs['speed'] ?? '1.0f';
1774
+ }
1775
+ if (family === 'slider_float_n') {
1776
+ base.min = attrs['min'] ?? '0.0f';
1777
+ base.max = attrs['max'] ?? '1.0f';
1778
+ }
1779
+ if (family === 'slider_int_n') {
1780
+ base.min = attrs['min'] ?? '0';
1781
+ base.max = attrs['max'] ?? '100';
1782
+ }
1783
+ body.push(base);
1784
+ }
1153
1785
  function lowerValueOnChange(rawAttrs, ctx) {
1154
1786
  let stateVar = '';
1155
1787
  let valueExpr;
@@ -1185,53 +1817,100 @@ function lowerSliderFloat(attrs, rawAttrs, body, ctx, loc) {
1185
1817
  const label = attrs['label'] ?? '""';
1186
1818
  const min = attrs['min'] ?? '0.0f';
1187
1819
  const max = attrs['max'] ?? '1.0f';
1820
+ const width = attrs['width'];
1188
1821
  const style = attrs['style'];
1189
1822
  const { stateVar, valueExpr, onChangeExpr, directBind } = lowerValueOnChange(rawAttrs, ctx);
1190
- body.push({ kind: 'slider_float', label, stateVar, valueExpr, onChangeExpr, directBind, min, max, style, loc });
1823
+ const item = lowerItemInteraction(attrs, rawAttrs, ctx);
1824
+ body.push({ kind: 'slider_float', label, stateVar, valueExpr, onChangeExpr, directBind, min, max, width, style, item, loc });
1191
1825
  }
1192
1826
  function lowerSliderInt(attrs, rawAttrs, body, ctx, loc) {
1193
1827
  const label = attrs['label'] ?? '""';
1194
1828
  const min = attrs['min'] ?? '0';
1195
1829
  const max = attrs['max'] ?? '100';
1830
+ const width = attrs['width'];
1831
+ const style = attrs['style'];
1832
+ const { stateVar, valueExpr, onChangeExpr, directBind } = lowerValueOnChange(rawAttrs, ctx);
1833
+ const item = lowerItemInteraction(attrs, rawAttrs, ctx);
1834
+ body.push({ kind: 'slider_int', label, stateVar, valueExpr, onChangeExpr, directBind, min, max, width, style, item, loc });
1835
+ }
1836
+ function lowerVSliderFloat(attrs, rawAttrs, body, ctx, loc) {
1837
+ const label = attrs['label'] ?? '""';
1838
+ const width = attrs['width'] ?? '20';
1839
+ const height = attrs['height'] ?? '100';
1840
+ const min = attrs['min'] ?? '0.0f';
1841
+ const max = attrs['max'] ?? '1.0f';
1842
+ const style = attrs['style'];
1843
+ const { stateVar, valueExpr, onChangeExpr, directBind } = lowerValueOnChange(rawAttrs, ctx);
1844
+ const item = lowerItemInteraction(attrs, rawAttrs, ctx);
1845
+ body.push({ kind: 'vslider_float', label, stateVar, valueExpr, onChangeExpr, directBind, width, height, min, max, style, item, loc });
1846
+ }
1847
+ function lowerVSliderInt(attrs, rawAttrs, body, ctx, loc) {
1848
+ const label = attrs['label'] ?? '""';
1849
+ const width = attrs['width'] ?? '20';
1850
+ const height = attrs['height'] ?? '100';
1851
+ const min = attrs['min'] ?? '0';
1852
+ const max = attrs['max'] ?? '100';
1853
+ const style = attrs['style'];
1854
+ const { stateVar, valueExpr, onChangeExpr, directBind } = lowerValueOnChange(rawAttrs, ctx);
1855
+ const item = lowerItemInteraction(attrs, rawAttrs, ctx);
1856
+ body.push({ kind: 'vslider_int', label, stateVar, valueExpr, onChangeExpr, directBind, width, height, min, max, style, item, loc });
1857
+ }
1858
+ function lowerSliderAngle(attrs, rawAttrs, body, ctx, loc) {
1859
+ const label = attrs['label'] ?? '""';
1860
+ const min = attrs['min'] ?? '-360.0f';
1861
+ const max = attrs['max'] ?? '360.0f';
1862
+ const width = attrs['width'];
1196
1863
  const style = attrs['style'];
1197
1864
  const { stateVar, valueExpr, onChangeExpr, directBind } = lowerValueOnChange(rawAttrs, ctx);
1198
- body.push({ kind: 'slider_int', label, stateVar, valueExpr, onChangeExpr, directBind, min, max, style, loc });
1865
+ const item = lowerItemInteraction(attrs, rawAttrs, ctx);
1866
+ body.push({ kind: 'slider_angle', label, stateVar, valueExpr, onChangeExpr, directBind, min, max, width, style, item, loc });
1199
1867
  }
1200
1868
  function lowerDragFloat(attrs, rawAttrs, body, ctx, loc) {
1201
1869
  const label = attrs['label'] ?? '""';
1202
1870
  const speed = attrs['speed'] ?? '1.0f';
1871
+ const width = attrs['width'];
1203
1872
  const style = attrs['style'];
1204
1873
  const { stateVar, valueExpr, onChangeExpr, directBind } = lowerValueOnChange(rawAttrs, ctx);
1205
- body.push({ kind: 'drag_float', label, stateVar, valueExpr, onChangeExpr, directBind, speed, style, loc });
1874
+ const item = lowerItemInteraction(attrs, rawAttrs, ctx);
1875
+ body.push({ kind: 'drag_float', label, stateVar, valueExpr, onChangeExpr, directBind, speed, width, style, item, loc });
1206
1876
  }
1207
1877
  function lowerDragInt(attrs, rawAttrs, body, ctx, loc) {
1208
1878
  const label = attrs['label'] ?? '""';
1209
1879
  const speed = attrs['speed'] ?? '1.0f';
1880
+ const width = attrs['width'];
1210
1881
  const style = attrs['style'];
1211
1882
  const { stateVar, valueExpr, onChangeExpr, directBind } = lowerValueOnChange(rawAttrs, ctx);
1212
- body.push({ kind: 'drag_int', label, stateVar, valueExpr, onChangeExpr, directBind, speed, style, loc });
1883
+ const item = lowerItemInteraction(attrs, rawAttrs, ctx);
1884
+ body.push({ kind: 'drag_int', label, stateVar, valueExpr, onChangeExpr, directBind, speed, width, style, item, loc });
1213
1885
  }
1214
1886
  function lowerCombo(attrs, rawAttrs, body, ctx, loc) {
1215
1887
  const label = attrs['label'] ?? '""';
1216
1888
  const items = attrs['items'] ?? '';
1889
+ const width = attrs['width'];
1217
1890
  const style = attrs['style'];
1218
1891
  const { stateVar, valueExpr, onChangeExpr, directBind } = lowerValueOnChange(rawAttrs, ctx);
1219
- body.push({ kind: 'combo', label, stateVar, valueExpr, onChangeExpr, directBind, items, style, loc });
1892
+ const item = lowerItemInteraction(attrs, rawAttrs, ctx);
1893
+ body.push({ kind: 'combo', label, stateVar, valueExpr, onChangeExpr, directBind, items, width, style, item, loc });
1220
1894
  }
1221
1895
  function lowerInputInt(attrs, rawAttrs, body, ctx, loc) {
1222
1896
  const label = attrs['label'] ?? '""';
1897
+ const width = attrs['width'];
1223
1898
  const style = attrs['style'];
1224
1899
  const { stateVar, valueExpr, onChangeExpr, directBind } = lowerValueOnChange(rawAttrs, ctx);
1225
- body.push({ kind: 'input_int', label, stateVar, valueExpr, onChangeExpr, directBind, style, loc });
1900
+ const item = lowerItemInteraction(attrs, rawAttrs, ctx);
1901
+ body.push({ kind: 'input_int', label, stateVar, valueExpr, onChangeExpr, directBind, width, style, item, loc });
1226
1902
  }
1227
1903
  function lowerInputFloat(attrs, rawAttrs, body, ctx, loc) {
1228
1904
  const label = attrs['label'] ?? '""';
1905
+ const width = attrs['width'];
1229
1906
  const style = attrs['style'];
1230
1907
  const { stateVar, valueExpr, onChangeExpr, directBind } = lowerValueOnChange(rawAttrs, ctx);
1231
- body.push({ kind: 'input_float', label, stateVar, valueExpr, onChangeExpr, directBind, style, loc });
1908
+ const item = lowerItemInteraction(attrs, rawAttrs, ctx);
1909
+ body.push({ kind: 'input_float', label, stateVar, valueExpr, onChangeExpr, directBind, width, style, item, loc });
1232
1910
  }
1233
1911
  function lowerColorEdit(attrs, rawAttrs, body, ctx, loc) {
1234
1912
  const label = attrs['label'] ?? '""';
1913
+ const width = attrs['width'];
1235
1914
  const style = attrs['style'];
1236
1915
  let stateVar = '';
1237
1916
  let valueExpr;
@@ -1257,14 +1936,48 @@ function lowerColorEdit(attrs, rawAttrs, body, ctx, loc) {
1257
1936
  directBind = true;
1258
1937
  }
1259
1938
  }
1260
- body.push({ kind: 'color_edit', label, stateVar, valueExpr, onChangeExpr, directBind, style, loc });
1939
+ const item = lowerItemInteraction(attrs, rawAttrs, ctx);
1940
+ body.push({ kind: 'color_edit', label, stateVar, valueExpr, onChangeExpr, directBind, width, style, item, loc });
1941
+ }
1942
+ function lowerColorEdit3(attrs, rawAttrs, body, ctx, loc) {
1943
+ const label = attrs['label'] ?? '""';
1944
+ const width = attrs['width'];
1945
+ const style = attrs['style'];
1946
+ let stateVar = '';
1947
+ let valueExpr;
1948
+ let onChangeExpr;
1949
+ let directBind;
1950
+ const valueRaw = rawAttrs.get('value');
1951
+ if (valueRaw && ts.isIdentifier(valueRaw) && ctx.stateVars.has(valueRaw.text)) {
1952
+ stateVar = valueRaw.text;
1953
+ }
1954
+ else if (valueRaw) {
1955
+ valueExpr = exprToCpp(valueRaw, ctx);
1956
+ const onChangeRaw = rawAttrs.get('onChange');
1957
+ if (onChangeRaw) {
1958
+ onChangeExpr = exprToCpp(onChangeRaw, ctx);
1959
+ if (onChangeExpr.startsWith('[')) {
1960
+ onChangeExpr = `(${onChangeExpr})()`;
1961
+ }
1962
+ else if (!onChangeExpr.endsWith(')')) {
1963
+ onChangeExpr = `${onChangeExpr}()`;
1964
+ }
1965
+ }
1966
+ else if (valueRaw && ts.isPropertyAccessExpression(valueRaw)) {
1967
+ directBind = true;
1968
+ }
1969
+ }
1970
+ const item = lowerItemInteraction(attrs, rawAttrs, ctx);
1971
+ body.push({ kind: 'color_edit3', label, stateVar, valueExpr, onChangeExpr, directBind, width, style, item, loc });
1261
1972
  }
1262
1973
  function lowerListBox(attrs, rawAttrs, body, ctx, loc) {
1263
1974
  const label = attrs['label'] ?? '""';
1264
1975
  const items = attrs['items'] ?? '';
1976
+ const width = attrs['width'];
1265
1977
  const style = attrs['style'];
1266
1978
  const { stateVar, valueExpr, onChangeExpr, directBind } = lowerValueOnChange(rawAttrs, ctx);
1267
- body.push({ kind: 'list_box', label, stateVar, valueExpr, onChangeExpr, directBind, items, style, loc });
1979
+ const item = lowerItemInteraction(attrs, rawAttrs, ctx);
1980
+ body.push({ kind: 'list_box', label, stateVar, valueExpr, onChangeExpr, directBind, items, width, style, item, loc });
1268
1981
  }
1269
1982
  function lowerProgressBar(attrs, rawAttrs, body, ctx, loc) {
1270
1983
  const value = attrs['value'] ?? '0.0f';