@typecad/ui 1.0.0-alpha.12 → 1.0.0-alpha.13

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.
Files changed (113) hide show
  1. package/assets/fonts/dejavu/DejaVuSans-Bold.ttf +0 -0
  2. package/assets/fonts/dejavu/DejaVuSans-ExtraLight.ttf +0 -0
  3. package/assets/fonts/dejavu/DejaVuSans-Oblique.ttf +0 -0
  4. package/assets/fonts/dejavu/DejaVuSans.ttf +0 -0
  5. package/assets/fonts/dejavu/DejaVuSansMono-Bold.ttf +0 -0
  6. package/assets/fonts/dejavu/DejaVuSansMono.ttf +0 -0
  7. package/assets/fonts/dejavu/LICENSE +187 -0
  8. package/assets/fonts/dejavu/README.md +49 -0
  9. package/dist/engine-index.js +2 -0
  10. package/dist/preview/build-program.d.ts +7 -1
  11. package/dist/preview/build-program.js +94 -15
  12. package/dist/preview/host-gfx.d.ts +3 -0
  13. package/dist/preview/host-gfx.js +30 -3
  14. package/dist/preview/host-ui-runtime.d.ts +115 -0
  15. package/dist/preview/host-ui-runtime.js +928 -46
  16. package/dist/ui-engine/block-layout.js +25 -11
  17. package/dist/ui-engine/color.d.ts +6 -0
  18. package/dist/ui-engine/color.js +80 -2
  19. package/dist/ui-engine/compat-report.d.ts +8 -0
  20. package/dist/ui-engine/compat-report.js +124 -0
  21. package/dist/ui-engine/css-imports.d.ts +4 -0
  22. package/dist/ui-engine/css-imports.js +70 -0
  23. package/dist/ui-engine/css-parser.js +58 -14
  24. package/dist/ui-engine/default-font.d.ts +25 -0
  25. package/dist/ui-engine/default-font.js +80 -0
  26. package/dist/ui-engine/font-assets.d.ts +2 -2
  27. package/dist/ui-engine/font-assets.js +22 -5
  28. package/dist/ui-engine/html-parser.d.ts +4 -0
  29. package/dist/ui-engine/html-parser.js +241 -26
  30. package/dist/ui-engine/image-assets.d.ts +8 -1
  31. package/dist/ui-engine/image-assets.js +35 -2
  32. package/dist/ui-engine/image-decode.d.ts +22 -0
  33. package/dist/ui-engine/image-decode.js +194 -0
  34. package/dist/ui-engine/inline-parser.js +1 -1
  35. package/dist/ui-engine/layout-engine.d.ts +19 -0
  36. package/dist/ui-engine/layout-engine.js +59 -3
  37. package/dist/ui-engine/model.d.ts +18 -0
  38. package/dist/ui-engine/model.js +75 -7
  39. package/dist/ui-engine/runtime-header/blend-bodies.js +29 -9
  40. package/dist/ui-engine/runtime-header/canvas-helpers.js +3 -0
  41. package/dist/ui-engine/runtime-header/canvas-scrollbar.js +115 -111
  42. package/dist/ui-engine/runtime-header/cuttlefish-gfx.js +21 -0
  43. package/dist/ui-engine/runtime-header/dirty-scroll-mutators.js +182 -179
  44. package/dist/ui-engine/runtime-header/forward-decls.js +215 -167
  45. package/dist/ui-engine/runtime-header/init-press-input.js +138 -135
  46. package/dist/ui-engine/runtime-header/keyboard.js +682 -380
  47. package/dist/ui-engine/runtime-header/node-draw-body.js +1656 -1386
  48. package/dist/ui-engine/runtime-header/paint-order-coords.js +259 -214
  49. package/dist/ui-engine/runtime-header/paint-rects-repair.js +736 -568
  50. package/dist/ui-engine/runtime-header/scroll-physics.js +25 -1
  51. package/dist/ui-engine/runtime-header/state-bindings-nav.js +9 -0
  52. package/dist/ui-engine/runtime-header/structs.js +228 -216
  53. package/dist/ui-engine/runtime-header/text-rendering.js +888 -770
  54. package/dist/ui-engine/runtime-header/tick/bindings-phase.js +118 -116
  55. package/dist/ui-engine/runtime-header/tick/dirty-draw-phase.js +897 -633
  56. package/dist/ui-engine/runtime-header/tick/scroll-canvas-phase.js +24 -25
  57. package/dist/ui-engine/runtime-header/touch-keyboard-fwd.js +78 -23
  58. package/dist/ui-engine/runtime-header/types-defines.js +110 -93
  59. package/dist/ui-engine/shadcn-kit.d.ts +1 -0
  60. package/dist/ui-engine/shadcn-kit.js +502 -0
  61. package/dist/ui-engine/style-resolver.d.ts +4 -0
  62. package/dist/ui-engine/style-resolver.js +58 -6
  63. package/dist/ui-engine/transpile-ui.js +12 -5
  64. package/dist/ui-engine/ua-stylesheet.d.ts +22 -1
  65. package/dist/ui-engine/ua-stylesheet.js +119 -32
  66. package/dist/ui-engine/ui-lowering.js +37 -3
  67. package/dist/ui-engine/ui-registry.js +32 -4
  68. package/dist/ui-engine/yoga-layout.js +57 -52
  69. package/package.json +9 -3
  70. package/src/engine-index.ts +53 -51
  71. package/src/preview/build-program.ts +810 -734
  72. package/src/preview/host-gfx.ts +30 -3
  73. package/src/preview/host-ui-runtime.ts +4133 -3289
  74. package/src/ui-engine/block-layout.ts +24 -11
  75. package/src/ui-engine/color.ts +77 -2
  76. package/src/ui-engine/compat-report.ts +139 -0
  77. package/src/ui-engine/css-imports.ts +69 -0
  78. package/src/ui-engine/css-parser.ts +64 -15
  79. package/src/ui-engine/default-font.ts +95 -0
  80. package/src/ui-engine/font-assets.ts +545 -525
  81. package/src/ui-engine/html-parser.ts +615 -397
  82. package/src/ui-engine/image-assets.ts +37 -2
  83. package/src/ui-engine/image-decode.ts +244 -0
  84. package/src/ui-engine/inline-parser.ts +1 -1
  85. package/src/ui-engine/layout-engine.ts +61 -3
  86. package/src/ui-engine/model.ts +1317 -1230
  87. package/src/ui-engine/runtime-header/blend-bodies.ts +29 -9
  88. package/src/ui-engine/runtime-header/canvas-helpers.ts +3 -0
  89. package/src/ui-engine/runtime-header/canvas-scrollbar.ts +121 -117
  90. package/src/ui-engine/runtime-header/cuttlefish-gfx.ts +21 -0
  91. package/src/ui-engine/runtime-header/dirty-scroll-mutators.ts +188 -185
  92. package/src/ui-engine/runtime-header/forward-decls.ts +227 -179
  93. package/src/ui-engine/runtime-header/init-press-input.ts +144 -141
  94. package/src/ui-engine/runtime-header/keyboard.ts +689 -386
  95. package/src/ui-engine/runtime-header/node-draw-body.ts +1681 -1411
  96. package/src/ui-engine/runtime-header/paint-order-coords.ts +265 -220
  97. package/src/ui-engine/runtime-header/paint-rects-repair.ts +742 -574
  98. package/src/ui-engine/runtime-header/scroll-physics.ts +25 -1
  99. package/src/ui-engine/runtime-header/state-bindings-nav.ts +9 -0
  100. package/src/ui-engine/runtime-header/structs.ts +234 -222
  101. package/src/ui-engine/runtime-header/text-rendering.ts +894 -776
  102. package/src/ui-engine/runtime-header/tick/bindings-phase.ts +124 -122
  103. package/src/ui-engine/runtime-header/tick/dirty-draw-phase.ts +902 -638
  104. package/src/ui-engine/runtime-header/tick/scroll-canvas-phase.ts +30 -31
  105. package/src/ui-engine/runtime-header/touch-keyboard-fwd.ts +78 -23
  106. package/src/ui-engine/runtime-header/types-defines.ts +116 -99
  107. package/src/ui-engine/shadcn-kit.ts +503 -0
  108. package/src/ui-engine/style-resolver.ts +471 -416
  109. package/src/ui-engine/transpile-ui.ts +12 -5
  110. package/src/ui-engine/ua-stylesheet.ts +137 -31
  111. package/src/ui-engine/ui-lowering.ts +486 -452
  112. package/src/ui-engine/ui-registry.ts +556 -524
  113. package/src/ui-engine/yoga-layout.ts +307 -309
