imxc 0.4.1 → 0.5.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.
@@ -184,7 +184,7 @@ export const HOST_COMPONENTS = {
184
184
  props: {
185
185
  label: { type: 'string', required: true },
186
186
  value: { type: 'string', required: true },
187
- onChange: { type: 'callback', required: true },
187
+ onChange: { type: 'callback', required: false },
188
188
  style: { type: 'style', required: false },
189
189
  },
190
190
  hasChildren: false, isContainer: false,
package/dist/emitter.js CHANGED
@@ -1096,9 +1096,10 @@ function emitListMap(node, lines, indent, depth) {
1096
1096
  if (node.loc) {
1097
1097
  lines.push(`${indent}// ${node.loc.file}:${node.loc.line} .map()`);
1098
1098
  }
1099
- lines.push(`${indent}for (size_t i = 0; i < ${node.array}.size(); i++) {`);
1100
- lines.push(`${indent}${INDENT}auto& ${node.itemVar} = ${node.array}[i];`);
1101
- lines.push(`${indent}${INDENT}ctx.begin_instance("${node.componentName}", (int)i, ${node.stateCount}, ${node.bufferCount});`);
1099
+ const idx = node.indexVar;
1100
+ lines.push(`${indent}for (size_t ${idx} = 0; ${idx} < ${node.array}.size(); ${idx}++) {`);
1101
+ lines.push(`${indent}${INDENT}auto& ${node.itemVar} = ${node.array}[${idx}];`);
1102
+ lines.push(`${indent}${INDENT}ctx.begin_instance("${node.componentName}", (int)${idx}, ${node.stateCount}, ${node.bufferCount});`);
1102
1103
  emitNodes(node.body, lines, depth + 2);
1103
1104
  lines.push(`${indent}${INDENT}ctx.end_instance();`);
1104
1105
  lines.push(`${indent}}`);
@@ -1346,6 +1347,19 @@ function emitColorEdit(node, lines, indent) {
1346
1347
  lines.push(`${indent}${INDENT}}`);
1347
1348
  lines.push(`${indent}}`);
1348
1349
  }
1350
+ else if (node.directBind && node.valueExpr) {
1351
+ lines.push(`${indent}imx::renderer::color_edit(${label}, ${node.valueExpr}.data());`);
1352
+ }
1353
+ else if (node.valueExpr !== undefined) {
1354
+ lines.push(`${indent}{`);
1355
+ lines.push(`${indent}${INDENT}auto val = ${node.valueExpr};`);
1356
+ lines.push(`${indent}${INDENT}if (imx::renderer::color_edit(${label}, val.data())) {`);
1357
+ if (node.onChangeExpr) {
1358
+ lines.push(`${indent}${INDENT}${INDENT}${node.onChangeExpr};`);
1359
+ }
1360
+ lines.push(`${indent}${INDENT}}`);
1361
+ lines.push(`${indent}}`);
1362
+ }
1349
1363
  }
1350
1364
  function emitListBox(node, lines, indent) {
1351
1365
  emitLocComment(node.loc, 'ListBox', lines, indent);
@@ -1469,44 +1483,79 @@ function emitInputTextMultiline(node, lines, indent) {
1469
1483
  }
1470
1484
  function emitColorPicker(node, lines, indent) {
1471
1485
  emitLocComment(node.loc, 'ColorPicker', lines, indent);
1486
+ const label = asCharPtr(node.label);
1472
1487
  if (node.stateVar) {
1473
1488
  lines.push(`${indent}{`);
1474
1489
  lines.push(`${indent}${INDENT}auto val = ${node.stateVar}.get();`);
1475
- lines.push(`${indent}${INDENT}if (imx::renderer::color_picker(${node.label}, val.data())) {`);
1490
+ lines.push(`${indent}${INDENT}if (imx::renderer::color_picker(${label}, val.data())) {`);
1476
1491
  lines.push(`${indent}${INDENT}${INDENT}${node.stateVar}.set(val);`);
1477
1492
  lines.push(`${indent}${INDENT}}`);
1478
1493
  lines.push(`${indent}}`);
1479
1494
  }
1495
+ else if (node.directBind && node.valueExpr) {
1496
+ lines.push(`${indent}imx::renderer::color_picker(${label}, ${node.valueExpr}.data());`);
1497
+ }
1498
+ else if (node.valueExpr !== undefined) {
1499
+ lines.push(`${indent}{`);
1500
+ lines.push(`${indent}${INDENT}auto val = ${node.valueExpr};`);
1501
+ lines.push(`${indent}${INDENT}if (imx::renderer::color_picker(${label}, val.data())) {`);
1502
+ if (node.onChangeExpr) {
1503
+ lines.push(`${indent}${INDENT}${INDENT}${node.onChangeExpr};`);
1504
+ }
1505
+ lines.push(`${indent}${INDENT}}`);
1506
+ lines.push(`${indent}}`);
1507
+ }
1480
1508
  }
1481
1509
  function emitPlotLines(node, lines, indent) {
1482
1510
  emitLocComment(node.loc, 'PlotLines', lines, indent);
1483
- const idx = plotCounter++;
1484
- const varName = `_plot_${idx}`;
1485
- const values = node.values.split(',').map(v => ensureFloatLiteral(v.trim()));
1486
- const count = values.length;
1487
- lines.push(`${indent}{`);
1488
- const innerIndent = indent + INDENT;
1489
- const styleVar = buildStyleVar(node.style, innerIndent, lines);
1490
- lines.push(`${innerIndent}float ${varName}[] = {${values.join(', ')}};`);
1491
1511
  const overlay = node.overlay ? `, ${node.overlay}` : ', nullptr';
1492
- const styleArg = styleVar ? `, ${styleVar}` : '';
1493
- lines.push(`${innerIndent}imx::renderer::plot_lines(${node.label}, ${varName}, ${count}${overlay}${styleArg});`);
1494
- lines.push(`${indent}}`);
1512
+ // Check if values is a variable/property access (struct binding) vs literal array
1513
+ if (node.values.includes('.') || /^[a-zA-Z_]\w*$/.test(node.values)) {
1514
+ lines.push(`${indent}{`);
1515
+ const innerIndent = indent + INDENT;
1516
+ const styleVar = buildStyleVar(node.style, innerIndent, lines);
1517
+ const styleArg = styleVar ? `, ${styleVar}` : '';
1518
+ lines.push(`${innerIndent}imx::renderer::plot_lines(${node.label}, ${node.values}.data(), static_cast<int>(${node.values}.size())${overlay}${styleArg});`);
1519
+ lines.push(`${indent}}`);
1520
+ }
1521
+ else {
1522
+ const idx = plotCounter++;
1523
+ const varName = `_plot_${idx}`;
1524
+ const values = node.values.split(',').map(v => ensureFloatLiteral(v.trim()));
1525
+ const count = values.length;
1526
+ lines.push(`${indent}{`);
1527
+ const innerIndent = indent + INDENT;
1528
+ const styleVar = buildStyleVar(node.style, innerIndent, lines);
1529
+ const styleArg = styleVar ? `, ${styleVar}` : '';
1530
+ lines.push(`${innerIndent}float ${varName}[] = {${values.join(', ')}};`);
1531
+ lines.push(`${innerIndent}imx::renderer::plot_lines(${node.label}, ${varName}, ${count}${overlay}${styleArg});`);
1532
+ lines.push(`${indent}}`);
1533
+ }
1495
1534
  }
1496
1535
  function emitPlotHistogram(node, lines, indent) {
1497
1536
  emitLocComment(node.loc, 'PlotHistogram', lines, indent);
1498
- const idx = plotCounter++;
1499
- const varName = `_plot_${idx}`;
1500
- const values = node.values.split(',').map(v => ensureFloatLiteral(v.trim()));
1501
- const count = values.length;
1502
- lines.push(`${indent}{`);
1503
- const innerIndent = indent + INDENT;
1504
- const styleVar = buildStyleVar(node.style, innerIndent, lines);
1505
- lines.push(`${innerIndent}float ${varName}[] = {${values.join(', ')}};`);
1506
1537
  const overlay = node.overlay ? `, ${node.overlay}` : ', nullptr';
1507
- const styleArg = styleVar ? `, ${styleVar}` : '';
1508
- lines.push(`${innerIndent}imx::renderer::plot_histogram(${node.label}, ${varName}, ${count}${overlay}${styleArg});`);
1509
- lines.push(`${indent}}`);
1538
+ if (node.values.includes('.') || /^[a-zA-Z_]\w*$/.test(node.values)) {
1539
+ lines.push(`${indent}{`);
1540
+ const innerIndent = indent + INDENT;
1541
+ const styleVar = buildStyleVar(node.style, innerIndent, lines);
1542
+ const styleArg = styleVar ? `, ${styleVar}` : '';
1543
+ lines.push(`${innerIndent}imx::renderer::plot_histogram(${node.label}, ${node.values}.data(), static_cast<int>(${node.values}.size())${overlay}${styleArg});`);
1544
+ lines.push(`${indent}}`);
1545
+ }
1546
+ else {
1547
+ const idx = plotCounter++;
1548
+ const varName = `_plot_${idx}`;
1549
+ const values = node.values.split(',').map(v => ensureFloatLiteral(v.trim()));
1550
+ const count = values.length;
1551
+ lines.push(`${indent}{`);
1552
+ const innerIndent = indent + INDENT;
1553
+ const styleVar = buildStyleVar(node.style, innerIndent, lines);
1554
+ const styleArg = styleVar ? `, ${styleVar}` : '';
1555
+ lines.push(`${innerIndent}float ${varName}[] = {${values.join(', ')}};`);
1556
+ lines.push(`${innerIndent}imx::renderer::plot_histogram(${node.label}, ${varName}, ${count}${overlay}${styleArg});`);
1557
+ lines.push(`${indent}}`);
1558
+ }
1510
1559
  }
1511
1560
  function emitImage(node, lines, indent) {
1512
1561
  emitLocComment(node.loc, 'Image', lines, indent);
package/dist/init.js CHANGED
@@ -100,8 +100,13 @@ int main() {
100
100
  app.state.onIncrement = [&]() { app.state.count++; };
101
101
 
102
102
  while (glfwWindowShouldClose(window) == 0) {
103
- glfwPollEvents();
103
+ if (app.runtime.needs_frame()) {
104
+ glfwPollEvents();
105
+ } else {
106
+ glfwWaitEventsTimeout(0.1);
107
+ }
104
108
  render_frame(app);
109
+ app.runtime.frame_rendered(ImGui::IsAnyItemActive());
105
110
  }
106
111
 
107
112
  ImGui_ImplOpenGL3_Shutdown();
package/dist/ir.d.ts CHANGED
@@ -99,6 +99,7 @@ export interface IRListMap {
99
99
  kind: 'list_map';
100
100
  array: string;
101
101
  itemVar: string;
102
+ indexVar: string;
102
103
  key: string;
103
104
  componentName: string;
104
105
  stateCount: number;
@@ -203,6 +204,8 @@ export interface IRColorEdit {
203
204
  kind: 'color_edit';
204
205
  label: string;
205
206
  stateVar: string;
207
+ valueExpr?: string;
208
+ onChangeExpr?: string;
206
209
  directBind?: boolean;
207
210
  style?: string;
208
211
  loc?: SourceLoc;
@@ -281,6 +284,8 @@ export interface IRColorPicker {
281
284
  kind: 'color_picker';
282
285
  label: string;
283
286
  stateVar: string;
287
+ valueExpr?: string;
288
+ onChangeExpr?: string;
284
289
  directBind?: boolean;
285
290
  style?: string;
286
291
  loc?: SourceLoc;
package/dist/lowering.js CHANGED
@@ -230,10 +230,14 @@ export function exprToCpp(node, ctx) {
230
230
  else if (op === '!==')
231
231
  op = '!=';
232
232
  // String + non-string: JS string concatenation -> C++ std::string + std::to_string
233
- if (op === '+' && (ts.isStringLiteral(node.left) || ts.isNoSubstitutionTemplateLiteral(node.left))) {
234
- const rightType = inferExprType(node.right, ctx);
235
- if (rightType === 'int' || rightType === 'float') {
236
- return `(std::string(${left}) + std::to_string(${right}))`;
233
+ if (op === '+') {
234
+ const leftIsString = ts.isStringLiteral(node.left) || ts.isNoSubstitutionTemplateLiteral(node.left) || inferExprType(node.left, ctx) === 'string';
235
+ const rightIsString = ts.isStringLiteral(node.right) || ts.isNoSubstitutionTemplateLiteral(node.right) || inferExprType(node.right, ctx) === 'string';
236
+ if (leftIsString && !rightIsString) {
237
+ return `(std::string(${left}) + std::to_string(${right})).c_str()`;
238
+ }
239
+ if (!leftIsString && rightIsString) {
240
+ return `(std::to_string(${left}) + std::string(${right})).c_str()`;
237
241
  }
238
242
  }
239
243
  return `${left} ${op} ${right}`;
@@ -677,8 +681,12 @@ function lowerCheckbox(attrs, rawAttrs, body, ctx, loc) {
677
681
  const onChangeRaw = rawAttrs.get('onChange');
678
682
  if (onChangeRaw) {
679
683
  onChangeExprStr = exprToCpp(onChangeRaw, ctx);
680
- // If it's not already a lambda/call, make it a call
681
- if (!onChangeExprStr.startsWith('[') && !onChangeExprStr.endsWith(')')) {
684
+ if (onChangeExprStr.startsWith('[')) {
685
+ // Lambda from arrow function — invoke it (IIFE)
686
+ onChangeExprStr = `(${onChangeExprStr})()`;
687
+ }
688
+ else if (!onChangeExprStr.endsWith(')')) {
689
+ // Plain identifier — make it a call
682
690
  onChangeExprStr = `${onChangeExprStr}()`;
683
691
  }
684
692
  }
@@ -835,11 +843,15 @@ function lowerListMap(node, body, ctx, loc) {
835
843
  const array = exprToCpp(propAccess.expression, ctx);
836
844
  const callback = node.arguments[0];
837
845
  let itemVar = 'item';
846
+ let indexVar = 'i';
838
847
  let mapBody = [];
839
848
  if (callback && (ts.isArrowFunction(callback) || ts.isFunctionExpression(callback))) {
840
849
  if (callback.parameters.length > 0 && ts.isIdentifier(callback.parameters[0].name)) {
841
850
  itemVar = callback.parameters[0].name.text;
842
851
  }
852
+ if (callback.parameters.length > 1 && ts.isIdentifier(callback.parameters[1].name)) {
853
+ indexVar = callback.parameters[1].name.text;
854
+ }
843
855
  if (ts.isBlock(callback.body)) {
844
856
  const ret = callback.body.statements.find(ts.isReturnStatement);
845
857
  if (ret?.expression) {
@@ -854,7 +866,8 @@ function lowerListMap(node, body, ctx, loc) {
854
866
  kind: 'list_map',
855
867
  array,
856
868
  itemVar,
857
- key: 'i',
869
+ indexVar,
870
+ key: indexVar,
858
871
  componentName: 'ListItem',
859
872
  stateCount: 0,
860
873
  bufferCount: 0,
@@ -1051,11 +1064,30 @@ function lowerColorPicker(attrs, rawAttrs, body, ctx, loc) {
1051
1064
  const label = attrs['label'] ?? '""';
1052
1065
  const style = attrs['style'];
1053
1066
  let stateVar = '';
1067
+ let valueExpr;
1068
+ let onChangeExpr;
1069
+ let directBind;
1054
1070
  const valueRaw = rawAttrs.get('value');
1055
1071
  if (valueRaw && ts.isIdentifier(valueRaw) && ctx.stateVars.has(valueRaw.text)) {
1056
1072
  stateVar = valueRaw.text;
1057
1073
  }
1058
- body.push({ kind: 'color_picker', label, stateVar, style, loc });
1074
+ else if (valueRaw) {
1075
+ valueExpr = exprToCpp(valueRaw, ctx);
1076
+ const onChangeRaw = rawAttrs.get('onChange');
1077
+ if (onChangeRaw) {
1078
+ onChangeExpr = exprToCpp(onChangeRaw, ctx);
1079
+ if (onChangeExpr.startsWith('[')) {
1080
+ onChangeExpr = `(${onChangeExpr})()`;
1081
+ }
1082
+ else if (!onChangeExpr.endsWith(')')) {
1083
+ onChangeExpr = `${onChangeExpr}()`;
1084
+ }
1085
+ }
1086
+ else if (valueRaw && ts.isPropertyAccessExpression(valueRaw)) {
1087
+ directBind = true;
1088
+ }
1089
+ }
1090
+ body.push({ kind: 'color_picker', label, stateVar, valueExpr, onChangeExpr, directBind, style, loc });
1059
1091
  }
1060
1092
  function lowerPlotLines(attrs, body, ctx, loc) {
1061
1093
  const label = attrs['label'] ?? '""';
@@ -1137,7 +1169,12 @@ function lowerValueOnChange(rawAttrs, ctx) {
1137
1169
  const onChangeRaw = rawAttrs.get('onChange');
1138
1170
  if (onChangeRaw) {
1139
1171
  onChangeExpr = exprToCpp(onChangeRaw, ctx);
1140
- if (!onChangeExpr.startsWith('[') && !onChangeExpr.endsWith(')')) {
1172
+ if (onChangeExpr.startsWith('[')) {
1173
+ // Lambda from arrow function — invoke it (IIFE)
1174
+ onChangeExpr = `(${onChangeExpr})()`;
1175
+ }
1176
+ else if (!onChangeExpr.endsWith(')')) {
1177
+ // Plain identifier — make it a call
1141
1178
  onChangeExpr = `${onChangeExpr}()`;
1142
1179
  }
1143
1180
  }
@@ -1201,13 +1238,31 @@ function lowerInputFloat(attrs, rawAttrs, body, ctx, loc) {
1201
1238
  function lowerColorEdit(attrs, rawAttrs, body, ctx, loc) {
1202
1239
  const label = attrs['label'] ?? '""';
1203
1240
  const style = attrs['style'];
1204
- // ColorEdit only supports state-bound values
1205
1241
  let stateVar = '';
1242
+ let valueExpr;
1243
+ let onChangeExpr;
1244
+ let directBind;
1206
1245
  const valueRaw = rawAttrs.get('value');
1207
1246
  if (valueRaw && ts.isIdentifier(valueRaw) && ctx.stateVars.has(valueRaw.text)) {
1208
1247
  stateVar = valueRaw.text;
1209
1248
  }
1210
- body.push({ kind: 'color_edit', label, stateVar, style, loc });
1249
+ else if (valueRaw) {
1250
+ valueExpr = exprToCpp(valueRaw, ctx);
1251
+ const onChangeRaw = rawAttrs.get('onChange');
1252
+ if (onChangeRaw) {
1253
+ onChangeExpr = exprToCpp(onChangeRaw, ctx);
1254
+ if (onChangeExpr.startsWith('[')) {
1255
+ onChangeExpr = `(${onChangeExpr})()`;
1256
+ }
1257
+ else if (!onChangeExpr.endsWith(')')) {
1258
+ onChangeExpr = `${onChangeExpr}()`;
1259
+ }
1260
+ }
1261
+ else if (valueRaw && ts.isPropertyAccessExpression(valueRaw)) {
1262
+ directBind = true;
1263
+ }
1264
+ }
1265
+ body.push({ kind: 'color_edit', label, stateVar, valueExpr, onChangeExpr, directBind, style, loc });
1211
1266
  }
1212
1267
  function lowerListBox(attrs, rawAttrs, body, ctx, loc) {
1213
1268
  const label = attrs['label'] ?? '""';
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "imxc",
3
- "version": "0.4.1",
3
+ "version": "0.5.1",
4
4
  "description": "Compiler for IMX — compiles React-like .tsx to native Dear ImGui C++",
5
5
  "type": "module",
6
6
  "bin": {