imxc 0.3.1 → 0.4.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
@@ -115,7 +115,7 @@ export function emitComponentHeader(comp, sourceFile) {
115
115
  lines.push('};');
116
116
  lines.push('');
117
117
  // Function forward declaration
118
- lines.push(`void ${comp.name}_render(imx::RenderContext& ctx, const ${comp.name}Props& props);`);
118
+ lines.push(`void ${comp.name}_render(imx::RenderContext& ctx, ${comp.name}Props& props);`);
119
119
  lines.push('');
120
120
  return lines.join('\n');
121
121
  }
@@ -132,24 +132,50 @@ export function emitComponent(comp, imports, sourceFile) {
132
132
  dragDropSourceStack.length = 0;
133
133
  dragDropTargetStack.length = 0;
134
134
  currentCompName = comp.name;
135
- const hasProps = comp.params.length > 0;
135
+ // hasProps: true for inline prop struct OR named interface
136
+ const hasProps = comp.params.length > 0 || !!comp.namedPropsType;
137
+ // propsTypeName: for named interface use it directly; for inline use ComponentProps convention
138
+ const propsTypeName = comp.namedPropsType ?? (comp.params.length > 0 ? `${comp.name}Props` : undefined);
136
139
  const hasColorType = comp.stateSlots.some(s => s.type === 'color');
137
140
  // File banner
138
141
  if (sourceFile) {
139
142
  lines.push(`// Generated from ${sourceFile} by imxc`);
140
143
  }
141
144
  if (hasProps) {
142
- // Component with props: include its own header instead of redeclaring struct
143
- lines.push(`#include "${comp.name}.gen.h"`);
144
- if (hasColorType) {
145
- lines.push('#include <array>');
145
+ if (comp.namedPropsType) {
146
+ // Named interface: include runtime + renderer, plus user header for the struct definition
147
+ lines.push('#include <imx/runtime.h>');
148
+ lines.push('#include <imx/renderer.h>');
149
+ lines.push(`#include "${comp.namedPropsType}.h"`);
150
+ if (hasColorType) {
151
+ lines.push('#include <array>');
152
+ }
153
+ // Include imported component headers
154
+ if (imports && imports.length > 0) {
155
+ for (const imp of imports) {
156
+ lines.push(`#include "${imp.headerFile}"`);
157
+ }
158
+ }
159
+ // Embed image includes
160
+ const embedKeysNamed = collectEmbedKeys(comp.body);
161
+ for (const key of embedKeysNamed) {
162
+ lines.push(`#include "${key}.embed.h"`);
163
+ }
164
+ lines.push('');
146
165
  }
147
- // Embed image includes
148
- const embedKeysProps = collectEmbedKeys(comp.body);
149
- for (const key of embedKeysProps) {
150
- lines.push(`#include "${key}.embed.h"`);
166
+ else {
167
+ // Component with inline props: include its own header instead of redeclaring struct
168
+ lines.push(`#include "${comp.name}.gen.h"`);
169
+ if (hasColorType) {
170
+ lines.push('#include <array>');
171
+ }
172
+ // Embed image includes
173
+ const embedKeysProps = collectEmbedKeys(comp.body);
174
+ for (const key of embedKeysProps) {
175
+ lines.push(`#include "${key}.embed.h"`);
176
+ }
177
+ lines.push('');
151
178
  }
152
- lines.push('');
153
179
  }
154
180
  else {
155
181
  // No props: standard headers
@@ -185,7 +211,7 @@ export function emitComponent(comp, imports, sourceFile) {
185
211
  emitDockSetupFunction(dockLayout, comp.name, lines);
186
212
  }
187
213
  // Function signature
188
- const propsArg = hasProps ? `, const ${comp.name}Props& props` : '';
214
+ const propsArg = propsTypeName ? `, ${propsTypeName}& props` : '';
189
215
  lines.push(`void ${comp.name}_render(imx::RenderContext& ctx${propsArg}) {`);
190
216
  // State declarations
191
217
  for (const slot of comp.stateSlots) {
@@ -205,24 +231,57 @@ export function emitComponent(comp, imports, sourceFile) {
205
231
  lines.push('');
206
232
  return lines.join('\n');
207
233
  }
208
- export function emitRoot(rootName, stateCount, bufferCount, sourceFile) {
234
+ export function emitRoot(rootName, stateCount, bufferCount, sourceFile, propsType, namedPropsType) {
209
235
  const lines = [];
210
236
  if (sourceFile) {
211
237
  lines.push(`// Generated from ${sourceFile} by imxc`);
212
238
  }
213
239
  lines.push('#include <imx/runtime.h>');
214
- lines.push('');
215
- lines.push(`void ${rootName}_render(imx::RenderContext& ctx);`);
216
- lines.push('');
217
- lines.push('namespace imx {');
218
- lines.push('void render_root(Runtime& runtime) {');
219
- lines.push(`${INDENT}auto& ctx = runtime.begin_frame();`);
220
- lines.push(`${INDENT}ctx.begin_instance("${rootName}", 0, ${stateCount}, ${bufferCount});`);
221
- lines.push(`${INDENT}${rootName}_render(ctx);`);
222
- lines.push(`${INDENT}ctx.end_instance();`);
223
- lines.push(`${INDENT}runtime.end_frame();`);
224
- lines.push('}');
225
- lines.push('} // namespace imx');
240
+ if (propsType) {
241
+ if (namedPropsType) {
242
+ // Named interface type (e.g. AppState defined in user code).
243
+ // Include the user header so the template specialization sees the full type.
244
+ lines.push(`#include "${propsType}.h"`);
245
+ lines.push('');
246
+ lines.push(`void ${rootName}_render(imx::RenderContext& ctx, ${propsType}& props);`);
247
+ }
248
+ else {
249
+ lines.push(`#include "${rootName}.gen.h"`);
250
+ lines.push('');
251
+ lines.push(`void ${rootName}_render(imx::RenderContext& ctx, ${propsType}& props);`);
252
+ }
253
+ lines.push('');
254
+ lines.push('namespace imx {');
255
+ if (namedPropsType) {
256
+ // Template specialization — matches the template<typename T> declaration in runtime.h
257
+ lines.push('template <>');
258
+ lines.push(`void render_root<${propsType}>(Runtime& runtime, ${propsType}& state) {`);
259
+ }
260
+ else {
261
+ lines.push(`void render_root(Runtime& runtime, ${propsType}& state) {`);
262
+ }
263
+ lines.push(`${INDENT}auto& ctx = runtime.begin_frame();`);
264
+ lines.push(`${INDENT}ctx.begin_instance("${rootName}", 0, ${stateCount}, ${bufferCount});`);
265
+ lines.push(`${INDENT}${rootName}_render(ctx, state);`);
266
+ lines.push(`${INDENT}ctx.end_instance();`);
267
+ lines.push(`${INDENT}runtime.end_frame();`);
268
+ lines.push('}');
269
+ lines.push('} // namespace imx');
270
+ }
271
+ else {
272
+ lines.push('');
273
+ lines.push(`void ${rootName}_render(imx::RenderContext& ctx);`);
274
+ lines.push('');
275
+ lines.push('namespace imx {');
276
+ lines.push('void render_root(Runtime& runtime) {');
277
+ lines.push(`${INDENT}auto& ctx = runtime.begin_frame();`);
278
+ lines.push(`${INDENT}ctx.begin_instance("${rootName}", 0, ${stateCount}, ${bufferCount});`);
279
+ lines.push(`${INDENT}${rootName}_render(ctx);`);
280
+ lines.push(`${INDENT}ctx.end_instance();`);
281
+ lines.push(`${INDENT}runtime.end_frame();`);
282
+ lines.push('}');
283
+ lines.push('} // namespace imx');
284
+ }
226
285
  lines.push('');
227
286
  return lines.join('\n');
228
287
  }
@@ -563,7 +622,11 @@ function emitBeginContainer(node, lines, indent) {
563
622
  }
564
623
  case 'DockSpace': {
565
624
  const style = buildStyleBlock(node, indent, lines);
566
- if (style) {
625
+ const hasMenuBar = node.props['hasMenuBar'] === 'true';
626
+ if (hasMenuBar) {
627
+ lines.push(`${indent}imx::renderer::begin_dockspace(${style ?? '{}'}, true);`);
628
+ }
629
+ else if (style) {
567
630
  lines.push(`${indent}imx::renderer::begin_dockspace(${style});`);
568
631
  }
569
632
  else {
@@ -585,8 +648,21 @@ function emitBeginContainer(node, lines, indent) {
585
648
  const columnNames = columnsRaw.split(',').map(s => s.trim()).filter(s => s.length > 0);
586
649
  const count = columnNames.length;
587
650
  const varName = `table_cols_${styleCounter++}`;
651
+ const style = buildStyleVar(node.style, indent, lines);
652
+ const scrollY = node.props['scrollY'] === 'true';
653
+ const noBorders = node.props['noBorders'] === 'true';
654
+ const noRowBg = node.props['noRowBg'] === 'true';
588
655
  lines.push(`${indent}const char* ${varName}[] = {${columnNames.join(', ')}};`);
589
- lines.push(`${indent}if (imx::renderer::begin_table("##table", ${count}, ${varName})) {`);
656
+ const styleArg = style ?? '{}';
657
+ if (scrollY || noBorders || noRowBg) {
658
+ lines.push(`${indent}if (imx::renderer::begin_table("##table", ${count}, ${varName}, ${styleArg}, ${scrollY}, ${noBorders}, ${noRowBg})) {`);
659
+ }
660
+ else if (style) {
661
+ lines.push(`${indent}if (imx::renderer::begin_table("##table", ${count}, ${varName}, ${styleArg})) {`);
662
+ }
663
+ else {
664
+ lines.push(`${indent}if (imx::renderer::begin_table("##table", ${count}, ${varName})) {`);
665
+ }
590
666
  break;
591
667
  }
592
668
  case 'TableRow': {
@@ -918,11 +994,12 @@ function emitText(node, lines, indent) {
918
994
  function emitButton(node, lines, indent, depth) {
919
995
  emitLocComment(node.loc, 'Button', lines, indent);
920
996
  const title = asCharPtr(node.title);
997
+ const disabledArg = node.disabled ? ', {}, true' : '';
921
998
  if (node.action.length === 0) {
922
- lines.push(`${indent}imx::renderer::button(${title});`);
999
+ lines.push(`${indent}imx::renderer::button(${title}${disabledArg});`);
923
1000
  }
924
1001
  else {
925
- lines.push(`${indent}if (imx::renderer::button(${title})) {`);
1002
+ lines.push(`${indent}if (imx::renderer::button(${title}${disabledArg})) {`);
926
1003
  for (const stmt of node.action) {
927
1004
  lines.push(`${indent}${INDENT}${stmt}`);
928
1005
  }
@@ -984,6 +1061,10 @@ function emitCheckbox(node, lines, indent) {
984
1061
  lines.push(`${indent}${INDENT}}`);
985
1062
  lines.push(`${indent}}`);
986
1063
  }
1064
+ else if (node.directBind && node.valueExpr) {
1065
+ // Direct pointer binding — no temp variable
1066
+ lines.push(`${indent}imx::renderer::checkbox(${label}, &${node.valueExpr});`);
1067
+ }
987
1068
  else if (node.valueExpr !== undefined) {
988
1069
  // Props-bound / expression-bound case
989
1070
  lines.push(`${indent}{`);
@@ -1017,7 +1098,7 @@ function emitListMap(node, lines, indent, depth) {
1017
1098
  }
1018
1099
  lines.push(`${indent}for (size_t i = 0; i < ${node.array}.size(); i++) {`);
1019
1100
  lines.push(`${indent}${INDENT}auto& ${node.itemVar} = ${node.array}[i];`);
1020
- lines.push(`${indent}${INDENT}ctx.begin_instance("${node.componentName}", i, ${node.stateCount}, ${node.bufferCount});`);
1101
+ lines.push(`${indent}${INDENT}ctx.begin_instance("${node.componentName}", (int)i, ${node.stateCount}, ${node.bufferCount});`);
1021
1102
  emitNodes(node.body, lines, depth + 2);
1022
1103
  lines.push(`${indent}${INDENT}ctx.end_instance();`);
1023
1104
  lines.push(`${indent}}`);
@@ -1068,20 +1149,25 @@ function ensureFloatLiteral(val) {
1068
1149
  }
1069
1150
  function emitSliderFloat(node, lines, indent) {
1070
1151
  emitLocComment(node.loc, 'SliderFloat', lines, indent);
1152
+ const label = asCharPtr(node.label);
1071
1153
  const min = ensureFloatLiteral(node.min);
1072
1154
  const max = ensureFloatLiteral(node.max);
1073
1155
  if (node.stateVar) {
1074
1156
  lines.push(`${indent}{`);
1075
1157
  lines.push(`${indent}${INDENT}float val = ${node.stateVar}.get();`);
1076
- lines.push(`${indent}${INDENT}if (imx::renderer::slider_float(${node.label}, &val, ${min}, ${max})) {`);
1158
+ lines.push(`${indent}${INDENT}if (imx::renderer::slider_float(${label}, &val, ${min}, ${max})) {`);
1077
1159
  lines.push(`${indent}${INDENT}${INDENT}${node.stateVar}.set(val);`);
1078
1160
  lines.push(`${indent}${INDENT}}`);
1079
1161
  lines.push(`${indent}}`);
1080
1162
  }
1163
+ else if (node.directBind && node.valueExpr) {
1164
+ // Direct pointer binding
1165
+ lines.push(`${indent}imx::renderer::slider_float(${label}, &${node.valueExpr}, ${min}, ${max});`);
1166
+ }
1081
1167
  else if (node.valueExpr !== undefined) {
1082
1168
  lines.push(`${indent}{`);
1083
1169
  lines.push(`${indent}${INDENT}float val = ${node.valueExpr};`);
1084
- lines.push(`${indent}${INDENT}if (imx::renderer::slider_float(${node.label}, &val, ${min}, ${max})) {`);
1170
+ lines.push(`${indent}${INDENT}if (imx::renderer::slider_float(${label}, &val, ${min}, ${max})) {`);
1085
1171
  if (node.onChangeExpr) {
1086
1172
  lines.push(`${indent}${INDENT}${INDENT}${node.onChangeExpr};`);
1087
1173
  }
@@ -1091,18 +1177,22 @@ function emitSliderFloat(node, lines, indent) {
1091
1177
  }
1092
1178
  function emitSliderInt(node, lines, indent) {
1093
1179
  emitLocComment(node.loc, 'SliderInt', lines, indent);
1180
+ const label = asCharPtr(node.label);
1094
1181
  if (node.stateVar) {
1095
1182
  lines.push(`${indent}{`);
1096
1183
  lines.push(`${indent}${INDENT}int val = ${node.stateVar}.get();`);
1097
- lines.push(`${indent}${INDENT}if (imx::renderer::slider_int(${node.label}, &val, ${node.min}, ${node.max})) {`);
1184
+ lines.push(`${indent}${INDENT}if (imx::renderer::slider_int(${label}, &val, ${node.min}, ${node.max})) {`);
1098
1185
  lines.push(`${indent}${INDENT}${INDENT}${node.stateVar}.set(val);`);
1099
1186
  lines.push(`${indent}${INDENT}}`);
1100
1187
  lines.push(`${indent}}`);
1101
1188
  }
1189
+ else if (node.directBind && node.valueExpr) {
1190
+ lines.push(`${indent}imx::renderer::slider_int(${label}, &${node.valueExpr}, ${node.min}, ${node.max});`);
1191
+ }
1102
1192
  else if (node.valueExpr !== undefined) {
1103
1193
  lines.push(`${indent}{`);
1104
1194
  lines.push(`${indent}${INDENT}int val = ${node.valueExpr};`);
1105
- lines.push(`${indent}${INDENT}if (imx::renderer::slider_int(${node.label}, &val, ${node.min}, ${node.max})) {`);
1195
+ lines.push(`${indent}${INDENT}if (imx::renderer::slider_int(${label}, &val, ${node.min}, ${node.max})) {`);
1106
1196
  if (node.onChangeExpr) {
1107
1197
  lines.push(`${indent}${INDENT}${INDENT}${node.onChangeExpr};`);
1108
1198
  }
@@ -1112,19 +1202,23 @@ function emitSliderInt(node, lines, indent) {
1112
1202
  }
1113
1203
  function emitDragFloat(node, lines, indent) {
1114
1204
  emitLocComment(node.loc, 'DragFloat', lines, indent);
1205
+ const label = asCharPtr(node.label);
1115
1206
  const speed = ensureFloatLiteral(node.speed);
1116
1207
  if (node.stateVar) {
1117
1208
  lines.push(`${indent}{`);
1118
1209
  lines.push(`${indent}${INDENT}float val = ${node.stateVar}.get();`);
1119
- lines.push(`${indent}${INDENT}if (imx::renderer::drag_float(${node.label}, &val, ${speed})) {`);
1210
+ lines.push(`${indent}${INDENT}if (imx::renderer::drag_float(${label}, &val, ${speed})) {`);
1120
1211
  lines.push(`${indent}${INDENT}${INDENT}${node.stateVar}.set(val);`);
1121
1212
  lines.push(`${indent}${INDENT}}`);
1122
1213
  lines.push(`${indent}}`);
1123
1214
  }
1215
+ else if (node.directBind && node.valueExpr) {
1216
+ lines.push(`${indent}imx::renderer::drag_float(${label}, &${node.valueExpr}, ${speed});`);
1217
+ }
1124
1218
  else if (node.valueExpr !== undefined) {
1125
1219
  lines.push(`${indent}{`);
1126
1220
  lines.push(`${indent}${INDENT}float val = ${node.valueExpr};`);
1127
- lines.push(`${indent}${INDENT}if (imx::renderer::drag_float(${node.label}, &val, ${speed})) {`);
1221
+ lines.push(`${indent}${INDENT}if (imx::renderer::drag_float(${label}, &val, ${speed})) {`);
1128
1222
  if (node.onChangeExpr) {
1129
1223
  lines.push(`${indent}${INDENT}${INDENT}${node.onChangeExpr};`);
1130
1224
  }
@@ -1134,19 +1228,23 @@ function emitDragFloat(node, lines, indent) {
1134
1228
  }
1135
1229
  function emitDragInt(node, lines, indent) {
1136
1230
  emitLocComment(node.loc, 'DragInt', lines, indent);
1231
+ const label = asCharPtr(node.label);
1137
1232
  const speed = ensureFloatLiteral(node.speed);
1138
1233
  if (node.stateVar) {
1139
1234
  lines.push(`${indent}{`);
1140
1235
  lines.push(`${indent}${INDENT}int val = ${node.stateVar}.get();`);
1141
- lines.push(`${indent}${INDENT}if (imx::renderer::drag_int(${node.label}, &val, ${speed})) {`);
1236
+ lines.push(`${indent}${INDENT}if (imx::renderer::drag_int(${label}, &val, ${speed})) {`);
1142
1237
  lines.push(`${indent}${INDENT}${INDENT}${node.stateVar}.set(val);`);
1143
1238
  lines.push(`${indent}${INDENT}}`);
1144
1239
  lines.push(`${indent}}`);
1145
1240
  }
1241
+ else if (node.directBind && node.valueExpr) {
1242
+ lines.push(`${indent}imx::renderer::drag_int(${label}, &${node.valueExpr}, ${speed});`);
1243
+ }
1146
1244
  else if (node.valueExpr !== undefined) {
1147
1245
  lines.push(`${indent}{`);
1148
1246
  lines.push(`${indent}${INDENT}int val = ${node.valueExpr};`);
1149
- lines.push(`${indent}${INDENT}if (imx::renderer::drag_int(${node.label}, &val, ${speed})) {`);
1247
+ lines.push(`${indent}${INDENT}if (imx::renderer::drag_int(${label}, &val, ${speed})) {`);
1150
1248
  if (node.onChangeExpr) {
1151
1249
  lines.push(`${indent}${INDENT}${INDENT}${node.onChangeExpr};`);
1152
1250
  }
@@ -1156,6 +1254,7 @@ function emitDragInt(node, lines, indent) {
1156
1254
  }
1157
1255
  function emitCombo(node, lines, indent) {
1158
1256
  emitLocComment(node.loc, 'Combo', lines, indent);
1257
+ const label = asCharPtr(node.label);
1159
1258
  const itemsList = node.items.split(',').map(s => s.trim()).filter(s => s.length > 0);
1160
1259
  const count = itemsList.length;
1161
1260
  const varName = `combo_items_${comboCounter++}`;
@@ -1163,16 +1262,22 @@ function emitCombo(node, lines, indent) {
1163
1262
  lines.push(`${indent}{`);
1164
1263
  lines.push(`${indent}${INDENT}const char* ${varName}[] = {${itemsList.join(', ')}};`);
1165
1264
  lines.push(`${indent}${INDENT}int val = ${node.stateVar}.get();`);
1166
- lines.push(`${indent}${INDENT}if (imx::renderer::combo(${node.label}, &val, ${varName}, ${count})) {`);
1265
+ lines.push(`${indent}${INDENT}if (imx::renderer::combo(${label}, &val, ${varName}, ${count})) {`);
1167
1266
  lines.push(`${indent}${INDENT}${INDENT}${node.stateVar}.set(val);`);
1168
1267
  lines.push(`${indent}${INDENT}}`);
1169
1268
  lines.push(`${indent}}`);
1170
1269
  }
1270
+ else if (node.directBind && node.valueExpr) {
1271
+ lines.push(`${indent}{`);
1272
+ lines.push(`${indent}${INDENT}const char* ${varName}[] = {${itemsList.join(', ')}};`);
1273
+ lines.push(`${indent}${INDENT}imx::renderer::combo(${label}, &${node.valueExpr}, ${varName}, ${count});`);
1274
+ lines.push(`${indent}}`);
1275
+ }
1171
1276
  else if (node.valueExpr !== undefined) {
1172
1277
  lines.push(`${indent}{`);
1173
1278
  lines.push(`${indent}${INDENT}const char* ${varName}[] = {${itemsList.join(', ')}};`);
1174
1279
  lines.push(`${indent}${INDENT}int val = ${node.valueExpr};`);
1175
- lines.push(`${indent}${INDENT}if (imx::renderer::combo(${node.label}, &val, ${varName}, ${count})) {`);
1280
+ lines.push(`${indent}${INDENT}if (imx::renderer::combo(${label}, &val, ${varName}, ${count})) {`);
1176
1281
  if (node.onChangeExpr) {
1177
1282
  lines.push(`${indent}${INDENT}${INDENT}${node.onChangeExpr};`);
1178
1283
  }
@@ -1182,18 +1287,22 @@ function emitCombo(node, lines, indent) {
1182
1287
  }
1183
1288
  function emitInputInt(node, lines, indent) {
1184
1289
  emitLocComment(node.loc, 'InputInt', lines, indent);
1290
+ const label = asCharPtr(node.label);
1185
1291
  if (node.stateVar) {
1186
1292
  lines.push(`${indent}{`);
1187
1293
  lines.push(`${indent}${INDENT}int val = ${node.stateVar}.get();`);
1188
- lines.push(`${indent}${INDENT}if (imx::renderer::input_int(${node.label}, &val)) {`);
1294
+ lines.push(`${indent}${INDENT}if (imx::renderer::input_int(${label}, &val)) {`);
1189
1295
  lines.push(`${indent}${INDENT}${INDENT}${node.stateVar}.set(val);`);
1190
1296
  lines.push(`${indent}${INDENT}}`);
1191
1297
  lines.push(`${indent}}`);
1192
1298
  }
1299
+ else if (node.directBind && node.valueExpr) {
1300
+ lines.push(`${indent}imx::renderer::input_int(${label}, &${node.valueExpr});`);
1301
+ }
1193
1302
  else if (node.valueExpr !== undefined) {
1194
1303
  lines.push(`${indent}{`);
1195
1304
  lines.push(`${indent}${INDENT}int val = ${node.valueExpr};`);
1196
- lines.push(`${indent}${INDENT}if (imx::renderer::input_int(${node.label}, &val)) {`);
1305
+ lines.push(`${indent}${INDENT}if (imx::renderer::input_int(${label}, &val)) {`);
1197
1306
  if (node.onChangeExpr) {
1198
1307
  lines.push(`${indent}${INDENT}${INDENT}${node.onChangeExpr};`);
1199
1308
  }
@@ -1203,18 +1312,22 @@ function emitInputInt(node, lines, indent) {
1203
1312
  }
1204
1313
  function emitInputFloat(node, lines, indent) {
1205
1314
  emitLocComment(node.loc, 'InputFloat', lines, indent);
1315
+ const label = asCharPtr(node.label);
1206
1316
  if (node.stateVar) {
1207
1317
  lines.push(`${indent}{`);
1208
1318
  lines.push(`${indent}${INDENT}float val = ${node.stateVar}.get();`);
1209
- lines.push(`${indent}${INDENT}if (imx::renderer::input_float(${node.label}, &val)) {`);
1319
+ lines.push(`${indent}${INDENT}if (imx::renderer::input_float(${label}, &val)) {`);
1210
1320
  lines.push(`${indent}${INDENT}${INDENT}${node.stateVar}.set(val);`);
1211
1321
  lines.push(`${indent}${INDENT}}`);
1212
1322
  lines.push(`${indent}}`);
1213
1323
  }
1324
+ else if (node.directBind && node.valueExpr) {
1325
+ lines.push(`${indent}imx::renderer::input_float(${label}, &${node.valueExpr});`);
1326
+ }
1214
1327
  else if (node.valueExpr !== undefined) {
1215
1328
  lines.push(`${indent}{`);
1216
1329
  lines.push(`${indent}${INDENT}float val = ${node.valueExpr};`);
1217
- lines.push(`${indent}${INDENT}if (imx::renderer::input_float(${node.label}, &val)) {`);
1330
+ lines.push(`${indent}${INDENT}if (imx::renderer::input_float(${label}, &val)) {`);
1218
1331
  if (node.onChangeExpr) {
1219
1332
  lines.push(`${indent}${INDENT}${INDENT}${node.onChangeExpr};`);
1220
1333
  }
@@ -1224,10 +1337,11 @@ function emitInputFloat(node, lines, indent) {
1224
1337
  }
1225
1338
  function emitColorEdit(node, lines, indent) {
1226
1339
  emitLocComment(node.loc, 'ColorEdit', lines, indent);
1340
+ const label = asCharPtr(node.label);
1227
1341
  if (node.stateVar) {
1228
1342
  lines.push(`${indent}{`);
1229
1343
  lines.push(`${indent}${INDENT}auto val = ${node.stateVar}.get();`);
1230
- lines.push(`${indent}${INDENT}if (imx::renderer::color_edit(${node.label}, val.data())) {`);
1344
+ lines.push(`${indent}${INDENT}if (imx::renderer::color_edit(${label}, val.data())) {`);
1231
1345
  lines.push(`${indent}${INDENT}${INDENT}${node.stateVar}.set(val);`);
1232
1346
  lines.push(`${indent}${INDENT}}`);
1233
1347
  lines.push(`${indent}}`);
@@ -1235,6 +1349,7 @@ function emitColorEdit(node, lines, indent) {
1235
1349
  }
1236
1350
  function emitListBox(node, lines, indent) {
1237
1351
  emitLocComment(node.loc, 'ListBox', lines, indent);
1352
+ const label = asCharPtr(node.label);
1238
1353
  const itemsList = node.items.split(',').map(s => s.trim()).filter(s => s.length > 0);
1239
1354
  const count = itemsList.length;
1240
1355
  const varName = `listbox_items_${listBoxCounter++}`;
@@ -1242,16 +1357,22 @@ function emitListBox(node, lines, indent) {
1242
1357
  lines.push(`${indent}{`);
1243
1358
  lines.push(`${indent}${INDENT}const char* ${varName}[] = {${itemsList.join(', ')}};`);
1244
1359
  lines.push(`${indent}${INDENT}int val = ${node.stateVar}.get();`);
1245
- lines.push(`${indent}${INDENT}if (imx::renderer::list_box(${node.label}, &val, ${varName}, ${count})) {`);
1360
+ lines.push(`${indent}${INDENT}if (imx::renderer::list_box(${label}, &val, ${varName}, ${count})) {`);
1246
1361
  lines.push(`${indent}${INDENT}${INDENT}${node.stateVar}.set(val);`);
1247
1362
  lines.push(`${indent}${INDENT}}`);
1248
1363
  lines.push(`${indent}}`);
1249
1364
  }
1365
+ else if (node.directBind && node.valueExpr) {
1366
+ lines.push(`${indent}{`);
1367
+ lines.push(`${indent}${INDENT}const char* ${varName}[] = {${itemsList.join(', ')}};`);
1368
+ lines.push(`${indent}${INDENT}imx::renderer::list_box(${label}, &${node.valueExpr}, ${varName}, ${count});`);
1369
+ lines.push(`${indent}}`);
1370
+ }
1250
1371
  else if (node.valueExpr !== undefined) {
1251
1372
  lines.push(`${indent}{`);
1252
1373
  lines.push(`${indent}${INDENT}const char* ${varName}[] = {${itemsList.join(', ')}};`);
1253
1374
  lines.push(`${indent}${INDENT}int val = ${node.valueExpr};`);
1254
- lines.push(`${indent}${INDENT}if (imx::renderer::list_box(${node.label}, &val, ${varName}, ${count})) {`);
1375
+ lines.push(`${indent}${INDENT}if (imx::renderer::list_box(${label}, &val, ${varName}, ${count})) {`);
1255
1376
  if (node.onChangeExpr) {
1256
1377
  lines.push(`${indent}${INDENT}${INDENT}${node.onChangeExpr};`);
1257
1378
  }
@@ -1315,6 +1436,9 @@ function emitRadio(node, lines, indent) {
1315
1436
  lines.push(`${indent}${INDENT}}`);
1316
1437
  lines.push(`${indent}}`);
1317
1438
  }
1439
+ else if (node.directBind && node.valueExpr) {
1440
+ lines.push(`${indent}imx::renderer::radio(${label}, &${node.valueExpr}, ${node.index});`);
1441
+ }
1318
1442
  else if (node.valueExpr !== undefined) {
1319
1443
  lines.push(`${indent}{`);
1320
1444
  lines.push(`${indent}${INDENT}int val = ${node.valueExpr};`);