@@ -98,6 +98,20 @@ static inline uint8_t ui_apply_scroll_delta(int16_t node, int16_t dy) {
98
98
  static inline uint8_t ui_scroll_release(int16_t node) {
99
99
  if (node < 0) return 0;
100
100
  uint8_t changed = 0;
101
+ // Only one settle animation owns the shared start state. If another node is
102
+ // still mid-settle, snap it to its end state so its stale animation can't
103
+ // consume this release's start values.
104
+ if (__ui_settle_node != 0xFFFFu && __ui_settle_node != static_cast<uint16_t>(node) &&
105
+ __ui_settle_node < __ui_node_count && __ui_nodes[__ui_settle_node].settling) {
106
+ __ui_nodes[__ui_settle_node].overscrollPx = 0;
107
+ if (__ui_settle_from_scrollY > 0) {
108
+ __ui_nodes[__ui_settle_node].scrollY = 0;
109
+ } else if (__ui_settle_from_scrollY < 0) {
110
+ __ui_nodes[__ui_settle_node].scrollY = ui_scroll_max(static_cast<int16_t>(__ui_settle_node));
111
+ }
112
+ __ui_nodes[__ui_settle_node].settling = 0;
113
+ ui_mark_scroll_view_dirty(__ui_settle_node);
114
+ }
101
115
  if (__ui_nodes[node].overscrollPx != 0) {
102
116
  __ui_nodes[node].settling = 1;
103
117
  __ui_settle_from_overscroll = __ui_nodes[node].overscrollPx;
@@ -122,6 +136,7 @@ static inline uint8_t ui_scroll_release(int16_t node) {
122
136
  changed = 1;
123
137
  }
124
138
  }
139
+ __ui_settle_node = static_cast<uint16_t>(node);
125
140
  return changed;
126
141
  }
127
142
 
@@ -130,6 +145,12 @@ static inline uint8_t ui_scroll_release(int16_t node) {
130
145
  static inline void ui_scroll_advance_settle(uint16_t node, uint16_t deltaMs) {
131
146
  (void)deltaMs;
132
147
  if (node >= __ui_node_count || !__ui_nodes[node].settling) return;
148
+ // The settle start state belongs to __ui_settle_node only; a settling flag
149
+ // on any other node is stale (superseded by a newer release) — drop it.
150
+ if (node != __ui_settle_node) {
151
+ __ui_nodes[node].settling = 0;
152
+ return;
153
+ }
133
154
  uint32_t elapsed = millis() - __ui_settle_start_ms;
134
155
  uint16_t dur = static_cast<uint16_t>(UI_SCROLL_SETTLE_MS);
135
156
  // ease-out: k = 1 - (1 - t)^2, t in [0,1]
@@ -151,7 +172,10 @@ static inline void ui_scroll_advance_settle(uint16_t node, uint16_t deltaMs) {
151
172
  if (t >= 100) __ui_nodes[node].scrollY = target;
152
173
  }
153
174
  }
154
- if (t >= 100) __ui_nodes[node].settling = 0;
175
+ if (t >= 100) {
176
+ __ui_nodes[node].settling = 0;
177
+ __ui_settle_node = 0xFFFF;
178
+ }
155
179
  ui_mark_scroll_view_dirty(node);
156
180
  }
157
181
  `;
@@ -32,6 +32,14 @@ struct UIScrollPaintCandidate {
32
32
  uint16_t node;
33
33
  int16_t screenY;
34
34
  int16_t faceH;
35
+ // Absolute clip bounds for the screen-band compositor: scrolled children
36
+ // clip to their scroll viewport (CSS overflow). Unclipped candidates use
37
+ // the full i16 range. The scroll-band renderer ignores these (its canvas
38
+ // IS the viewport) and only sets node/screenY/faceH.
39
+ int16_t clipTop;
40
+ int16_t clipBottom;
41
+ int16_t clipLeft;
42
+ int16_t clipRight;
35
43
  };
36
44
  static UIScrollPaintCandidate* __ui_scroll_candidates = nullptr;
37
45
  static uint16_t __ui_scroll_candidate_count = 0;
@@ -40,6 +48,7 @@ static uint16_t __ui_active_screen_bg_node = 0xFFFF;
40
48
  static uint32_t __ui_settle_start_ms = 0; // when the active settle animation began
41
49
  static int16_t __ui_settle_from_overscroll = 0; // settle start value (bounce-back)
42
50
  static int16_t __ui_settle_from_scrollY = 0; // settle start value (edge snap; sign: +toward 0, -toward max)
51
+ static uint16_t __ui_settle_node = 0xFFFF; // node owning the active settle; only one settles at a time
43
52
  static uint8_t __ui_kb_visible = 0;
44
53
 
45
54
  static uint8_t __ui_active_screen = 0; // which screen is visible/interactive
@@ -1,222 +1,234 @@
1
- // Slice of the C++ runtime header (original source lines 90-303).
2
- // Enums (UINodeKind, UIProperty) and all struct definitions + extern table decls.
3
- // See docs/superpowers/specs/2026-07-12-split-runtime-header-design.md.
4
- export function emitStructs(): string {
5
- return `
6
- enum UINodeKind { NODE_FILL, NODE_TEXT, NODE_BUTTON, NODE_CHECK, NODE_RADIO, NODE_PROGRESS, NODE_RANGE, NODE_INPUT, NODE_IMG, NODE_LIST, NODE_CANVAS, NODE_SELECT };
7
- enum UIProperty { PROP_BG, PROP_FG, PROP_TEXT, PROP_VISIBLE, PROP_BORDER_COLOR, PROP_VALUE };
8
-
9
- struct UIRect { int16_t x, y, w, h; };
10
- struct UIFontGlyph {
11
- uint16_t codepoint;
12
- int8_t xOffset;
13
- int8_t yOffset;
14
- uint8_t width;
15
- uint8_t height;
16
- uint8_t advance;
17
- uint16_t dataOffset; // 4-bit alpha pixel offset
18
- };
19
- struct UIFontFace {
20
- uint8_t id;
21
- uint8_t glyphCount;
22
- uint8_t lineHeight;
23
- uint8_t baseline;
24
- const UIFontGlyph* glyphs;
25
- const uint8_t* alpha;
26
- };
27
- struct UINode {
28
- UIRect box;
29
- uint32_t bg;
30
- uint32_t fg;
31
- UINodeKind kind;
32
- const char* text;
33
- char textBuffer[UI_TEXT_BUF + 1]; // dynamic text — read only when hasTextBinding == 1
34
- uint8_t hasTextBinding; // set by ui_init when a PROP_TEXT binding targets this node
35
- const uint8_t* font;
36
- uint8_t hasBg;
37
- uint8_t textAlign; // 0=left, 1=center, 2=right
38
- uint8_t textSize; // GFX text size: 1-4
39
- uint8_t lineHeight; // px per text line (0 = font default)
40
- int8_t letterSpacing; // px between chars (0 = default advance)
41
- uint8_t fontAntialias; // 1 = smooth text edges when UI_AA is available
42
- uint8_t fontFace; // 0 = classic GFX bitmap font; otherwise UIFontFace id
43
- uint32_t borderColor; // resolved color for the border (0 = use fg)
44
- uint8_t borderStyle; // 0=none, 1=solid, 2=dashed
45
- uint8_t borderWidth; // px, 0=none (uniform fallback)
46
- uint8_t borderTopWidth; // per-side; equals borderWidth when uniform
47
- uint8_t borderRightWidth;
48
- uint8_t borderBottomWidth;
49
- uint8_t borderLeftWidth;
50
- uint8_t hasPerSideBorder; // 1 when any per-side width differs from borderWidth
51
- uint8_t borderRadius; // px, 0=square
52
- uint8_t paddingTop;
53
- uint8_t paddingRight;
54
- uint8_t paddingBottom;
55
- uint8_t paddingLeft;
56
- uint8_t gradientEnabled; // 0=none, 1=vertical, 2=horizontal
57
- uint32_t gradientColor1;
58
- uint32_t gradientColor2;
59
- uint32_t outlineColor;
60
- uint8_t outlineStyle; // 0=none, 1=solid, 2=dashed
61
- uint8_t outlineWidth;
62
- int16_t zIndex; // effective draw layer; higher layers draw later
63
- int16_t transformOffsetX; // draw-only transform: translate(...)
64
- int16_t transformOffsetY;
65
- int16_t rotateDeg;
66
- int8_t pressedOffsetX; // draw-only :pressed offset, no Yoga relayout
67
- int8_t pressedOffsetY;
68
- uint8_t shadowCount; // 0-4 active shadows
69
- int8_t shadowOffsetX[4];
70
- int8_t shadowOffsetY[4];
71
- uint8_t shadowBlur[4];
72
- uint32_t shadowColor[4];
73
- uint8_t shadowAlpha[4];
74
- uint8_t shadowInset[4]; // 0=outset, 1=inset
75
- uint8_t textShadowCount;
76
- int8_t textShadowOffsetX;
77
- int8_t textShadowOffsetY;
78
- uint8_t textShadowBlur;
79
- uint32_t textShadowColor;
80
- uint8_t textShadowAlpha;
81
- uint8_t underline; // text-decoration: 0=none,1=underline,2=line-through,3=both
82
- uint8_t textOverflow; // text-overflow: 0=clip, 1=ellipsis (truncate + ...)
83
- uint8_t nowrap; // 1 = no text wrapping (white-space: nowrap/pre)
84
- uint8_t whiteSpaceMode; // 0=normal, 1=nowrap, 2=pre, 3=pre-line
85
- uint8_t visible; // 0=hidden, 1=visible
86
- uint8_t opacity; // 0-100
87
- uint32_t clearColor; // ancestor's background — used to wipe transparent text before redraw
88
- int16_t lastTextWidth;
89
- int16_t lastTextHeight;
90
- uint32_t layoutCacheKey; // 0 = invalid; non-zero hashes layout inputs
91
- uint16_t layoutMetricsW; // cached ui_text_layout_metrics width
92
- uint16_t layoutMetricsH; // cached ui_text_layout_metrics height
93
- // scroll (unified: containers and virtualized lists share these)
94
- uint8_t scrollable; // 1 = children offset by scrollY, clipped to this box
95
- uint8_t virtualized; // 1 = children produced by list*Fn callbacks (<list>)
96
- int16_t scrollY; // committed offset (always in [0, maxScroll]); draw subtracts it
97
- int16_t contentHeight; // total child height (clamp bound + scrollbar ratio)
98
- int16_t overscrollPx; // elastic excursion past a boundary (0 in-bounds; +top, -bottom)
99
- uint8_t settling; // 1 while a bounce-back/snap animation runs
100
- int16_t lastPaintedScrollY; // scrollY at last container repaint (Mode B shift delta)
101
- uint16_t listCount; // virtualized: current item count (refreshed each frame)
102
- uint16_t (*listCountFn)(void);
103
- void (*listItemFn)(uint16_t idx, char* buf, uint8_t size);
104
- void (*listTapFn)(uint16_t idx); // nullptr if no tap handler
105
- uint16_t parent; // 0xFFFF = root/no parent (UI_NO_PARENT)
106
- uint16_t subtreeEnd; // exclusive pre-order end index
107
- uint8_t screenId; // which <screen> this node belongs to (for navigation)
108
- uint8_t imgDataId; // index into __ui_images[] (255 = no image)
109
- uint8_t objectFit; // 0=none, 1=fill, 2=contain, 3=cover, 4=scale-down
110
- uint16_t listItemHeight; // px per item for <list> (0 = not a list)
111
- int16_t rangeMin; // for <range>: minimum value
112
- int16_t rangeMax; // for <range>: maximum value
113
- int16_t maxlen; // for <input>: max character length (0 = UI_TEXT_BUF)
114
- uint16_t canvasW; // canvas buffer width (for <canvas>)
115
- uint16_t canvasH; // canvas buffer height (for <canvas>)
116
- // Rich-text runs (runCount > 0 for text nodes with mixed inline content).
117
- // The node references a contiguous slice of the global run / segment / line
118
- // arrays; geometry is precomputed at transpile time (runs are static-only).
119
- uint8_t runCount; // number of runs in this node (0 = plain single-string text)
120
- uint8_t richLineCount; // number of wrapped lines
121
- uint16_t runStart; // first index into __ui_runs[]
122
- uint16_t richSegStart; // first index into __ui_rich_segs[]
123
- uint16_t richSegCount; // total segments across all lines
124
- uint16_t richLineStart; // first index into __ui_rich_lines[]
125
- // runtime slot
126
- uint8_t dirty;
127
- int16_t value; // unified element state
128
- };
129
- // Rich-text run: one piece of styled inline text within a node's run list.
130
- struct UIRichRun {
131
- const char* text;
132
- uint32_t fg;
133
- uint8_t textSize;
134
- uint8_t fontFace;
135
- uint8_t underline; // 0=none,1=underline,2=line-through,3=both
136
- int8_t letterSpacing;
137
- int8_t linkTarget; // resolved screen index, -1 = not a link
138
- };
139
- // One laid-out segment of a run on one line (precomputed geometry).
140
- struct UIRichSeg {
141
- uint8_t runIndex; // index into the node's runs (0..runCount-1)
142
- const char* text;
143
- int16_t x; // offset from the line's left edge (pre-alignment)
144
- uint16_t w; // measured width
145
- uint8_t line; // which line (0..richLineCount-1) this segment is on
146
- };
147
- // One wrapped line of rich text (precomputed).
148
- struct UIRichLine {
149
- int16_t y; // top y relative to the node's text top
150
- uint16_t h; // line height (tallest run on this line)
151
- int16_t baseline; // baseline y (for mixed-size baseline alignment)
152
- uint16_t w; // total line width (for alignment)
153
- };
154
- struct UITransition {
155
- uint16_t node;
156
- UIProperty prop;
157
- uint16_t durationMs;
158
- // The :pressed and base-state target colors. ui_on_press arms toward
159
- // pressedTarget; ui_on_release arms toward baseTarget.
160
- uint32_t pressedTarget;
161
- uint32_t baseTarget;
162
- // runtime
163
- uint32_t elapsed;
164
- uint32_t prevValue;
165
- uint32_t targetValue;
166
- uint8_t active;
167
- };
168
- struct UIBinding {
169
- uint16_t node;
170
- UIProperty prop;
171
- uint32_t (*fn)(void); // for color/numeric bindings
172
- void (*textFn)(char* buf, uint8_t size); // for text bindings (PROP_TEXT): fills buf
173
- uint32_t lastValue; // numeric binding cache; avoids redundant dirty work
174
- uint8_t initialized; // first evaluation establishes the baseline
175
- };
176
-
177
- // Color lerp for transitions (rgb565). For mono, this collapses to a snap.
178
- static inline uint16_t lerp_color(uint16_t a, uint16_t b, uint8_t k100) {
179
- if (k100 >= 100) return b;
180
- // Lerp in 888 internally for smoother color transitions (keyframe animation,
181
- // :pressed transitions). Unpack 565→888, lerp at 8-bit, re-quantize to 565.
182
- uint8_t ar5 = (a >> 11) & 0x1f, ag6 = (a >> 5) & 0x3f, ab5 = a & 0x1f;
183
- uint8_t br5 = (b >> 11) & 0x1f, bg6 = (b >> 5) & 0x3f, bb5 = b & 0x1f;
184
- uint16_t ar8 = (ar5 << 3) | (ar5 >> 2), ag8 = (ag6 << 2) | (ag6 >> 4), ab8 = (ab5 << 3) | (ab5 >> 2);
185
- uint16_t br8 = (br5 << 3) | (br5 >> 2), bg8 = (bg6 << 2) | (bg6 >> 4), bb8 = (bb5 << 3) | (bb5 >> 2);
186
- int16_t r = static_cast<int16_t>(ar8 + static_cast<int16_t>((br8 - ar8) * k100 / 100));
187
- int16_t g = static_cast<int16_t>(ag8 + static_cast<int16_t>((bg8 - ag8) * k100 / 100));
188
- int16_t bl = static_cast<int16_t>(ab8 + static_cast<int16_t>((bb8 - ab8) * k100 / 100));
189
- return (static_cast<uint16_t>((r >> 3) & 0x1f) << 11) | (static_cast<uint16_t>((g >> 2) & 0x3f) << 5) | static_cast<uint16_t>((bl >> 3) & 0x1f);
190
- }
191
-
192
- // RGB888 lerp for transitions on RGB888/RGB666 targets (Phase 2+). Unused in
193
- // Phase 1; the 565 lerp_color above remains the active path for TFT targets,
194
- // whose node colors are still emitted as 565 values stored in uint32_t fields.
195
- static inline uint32_t lerp_color_888(uint32_t a, uint32_t b, uint8_t k100) {
196
- if (k100 >= 100) return b;
197
- uint8_t ar = (a >> 16) & 0xff, ag = (a >> 8) & 0xff, ab = a & 0xff;
198
- uint8_t br = (b >> 16) & 0xff, bg = (b >> 8) & 0xff, bb = b & 0xff;
199
- int16_t r = ar + static_cast<int16_t>((static_cast<int16_t>(br) - static_cast<int16_t>(ar)) * k100 / 100);
200
- int16_t g = ag + static_cast<int16_t>((static_cast<int16_t>(bg) - static_cast<int16_t>(ag)) * k100 / 100);
201
- int16_t bl = ab + static_cast<int16_t>((static_cast<int16_t>(bb) - static_cast<int16_t>(ab)) * k100 / 100);
202
- return (static_cast<uint32_t>(r & 0xff) << 16) | (static_cast<uint32_t>(g & 0xff) << 8) | static_cast<uint32_t>(bl & 0xff);
203
- }
204
-
205
- // Declared by the lowering output (the tables). Matches the mutable (non-const)
206
- // definitions: ui_tick updates node bg/dirty and transition elapsed/active.
207
- extern UINode __ui_nodes[];
208
- extern UITransition __ui_trans[];
209
- extern UIBinding __ui_bindings[];
210
- extern const UIFontFace __ui_font_faces[];
211
- // Rich-text run / segment / line tables (parallel arrays; nodes reference
212
- // contiguous slices via runStart/richSegStart/richLineStart + counts).
213
- extern UIRichRun __ui_runs[];
214
- extern UIRichSeg __ui_rich_segs[];
215
- extern UIRichLine __ui_rich_lines[];
216
- extern const uint16_t __ui_node_count;
217
- extern const uint16_t __ui_trans_count;
218
- extern const uint16_t __ui_binding_count;
219
- extern const uint16_t __ui_run_count;
220
- extern const uint16_t __ui_rich_seg_count;
221
- extern const uint16_t __ui_rich_line_count;`;
222
- }
1
+ // Slice of the C++ runtime header (original source lines 90-303).
2
+ // Enums (UINodeKind, UIProperty) and all struct definitions + extern table decls.
3
+ // See docs/superpowers/specs/2026-07-12-split-runtime-header-design.md.
4
+ export function emitStructs(): string {
5
+ return `
6
+ enum UINodeKind { NODE_FILL, NODE_TEXT, NODE_BUTTON, NODE_CHECK, NODE_RADIO, NODE_PROGRESS, NODE_RANGE, NODE_INPUT, NODE_IMG, NODE_LIST, NODE_CANVAS, NODE_SELECT };
7
+ enum UIProperty { PROP_BG, PROP_FG, PROP_TEXT, PROP_VISIBLE, PROP_BORDER_COLOR, PROP_VALUE };
8
+
9
+ struct UIRect { int16_t x, y, w, h; };
10
+ struct UIFontGlyph {
11
+ uint16_t codepoint;
12
+ int8_t xOffset;
13
+ int8_t yOffset;
14
+ uint8_t width;
15
+ uint8_t height;
16
+ uint8_t advance;
17
+ uint16_t dataOffset; // 4-bit alpha pixel offset
18
+ };
19
+ struct UIFontFace {
20
+ uint8_t id;
21
+ uint8_t glyphCount;
22
+ uint8_t lineHeight;
23
+ uint8_t baseline;
24
+ const UIFontGlyph* glyphs;
25
+ const uint8_t* alpha;
26
+ };
27
+ struct UINode {
28
+ UIRect box;
29
+ uint32_t bg;
30
+ uint32_t fg;
31
+ UINodeKind kind;
32
+ const char* text;
33
+ char textBuffer[UI_TEXT_BUF + 1]; // dynamic text — read only when hasTextBinding == 1
34
+ uint8_t hasTextBinding; // set by ui_init when a PROP_TEXT binding targets this node
35
+ const uint8_t* font;
36
+ uint8_t hasBg;
37
+ uint8_t textAlign; // 0=left, 1=center, 2=right
38
+ uint8_t textSize; // GFX text size: 1-4
39
+ uint8_t lineHeight; // px per text line (0 = font default)
40
+ int8_t letterSpacing; // px between chars (0 = default advance)
41
+ uint8_t fontAntialias; // 1 = smooth text edges when UI_AA is available
42
+ uint8_t fontFace; // 0 = classic GFX bitmap font; otherwise UIFontFace id
43
+ uint32_t borderColor; // resolved color for the border (0 = use fg)
44
+ uint8_t borderStyle; // 0=none, 1=solid, 2=dashed
45
+ uint8_t borderWidth; // px, 0=none (uniform fallback)
46
+ uint8_t borderTopWidth; // per-side; equals borderWidth when uniform
47
+ uint8_t borderRightWidth;
48
+ uint8_t borderBottomWidth;
49
+ uint8_t borderLeftWidth;
50
+ uint8_t hasPerSideBorder; // 1 when any per-side width differs from borderWidth
51
+ uint8_t borderRadius; // px, 0=square
52
+ uint8_t paddingTop;
53
+ uint8_t paddingRight;
54
+ uint8_t paddingBottom;
55
+ uint8_t paddingLeft;
56
+ uint8_t gradientEnabled; // 0=none, 1=vertical, 2=horizontal
57
+ uint32_t gradientColor1;
58
+ uint32_t gradientColor2;
59
+ uint32_t outlineColor;
60
+ uint8_t outlineStyle; // 0=none, 1=solid, 2=dashed
61
+ uint8_t outlineWidth;
62
+ int16_t zIndex; // effective draw layer; higher layers draw later
63
+ int16_t transformOffsetX; // draw-only transform: translate(...)
64
+ int16_t transformOffsetY;
65
+ int16_t rotateDeg;
66
+ int8_t pressedOffsetX; // draw-only :pressed offset, no Yoga relayout
67
+ int8_t pressedOffsetY;
68
+ uint8_t shadowCount; // 0-4 active shadows
69
+ int8_t shadowOffsetX[4];
70
+ int8_t shadowOffsetY[4];
71
+ uint8_t shadowBlur[4];
72
+ uint32_t shadowColor[4];
73
+ uint8_t shadowAlpha[4];
74
+ uint8_t shadowInset[4]; // 0=outset, 1=inset
75
+ uint8_t textShadowCount;
76
+ int8_t textShadowOffsetX;
77
+ int8_t textShadowOffsetY;
78
+ uint8_t textShadowBlur;
79
+ uint32_t textShadowColor;
80
+ uint8_t textShadowAlpha;
81
+ uint8_t underline; // text-decoration: 0=none,1=underline,2=line-through,3=both
82
+ uint8_t textOverflow; // text-overflow: 0=clip, 1=ellipsis (truncate + ...)
83
+ uint8_t nowrap; // 1 = no text wrapping (white-space: nowrap/pre)
84
+ uint8_t whiteSpaceMode; // 0=normal, 1=nowrap, 2=pre, 3=pre-line
85
+ uint8_t visible; // 0=hidden, 1=visible
86
+ uint8_t opacity; // 0-100
87
+ uint32_t clearColor; // ancestor's background — used to wipe transparent text before redraw
88
+ int16_t lastTextWidth;
89
+ int16_t lastTextHeight;
90
+ uint32_t layoutCacheKey; // 0 = invalid; non-zero hashes layout inputs
91
+ uint16_t layoutMetricsW; // cached ui_text_layout_metrics width
92
+ uint16_t layoutMetricsH; // cached ui_text_layout_metrics height
93
+ // scroll (unified: containers and virtualized lists share these)
94
+ uint8_t scrollable; // 1 = children offset by scrollY, clipped to this box
95
+ uint8_t virtualized; // 1 = children produced by list*Fn callbacks (<list>)
96
+ int16_t scrollY; // committed offset (always in [0, maxScroll]); draw subtracts it
97
+ int16_t contentHeight; // total child height (clamp bound + scrollbar ratio)
98
+ int16_t overscrollPx; // elastic excursion past a boundary (0 in-bounds; +top, -bottom)
99
+ uint8_t settling; // 1 while a bounce-back/snap animation runs
100
+ int16_t lastPaintedScrollY; // scrollY at last container repaint (Mode B shift delta)
101
+ uint16_t listCount; // virtualized: current item count (refreshed each frame)
102
+ uint16_t (*listCountFn)(void);
103
+ void (*listItemFn)(uint16_t idx, char* buf, uint8_t size);
104
+ void (*listTapFn)(uint16_t idx); // nullptr if no tap handler
105
+ uint16_t parent; // 0xFFFF = root/no parent (UI_NO_PARENT)
106
+ uint16_t subtreeEnd; // exclusive pre-order end index
107
+ uint8_t screenId; // which <screen> this node belongs to (for navigation)
108
+ uint8_t imgDataId; // index into __ui_images[] (255 = no image)
109
+ uint8_t objectFit; // 0=none, 1=fill, 2=contain, 3=cover, 4=scale-down
110
+ uint16_t listItemHeight; // px per item for <list> (0 = not a list)
111
+ int16_t rangeMin; // for <range>: minimum value
112
+ int16_t rangeMax; // for <range>: maximum value
113
+ int16_t maxlen; // for <input>: max character length (0 = UI_TEXT_BUF)
114
+ uint16_t canvasW; // canvas buffer width (for <canvas>)
115
+ uint16_t canvasH; // canvas buffer height (for <canvas>)
116
+ // Rich-text runs (runCount > 0 for text nodes with mixed inline content).
117
+ // The node references a contiguous slice of the global run / segment / line
118
+ // arrays; geometry is precomputed at transpile time (runs are static-only).
119
+ uint8_t runCount; // number of runs in this node (0 = plain single-string text)
120
+ uint8_t richLineCount; // number of wrapped lines
121
+ uint16_t runStart; // first index into __ui_runs[]
122
+ uint16_t richSegStart; // first index into __ui_rich_segs[]
123
+ uint16_t richSegCount; // total segments across all lines
124
+ uint16_t richLineStart; // first index into __ui_rich_lines[]
125
+ // runtime slot
126
+ uint8_t dirty;
127
+ int16_t value; // unified element
128
+ uint8_t disabled; // HTML disabled attr: no taps/keyboard, dimmed draw state
129
+ uint8_t optionCount; // <select>: number of options
130
+ void (*optionTextFn)(uint8_t idx, char* buf, uint8_t size); // option text by index
131
+ int8_t drawerSide; // <drawer>/<toast>: 0=bottom 1=top 2=left 3=right; 4=<dialog> center; -1 = none
132
+ uint16_t toastDuration; // <toast duration>: ms before auto-close (0 = manual)
133
+ uint32_t checkedBg; // :checked background (RGB565) — switch track / checkbox face / radio dot / select selected row
134
+ uint32_t checkedFg; // :checked color (RGB565) — knob / checkmark / selected-row text
135
+ uint8_t hasCheckedBg; // 1 when checkedBg was authored (0 = swap not wired)
136
+ uint8_t hasCheckedFg; // 1 when checkedFg was authored
137
+ int8_t flowAxis; // visibility reflow: 0 none, 1 column, 2 row (ui_reflow_visibility)
138
+ uint8_t flowGap; // visibility reflow: main-axis gap between in-flow children
139
+ uint8_t flowFlags; // bit0 auto height, bit1 auto width, bit2 out-of-flow
140
+ };
141
+ // Rich-text run: one piece of styled inline text within a node's run list.
142
+ struct UIRichRun {
143
+ const char* text;
144
+ uint32_t fg;
145
+ uint8_t textSize;
146
+ uint8_t fontFace;
147
+ uint8_t underline; // 0=none,1=underline,2=line-through,3=both
148
+ int8_t letterSpacing;
149
+ int8_t linkTarget; // resolved screen index, -1 = not a link
150
+ };
151
+ // One laid-out segment of a run on one line (precomputed geometry).
152
+ struct UIRichSeg {
153
+ uint8_t runIndex; // index into the node's runs (0..runCount-1)
154
+ const char* text;
155
+ int16_t x; // offset from the line's left edge (pre-alignment)
156
+ uint16_t w; // measured width
157
+ uint8_t line; // which line (0..richLineCount-1) this segment is on
158
+ };
159
+ // One wrapped line of rich text (precomputed).
160
+ struct UIRichLine {
161
+ int16_t y; // top y relative to the node's text top
162
+ uint16_t h; // line height (tallest run on this line)
163
+ int16_t baseline; // baseline y (for mixed-size baseline alignment)
164
+ uint16_t w; // total line width (for alignment)
165
+ };
166
+ struct UITransition {
167
+ uint16_t node;
168
+ UIProperty prop;
169
+ uint16_t durationMs;
170
+ // The :pressed and base-state target colors. ui_on_press arms toward
171
+ // pressedTarget; ui_on_release arms toward baseTarget.
172
+ uint32_t pressedTarget;
173
+ uint32_t baseTarget;
174
+ // runtime
175
+ uint32_t elapsed;
176
+ uint32_t prevValue;
177
+ uint32_t targetValue;
178
+ uint8_t active;
179
+ };
180
+ struct UIBinding {
181
+ uint16_t node;
182
+ UIProperty prop;
183
+ uint32_t (*fn)(void); // for color/numeric bindings
184
+ void (*textFn)(char* buf, uint8_t size); // for text bindings (PROP_TEXT): fills buf
185
+ uint32_t lastValue; // numeric binding cache; avoids redundant dirty work
186
+ uint8_t initialized; // first evaluation establishes the baseline
187
+ };
188
+
189
+ // Color lerp for transitions (rgb565). For mono, this collapses to a snap.
190
+ static inline uint16_t lerp_color(uint16_t a, uint16_t b, uint8_t k100) {
191
+ if (k100 >= 100) return b;
192
+ // Lerp in 888 internally for smoother color transitions (keyframe animation,
193
+ // :pressed transitions). Unpack 565→888, lerp at 8-bit, re-quantize to 565.
194
+ uint8_t ar5 = (a >> 11) & 0x1f, ag6 = (a >> 5) & 0x3f, ab5 = a & 0x1f;
195
+ uint8_t br5 = (b >> 11) & 0x1f, bg6 = (b >> 5) & 0x3f, bb5 = b & 0x1f;
196
+ uint16_t ar8 = (ar5 << 3) | (ar5 >> 2), ag8 = (ag6 << 2) | (ag6 >> 4), ab8 = (ab5 << 3) | (ab5 >> 2);
197
+ uint16_t br8 = (br5 << 3) | (br5 >> 2), bg8 = (bg6 << 2) | (bg6 >> 4), bb8 = (bb5 << 3) | (bb5 >> 2);
198
+ int16_t r = static_cast<int16_t>(ar8 + static_cast<int16_t>((br8 - ar8) * k100 / 100));
199
+ int16_t g = static_cast<int16_t>(ag8 + static_cast<int16_t>((bg8 - ag8) * k100 / 100));
200
+ int16_t bl = static_cast<int16_t>(ab8 + static_cast<int16_t>((bb8 - ab8) * k100 / 100));
201
+ return (static_cast<uint16_t>((r >> 3) & 0x1f) << 11) | (static_cast<uint16_t>((g >> 2) & 0x3f) << 5) | static_cast<uint16_t>((bl >> 3) & 0x1f);
202
+ }
203
+
204
+ // RGB888 lerp — for transitions on RGB888/RGB666 targets (Phase 2+). Unused in
205
+ // Phase 1; the 565 lerp_color above remains the active path for TFT targets,
206
+ // whose node colors are still emitted as 565 values stored in uint32_t fields.
207
+ static inline uint32_t lerp_color_888(uint32_t a, uint32_t b, uint8_t k100) {
208
+ if (k100 >= 100) return b;
209
+ uint8_t ar = (a >> 16) & 0xff, ag = (a >> 8) & 0xff, ab = a & 0xff;
210
+ uint8_t br = (b >> 16) & 0xff, bg = (b >> 8) & 0xff, bb = b & 0xff;
211
+ int16_t r = ar + static_cast<int16_t>((static_cast<int16_t>(br) - static_cast<int16_t>(ar)) * k100 / 100);
212
+ int16_t g = ag + static_cast<int16_t>((static_cast<int16_t>(bg) - static_cast<int16_t>(ag)) * k100 / 100);
213
+ int16_t bl = ab + static_cast<int16_t>((static_cast<int16_t>(bb) - static_cast<int16_t>(ab)) * k100 / 100);
214
+ return (static_cast<uint32_t>(r & 0xff) << 16) | (static_cast<uint32_t>(g & 0xff) << 8) | static_cast<uint32_t>(bl & 0xff);
215
+ }
216
+
217
+ // Declared by the lowering output (the tables). Matches the mutable (non-const)
218
+ // definitions: ui_tick updates node bg/dirty and transition elapsed/active.
219
+ extern UINode __ui_nodes[];
220
+ extern UITransition __ui_trans[];
221
+ extern UIBinding __ui_bindings[];
222
+ extern const UIFontFace __ui_font_faces[];
223
+ // Rich-text run / segment / line tables (parallel arrays; nodes reference
224
+ // contiguous slices via runStart/richSegStart/richLineStart + counts).
225
+ extern UIRichRun __ui_runs[];
226
+ extern UIRichSeg __ui_rich_segs[];
227
+ extern UIRichLine __ui_rich_lines[];
228
+ extern const uint16_t __ui_node_count;
229
+ extern const uint16_t __ui_trans_count;
230
+ extern const uint16_t __ui_binding_count;
231
+ extern const uint16_t __ui_run_count;
232
+ extern const uint16_t __ui_rich_seg_count;
233
+ extern const uint16_t __ui_rich_line_count;`;
234
+ }