imxc 0.2.0 → 0.3.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
@@ -1,4 +1,10 @@
1
1
  const INDENT = ' ';
2
+ let currentCompName = '';
3
+ function emitLocComment(loc, tag, lines, indent) {
4
+ if (loc) {
5
+ lines.push(`${indent}// ${loc.file}:${loc.line} <${tag}>`);
6
+ }
7
+ }
2
8
  function cppType(t) {
3
9
  switch (t) {
4
10
  case 'int': return 'int';
@@ -28,15 +34,76 @@ function asCharPtr(expr) {
28
34
  // Expression — assume std::string, add .c_str()
29
35
  return `${expr}.c_str()`;
30
36
  }
37
+ function emitImVec4(arrayStr) {
38
+ const parts = arrayStr.split(',').map(s => {
39
+ const v = s.trim();
40
+ return v.includes('.') ? `${v}f` : `${v}.0f`;
41
+ });
42
+ return `ImVec4(${parts.join(', ')})`;
43
+ }
44
+ function emitImVec2(arrayStr) {
45
+ const parts = arrayStr.split(',').map(s => {
46
+ const v = s.trim();
47
+ return v.includes('.') ? `${v}f` : `${v}.0f`;
48
+ });
49
+ return `ImVec2(${parts.join(', ')})`;
50
+ }
51
+ function emitFloat(val) {
52
+ return val.includes('.') ? `${val}F` : `${val}.0F`;
53
+ }
54
+ function findDockLayout(nodes) {
55
+ for (const node of nodes) {
56
+ if (node.kind === 'dock_layout')
57
+ return node;
58
+ }
59
+ return null;
60
+ }
61
+ function emitDockSetupFunction(layout, compName, lines) {
62
+ lines.push(`void ${compName}_setup_dock_layout(ImGuiID dockspace_id) {`);
63
+ lines.push(`${INDENT}ImGui::DockBuilderRemoveNode(dockspace_id);`);
64
+ lines.push(`${INDENT}ImGui::DockBuilderAddNode(dockspace_id, ImGuiDockNodeFlags_None);`);
65
+ lines.push(`${INDENT}ImGui::DockBuilderSetNodeSize(dockspace_id, ImGui::GetMainViewport()->WorkSize);`);
66
+ lines.push('');
67
+ let counter = 0;
68
+ function emitDockNode(node, parentVar) {
69
+ if (node.kind === 'dock_panel') {
70
+ for (const title of node.windows) {
71
+ lines.push(`${INDENT}ImGui::DockBuilderDockWindow(${title}, ${parentVar});`);
72
+ }
73
+ }
74
+ else {
75
+ const dirRaw = node.direction.replace(/"/g, '');
76
+ const dir = dirRaw === 'horizontal' ? 'ImGuiDir_Left' : 'ImGuiDir_Up';
77
+ const sizeF = emitFloat(node.size);
78
+ const firstVar = `dock_${counter++}`;
79
+ const secondVar = `dock_${counter++}`;
80
+ lines.push(`${INDENT}ImGuiID ${firstVar}, ${secondVar};`);
81
+ lines.push(`${INDENT}ImGui::DockBuilderSplitNode(${parentVar}, ${dir}, ${sizeF}, &${firstVar}, &${secondVar});`);
82
+ if (node.children.length >= 1)
83
+ emitDockNode(node.children[0], firstVar);
84
+ if (node.children.length >= 2)
85
+ emitDockNode(node.children[1], secondVar);
86
+ }
87
+ }
88
+ for (const child of layout.children) {
89
+ emitDockNode(child, 'dockspace_id');
90
+ }
91
+ lines.push(`${INDENT}ImGui::DockBuilderFinish(dockspace_id);`);
92
+ lines.push('}');
93
+ lines.push('');
94
+ }
31
95
  /**
32
96
  * Emit a .gen.h header for a component that has props.
33
97
  * Contains the props struct and function forward declaration.
34
98
  */
35
- export function emitComponentHeader(comp) {
99
+ export function emitComponentHeader(comp, sourceFile) {
36
100
  const lines = [];
101
+ if (sourceFile) {
102
+ lines.push(`// Generated from ${sourceFile} by imxc`);
103
+ }
37
104
  lines.push('#pragma once');
38
- lines.push('#include <reimgui/runtime.h>');
39
- lines.push('#include <reimgui/renderer.h>');
105
+ lines.push('#include <imx/runtime.h>');
106
+ lines.push('#include <imx/renderer.h>');
40
107
  lines.push('#include <functional>');
41
108
  lines.push('#include <string>');
42
109
  lines.push('');
@@ -48,11 +115,11 @@ export function emitComponentHeader(comp) {
48
115
  lines.push('};');
49
116
  lines.push('');
50
117
  // Function forward declaration
51
- lines.push(`void ${comp.name}_render(reimgui::RenderContext& ctx, const ${comp.name}Props& props);`);
118
+ lines.push(`void ${comp.name}_render(imx::RenderContext& ctx, const ${comp.name}Props& props);`);
52
119
  lines.push('');
53
120
  return lines.join('\n');
54
121
  }
55
- export function emitComponent(comp, imports) {
122
+ export function emitComponent(comp, imports, sourceFile) {
56
123
  const lines = [];
57
124
  // Reset counters for each component
58
125
  styleCounter = 0;
@@ -60,20 +127,34 @@ export function emitComponent(comp, imports) {
60
127
  checkboxCounter = 0;
61
128
  comboCounter = 0;
62
129
  listBoxCounter = 0;
130
+ nativeWidgetCounter = 0;
131
+ plotCounter = 0;
132
+ dragDropSourceStack.length = 0;
133
+ dragDropTargetStack.length = 0;
134
+ currentCompName = comp.name;
63
135
  const hasProps = comp.params.length > 0;
64
136
  const hasColorType = comp.stateSlots.some(s => s.type === 'color');
137
+ // File banner
138
+ if (sourceFile) {
139
+ lines.push(`// Generated from ${sourceFile} by imxc`);
140
+ }
65
141
  if (hasProps) {
66
142
  // Component with props: include its own header instead of redeclaring struct
67
143
  lines.push(`#include "${comp.name}.gen.h"`);
68
144
  if (hasColorType) {
69
145
  lines.push('#include <array>');
70
146
  }
147
+ // Embed image includes
148
+ const embedKeysProps = collectEmbedKeys(comp.body);
149
+ for (const key of embedKeysProps) {
150
+ lines.push(`#include "${key}.embed.h"`);
151
+ }
71
152
  lines.push('');
72
153
  }
73
154
  else {
74
155
  // No props: standard headers
75
- lines.push('#include <reimgui/runtime.h>');
76
- lines.push('#include <reimgui/renderer.h>');
156
+ lines.push('#include <imx/runtime.h>');
157
+ lines.push('#include <imx/renderer.h>');
77
158
  if (hasColorType) {
78
159
  lines.push('#include <array>');
79
160
  }
@@ -83,11 +164,29 @@ export function emitComponent(comp, imports) {
83
164
  lines.push(`#include "${imp.headerFile}"`);
84
165
  }
85
166
  }
167
+ // Embed image includes
168
+ const embedKeys = collectEmbedKeys(comp.body);
169
+ for (const key of embedKeys) {
170
+ lines.push(`#include "${key}.embed.h"`);
171
+ }
172
+ lines.push('');
173
+ }
174
+ const dockLayout = findDockLayout(comp.body);
175
+ if (dockLayout) {
176
+ lines.push('#include <imgui_internal.h>');
177
+ lines.push('');
178
+ lines.push('static bool g_layout_applied = false;');
179
+ lines.push('static bool g_reset_layout = false;');
180
+ lines.push('');
181
+ lines.push('void imx_reset_layout() {');
182
+ lines.push(`${INDENT}g_reset_layout = true;`);
183
+ lines.push('}');
86
184
  lines.push('');
185
+ emitDockSetupFunction(dockLayout, comp.name, lines);
87
186
  }
88
187
  // Function signature
89
188
  const propsArg = hasProps ? `, const ${comp.name}Props& props` : '';
90
- lines.push(`void ${comp.name}_render(reimgui::RenderContext& ctx${propsArg}) {`);
189
+ lines.push(`void ${comp.name}_render(imx::RenderContext& ctx${propsArg}) {`);
91
190
  // State declarations
92
191
  for (const slot of comp.stateSlots) {
93
192
  const initVal = slot.type === 'string'
@@ -106,13 +205,16 @@ export function emitComponent(comp, imports) {
106
205
  lines.push('');
107
206
  return lines.join('\n');
108
207
  }
109
- export function emitRoot(rootName, stateCount, bufferCount) {
208
+ export function emitRoot(rootName, stateCount, bufferCount, sourceFile) {
110
209
  const lines = [];
111
- lines.push('#include <reimgui/runtime.h>');
210
+ if (sourceFile) {
211
+ lines.push(`// Generated from ${sourceFile} by imxc`);
212
+ }
213
+ lines.push('#include <imx/runtime.h>');
112
214
  lines.push('');
113
- lines.push(`void ${rootName}_render(reimgui::RenderContext& ctx);`);
215
+ lines.push(`void ${rootName}_render(imx::RenderContext& ctx);`);
114
216
  lines.push('');
115
- lines.push('namespace reimgui {');
217
+ lines.push('namespace imx {');
116
218
  lines.push('void render_root(Runtime& runtime) {');
117
219
  lines.push(`${INDENT}auto& ctx = runtime.begin_frame();`);
118
220
  lines.push(`${INDENT}ctx.begin_instance("${rootName}", 0, ${stateCount}, ${bufferCount});`);
@@ -120,7 +222,7 @@ export function emitRoot(rootName, stateCount, bufferCount) {
120
222
  lines.push(`${INDENT}ctx.end_instance();`);
121
223
  lines.push(`${INDENT}runtime.end_frame();`);
122
224
  lines.push('}');
123
- lines.push('} // namespace reimgui');
225
+ lines.push('} // namespace imx');
124
226
  lines.push('');
125
227
  return lines.join('\n');
126
228
  }
@@ -151,17 +253,19 @@ function emitNode(node, lines, depth) {
151
253
  emitCheckbox(node, lines, indent);
152
254
  break;
153
255
  case 'separator':
154
- lines.push(`${indent}reimgui::renderer::separator();`);
256
+ lines.push(`${indent}imx::renderer::separator();`);
155
257
  break;
156
258
  case 'begin_popup':
157
- lines.push(`${indent}if (reimgui::renderer::begin_popup(${node.id})) {`);
259
+ emitLocComment(node.loc, 'Popup', lines, indent);
260
+ lines.push(`${indent}if (imx::renderer::begin_popup(${node.id})) {`);
158
261
  break;
159
262
  case 'end_popup':
160
- lines.push(`${indent}reimgui::renderer::end_popup();`);
263
+ lines.push(`${indent}imx::renderer::end_popup();`);
161
264
  lines.push(`${indent}}`);
162
265
  break;
163
266
  case 'open_popup':
164
- lines.push(`${indent}reimgui::renderer::open_popup(${node.id});`);
267
+ emitLocComment(node.loc, 'OpenPopup', lines, indent);
268
+ lines.push(`${indent}imx::renderer::open_popup(${node.id});`);
165
269
  break;
166
270
  case 'conditional':
167
271
  emitConditional(node, lines, indent, depth);
@@ -208,6 +312,64 @@ function emitNode(node, lines, depth) {
208
312
  case 'tooltip':
209
313
  emitTooltip(node, lines, indent);
210
314
  break;
315
+ case 'bullet_text':
316
+ emitBulletText(node, lines, indent);
317
+ break;
318
+ case 'label_text':
319
+ emitLabelText(node, lines, indent);
320
+ break;
321
+ case 'selectable':
322
+ emitSelectable(node, lines, indent);
323
+ break;
324
+ case 'radio':
325
+ emitRadio(node, lines, indent);
326
+ break;
327
+ case 'input_text_multiline':
328
+ emitInputTextMultiline(node, lines, indent);
329
+ break;
330
+ case 'color_picker':
331
+ emitColorPicker(node, lines, indent);
332
+ break;
333
+ case 'plot_lines':
334
+ emitPlotLines(node, lines, indent);
335
+ break;
336
+ case 'plot_histogram':
337
+ emitPlotHistogram(node, lines, indent);
338
+ break;
339
+ case 'image':
340
+ emitImage(node, lines, indent);
341
+ break;
342
+ case 'draw_line':
343
+ emitDrawLine(node, lines, indent);
344
+ break;
345
+ case 'draw_rect':
346
+ emitDrawRect(node, lines, indent);
347
+ break;
348
+ case 'draw_circle':
349
+ emitDrawCircle(node, lines, indent);
350
+ break;
351
+ case 'draw_text':
352
+ emitDrawText(node, lines, indent);
353
+ break;
354
+ case 'native_widget':
355
+ emitNativeWidget(node, lines, indent);
356
+ break;
357
+ case 'dock_layout': {
358
+ lines.push(`${indent}{`);
359
+ lines.push(`${indent}${INDENT}ImGuiID dock_id = ImGui::GetID("MainDockSpace");`);
360
+ lines.push(`${indent}${INDENT}if (g_reset_layout) {`);
361
+ lines.push(`${indent}${INDENT}${INDENT}${currentCompName}_setup_dock_layout(dock_id);`);
362
+ lines.push(`${indent}${INDENT}${INDENT}g_reset_layout = false;`);
363
+ lines.push(`${indent}${INDENT}} else if (!g_layout_applied) {`);
364
+ lines.push(`${indent}${INDENT}${INDENT}g_layout_applied = true;`);
365
+ lines.push(`${indent}${INDENT}${INDENT}ImGuiDockNode* node = ImGui::DockBuilderGetNode(dock_id);`);
366
+ lines.push(`${indent}${INDENT}${INDENT}if (node == nullptr || !node->IsSplitNode()) {`);
367
+ lines.push(`${indent}${INDENT}${INDENT}${INDENT}${currentCompName}_setup_dock_layout(dock_id);`);
368
+ lines.push(`${indent}${INDENT}${INDENT}}`);
369
+ lines.push(`${indent}${INDENT}}`);
370
+ lines.push(`${indent}}`);
371
+ break;
372
+ }
211
373
  }
212
374
  }
213
375
  let styleCounter = 0;
@@ -215,6 +377,91 @@ let customComponentCounter = 0;
215
377
  let checkboxCounter = 0;
216
378
  let comboCounter = 0;
217
379
  let listBoxCounter = 0;
380
+ let nativeWidgetCounter = 0;
381
+ let plotCounter = 0;
382
+ const windowOpenStack = []; // tracks if begin_window used open prop
383
+ const modalOnCloseStack = []; // tracks modal onClose expressions
384
+ const dragDropSourceStack = [];
385
+ const dragDropTargetStack = [];
386
+ /**
387
+ * Build a Style variable from a raw style expression string for self-closing components.
388
+ * Handles JS-like object literals: { width: 300, height: 100 } -> imx::Style with assignments.
389
+ * Returns the variable name, or null if no style.
390
+ */
391
+ /**
392
+ * Split a comma-separated list of key:value pairs in a style object literal,
393
+ * respecting brackets so that array values like [0.1, 0.1, 0.1, 1.0] are not split.
394
+ */
395
+ function splitStylePairs(inner) {
396
+ const pairs = [];
397
+ let depth = 0;
398
+ let start = 0;
399
+ for (let i = 0; i < inner.length; i++) {
400
+ const ch = inner[i];
401
+ if (ch === '[' || ch === '(')
402
+ depth++;
403
+ else if (ch === ']' || ch === ')')
404
+ depth--;
405
+ else if (ch === ',' && depth === 0) {
406
+ const piece = inner.substring(start, i).trim();
407
+ if (piece)
408
+ pairs.push(piece);
409
+ start = i + 1;
410
+ }
411
+ }
412
+ const last = inner.substring(start).trim();
413
+ if (last)
414
+ pairs.push(last);
415
+ return pairs;
416
+ }
417
+ function buildStyleVar(styleExpr, indent, lines) {
418
+ if (!styleExpr)
419
+ return null;
420
+ // Check if it looks like an object literal: { key: value, ... }
421
+ const trimmed = styleExpr.trim();
422
+ if (trimmed.startsWith('{') && trimmed.endsWith('}')) {
423
+ const inner = trimmed.slice(1, -1).trim();
424
+ if (!inner)
425
+ return null;
426
+ const varName = `style_${styleCounter++}`;
427
+ lines.push(`${indent}imx::Style ${varName};`);
428
+ // Parse key: value pairs (bracket-aware to handle array values)
429
+ const pairs = splitStylePairs(inner);
430
+ for (const pair of pairs) {
431
+ const colonIdx = pair.indexOf(':');
432
+ if (colonIdx === -1)
433
+ continue;
434
+ const key = pair.substring(0, colonIdx).trim();
435
+ const val = pair.substring(colonIdx + 1).trim();
436
+ // Map camelCase to snake_case
437
+ const cppKey = key === 'paddingHorizontal' ? 'padding_horizontal'
438
+ : key === 'paddingVertical' ? 'padding_vertical'
439
+ : key === 'minWidth' ? 'min_width'
440
+ : key === 'minHeight' ? 'min_height'
441
+ : key === 'backgroundColor' ? 'background_color'
442
+ : key === 'textColor' ? 'text_color'
443
+ : key === 'fontSize' ? 'font_size'
444
+ : key;
445
+ // ImVec4 fields (color arrays)
446
+ if (cppKey === 'background_color' || cppKey === 'text_color') {
447
+ // val is like [r, g, b, a] — convert to ImVec4(r, g, b, a)
448
+ const arrInner = val.trim().replace(/^\[/, '').replace(/\]$/, '');
449
+ const components = arrInner.split(',').map(c => {
450
+ const s = c.trim();
451
+ return s.includes('.') ? `${s}f` : `${s}.0f`;
452
+ });
453
+ lines.push(`${indent}${varName}.${cppKey} = ImVec4(${components.join(', ')});`);
454
+ }
455
+ else {
456
+ const floatVal = val.includes('.') ? `${val}F` : `${val}.0F`;
457
+ lines.push(`${indent}${varName}.${cppKey} = ${floatVal};`);
458
+ }
459
+ }
460
+ return varName;
461
+ }
462
+ // Already a variable name or expression — return as-is
463
+ return styleExpr;
464
+ }
218
465
  function buildStyleBlock(node, indent, lines) {
219
466
  // Check for style-related props (gap, padding, width, height, etc.)
220
467
  const styleProps = {};
@@ -229,17 +476,18 @@ function buildStyleBlock(node, indent, lines) {
229
476
  styleProps[cppKey] = val;
230
477
  }
231
478
  }
232
- // Also check node.style (explicit style prop)
479
+ // Also check node.style (explicit style prop) — route through buildStyleVar to handle
480
+ // object literals like { backgroundColor: [r,g,b,a] } safely
233
481
  const explicitStyle = node.style ?? node.props['style'];
234
482
  if (explicitStyle) {
235
- return explicitStyle;
483
+ return buildStyleVar(explicitStyle, indent, lines);
236
484
  }
237
485
  if (Object.keys(styleProps).length === 0) {
238
486
  return null;
239
487
  }
240
488
  // Generate MSVC-compatible style construction
241
489
  const varName = `style_${styleCounter++}`;
242
- lines.push(`${indent}reimgui::Style ${varName};`);
490
+ lines.push(`${indent}imx::Style ${varName};`);
243
491
  for (const [key, val] of Object.entries(styleProps)) {
244
492
  // Ensure the value is a float literal (e.g., 8 -> 8.0F, 8.5 -> 8.5F)
245
493
  const floatVal = val.includes('.') ? `${val}F` : `${val}.0F`;
@@ -248,59 +496,88 @@ function buildStyleBlock(node, indent, lines) {
248
496
  return varName;
249
497
  }
250
498
  function emitBeginContainer(node, lines, indent) {
499
+ emitLocComment(node.loc, node.tag, lines, indent);
251
500
  switch (node.tag) {
252
501
  case 'Window': {
253
502
  const title = asCharPtr(node.props['title'] ?? '""');
254
- lines.push(`${indent}reimgui::renderer::begin_window(${title});`);
503
+ const flagParts = [];
504
+ if (node.props['noTitleBar'] === 'true')
505
+ flagParts.push('ImGuiWindowFlags_NoTitleBar');
506
+ if (node.props['noResize'] === 'true')
507
+ flagParts.push('ImGuiWindowFlags_NoResize');
508
+ if (node.props['noMove'] === 'true')
509
+ flagParts.push('ImGuiWindowFlags_NoMove');
510
+ if (node.props['noCollapse'] === 'true')
511
+ flagParts.push('ImGuiWindowFlags_NoCollapse');
512
+ if (node.props['noDocking'] === 'true')
513
+ flagParts.push('ImGuiWindowFlags_NoDocking');
514
+ if (node.props['noScrollbar'] === 'true')
515
+ flagParts.push('ImGuiWindowFlags_NoScrollbar');
516
+ const flags = flagParts.length > 0 ? flagParts.join(' | ') : '0';
517
+ const openExpr = node.props['open'];
518
+ const onCloseExpr = node.props['onClose'];
519
+ if (openExpr) {
520
+ windowOpenStack.push(true);
521
+ lines.push(`${indent}{`);
522
+ lines.push(`${indent} bool win_open = ${openExpr};`);
523
+ lines.push(`${indent} imx::renderer::begin_window(${title}, ${flags}, &win_open);`);
524
+ if (onCloseExpr) {
525
+ lines.push(`${indent} if (!win_open) { ${onCloseExpr}; }`);
526
+ }
527
+ }
528
+ else {
529
+ windowOpenStack.push(false);
530
+ lines.push(`${indent}imx::renderer::begin_window(${title}, ${flags});`);
531
+ }
255
532
  break;
256
533
  }
257
534
  case 'Row': {
258
535
  const style = buildStyleBlock(node, indent, lines);
259
536
  if (style) {
260
- lines.push(`${indent}reimgui::renderer::begin_row(${style});`);
537
+ lines.push(`${indent}imx::renderer::begin_row(${style});`);
261
538
  }
262
539
  else {
263
- lines.push(`${indent}reimgui::renderer::begin_row();`);
540
+ lines.push(`${indent}imx::renderer::begin_row();`);
264
541
  }
265
542
  break;
266
543
  }
267
544
  case 'Column': {
268
545
  const style = buildStyleBlock(node, indent, lines);
269
546
  if (style) {
270
- lines.push(`${indent}reimgui::renderer::begin_column(${style});`);
547
+ lines.push(`${indent}imx::renderer::begin_column(${style});`);
271
548
  }
272
549
  else {
273
- lines.push(`${indent}reimgui::renderer::begin_column();`);
550
+ lines.push(`${indent}imx::renderer::begin_column();`);
274
551
  }
275
552
  break;
276
553
  }
277
554
  case 'View': {
278
555
  const style = buildStyleBlock(node, indent, lines);
279
556
  if (style) {
280
- lines.push(`${indent}reimgui::renderer::begin_view(${style});`);
557
+ lines.push(`${indent}imx::renderer::begin_view(${style});`);
281
558
  }
282
559
  else {
283
- lines.push(`${indent}reimgui::renderer::begin_view();`);
560
+ lines.push(`${indent}imx::renderer::begin_view();`);
284
561
  }
285
562
  break;
286
563
  }
287
564
  case 'DockSpace': {
288
565
  const style = buildStyleBlock(node, indent, lines);
289
566
  if (style) {
290
- lines.push(`${indent}reimgui::renderer::begin_dockspace(${style});`);
567
+ lines.push(`${indent}imx::renderer::begin_dockspace(${style});`);
291
568
  }
292
569
  else {
293
- lines.push(`${indent}reimgui::renderer::begin_dockspace();`);
570
+ lines.push(`${indent}imx::renderer::begin_dockspace();`);
294
571
  }
295
572
  break;
296
573
  }
297
574
  case 'MenuBar': {
298
- lines.push(`${indent}if (reimgui::renderer::begin_menu_bar()) {`);
575
+ lines.push(`${indent}if (imx::renderer::begin_menu_bar()) {`);
299
576
  break;
300
577
  }
301
578
  case 'Menu': {
302
579
  const label = asCharPtr(node.props['label'] ?? '""');
303
- lines.push(`${indent}if (reimgui::renderer::begin_menu(${label})) {`);
580
+ lines.push(`${indent}if (imx::renderer::begin_menu(${label})) {`);
304
581
  break;
305
582
  }
306
583
  case 'Table': {
@@ -309,100 +586,343 @@ function emitBeginContainer(node, lines, indent) {
309
586
  const count = columnNames.length;
310
587
  const varName = `table_cols_${styleCounter++}`;
311
588
  lines.push(`${indent}const char* ${varName}[] = {${columnNames.join(', ')}};`);
312
- lines.push(`${indent}if (reimgui::renderer::begin_table("##table", ${count}, ${varName})) {`);
589
+ lines.push(`${indent}if (imx::renderer::begin_table("##table", ${count}, ${varName})) {`);
313
590
  break;
314
591
  }
315
592
  case 'TableRow': {
316
- lines.push(`${indent}reimgui::renderer::begin_table_row();`);
593
+ lines.push(`${indent}imx::renderer::begin_table_row();`);
317
594
  break;
318
595
  }
319
596
  case 'TabBar': {
320
- lines.push(`${indent}if (reimgui::renderer::begin_tab_bar()) {`);
597
+ lines.push(`${indent}if (imx::renderer::begin_tab_bar()) {`);
321
598
  break;
322
599
  }
323
600
  case 'TabItem': {
324
601
  const label = asCharPtr(node.props['label'] ?? '""');
325
- lines.push(`${indent}if (reimgui::renderer::begin_tab_item(${label})) {`);
602
+ lines.push(`${indent}if (imx::renderer::begin_tab_item(${label})) {`);
326
603
  break;
327
604
  }
328
605
  case 'TreeNode': {
329
606
  const label = asCharPtr(node.props['label'] ?? '""');
330
- lines.push(`${indent}if (reimgui::renderer::begin_tree_node(${label})) {`);
607
+ lines.push(`${indent}if (imx::renderer::begin_tree_node(${label})) {`);
331
608
  break;
332
609
  }
333
610
  case 'CollapsingHeader': {
334
611
  const label = asCharPtr(node.props['label'] ?? '""');
335
- lines.push(`${indent}if (reimgui::renderer::begin_collapsing_header(${label})) {`);
612
+ lines.push(`${indent}if (imx::renderer::begin_collapsing_header(${label})) {`);
613
+ break;
614
+ }
615
+ case 'Theme': {
616
+ const preset = asCharPtr(node.props['preset'] ?? '"dark"');
617
+ const varName = `theme_${styleCounter++}`;
618
+ lines.push(`${indent}imx::ThemeConfig ${varName};`);
619
+ if (node.props['accentColor']) {
620
+ lines.push(`${indent}${varName}.accent_color = ${emitImVec4(node.props['accentColor'])};`);
621
+ }
622
+ if (node.props['backgroundColor']) {
623
+ lines.push(`${indent}${varName}.background_color = ${emitImVec4(node.props['backgroundColor'])};`);
624
+ }
625
+ if (node.props['textColor']) {
626
+ lines.push(`${indent}${varName}.text_color = ${emitImVec4(node.props['textColor'])};`);
627
+ }
628
+ if (node.props['borderColor']) {
629
+ lines.push(`${indent}${varName}.border_color = ${emitImVec4(node.props['borderColor'])};`);
630
+ }
631
+ if (node.props['surfaceColor']) {
632
+ lines.push(`${indent}${varName}.surface_color = ${emitImVec4(node.props['surfaceColor'])};`);
633
+ }
634
+ if (node.props['rounding']) {
635
+ lines.push(`${indent}${varName}.rounding = ${emitFloat(node.props['rounding'])};`);
636
+ }
637
+ if (node.props['borderSize']) {
638
+ lines.push(`${indent}${varName}.border_size = ${emitFloat(node.props['borderSize'])};`);
639
+ }
640
+ if (node.props['spacing']) {
641
+ lines.push(`${indent}${varName}.spacing = ${emitFloat(node.props['spacing'])};`);
642
+ }
643
+ lines.push(`${indent}imx::renderer::begin_theme(${preset}, ${varName});`);
644
+ break;
645
+ }
646
+ case 'Modal': {
647
+ const title = asCharPtr(node.props['title'] ?? '""');
648
+ const openExpr = node.props['open'];
649
+ const onCloseExpr = node.props['onClose'];
650
+ if (openExpr) {
651
+ windowOpenStack.push(true);
652
+ // Extract onClose body for use in end emitter
653
+ let onCloseBody = null;
654
+ if (onCloseExpr) {
655
+ const lambdaMatch = onCloseExpr.match(/^\[&\]\(\)\s*\{\s*(.*?)\s*\}$/);
656
+ onCloseBody = lambdaMatch ? lambdaMatch[1] : `${onCloseExpr};`;
657
+ }
658
+ modalOnCloseStack.push(onCloseBody);
659
+ lines.push(`${indent}{`);
660
+ lines.push(`${indent} bool modal_closed = false;`);
661
+ lines.push(`${indent} if (imx::renderer::begin_modal(${title}, ${openExpr}, &modal_closed)) {`);
662
+ }
663
+ else {
664
+ windowOpenStack.push(false);
665
+ modalOnCloseStack.push(null);
666
+ lines.push(`${indent}if (imx::renderer::begin_modal(${title}, true, nullptr)) {`);
667
+ }
668
+ break;
669
+ }
670
+ case 'StyleColor': {
671
+ const varName = `sc_${styleCounter++}`;
672
+ lines.push(`${indent}imx::StyleColorOverrides ${varName};`);
673
+ const colorProps = [
674
+ ['text', 'text'], ['textDisabled', 'text_disabled'],
675
+ ['windowBg', 'window_bg'], ['frameBg', 'frame_bg'],
676
+ ['frameBgHovered', 'frame_bg_hovered'], ['frameBgActive', 'frame_bg_active'],
677
+ ['titleBg', 'title_bg'], ['titleBgActive', 'title_bg_active'],
678
+ ['button', 'button'], ['buttonHovered', 'button_hovered'],
679
+ ['buttonActive', 'button_active'], ['header', 'header'],
680
+ ['headerHovered', 'header_hovered'], ['headerActive', 'header_active'],
681
+ ['separator', 'separator'], ['checkMark', 'check_mark'],
682
+ ['sliderGrab', 'slider_grab'], ['border', 'border'],
683
+ ['popupBg', 'popup_bg'], ['tab', 'tab'],
684
+ ];
685
+ for (const [tsName, cppName] of colorProps) {
686
+ if (node.props[tsName]) {
687
+ lines.push(`${indent}${varName}.${cppName} = ${emitImVec4(node.props[tsName])};`);
688
+ }
689
+ }
690
+ lines.push(`${indent}imx::renderer::begin_style_color(${varName});`);
691
+ break;
692
+ }
693
+ case 'StyleVar': {
694
+ const varName = `sv_${styleCounter++}`;
695
+ lines.push(`${indent}imx::StyleVarOverrides ${varName};`);
696
+ const floatProps = [
697
+ ['alpha', 'alpha'], ['windowRounding', 'window_rounding'],
698
+ ['frameRounding', 'frame_rounding'], ['frameBorderSize', 'frame_border_size'],
699
+ ['indentSpacing', 'indent_spacing'], ['tabRounding', 'tab_rounding'],
700
+ ];
701
+ for (const [tsName, cppName] of floatProps) {
702
+ if (node.props[tsName]) {
703
+ lines.push(`${indent}${varName}.${cppName} = ${emitFloat(node.props[tsName])};`);
704
+ }
705
+ }
706
+ const vec2Props = [
707
+ ['windowPadding', 'window_padding'], ['framePadding', 'frame_padding'],
708
+ ['itemSpacing', 'item_spacing'], ['itemInnerSpacing', 'item_inner_spacing'],
709
+ ['cellPadding', 'cell_padding'],
710
+ ];
711
+ for (const [tsName, cppName] of vec2Props) {
712
+ if (node.props[tsName]) {
713
+ lines.push(`${indent}${varName}.${cppName} = ${emitImVec2(node.props[tsName])};`);
714
+ }
715
+ }
716
+ lines.push(`${indent}imx::renderer::begin_style_var(${varName});`);
717
+ break;
718
+ }
719
+ case 'Group': {
720
+ lines.push(`${indent}ImGui::BeginGroup();`);
721
+ break;
722
+ }
723
+ case 'ID': {
724
+ const scope = node.props['scope'] ?? '""';
725
+ if (scope.startsWith('"')) {
726
+ lines.push(`${indent}ImGui::PushID(${scope});`);
727
+ }
728
+ else {
729
+ lines.push(`${indent}ImGui::PushID(static_cast<int>(${scope}));`);
730
+ }
731
+ break;
732
+ }
733
+ case 'DragDropSource': {
734
+ dragDropSourceStack.push(node.props);
735
+ lines.push(`${indent}ImGui::BeginGroup();`);
736
+ break;
737
+ }
738
+ case 'DragDropTarget': {
739
+ dragDropTargetStack.push(node.props);
740
+ lines.push(`${indent}ImGui::BeginGroup();`);
741
+ break;
742
+ }
743
+ case 'Disabled': {
744
+ const disabled = node.props['disabled'] ?? 'true';
745
+ lines.push(`${indent}ImGui::BeginDisabled(${disabled});`);
746
+ break;
747
+ }
748
+ case 'Child': {
749
+ const id = asCharPtr(node.props['id'] ?? '"##child"');
750
+ const width = emitFloat(node.props['width'] ?? '0');
751
+ const height = emitFloat(node.props['height'] ?? '0');
752
+ const border = node.props['border'] === 'true' ? 'true' : 'false';
753
+ lines.push(`${indent}ImGui::BeginChild(${id}, ImVec2(${width}, ${height}), ${border});`);
336
754
  break;
337
755
  }
756
+ case 'Canvas': {
757
+ const width = emitFloat(node.props['width'] ?? '0');
758
+ const height = emitFloat(node.props['height'] ?? '0');
759
+ const style = buildStyleBlock(node, indent, lines);
760
+ if (style) {
761
+ lines.push(`${indent}imx::renderer::begin_canvas(${width}, ${height}, ${style});`);
762
+ }
763
+ else {
764
+ lines.push(`${indent}imx::renderer::begin_canvas(${width}, ${height});`);
765
+ }
766
+ break;
767
+ }
768
+ case 'DockLayout':
769
+ case 'DockSplit':
770
+ case 'DockPanel':
771
+ break;
338
772
  }
339
773
  }
340
774
  function emitEndContainer(node, lines, indent) {
341
775
  switch (node.tag) {
342
- case 'Window':
343
- lines.push(`${indent}reimgui::renderer::end_window();`);
776
+ case 'Window': {
777
+ lines.push(`${indent}imx::renderer::end_window();`);
778
+ const hadOpen = windowOpenStack.pop() ?? false;
779
+ if (hadOpen) {
780
+ lines.push(`${indent}}`);
781
+ }
344
782
  break;
783
+ }
345
784
  case 'Row':
346
- lines.push(`${indent}reimgui::renderer::end_row();`);
785
+ lines.push(`${indent}imx::renderer::end_row();`);
347
786
  break;
348
787
  case 'Column':
349
- lines.push(`${indent}reimgui::renderer::end_column();`);
788
+ lines.push(`${indent}imx::renderer::end_column();`);
350
789
  break;
351
790
  case 'View':
352
- lines.push(`${indent}reimgui::renderer::end_view();`);
791
+ lines.push(`${indent}imx::renderer::end_view();`);
353
792
  break;
354
793
  case 'DockSpace':
355
- lines.push(`${indent}reimgui::renderer::end_dockspace();`);
794
+ lines.push(`${indent}imx::renderer::end_dockspace();`);
356
795
  break;
357
796
  case 'MenuBar':
358
- lines.push(`${indent}reimgui::renderer::end_menu_bar();`);
797
+ lines.push(`${indent}imx::renderer::end_menu_bar();`);
359
798
  lines.push(`${indent}}`);
360
799
  break;
361
800
  case 'Menu':
362
- lines.push(`${indent}reimgui::renderer::end_menu();`);
801
+ lines.push(`${indent}imx::renderer::end_menu();`);
363
802
  lines.push(`${indent}}`);
364
803
  break;
365
804
  case 'Table':
366
- lines.push(`${indent}reimgui::renderer::end_table();`);
805
+ lines.push(`${indent}imx::renderer::end_table();`);
367
806
  lines.push(`${indent}}`);
368
807
  break;
369
808
  case 'TableRow':
370
- lines.push(`${indent}reimgui::renderer::end_table_row();`);
809
+ lines.push(`${indent}imx::renderer::end_table_row();`);
371
810
  break;
372
811
  case 'TabBar':
373
- lines.push(`${indent}reimgui::renderer::end_tab_bar();`);
812
+ lines.push(`${indent}imx::renderer::end_tab_bar();`);
374
813
  lines.push(`${indent}}`);
375
814
  break;
376
815
  case 'TabItem':
377
- lines.push(`${indent}reimgui::renderer::end_tab_item();`);
816
+ lines.push(`${indent}imx::renderer::end_tab_item();`);
378
817
  lines.push(`${indent}}`);
379
818
  break;
380
819
  case 'TreeNode':
381
- lines.push(`${indent}reimgui::renderer::end_tree_node();`);
820
+ lines.push(`${indent}imx::renderer::end_tree_node();`);
382
821
  lines.push(`${indent}}`);
383
822
  break;
384
823
  case 'CollapsingHeader':
385
- lines.push(`${indent}reimgui::renderer::end_collapsing_header();`);
824
+ lines.push(`${indent}imx::renderer::end_collapsing_header();`);
825
+ lines.push(`${indent}}`);
826
+ break;
827
+ case 'Theme':
828
+ lines.push(`${indent}imx::renderer::end_theme();`);
829
+ break;
830
+ case 'Modal': {
831
+ lines.push(`${indent}imx::renderer::end_modal();`);
832
+ lines.push(`${indent}}`); // close the if (begin_modal) block
833
+ const hadOpen = windowOpenStack.pop() ?? false;
834
+ const onCloseBody = modalOnCloseStack.pop() ?? null;
835
+ if (hadOpen && onCloseBody) {
836
+ // Check modal_closed OUTSIDE the if(begin_modal) block,
837
+ // because BeginPopupModal returns false when X is clicked
838
+ // (it calls EndPopup internally).
839
+ lines.push(`${indent}if (modal_closed) { ${onCloseBody} }`);
840
+ lines.push(`${indent}}`); // close the { bool modal_closed scope
841
+ }
842
+ else if (hadOpen) {
843
+ lines.push(`${indent}}`); // close scope without onClose
844
+ }
845
+ break;
846
+ }
847
+ case 'StyleColor':
848
+ lines.push(`${indent}imx::renderer::end_style_color();`);
849
+ break;
850
+ case 'StyleVar':
851
+ lines.push(`${indent}imx::renderer::end_style_var();`);
852
+ break;
853
+ case 'Group':
854
+ lines.push(`${indent}ImGui::EndGroup();`);
855
+ break;
856
+ case 'ID':
857
+ lines.push(`${indent}ImGui::PopID();`);
858
+ break;
859
+ case 'DragDropSource': {
860
+ const props = dragDropSourceStack.pop() ?? {};
861
+ const typeStr = asCharPtr(props['type'] ?? '""');
862
+ const payload = props['payload'] ?? '0';
863
+ lines.push(`${indent}ImGui::EndGroup();`);
864
+ lines.push(`${indent}if (ImGui::BeginDragDropSource(ImGuiDragDropFlags_SourceAllowNullID)) {`);
865
+ lines.push(`${indent} float _dd_payload = static_cast<float>(${payload});`);
866
+ lines.push(`${indent} ImGui::SetDragDropPayload(${typeStr}, &_dd_payload, sizeof(_dd_payload));`);
867
+ lines.push(`${indent} ImGui::Text("Dragging...");`);
868
+ lines.push(`${indent} ImGui::EndDragDropSource();`);
386
869
  lines.push(`${indent}}`);
387
870
  break;
871
+ }
872
+ case 'Disabled':
873
+ lines.push(`${indent}ImGui::EndDisabled();`);
874
+ break;
875
+ case 'Child':
876
+ lines.push(`${indent}ImGui::EndChild();`);
877
+ break;
878
+ case 'Canvas':
879
+ lines.push(`${indent}imx::renderer::end_canvas();`);
880
+ break;
881
+ case 'DragDropTarget': {
882
+ const props = dragDropTargetStack.pop() ?? {};
883
+ const typeStr = asCharPtr(props['type'] ?? '""');
884
+ const onDrop = props['onDrop'] ?? '';
885
+ lines.push(`${indent}ImGui::EndGroup();`);
886
+ lines.push(`${indent}if (ImGui::BeginDragDropTarget()) {`);
887
+ lines.push(`${indent} if (const ImGuiPayload* _dd_p = ImGui::AcceptDragDropPayload(${typeStr})) {`);
888
+ // Parse the structured callback: "cppType|paramName|bodyCode"
889
+ const parts = onDrop.split('|');
890
+ if (parts.length >= 3) {
891
+ const cppType = parts[0];
892
+ const paramName = parts[1];
893
+ const bodyCode = parts.slice(2).join('|'); // rejoin in case body contained |
894
+ lines.push(`${indent} ${cppType} ${paramName} = *(const ${cppType}*)_dd_p->Data;`);
895
+ lines.push(`${indent} ${bodyCode}`);
896
+ }
897
+ lines.push(`${indent} }`);
898
+ lines.push(`${indent} ImGui::EndDragDropTarget();`);
899
+ lines.push(`${indent}}`);
900
+ break;
901
+ }
902
+ case 'DockLayout':
903
+ case 'DockSplit':
904
+ case 'DockPanel':
905
+ break;
388
906
  }
389
907
  }
390
908
  function emitText(node, lines, indent) {
909
+ emitLocComment(node.loc, 'Text', lines, indent);
391
910
  if (node.args.length === 0) {
392
- lines.push(`${indent}reimgui::renderer::text(${JSON.stringify(node.format)});`);
911
+ lines.push(`${indent}imx::renderer::text(${JSON.stringify(node.format)});`);
393
912
  }
394
913
  else {
395
914
  const argsStr = node.args.join(', ');
396
- lines.push(`${indent}reimgui::renderer::text(${JSON.stringify(node.format)}, ${argsStr});`);
915
+ lines.push(`${indent}imx::renderer::text(${JSON.stringify(node.format)}, ${argsStr});`);
397
916
  }
398
917
  }
399
918
  function emitButton(node, lines, indent, depth) {
919
+ emitLocComment(node.loc, 'Button', lines, indent);
400
920
  const title = asCharPtr(node.title);
401
921
  if (node.action.length === 0) {
402
- lines.push(`${indent}reimgui::renderer::button(${title});`);
922
+ lines.push(`${indent}imx::renderer::button(${title});`);
403
923
  }
404
924
  else {
405
- lines.push(`${indent}if (reimgui::renderer::button(${title})) {`);
925
+ lines.push(`${indent}if (imx::renderer::button(${title})) {`);
406
926
  for (const stmt of node.action) {
407
927
  lines.push(`${indent}${INDENT}${stmt}`);
408
928
  }
@@ -410,22 +930,23 @@ function emitButton(node, lines, indent, depth) {
410
930
  }
411
931
  }
412
932
  function emitMenuItem(node, lines, indent, depth) {
933
+ emitLocComment(node.loc, 'MenuItem', lines, indent);
413
934
  const label = asCharPtr(node.label);
414
935
  const shortcut = node.shortcut ? asCharPtr(node.shortcut) : undefined;
415
936
  if (node.action.length === 0) {
416
937
  if (shortcut) {
417
- lines.push(`${indent}reimgui::renderer::menu_item(${label}, ${shortcut});`);
938
+ lines.push(`${indent}imx::renderer::menu_item(${label}, ${shortcut});`);
418
939
  }
419
940
  else {
420
- lines.push(`${indent}reimgui::renderer::menu_item(${label});`);
941
+ lines.push(`${indent}imx::renderer::menu_item(${label});`);
421
942
  }
422
943
  }
423
944
  else {
424
945
  if (shortcut) {
425
- lines.push(`${indent}if (reimgui::renderer::menu_item(${label}, ${shortcut})) {`);
946
+ lines.push(`${indent}if (imx::renderer::menu_item(${label}, ${shortcut})) {`);
426
947
  }
427
948
  else {
428
- lines.push(`${indent}if (reimgui::renderer::menu_item(${label})) {`);
949
+ lines.push(`${indent}if (imx::renderer::menu_item(${label})) {`);
429
950
  }
430
951
  for (const stmt of node.action) {
431
952
  lines.push(`${indent} ${stmt}`);
@@ -434,29 +955,31 @@ function emitMenuItem(node, lines, indent, depth) {
434
955
  }
435
956
  }
436
957
  function emitTextInput(node, lines, indent) {
958
+ emitLocComment(node.loc, 'TextInput', lines, indent);
437
959
  const label = asCharPtr(node.label && node.label !== '""' ? node.label : `"##textinput_${node.bufferIndex}"`);
438
960
  if (node.stateVar) {
439
961
  lines.push(`${indent}{`);
440
962
  lines.push(`${indent}${INDENT}auto& buf = ctx.get_buffer(${node.bufferIndex});`);
441
963
  lines.push(`${indent}${INDENT}buf.sync_from(${node.stateVar}.get());`);
442
- lines.push(`${indent}${INDENT}if (reimgui::renderer::text_input(${label}, buf)) {`);
964
+ lines.push(`${indent}${INDENT}if (imx::renderer::text_input(${label}, buf)) {`);
443
965
  lines.push(`${indent}${INDENT}${INDENT}${node.stateVar}.set(buf.value());`);
444
966
  lines.push(`${indent}${INDENT}}`);
445
967
  lines.push(`${indent}}`);
446
968
  }
447
969
  else {
448
970
  lines.push(`${indent}auto& buf_${node.bufferIndex} = ctx.get_buffer(${node.bufferIndex});`);
449
- lines.push(`${indent}reimgui::renderer::text_input(${label}, buf_${node.bufferIndex});`);
971
+ lines.push(`${indent}imx::renderer::text_input(${label}, buf_${node.bufferIndex});`);
450
972
  }
451
973
  }
452
974
  function emitCheckbox(node, lines, indent) {
975
+ emitLocComment(node.loc, 'Checkbox', lines, indent);
453
976
  const label = asCharPtr(node.label && node.label !== '""' ? node.label : `"##checkbox_${checkboxCounter}"`);
454
977
  checkboxCounter++;
455
978
  if (node.stateVar) {
456
979
  // State-bound case
457
980
  lines.push(`${indent}{`);
458
981
  lines.push(`${indent}${INDENT}bool val = ${node.stateVar}.get();`);
459
- lines.push(`${indent}${INDENT}if (reimgui::renderer::checkbox(${label}, &val)) {`);
982
+ lines.push(`${indent}${INDENT}if (imx::renderer::checkbox(${label}, &val)) {`);
460
983
  lines.push(`${indent}${INDENT}${INDENT}${node.stateVar}.set(val);`);
461
984
  lines.push(`${indent}${INDENT}}`);
462
985
  lines.push(`${indent}}`);
@@ -465,7 +988,7 @@ function emitCheckbox(node, lines, indent) {
465
988
  // Props-bound / expression-bound case
466
989
  lines.push(`${indent}{`);
467
990
  lines.push(`${indent}${INDENT}bool val = ${node.valueExpr};`);
468
- lines.push(`${indent}${INDENT}if (reimgui::renderer::checkbox(${label}, &val)) {`);
991
+ lines.push(`${indent}${INDENT}if (imx::renderer::checkbox(${label}, &val)) {`);
469
992
  if (node.onChangeExpr) {
470
993
  lines.push(`${indent}${INDENT}${INDENT}${node.onChangeExpr};`);
471
994
  }
@@ -473,10 +996,13 @@ function emitCheckbox(node, lines, indent) {
473
996
  lines.push(`${indent}}`);
474
997
  }
475
998
  else {
476
- lines.push(`${indent}reimgui::renderer::checkbox(${label}, nullptr);`);
999
+ lines.push(`${indent}imx::renderer::checkbox(${label}, nullptr);`);
477
1000
  }
478
1001
  }
479
1002
  function emitConditional(node, lines, indent, depth) {
1003
+ if (node.loc) {
1004
+ lines.push(`${indent}// ${node.loc.file}:${node.loc.line} conditional`);
1005
+ }
480
1006
  lines.push(`${indent}if (${node.condition}) {`);
481
1007
  emitNodes(node.body, lines, depth + 1);
482
1008
  if (node.elseBody && node.elseBody.length > 0) {
@@ -486,6 +1012,9 @@ function emitConditional(node, lines, indent, depth) {
486
1012
  lines.push(`${indent}}`);
487
1013
  }
488
1014
  function emitListMap(node, lines, indent, depth) {
1015
+ if (node.loc) {
1016
+ lines.push(`${indent}// ${node.loc.file}:${node.loc.line} .map()`);
1017
+ }
489
1018
  lines.push(`${indent}for (size_t i = 0; i < ${node.array}.size(); i++) {`);
490
1019
  lines.push(`${indent}${INDENT}auto& ${node.itemVar} = ${node.array}[i];`);
491
1020
  lines.push(`${indent}${INDENT}ctx.begin_instance("${node.componentName}", i, ${node.stateCount}, ${node.bufferCount});`);
@@ -494,6 +1023,7 @@ function emitListMap(node, lines, indent, depth) {
494
1023
  lines.push(`${indent}}`);
495
1024
  }
496
1025
  function emitCustomComponent(node, lines, indent) {
1026
+ emitLocComment(node.loc, node.name, lines, indent);
497
1027
  const instanceIndex = customComponentCounter++;
498
1028
  const propsEntries = Object.entries(node.props);
499
1029
  lines.push(`${indent}ctx.begin_instance("${node.name}", ${instanceIndex}, ${node.stateCount}, ${node.bufferCount});`);
@@ -512,6 +1042,23 @@ function emitCustomComponent(node, lines, indent) {
512
1042
  }
513
1043
  lines.push(`${indent}ctx.end_instance();`);
514
1044
  }
1045
+ function emitNativeWidget(node, lines, indent) {
1046
+ emitLocComment(node.loc, node.name, lines, indent);
1047
+ const idx = nativeWidgetCounter++;
1048
+ const label = `${node.name}##nw_${idx}`;
1049
+ lines.push(`${indent}{`);
1050
+ lines.push(`${indent}${INDENT}imx::WidgetArgs _wa("${label}");`);
1051
+ // Value props
1052
+ for (const [k, v] of Object.entries(node.props)) {
1053
+ lines.push(`${indent}${INDENT}_wa.set("${k}", ${v});`);
1054
+ }
1055
+ // Callback props — already lowered to [&](std::any _v) { ... } lambdas
1056
+ for (const [k, v] of Object.entries(node.callbackProps)) {
1057
+ lines.push(`${indent}${INDENT}_wa.set_callback("${k}", ${v});`);
1058
+ }
1059
+ lines.push(`${indent}${INDENT}imx::call_widget("${node.name}", _wa);`);
1060
+ lines.push(`${indent}}`);
1061
+ }
515
1062
  function ensureFloatLiteral(val) {
516
1063
  // If already has 'f' suffix or '.', leave as-is
517
1064
  if (val.endsWith('f') || val.endsWith('F') || val.includes('.'))
@@ -520,12 +1067,13 @@ function ensureFloatLiteral(val) {
520
1067
  return `${val}.0f`;
521
1068
  }
522
1069
  function emitSliderFloat(node, lines, indent) {
1070
+ emitLocComment(node.loc, 'SliderFloat', lines, indent);
523
1071
  const min = ensureFloatLiteral(node.min);
524
1072
  const max = ensureFloatLiteral(node.max);
525
1073
  if (node.stateVar) {
526
1074
  lines.push(`${indent}{`);
527
1075
  lines.push(`${indent}${INDENT}float val = ${node.stateVar}.get();`);
528
- lines.push(`${indent}${INDENT}if (reimgui::renderer::slider_float(${node.label}, &val, ${min}, ${max})) {`);
1076
+ lines.push(`${indent}${INDENT}if (imx::renderer::slider_float(${node.label}, &val, ${min}, ${max})) {`);
529
1077
  lines.push(`${indent}${INDENT}${INDENT}${node.stateVar}.set(val);`);
530
1078
  lines.push(`${indent}${INDENT}}`);
531
1079
  lines.push(`${indent}}`);
@@ -533,7 +1081,7 @@ function emitSliderFloat(node, lines, indent) {
533
1081
  else if (node.valueExpr !== undefined) {
534
1082
  lines.push(`${indent}{`);
535
1083
  lines.push(`${indent}${INDENT}float val = ${node.valueExpr};`);
536
- lines.push(`${indent}${INDENT}if (reimgui::renderer::slider_float(${node.label}, &val, ${min}, ${max})) {`);
1084
+ lines.push(`${indent}${INDENT}if (imx::renderer::slider_float(${node.label}, &val, ${min}, ${max})) {`);
537
1085
  if (node.onChangeExpr) {
538
1086
  lines.push(`${indent}${INDENT}${INDENT}${node.onChangeExpr};`);
539
1087
  }
@@ -542,10 +1090,11 @@ function emitSliderFloat(node, lines, indent) {
542
1090
  }
543
1091
  }
544
1092
  function emitSliderInt(node, lines, indent) {
1093
+ emitLocComment(node.loc, 'SliderInt', lines, indent);
545
1094
  if (node.stateVar) {
546
1095
  lines.push(`${indent}{`);
547
1096
  lines.push(`${indent}${INDENT}int val = ${node.stateVar}.get();`);
548
- lines.push(`${indent}${INDENT}if (reimgui::renderer::slider_int(${node.label}, &val, ${node.min}, ${node.max})) {`);
1097
+ lines.push(`${indent}${INDENT}if (imx::renderer::slider_int(${node.label}, &val, ${node.min}, ${node.max})) {`);
549
1098
  lines.push(`${indent}${INDENT}${INDENT}${node.stateVar}.set(val);`);
550
1099
  lines.push(`${indent}${INDENT}}`);
551
1100
  lines.push(`${indent}}`);
@@ -553,7 +1102,7 @@ function emitSliderInt(node, lines, indent) {
553
1102
  else if (node.valueExpr !== undefined) {
554
1103
  lines.push(`${indent}{`);
555
1104
  lines.push(`${indent}${INDENT}int val = ${node.valueExpr};`);
556
- lines.push(`${indent}${INDENT}if (reimgui::renderer::slider_int(${node.label}, &val, ${node.min}, ${node.max})) {`);
1105
+ lines.push(`${indent}${INDENT}if (imx::renderer::slider_int(${node.label}, &val, ${node.min}, ${node.max})) {`);
557
1106
  if (node.onChangeExpr) {
558
1107
  lines.push(`${indent}${INDENT}${INDENT}${node.onChangeExpr};`);
559
1108
  }
@@ -562,11 +1111,12 @@ function emitSliderInt(node, lines, indent) {
562
1111
  }
563
1112
  }
564
1113
  function emitDragFloat(node, lines, indent) {
1114
+ emitLocComment(node.loc, 'DragFloat', lines, indent);
565
1115
  const speed = ensureFloatLiteral(node.speed);
566
1116
  if (node.stateVar) {
567
1117
  lines.push(`${indent}{`);
568
1118
  lines.push(`${indent}${INDENT}float val = ${node.stateVar}.get();`);
569
- lines.push(`${indent}${INDENT}if (reimgui::renderer::drag_float(${node.label}, &val, ${speed})) {`);
1119
+ lines.push(`${indent}${INDENT}if (imx::renderer::drag_float(${node.label}, &val, ${speed})) {`);
570
1120
  lines.push(`${indent}${INDENT}${INDENT}${node.stateVar}.set(val);`);
571
1121
  lines.push(`${indent}${INDENT}}`);
572
1122
  lines.push(`${indent}}`);
@@ -574,7 +1124,7 @@ function emitDragFloat(node, lines, indent) {
574
1124
  else if (node.valueExpr !== undefined) {
575
1125
  lines.push(`${indent}{`);
576
1126
  lines.push(`${indent}${INDENT}float val = ${node.valueExpr};`);
577
- lines.push(`${indent}${INDENT}if (reimgui::renderer::drag_float(${node.label}, &val, ${speed})) {`);
1127
+ lines.push(`${indent}${INDENT}if (imx::renderer::drag_float(${node.label}, &val, ${speed})) {`);
578
1128
  if (node.onChangeExpr) {
579
1129
  lines.push(`${indent}${INDENT}${INDENT}${node.onChangeExpr};`);
580
1130
  }
@@ -583,11 +1133,12 @@ function emitDragFloat(node, lines, indent) {
583
1133
  }
584
1134
  }
585
1135
  function emitDragInt(node, lines, indent) {
1136
+ emitLocComment(node.loc, 'DragInt', lines, indent);
586
1137
  const speed = ensureFloatLiteral(node.speed);
587
1138
  if (node.stateVar) {
588
1139
  lines.push(`${indent}{`);
589
1140
  lines.push(`${indent}${INDENT}int val = ${node.stateVar}.get();`);
590
- lines.push(`${indent}${INDENT}if (reimgui::renderer::drag_int(${node.label}, &val, ${speed})) {`);
1141
+ lines.push(`${indent}${INDENT}if (imx::renderer::drag_int(${node.label}, &val, ${speed})) {`);
591
1142
  lines.push(`${indent}${INDENT}${INDENT}${node.stateVar}.set(val);`);
592
1143
  lines.push(`${indent}${INDENT}}`);
593
1144
  lines.push(`${indent}}`);
@@ -595,7 +1146,7 @@ function emitDragInt(node, lines, indent) {
595
1146
  else if (node.valueExpr !== undefined) {
596
1147
  lines.push(`${indent}{`);
597
1148
  lines.push(`${indent}${INDENT}int val = ${node.valueExpr};`);
598
- lines.push(`${indent}${INDENT}if (reimgui::renderer::drag_int(${node.label}, &val, ${speed})) {`);
1149
+ lines.push(`${indent}${INDENT}if (imx::renderer::drag_int(${node.label}, &val, ${speed})) {`);
599
1150
  if (node.onChangeExpr) {
600
1151
  lines.push(`${indent}${INDENT}${INDENT}${node.onChangeExpr};`);
601
1152
  }
@@ -604,6 +1155,7 @@ function emitDragInt(node, lines, indent) {
604
1155
  }
605
1156
  }
606
1157
  function emitCombo(node, lines, indent) {
1158
+ emitLocComment(node.loc, 'Combo', lines, indent);
607
1159
  const itemsList = node.items.split(',').map(s => s.trim()).filter(s => s.length > 0);
608
1160
  const count = itemsList.length;
609
1161
  const varName = `combo_items_${comboCounter++}`;
@@ -611,7 +1163,7 @@ function emitCombo(node, lines, indent) {
611
1163
  lines.push(`${indent}{`);
612
1164
  lines.push(`${indent}${INDENT}const char* ${varName}[] = {${itemsList.join(', ')}};`);
613
1165
  lines.push(`${indent}${INDENT}int val = ${node.stateVar}.get();`);
614
- lines.push(`${indent}${INDENT}if (reimgui::renderer::combo(${node.label}, &val, ${varName}, ${count})) {`);
1166
+ lines.push(`${indent}${INDENT}if (imx::renderer::combo(${node.label}, &val, ${varName}, ${count})) {`);
615
1167
  lines.push(`${indent}${INDENT}${INDENT}${node.stateVar}.set(val);`);
616
1168
  lines.push(`${indent}${INDENT}}`);
617
1169
  lines.push(`${indent}}`);
@@ -620,7 +1172,7 @@ function emitCombo(node, lines, indent) {
620
1172
  lines.push(`${indent}{`);
621
1173
  lines.push(`${indent}${INDENT}const char* ${varName}[] = {${itemsList.join(', ')}};`);
622
1174
  lines.push(`${indent}${INDENT}int val = ${node.valueExpr};`);
623
- lines.push(`${indent}${INDENT}if (reimgui::renderer::combo(${node.label}, &val, ${varName}, ${count})) {`);
1175
+ lines.push(`${indent}${INDENT}if (imx::renderer::combo(${node.label}, &val, ${varName}, ${count})) {`);
624
1176
  if (node.onChangeExpr) {
625
1177
  lines.push(`${indent}${INDENT}${INDENT}${node.onChangeExpr};`);
626
1178
  }
@@ -629,10 +1181,11 @@ function emitCombo(node, lines, indent) {
629
1181
  }
630
1182
  }
631
1183
  function emitInputInt(node, lines, indent) {
1184
+ emitLocComment(node.loc, 'InputInt', lines, indent);
632
1185
  if (node.stateVar) {
633
1186
  lines.push(`${indent}{`);
634
1187
  lines.push(`${indent}${INDENT}int val = ${node.stateVar}.get();`);
635
- lines.push(`${indent}${INDENT}if (reimgui::renderer::input_int(${node.label}, &val)) {`);
1188
+ lines.push(`${indent}${INDENT}if (imx::renderer::input_int(${node.label}, &val)) {`);
636
1189
  lines.push(`${indent}${INDENT}${INDENT}${node.stateVar}.set(val);`);
637
1190
  lines.push(`${indent}${INDENT}}`);
638
1191
  lines.push(`${indent}}`);
@@ -640,7 +1193,7 @@ function emitInputInt(node, lines, indent) {
640
1193
  else if (node.valueExpr !== undefined) {
641
1194
  lines.push(`${indent}{`);
642
1195
  lines.push(`${indent}${INDENT}int val = ${node.valueExpr};`);
643
- lines.push(`${indent}${INDENT}if (reimgui::renderer::input_int(${node.label}, &val)) {`);
1196
+ lines.push(`${indent}${INDENT}if (imx::renderer::input_int(${node.label}, &val)) {`);
644
1197
  if (node.onChangeExpr) {
645
1198
  lines.push(`${indent}${INDENT}${INDENT}${node.onChangeExpr};`);
646
1199
  }
@@ -649,10 +1202,11 @@ function emitInputInt(node, lines, indent) {
649
1202
  }
650
1203
  }
651
1204
  function emitInputFloat(node, lines, indent) {
1205
+ emitLocComment(node.loc, 'InputFloat', lines, indent);
652
1206
  if (node.stateVar) {
653
1207
  lines.push(`${indent}{`);
654
1208
  lines.push(`${indent}${INDENT}float val = ${node.stateVar}.get();`);
655
- lines.push(`${indent}${INDENT}if (reimgui::renderer::input_float(${node.label}, &val)) {`);
1209
+ lines.push(`${indent}${INDENT}if (imx::renderer::input_float(${node.label}, &val)) {`);
656
1210
  lines.push(`${indent}${INDENT}${INDENT}${node.stateVar}.set(val);`);
657
1211
  lines.push(`${indent}${INDENT}}`);
658
1212
  lines.push(`${indent}}`);
@@ -660,7 +1214,7 @@ function emitInputFloat(node, lines, indent) {
660
1214
  else if (node.valueExpr !== undefined) {
661
1215
  lines.push(`${indent}{`);
662
1216
  lines.push(`${indent}${INDENT}float val = ${node.valueExpr};`);
663
- lines.push(`${indent}${INDENT}if (reimgui::renderer::input_float(${node.label}, &val)) {`);
1217
+ lines.push(`${indent}${INDENT}if (imx::renderer::input_float(${node.label}, &val)) {`);
664
1218
  if (node.onChangeExpr) {
665
1219
  lines.push(`${indent}${INDENT}${INDENT}${node.onChangeExpr};`);
666
1220
  }
@@ -669,16 +1223,18 @@ function emitInputFloat(node, lines, indent) {
669
1223
  }
670
1224
  }
671
1225
  function emitColorEdit(node, lines, indent) {
1226
+ emitLocComment(node.loc, 'ColorEdit', lines, indent);
672
1227
  if (node.stateVar) {
673
1228
  lines.push(`${indent}{`);
674
1229
  lines.push(`${indent}${INDENT}auto val = ${node.stateVar}.get();`);
675
- lines.push(`${indent}${INDENT}if (reimgui::renderer::color_edit(${node.label}, val.data())) {`);
1230
+ lines.push(`${indent}${INDENT}if (imx::renderer::color_edit(${node.label}, val.data())) {`);
676
1231
  lines.push(`${indent}${INDENT}${INDENT}${node.stateVar}.set(val);`);
677
1232
  lines.push(`${indent}${INDENT}}`);
678
1233
  lines.push(`${indent}}`);
679
1234
  }
680
1235
  }
681
1236
  function emitListBox(node, lines, indent) {
1237
+ emitLocComment(node.loc, 'ListBox', lines, indent);
682
1238
  const itemsList = node.items.split(',').map(s => s.trim()).filter(s => s.length > 0);
683
1239
  const count = itemsList.length;
684
1240
  const varName = `listbox_items_${listBoxCounter++}`;
@@ -686,7 +1242,7 @@ function emitListBox(node, lines, indent) {
686
1242
  lines.push(`${indent}{`);
687
1243
  lines.push(`${indent}${INDENT}const char* ${varName}[] = {${itemsList.join(', ')}};`);
688
1244
  lines.push(`${indent}${INDENT}int val = ${node.stateVar}.get();`);
689
- lines.push(`${indent}${INDENT}if (reimgui::renderer::list_box(${node.label}, &val, ${varName}, ${count})) {`);
1245
+ lines.push(`${indent}${INDENT}if (imx::renderer::list_box(${node.label}, &val, ${varName}, ${count})) {`);
690
1246
  lines.push(`${indent}${INDENT}${INDENT}${node.stateVar}.set(val);`);
691
1247
  lines.push(`${indent}${INDENT}}`);
692
1248
  lines.push(`${indent}}`);
@@ -695,7 +1251,7 @@ function emitListBox(node, lines, indent) {
695
1251
  lines.push(`${indent}{`);
696
1252
  lines.push(`${indent}${INDENT}const char* ${varName}[] = {${itemsList.join(', ')}};`);
697
1253
  lines.push(`${indent}${INDENT}int val = ${node.valueExpr};`);
698
- lines.push(`${indent}${INDENT}if (reimgui::renderer::list_box(${node.label}, &val, ${varName}, ${count})) {`);
1254
+ lines.push(`${indent}${INDENT}if (imx::renderer::list_box(${node.label}, &val, ${varName}, ${count})) {`);
699
1255
  if (node.onChangeExpr) {
700
1256
  lines.push(`${indent}${INDENT}${INDENT}${node.onChangeExpr};`);
701
1257
  }
@@ -704,13 +1260,187 @@ function emitListBox(node, lines, indent) {
704
1260
  }
705
1261
  }
706
1262
  function emitProgressBar(node, lines, indent) {
1263
+ emitLocComment(node.loc, 'ProgressBar', lines, indent);
707
1264
  if (node.overlay) {
708
- lines.push(`${indent}reimgui::renderer::progress_bar(${node.value}, ${node.overlay});`);
1265
+ lines.push(`${indent}imx::renderer::progress_bar(${node.value}, ${node.overlay});`);
709
1266
  }
710
1267
  else {
711
- lines.push(`${indent}reimgui::renderer::progress_bar(${node.value});`);
1268
+ lines.push(`${indent}imx::renderer::progress_bar(${node.value});`);
712
1269
  }
713
1270
  }
714
1271
  function emitTooltip(node, lines, indent) {
715
- lines.push(`${indent}reimgui::renderer::tooltip(${node.text});`);
1272
+ emitLocComment(node.loc, 'Tooltip', lines, indent);
1273
+ lines.push(`${indent}imx::renderer::tooltip(${node.text});`);
1274
+ }
1275
+ function emitBulletText(node, lines, indent) {
1276
+ emitLocComment(node.loc, 'BulletText', lines, indent);
1277
+ if (node.args.length === 0) {
1278
+ lines.push(`${indent}imx::renderer::bullet_text("${node.format}");`);
1279
+ }
1280
+ else {
1281
+ const fmtArgs = node.args.map(a => {
1282
+ if (a.startsWith('"'))
1283
+ return a;
1284
+ return `std::to_string(${a}).c_str()`;
1285
+ }).join(', ');
1286
+ lines.push(`${indent}imx::renderer::bullet_text("${node.format}", ${fmtArgs});`);
1287
+ }
1288
+ }
1289
+ function emitLabelText(node, lines, indent) {
1290
+ emitLocComment(node.loc, 'LabelText', lines, indent);
1291
+ lines.push(`${indent}imx::renderer::label_text(${asCharPtr(node.label)}, ${asCharPtr(node.value)});`);
1292
+ }
1293
+ function emitSelectable(node, lines, indent) {
1294
+ emitLocComment(node.loc, 'Selectable', lines, indent);
1295
+ const label = asCharPtr(node.label);
1296
+ if (node.action.length > 0) {
1297
+ lines.push(`${indent}if (imx::renderer::selectable(${label}, ${node.selected})) {`);
1298
+ for (const stmt of node.action) {
1299
+ lines.push(`${indent}${INDENT}${stmt}`);
1300
+ }
1301
+ lines.push(`${indent}}`);
1302
+ }
1303
+ else {
1304
+ lines.push(`${indent}imx::renderer::selectable(${label}, ${node.selected});`);
1305
+ }
1306
+ }
1307
+ function emitRadio(node, lines, indent) {
1308
+ emitLocComment(node.loc, 'Radio', lines, indent);
1309
+ const label = asCharPtr(node.label);
1310
+ if (node.stateVar) {
1311
+ lines.push(`${indent}{`);
1312
+ lines.push(`${indent}${INDENT}int val = ${node.stateVar}.get();`);
1313
+ lines.push(`${indent}${INDENT}if (imx::renderer::radio(${label}, &val, ${node.index})) {`);
1314
+ lines.push(`${indent}${INDENT}${INDENT}${node.stateVar}.set(val);`);
1315
+ lines.push(`${indent}${INDENT}}`);
1316
+ lines.push(`${indent}}`);
1317
+ }
1318
+ else if (node.valueExpr !== undefined) {
1319
+ lines.push(`${indent}{`);
1320
+ lines.push(`${indent}${INDENT}int val = ${node.valueExpr};`);
1321
+ lines.push(`${indent}${INDENT}if (imx::renderer::radio(${label}, &val, ${node.index})) {`);
1322
+ if (node.onChangeExpr) {
1323
+ lines.push(`${indent}${INDENT}${INDENT}${node.onChangeExpr};`);
1324
+ }
1325
+ lines.push(`${indent}${INDENT}}`);
1326
+ lines.push(`${indent}}`);
1327
+ }
1328
+ }
1329
+ function emitInputTextMultiline(node, lines, indent) {
1330
+ emitLocComment(node.loc, 'InputTextMultiline', lines, indent);
1331
+ lines.push(`${indent}{`);
1332
+ const innerIndent = indent + INDENT;
1333
+ const styleVar = buildStyleVar(node.style, innerIndent, lines);
1334
+ lines.push(`${innerIndent}auto& buf = ctx.get_buffer(${node.bufferIndex});`);
1335
+ if (node.stateVar) {
1336
+ lines.push(`${innerIndent}buf.sync_from(${node.stateVar}.get());`);
1337
+ }
1338
+ const styleArg = styleVar ? `, ${styleVar}` : '';
1339
+ lines.push(`${innerIndent}if (imx::renderer::text_input_multiline(${node.label}, buf${styleArg})) {`);
1340
+ if (node.stateVar) {
1341
+ lines.push(`${innerIndent}${INDENT}${node.stateVar}.set(buf.value());`);
1342
+ }
1343
+ lines.push(`${innerIndent}}`);
1344
+ lines.push(`${indent}}`);
1345
+ }
1346
+ function emitColorPicker(node, lines, indent) {
1347
+ emitLocComment(node.loc, 'ColorPicker', lines, indent);
1348
+ if (node.stateVar) {
1349
+ lines.push(`${indent}{`);
1350
+ lines.push(`${indent}${INDENT}auto val = ${node.stateVar}.get();`);
1351
+ lines.push(`${indent}${INDENT}if (imx::renderer::color_picker(${node.label}, val.data())) {`);
1352
+ lines.push(`${indent}${INDENT}${INDENT}${node.stateVar}.set(val);`);
1353
+ lines.push(`${indent}${INDENT}}`);
1354
+ lines.push(`${indent}}`);
1355
+ }
1356
+ }
1357
+ function emitPlotLines(node, lines, indent) {
1358
+ emitLocComment(node.loc, 'PlotLines', lines, indent);
1359
+ const idx = plotCounter++;
1360
+ const varName = `_plot_${idx}`;
1361
+ const values = node.values.split(',').map(v => ensureFloatLiteral(v.trim()));
1362
+ const count = values.length;
1363
+ lines.push(`${indent}{`);
1364
+ const innerIndent = indent + INDENT;
1365
+ const styleVar = buildStyleVar(node.style, innerIndent, lines);
1366
+ lines.push(`${innerIndent}float ${varName}[] = {${values.join(', ')}};`);
1367
+ const overlay = node.overlay ? `, ${node.overlay}` : ', nullptr';
1368
+ const styleArg = styleVar ? `, ${styleVar}` : '';
1369
+ lines.push(`${innerIndent}imx::renderer::plot_lines(${node.label}, ${varName}, ${count}${overlay}${styleArg});`);
1370
+ lines.push(`${indent}}`);
1371
+ }
1372
+ function emitPlotHistogram(node, lines, indent) {
1373
+ emitLocComment(node.loc, 'PlotHistogram', lines, indent);
1374
+ const idx = plotCounter++;
1375
+ const varName = `_plot_${idx}`;
1376
+ const values = node.values.split(',').map(v => ensureFloatLiteral(v.trim()));
1377
+ const count = values.length;
1378
+ lines.push(`${indent}{`);
1379
+ const innerIndent = indent + INDENT;
1380
+ const styleVar = buildStyleVar(node.style, innerIndent, lines);
1381
+ lines.push(`${innerIndent}float ${varName}[] = {${values.join(', ')}};`);
1382
+ const overlay = node.overlay ? `, ${node.overlay}` : ', nullptr';
1383
+ const styleArg = styleVar ? `, ${styleVar}` : '';
1384
+ lines.push(`${innerIndent}imx::renderer::plot_histogram(${node.label}, ${varName}, ${count}${overlay}${styleArg});`);
1385
+ lines.push(`${indent}}`);
1386
+ }
1387
+ function emitImage(node, lines, indent) {
1388
+ emitLocComment(node.loc, 'Image', lines, indent);
1389
+ const width = node.width ? ensureFloatLiteral(node.width) : '0';
1390
+ const height = node.height ? ensureFloatLiteral(node.height) : '0';
1391
+ if (node.embed && node.embedKey) {
1392
+ // Embedded mode: reference the data from the .embed.h header
1393
+ lines.push(`${indent}imx::renderer::image_embedded("${node.embedKey}", ${node.embedKey}_data, ${node.embedKey}_size, ${width}, ${height});`);
1394
+ }
1395
+ else {
1396
+ // File mode: pass the path string
1397
+ lines.push(`${indent}imx::renderer::image(${node.src}, ${width}, ${height});`);
1398
+ }
1399
+ }
1400
+ function emitDrawLine(node, lines, indent) {
1401
+ const p1Parts = node.p1.split(',').map((s) => emitFloat(s.trim()));
1402
+ const p2Parts = node.p2.split(',').map((s) => emitFloat(s.trim()));
1403
+ const color = emitImVec4(node.color);
1404
+ const thickness = emitFloat(node.thickness);
1405
+ lines.push(`${indent}imx::renderer::draw_line(${p1Parts.join(', ')}, ${p2Parts.join(', ')}, ${color}, ${thickness});`);
1406
+ }
1407
+ function emitDrawRect(node, lines, indent) {
1408
+ const minParts = node.min.split(',').map((s) => emitFloat(s.trim()));
1409
+ const maxParts = node.max.split(',').map((s) => emitFloat(s.trim()));
1410
+ const color = emitImVec4(node.color);
1411
+ const filled = node.filled;
1412
+ const thickness = emitFloat(node.thickness);
1413
+ const rounding = emitFloat(node.rounding);
1414
+ lines.push(`${indent}imx::renderer::draw_rect(${minParts.join(', ')}, ${maxParts.join(', ')}, ${color}, ${filled}, ${thickness}, ${rounding});`);
1415
+ }
1416
+ function emitDrawCircle(node, lines, indent) {
1417
+ const centerParts = node.center.split(',').map((s) => emitFloat(s.trim()));
1418
+ const radius = emitFloat(node.radius);
1419
+ const color = emitImVec4(node.color);
1420
+ const filled = node.filled;
1421
+ const thickness = emitFloat(node.thickness);
1422
+ lines.push(`${indent}imx::renderer::draw_circle(${centerParts.join(', ')}, ${radius}, ${color}, ${filled}, ${thickness});`);
1423
+ }
1424
+ function emitDrawText(node, lines, indent) {
1425
+ const posParts = node.pos.split(',').map((s) => emitFloat(s.trim()));
1426
+ const color = emitImVec4(node.color);
1427
+ const text = asCharPtr(node.text);
1428
+ lines.push(`${indent}imx::renderer::draw_text(${posParts.join(', ')}, ${color}, ${text});`);
1429
+ }
1430
+ function collectEmbedKeys(nodes) {
1431
+ const keys = [];
1432
+ for (const node of nodes) {
1433
+ if (node.kind === 'image' && node.embed && node.embedKey) {
1434
+ keys.push(node.embedKey);
1435
+ }
1436
+ else if (node.kind === 'conditional') {
1437
+ keys.push(...collectEmbedKeys(node.body));
1438
+ if (node.elseBody)
1439
+ keys.push(...collectEmbedKeys(node.elseBody));
1440
+ }
1441
+ else if (node.kind === 'list_map') {
1442
+ keys.push(...collectEmbedKeys(node.body));
1443
+ }
1444
+ }
1445
+ return keys;
716
1446
  }