@typecad/ui 0.1.0-alpha.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (183) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +1443 -0
  3. package/dist/engine-index.d.ts +7 -0
  4. package/dist/engine-index.js +41 -0
  5. package/dist/index.d.ts +165 -0
  6. package/dist/index.js +42 -0
  7. package/dist/preview/build-program.d.ts +7 -0
  8. package/dist/preview/build-program.js +665 -0
  9. package/dist/preview/host-gfx.d.ts +90 -0
  10. package/dist/preview/host-gfx.js +552 -0
  11. package/dist/preview/host-ui-runtime.d.ts +241 -0
  12. package/dist/preview/host-ui-runtime.js +3236 -0
  13. package/dist/preview/types.d.ts +83 -0
  14. package/dist/preview/types.js +1 -0
  15. package/dist/types.d.ts +108 -0
  16. package/dist/types.js +10 -0
  17. package/dist/ui-engine/block-layout.d.ts +8 -0
  18. package/dist/ui-engine/block-layout.js +95 -0
  19. package/dist/ui-engine/color.d.ts +49 -0
  20. package/dist/ui-engine/color.js +273 -0
  21. package/dist/ui-engine/css-parser.d.ts +155 -0
  22. package/dist/ui-engine/css-parser.js +1058 -0
  23. package/dist/ui-engine/default-keyboards.d.ts +3 -0
  24. package/dist/ui-engine/default-keyboards.js +53 -0
  25. package/dist/ui-engine/easing.d.ts +12 -0
  26. package/dist/ui-engine/easing.js +76 -0
  27. package/dist/ui-engine/font-assets.d.ts +50 -0
  28. package/dist/ui-engine/font-assets.js +431 -0
  29. package/dist/ui-engine/html-parser.d.ts +116 -0
  30. package/dist/ui-engine/html-parser.js +284 -0
  31. package/dist/ui-engine/image-assets.d.ts +21 -0
  32. package/dist/ui-engine/image-assets.js +125 -0
  33. package/dist/ui-engine/inline-parser.d.ts +30 -0
  34. package/dist/ui-engine/inline-parser.js +66 -0
  35. package/dist/ui-engine/keyframes.d.ts +3 -0
  36. package/dist/ui-engine/keyframes.js +39 -0
  37. package/dist/ui-engine/layout-engine.d.ts +35 -0
  38. package/dist/ui-engine/layout-engine.js +203 -0
  39. package/dist/ui-engine/model.d.ts +239 -0
  40. package/dist/ui-engine/model.js +987 -0
  41. package/dist/ui-engine/rich-layout.d.ts +51 -0
  42. package/dist/ui-engine/rich-layout.js +220 -0
  43. package/dist/ui-engine/run-types.d.ts +13 -0
  44. package/dist/ui-engine/run-types.js +1 -0
  45. package/dist/ui-engine/runtime-header/antialiasing.d.ts +1 -0
  46. package/dist/ui-engine/runtime-header/antialiasing.js +141 -0
  47. package/dist/ui-engine/runtime-header/blend-bodies.d.ts +1 -0
  48. package/dist/ui-engine/runtime-header/blend-bodies.js +39 -0
  49. package/dist/ui-engine/runtime-header/canvas-helpers.d.ts +1 -0
  50. package/dist/ui-engine/runtime-header/canvas-helpers.js +155 -0
  51. package/dist/ui-engine/runtime-header/canvas-scrollbar.d.ts +1 -0
  52. package/dist/ui-engine/runtime-header/canvas-scrollbar.js +105 -0
  53. package/dist/ui-engine/runtime-header/color-mono-refresh.d.ts +1 -0
  54. package/dist/ui-engine/runtime-header/color-mono-refresh.js +154 -0
  55. package/dist/ui-engine/runtime-header/dirty-scroll-mutators.d.ts +1 -0
  56. package/dist/ui-engine/runtime-header/dirty-scroll-mutators.js +169 -0
  57. package/dist/ui-engine/runtime-header/display-shim.d.ts +1 -0
  58. package/dist/ui-engine/runtime-header/display-shim.js +104 -0
  59. package/dist/ui-engine/runtime-header/forward-decls.d.ts +1 -0
  60. package/dist/ui-engine/runtime-header/forward-decls.js +137 -0
  61. package/dist/ui-engine/runtime-header/guard-close.d.ts +1 -0
  62. package/dist/ui-engine/runtime-header/guard-close.js +8 -0
  63. package/dist/ui-engine/runtime-header/image-drawing.d.ts +1 -0
  64. package/dist/ui-engine/runtime-header/image-drawing.js +300 -0
  65. package/dist/ui-engine/runtime-header/init-press-input.d.ts +1 -0
  66. package/dist/ui-engine/runtime-header/init-press-input.js +126 -0
  67. package/dist/ui-engine/runtime-header/keyboard.d.ts +1 -0
  68. package/dist/ui-engine/runtime-header/keyboard.js +342 -0
  69. package/dist/ui-engine/runtime-header/node-decoration.d.ts +1 -0
  70. package/dist/ui-engine/runtime-header/node-decoration.js +212 -0
  71. package/dist/ui-engine/runtime-header/paint-order-coords.d.ts +1 -0
  72. package/dist/ui-engine/runtime-header/paint-order-coords.js +190 -0
  73. package/dist/ui-engine/runtime-header/paint-rects-repair.d.ts +1 -0
  74. package/dist/ui-engine/runtime-header/paint-rects-repair.js +553 -0
  75. package/dist/ui-engine/runtime-header/scroll-physics.d.ts +1 -0
  76. package/dist/ui-engine/runtime-header/scroll-physics.js +161 -0
  77. package/dist/ui-engine/runtime-header/state-bindings-nav.d.ts +1 -0
  78. package/dist/ui-engine/runtime-header/state-bindings-nav.js +176 -0
  79. package/dist/ui-engine/runtime-header/structs.d.ts +1 -0
  80. package/dist/ui-engine/runtime-header/structs.js +220 -0
  81. package/dist/ui-engine/runtime-header/text-rendering.d.ts +1 -0
  82. package/dist/ui-engine/runtime-header/text-rendering.js +715 -0
  83. package/dist/ui-engine/runtime-header/tick/bindings-phase.d.ts +1 -0
  84. package/dist/ui-engine/runtime-header/tick/bindings-phase.js +103 -0
  85. package/dist/ui-engine/runtime-header/tick/dirty-draw-phase.d.ts +1 -0
  86. package/dist/ui-engine/runtime-header/tick/dirty-draw-phase.js +1051 -0
  87. package/dist/ui-engine/runtime-header/tick/flush-phase.d.ts +1 -0
  88. package/dist/ui-engine/runtime-header/tick/flush-phase.js +17 -0
  89. package/dist/ui-engine/runtime-header/tick/scroll-canvas-phase.d.ts +1 -0
  90. package/dist/ui-engine/runtime-header/tick/scroll-canvas-phase.js +15 -0
  91. package/dist/ui-engine/runtime-header/tick/transitions-phase.d.ts +1 -0
  92. package/dist/ui-engine/runtime-header/tick/transitions-phase.js +213 -0
  93. package/dist/ui-engine/runtime-header/tick-motion-helpers.d.ts +1 -0
  94. package/dist/ui-engine/runtime-header/tick-motion-helpers.js +26 -0
  95. package/dist/ui-engine/runtime-header/tick.d.ts +1 -0
  96. package/dist/ui-engine/runtime-header/tick.js +17 -0
  97. package/dist/ui-engine/runtime-header/touch-keyboard-fwd.d.ts +1 -0
  98. package/dist/ui-engine/runtime-header/touch-keyboard-fwd.js +406 -0
  99. package/dist/ui-engine/runtime-header/types-defines.d.ts +1 -0
  100. package/dist/ui-engine/runtime-header/types-defines.js +79 -0
  101. package/dist/ui-engine/runtime-header.d.ts +1 -0
  102. package/dist/ui-engine/runtime-header.js +69 -0
  103. package/dist/ui-engine/scroll-memory-diagnostics.d.ts +16 -0
  104. package/dist/ui-engine/scroll-memory-diagnostics.js +58 -0
  105. package/dist/ui-engine/select-engine.d.ts +4 -0
  106. package/dist/ui-engine/select-engine.js +19 -0
  107. package/dist/ui-engine/style-resolver.d.ts +77 -0
  108. package/dist/ui-engine/style-resolver.js +341 -0
  109. package/dist/ui-engine/text-layout.d.ts +19 -0
  110. package/dist/ui-engine/text-layout.js +92 -0
  111. package/dist/ui-engine/transpile-ui.d.ts +11 -0
  112. package/dist/ui-engine/transpile-ui.js +33 -0
  113. package/dist/ui-engine/ua-stylesheet.d.ts +3 -0
  114. package/dist/ui-engine/ua-stylesheet.js +56 -0
  115. package/dist/ui-engine/ui-file-splitter.d.ts +14 -0
  116. package/dist/ui-engine/ui-file-splitter.js +47 -0
  117. package/dist/ui-engine/ui-lowering.d.ts +33 -0
  118. package/dist/ui-engine/ui-lowering.js +390 -0
  119. package/dist/ui-engine/ui-registry.d.ts +81 -0
  120. package/dist/ui-engine/ui-registry.js +429 -0
  121. package/dist/ui-engine/yoga-layout.d.ts +12 -0
  122. package/dist/ui-engine/yoga-layout.js +314 -0
  123. package/package.json +86 -0
  124. package/src/engine-index.ts +51 -0
  125. package/src/index.ts +232 -0
  126. package/src/preview/build-program.ts +734 -0
  127. package/src/preview/host-gfx.ts +580 -0
  128. package/src/preview/host-ui-runtime.ts +3245 -0
  129. package/src/preview/types.ts +96 -0
  130. package/src/types.ts +127 -0
  131. package/src/ui-engine/block-layout.ts +104 -0
  132. package/src/ui-engine/color.ts +263 -0
  133. package/src/ui-engine/css-parser.ts +1003 -0
  134. package/src/ui-engine/default-keyboards.ts +58 -0
  135. package/src/ui-engine/easing.ts +53 -0
  136. package/src/ui-engine/font-assets.ts +512 -0
  137. package/src/ui-engine/html-parser.ts +397 -0
  138. package/src/ui-engine/image-assets.ts +170 -0
  139. package/src/ui-engine/inline-parser.ts +78 -0
  140. package/src/ui-engine/keyframes.ts +47 -0
  141. package/src/ui-engine/layout-engine.ts +228 -0
  142. package/src/ui-engine/model.ts +1225 -0
  143. package/src/ui-engine/rich-layout.ts +257 -0
  144. package/src/ui-engine/run-types.ts +14 -0
  145. package/src/ui-engine/runtime-header/antialiasing.ts +141 -0
  146. package/src/ui-engine/runtime-header/blend-bodies.ts +39 -0
  147. package/src/ui-engine/runtime-header/canvas-helpers.ts +155 -0
  148. package/src/ui-engine/runtime-header/canvas-scrollbar.ts +105 -0
  149. package/src/ui-engine/runtime-header/color-mono-refresh.ts +154 -0
  150. package/src/ui-engine/runtime-header/dirty-scroll-mutators.ts +169 -0
  151. package/src/ui-engine/runtime-header/display-shim.ts +104 -0
  152. package/src/ui-engine/runtime-header/forward-decls.ts +137 -0
  153. package/src/ui-engine/runtime-header/guard-close.ts +8 -0
  154. package/src/ui-engine/runtime-header/image-drawing.ts +300 -0
  155. package/src/ui-engine/runtime-header/init-press-input.ts +126 -0
  156. package/src/ui-engine/runtime-header/keyboard.ts +342 -0
  157. package/src/ui-engine/runtime-header/node-decoration.ts +212 -0
  158. package/src/ui-engine/runtime-header/paint-order-coords.ts +190 -0
  159. package/src/ui-engine/runtime-header/paint-rects-repair.ts +553 -0
  160. package/src/ui-engine/runtime-header/scroll-physics.ts +161 -0
  161. package/src/ui-engine/runtime-header/state-bindings-nav.ts +176 -0
  162. package/src/ui-engine/runtime-header/structs.ts +220 -0
  163. package/src/ui-engine/runtime-header/text-rendering.ts +715 -0
  164. package/src/ui-engine/runtime-header/tick/bindings-phase.ts +103 -0
  165. package/src/ui-engine/runtime-header/tick/dirty-draw-phase.ts +1051 -0
  166. package/src/ui-engine/runtime-header/tick/flush-phase.ts +17 -0
  167. package/src/ui-engine/runtime-header/tick/scroll-canvas-phase.ts +15 -0
  168. package/src/ui-engine/runtime-header/tick/transitions-phase.ts +213 -0
  169. package/src/ui-engine/runtime-header/tick-motion-helpers.ts +26 -0
  170. package/src/ui-engine/runtime-header/tick.ts +18 -0
  171. package/src/ui-engine/runtime-header/touch-keyboard-fwd.ts +406 -0
  172. package/src/ui-engine/runtime-header/types-defines.ts +79 -0
  173. package/src/ui-engine/runtime-header.ts +71 -0
  174. package/src/ui-engine/scroll-memory-diagnostics.ts +79 -0
  175. package/src/ui-engine/select-engine.ts +21 -0
  176. package/src/ui-engine/style-resolver.ts +416 -0
  177. package/src/ui-engine/text-layout.ts +114 -0
  178. package/src/ui-engine/transpile-ui.ts +45 -0
  179. package/src/ui-engine/ua-stylesheet.ts +60 -0
  180. package/src/ui-engine/ui-file-splitter.ts +59 -0
  181. package/src/ui-engine/ui-lowering.ts +459 -0
  182. package/src/ui-engine/ui-registry.ts +530 -0
  183. package/src/ui-engine/yoga-layout.ts +309 -0
@@ -0,0 +1,17 @@
1
+ // Slice of the C++ runtime header (original source lines 5288-5298).
2
+ // Phase 4/5: fb bulk push + refresh flush + closing brace of ui_tick.
3
+ // See docs/superpowers/specs/2026-07-12-split-runtime-header-design.md.
4
+ export function emitTickFlushPhase(): string {
5
+ return `
6
+ }
7
+ // ── Framebuffer bulk push ────────────────────────────────────────────────
8
+ // When a framebuffer was used this frame, flush it to the display in a single
9
+ // SPI transaction and restore the direct-draw target. No-op without one.
10
+ if (__ui_fb) {
11
+ ui_push_framebuffer();
12
+ }
13
+ ui_display_use_default_target();
14
+ // ③ Flush — ILI9341 is immediate, no separate flush needed. On deferred-
15
+ // refresh panels (e-ink), flush the union of this frame's dirty paint rects
16
+ // as one partial refresh. No-op on TFT.`;
17
+ }
@@ -0,0 +1,15 @@
1
+ // Slice of the C++ runtime header (original source lines 5279-5287).
2
+ // scrollbar + buffered scroll canvas push.
3
+ // See docs/superpowers/specs/2026-07-12-split-runtime-header-design.md.
4
+ export function emitTickScrollCanvasPhase(): string {
5
+ return `
6
+ }
7
+ // ②b Draw scrollbar + push canvas for the buffered scroll container.
8
+ if (bufferedScrollNode >= 0 && bufferedScrollCanvas) {
9
+ ui_push_buffered_scroll_canvas(bufferedScrollCanvas, bufferedScrollRepaintCanvas,
10
+ bufferedScrollNode, bufferedScrollVX, bufferedScrollVY,
11
+ bufferedScrollRepaintY, bufferedScrollRepaintH, __ui_draw_target);
12
+ } else if (bufferedScrollNode >= 0 && bufferedScrollDirectStrip) {
13
+ ui_draw_scrollbar_direct(bufferedScrollNode, bufferedScrollVX, bufferedScrollVY);
14
+ __ui_nodes[bufferedScrollNode].lastPaintedScrollY = __ui_nodes[bufferedScrollNode].scrollY;`;
15
+ }
@@ -0,0 +1,213 @@
1
+ // Slice of the C++ runtime header (original source lines 4027-4233).
2
+ // transitions + keyframes.
3
+ // See docs/superpowers/specs/2026-07-12-split-runtime-header-design.md.
4
+ export function emitTickTransitionsPhase(): string {
5
+ return `
6
+ }
7
+ // ① Advance transitions.
8
+ for (uint16_t i = 0; i < __ui_trans_count; i++) {
9
+ if (!__ui_trans[i].active) continue;
10
+ __ui_trans[i].elapsed += deltaMs;
11
+ uint16_t k = __ui_trans[i].durationMs == 0
12
+ ? 100
13
+ : (uint16_t)((uint32_t)__ui_trans[i].elapsed * 100 / __ui_trans[i].durationMs);
14
+ if (__ui_trans[i].durationMs > 0 && __ui_trans[i].durationMs <= UI_TRANSITION_SNAP_MS) k = 100;
15
+ uint32_t v = UI_LERP_COLOR(__ui_trans[i].prevValue, __ui_trans[i].targetValue, (uint8_t)k);
16
+ if (__ui_trans[i].prop == PROP_FG) {
17
+ __ui_nodes[__ui_trans[i].node].fg = v;
18
+ } else {
19
+ __ui_nodes[__ui_trans[i].node].bg = v;
20
+ }
21
+ ui_mark_dirty(__ui_trans[i].node);
22
+ if (k >= 100) __ui_trans[i].active = 0;
23
+ }
24
+
25
+ // ①b Advance @keyframes animations.
26
+ uint8_t scrollMotionActive = ui_scroll_motion_active(settlingOnActiveScreen);
27
+ for (uint16_t i = 0; i < __ui_anim_count; i++) {
28
+ if (!__ui_anims[i].active) continue;
29
+ // Skip animations on non-visible screens OR hidden subtrees — their nodes
30
+ // are not drawn, so advancing them would paint stray fragments ("blotches")
31
+ // on the active screen. The screenId check handles multi-screen nav; the
32
+ // effective-visibility check handles master-detail panes (all on screen 0,
33
+ // but only one pane is visible at a time — the rest have visible=0 set by
34
+ // a ui.bind(..., 'visible', ...) binding). Without this, infinite animations
35
+ // on a hidden pane (e.g. the transforms dots) keep ticking, marking their
36
+ // nodes dirty, and repainting on top of the visible pane's content.
37
+ {
38
+ uint16_t animNode = __ui_anims[i].node;
39
+ if (animNode >= __ui_node_count) continue;
40
+ if (__ui_nodes[animNode].screenId != __ui_active_screen) continue;
41
+ if (!ui_is_effectively_visible(animNode)) continue;
42
+ }
43
+ if (scrollMotionActive &&
44
+ ui_keyframe_set_has_scroll_sensitive_geometry(__ui_anims[i].keyframeSet)) {
45
+ continue;
46
+ }
47
+ __ui_anims[i].elapsed += (uint32_t)deltaMs;
48
+ uint32_t elapsedNoDelay = __ui_anims[i].elapsed;
49
+ if (elapsedNoDelay < __ui_anims[i].delayMs) continue;
50
+ elapsedNoDelay -= __ui_anims[i].delayMs;
51
+ // Check iteration limit (finite).
52
+ uint8_t completing = 0;
53
+ if (__ui_anims[i].iterations > 0) {
54
+ uint32_t totalDuration = (uint32_t)__ui_anims[i].iterations * (uint32_t)__ui_anims[i].durationMs;
55
+ if (elapsedNoDelay >= totalDuration) {
56
+ elapsedNoDelay = totalDuration;
57
+ completing = 1;
58
+ }
59
+ }
60
+ // Compute cycle position: 0.0 - 1.0 within one loop.
61
+ uint8_t pct = 100;
62
+ if (!completing && __ui_anims[i].durationMs > 0) {
63
+ uint32_t cycleMs = elapsedNoDelay % __ui_anims[i].durationMs;
64
+ pct = (uint8_t)((uint32_t)cycleMs * 100 / __ui_anims[i].durationMs);
65
+ }
66
+ // Find surrounding keyframe stops.
67
+ if (__ui_anims[i].keyframeSet >= __ui_keyframe_set_count) continue;
68
+ const UIKeyframeSet* ks = &__ui_keyframe_sets[__ui_anims[i].keyframeSet];
69
+ if (ks->stopCount == 0) continue;
70
+ // Find the two stops that bracket pct.
71
+ uint8_t lo = 0, hi = ks->stopCount - 1;
72
+ for (uint8_t s = 0; s < ks->stopCount; s++) {
73
+ if (ks->stops[s].percent <= pct) lo = s;
74
+ if (ks->stops[s].percent >= pct) { hi = s; break; }
75
+ }
76
+ const UIKeyframeStop* sLo = &ks->stops[lo];
77
+ const UIKeyframeStop* sHi = &ks->stops[hi];
78
+ // Lerp factor between lo and hi.
79
+ uint8_t range = sHi->percent - sLo->percent;
80
+ uint8_t lerpK = range > 0 ? (uint8_t)((uint16_t)(pct - sLo->percent) * 100 / range) : 0;
81
+ // Shape the lerp by the animation's timing function (ease-in-out, etc.).
82
+ // CSS attaches it to the animation and applies it between stops.
83
+ lerpK = ui_ease_lerp_k(__ui_anims[i].timingFunction, lerpK);
84
+ // Apply to node — only mark dirty if a value actually changed.
85
+ uint16_t n = __ui_anims[i].node;
86
+ if (n >= __ui_node_count) continue;
87
+ uint8_t changed = 0;
88
+ if ((sLo->props & UI_KF_BG) && (sHi->props & UI_KF_BG)) {
89
+ uint32_t newBg = range > 0 ? UI_LERP_COLOR(sLo->bg, sHi->bg, lerpK) : sLo->bg;
90
+ if (newBg != __ui_nodes[n].bg) { __ui_nodes[n].bg = newBg; __ui_nodes[n].hasBg = 1; changed = 1; }
91
+ }
92
+ if ((sLo->props & UI_KF_FG) && (sHi->props & UI_KF_FG)) {
93
+ uint32_t newFg = range > 0 ? UI_LERP_COLOR(sLo->fg, sHi->fg, lerpK) : sLo->fg;
94
+ if (newFg != __ui_nodes[n].fg) { __ui_nodes[n].fg = newFg; changed = 1; }
95
+ }
96
+ if ((sLo->props & UI_KF_OPACITY) && (sHi->props & UI_KF_OPACITY)) {
97
+ uint8_t newOp = range > 0
98
+ ? (uint8_t)((int16_t)sLo->opacity + ((int16_t)sHi->opacity - (int16_t)sLo->opacity) * lerpK / 100)
99
+ : sLo->opacity;
100
+ if (newOp != __ui_nodes[n].opacity) { __ui_nodes[n].opacity = newOp; changed = 1; }
101
+ }
102
+ int16_t nextTransformX = __ui_nodes[n].transformOffsetX;
103
+ int16_t nextTransformY = __ui_nodes[n].transformOffsetY;
104
+ int16_t nextRotateDeg = __ui_nodes[n].rotateDeg;
105
+ int16_t nextWidth = __ui_nodes[n].box.w;
106
+ int16_t nextHeight = __ui_nodes[n].box.h;
107
+ uint8_t geometryChanged = 0;
108
+ uint8_t hasSizeFrame = (sLo->props & UI_KF_SIZE) && (sHi->props & UI_KF_SIZE);
109
+ if (hasSizeFrame) {
110
+ nextWidth = range > 0
111
+ ? (int16_t)((int32_t)sLo->width + ((int32_t)sHi->width - (int32_t)sLo->width) * lerpK / 100)
112
+ : sLo->width;
113
+ nextHeight = range > 0
114
+ ? (int16_t)((int32_t)sLo->height + ((int32_t)sHi->height - (int32_t)sLo->height) * lerpK / 100)
115
+ : sLo->height;
116
+ if (nextWidth < 0) nextWidth = 0;
117
+ if (nextHeight < 0) nextHeight = 0;
118
+ if (nextWidth != __ui_nodes[n].box.w || nextHeight != __ui_nodes[n].box.h) {
119
+ changed = 1;
120
+ geometryChanged = 1;
121
+ }
122
+ }
123
+ if ((sLo->props & UI_KF_TRANSFORM) && (sHi->props & UI_KF_TRANSFORM)) {
124
+ int16_t pxX = range > 0
125
+ ? (int16_t)((int32_t)sLo->transformOffsetX + ((int32_t)sHi->transformOffsetX - (int32_t)sLo->transformOffsetX) * lerpK / 100)
126
+ : sLo->transformOffsetX;
127
+ int16_t pxY = range > 0
128
+ ? (int16_t)((int32_t)sLo->transformOffsetY + ((int32_t)sHi->transformOffsetY - (int32_t)sLo->transformOffsetY) * lerpK / 100)
129
+ : sLo->transformOffsetY;
130
+ int16_t pctX = range > 0
131
+ ? (int16_t)((int32_t)sLo->translatePctX + ((int32_t)sHi->translatePctX - (int32_t)sLo->translatePctX) * lerpK / 100)
132
+ : sLo->translatePctX;
133
+ int16_t pctY = range > 0
134
+ ? (int16_t)((int32_t)sLo->translatePctY + ((int32_t)sHi->translatePctY - (int32_t)sLo->translatePctY) * lerpK / 100)
135
+ : sLo->translatePctY;
136
+ int16_t scaleX = range > 0
137
+ ? (int16_t)((int32_t)sLo->scaleX + ((int32_t)sHi->scaleX - (int32_t)sLo->scaleX) * lerpK / 100)
138
+ : sLo->scaleX;
139
+ int16_t scaleY = range > 0
140
+ ? (int16_t)((int32_t)sLo->scaleY + ((int32_t)sHi->scaleY - (int32_t)sLo->scaleY) * lerpK / 100)
141
+ : sLo->scaleY;
142
+ nextRotateDeg = range > 0
143
+ ? (int16_t)((int32_t)sLo->rotateDeg + ((int32_t)sHi->rotateDeg - (int32_t)sLo->rotateDeg) * lerpK / 100)
144
+ : sLo->rotateDeg;
145
+ if (scaleX < 0) scaleX = 0;
146
+ if (scaleY < 0) scaleY = 0;
147
+ int16_t refW = hasSizeFrame ? nextWidth : __ui_anims[i].baseWidth;
148
+ int16_t refH = hasSizeFrame ? nextHeight : __ui_anims[i].baseHeight;
149
+ if (refW <= 0) refW = __ui_nodes[n].box.w;
150
+ if (refH <= 0) refH = __ui_nodes[n].box.h;
151
+ int16_t originPxX = (int16_t)((int32_t)refW * __ui_anims[i].originX / 100);
152
+ int16_t originPxY = (int16_t)((int32_t)refH * __ui_anims[i].originY / 100);
153
+ int16_t scaledW = (int16_t)((int32_t)refW * scaleX / 100);
154
+ int16_t scaledH = (int16_t)((int32_t)refH * scaleY / 100);
155
+ int16_t scaleOffsetX = originPxX - (int16_t)((int32_t)originPxX * scaleX / 100);
156
+ int16_t scaleOffsetY = originPxY - (int16_t)((int32_t)originPxY * scaleY / 100);
157
+ nextTransformX = pxX + (int16_t)((int32_t)refW * pctX / 100) + scaleOffsetX;
158
+ nextTransformY = pxY + (int16_t)((int32_t)refH * pctY / 100) + scaleOffsetY;
159
+ nextWidth = scaledW < 0 ? 0 : scaledW;
160
+ nextHeight = scaledH < 0 ? 0 : scaledH;
161
+ if (nextTransformX != __ui_nodes[n].transformOffsetX ||
162
+ nextTransformY != __ui_nodes[n].transformOffsetY ||
163
+ nextRotateDeg != __ui_nodes[n].rotateDeg ||
164
+ nextWidth != __ui_nodes[n].box.w ||
165
+ nextHeight != __ui_nodes[n].box.h) {
166
+ changed = 1;
167
+ geometryChanged = 1;
168
+ }
169
+ }
170
+ if (changed) {
171
+ // Throttle color/opacity-only redraws to ~10fps to avoid ILI9341 tearing
172
+ // from rapid SPI writes (ada49b4). Spatial transforms (translate/scale/
173
+ // rotate/size) are exempt: at 10fps a small dot moving a few px reads as
174
+ // a jump, and the redraw is only the node's own tiny footprint, so the
175
+ // tearing risk that motivated the gate doesn't apply. Geometry redraws
176
+ // every frame a value actually changes (the 'changed' guard above still
177
+ // suppresses no-op repaints).
178
+ uint8_t throttleRedraw = !geometryChanged &&
179
+ !(completing || __ui_anims[i].elapsed - __ui_anims[i].lastUpdateMs >= 100);
180
+ if (!throttleRedraw) {
181
+ UIRect oldGeometryRect = {0, 0, 0, 0};
182
+ uint8_t hasOldGeometryRect = 0;
183
+ if (geometryChanged) {
184
+ ui_node_current_paint_rect(n, &oldGeometryRect);
185
+ hasOldGeometryRect = oldGeometryRect.w > 0 && oldGeometryRect.h > 0;
186
+ __ui_nodes[n].transformOffsetX = nextTransformX;
187
+ __ui_nodes[n].transformOffsetY = nextTransformY;
188
+ __ui_nodes[n].rotateDeg = nextRotateDeg;
189
+ __ui_nodes[n].box.w = nextWidth;
190
+ __ui_nodes[n].box.h = nextHeight;
191
+ ui_invalidate_text_layout_cache(n);
192
+ }
193
+ uint8_t repairedGeometry = 0;
194
+ if (geometryChanged && hasOldGeometryRect) {
195
+ // Small moving solid fills can be repaired as one old+new union
196
+ // bitmap. This avoids the visible erase-then-redraw blink that shows
197
+ // up when transform animations run at full frame rate on SPI TFTs.
198
+ repairedGeometry = ui_try_repair_geometry_fill(n, &oldGeometryRect);
199
+ }
200
+ if (geometryChanged && !repairedGeometry) {
201
+ // Fallback: preserve the old behavior, but clear the captured OLD
202
+ // footprint after the node fields have been updated.
203
+ if (hasOldGeometryRect) ui_clear_node_paint_rect(n, &oldGeometryRect);
204
+ else ui_clear_current_node_paint(n);
205
+ ui_invalidate_scroll_canvas_for_node(n);
206
+ }
207
+ if (!repairedGeometry) ui_mark_dirty(n);
208
+ __ui_anims[i].lastUpdateMs = __ui_anims[i].elapsed;
209
+ }
210
+ }
211
+ if (completing) __ui_anims[i].active = 0;
212
+ }`;
213
+ }
@@ -0,0 +1,26 @@
1
+ // Slice of the C++ runtime header (original source lines 3910-3929).
2
+ // scroll_motion_active, keyframe_set_has_scroll_sensitive_geometry.
3
+ // See docs/superpowers/specs/2026-07-12-split-runtime-header-design.md.
4
+ export function emitTickMotionHelpers(): string {
5
+ return `
6
+ static inline uint8_t ui_scroll_motion_active(uint8_t settlingOnActiveScreen) {
7
+ if (__ui_scroll_node >= 0 && __ui_is_dragging) return 1;
8
+ return settlingOnActiveScreen;
9
+ }
10
+
11
+ static inline uint8_t ui_keyframe_set_has_scroll_sensitive_geometry(uint8_t setIdx) {
12
+ if (setIdx >= __ui_keyframe_set_count) return 0;
13
+ const UIKeyframeSet* ks = &__ui_keyframe_sets[setIdx];
14
+ for (uint8_t s = 0; s < ks->stopCount; s++) {
15
+ if (ks->stops[s].props & (UI_KF_TRANSFORM | UI_KF_SIZE)) return 1;
16
+ }
17
+ return 0;
18
+ }
19
+
20
+ // Per-frame driver. The host async/loop pump calls this each tick (~16ms).
21
+ // Phase -2: poll touch (if configured). Phase -1: poll input pins.
22
+ // Phase 0: evaluate bindings. Phase 1: transitions. Phase 2: draw.
23
+ // Forward decl: the keyboard overlay is defined below but drawn at the end.
24
+ // (ui_kb_draw and ui_kb_handle_tap forward declarations are near the touch
25
+ // state machine, above.)`;
26
+ }
@@ -0,0 +1,18 @@
1
+ // ui_tick (original source lines 3930-5298) split into 5 contiguous phase slices.
2
+ // The phases tile 3930-5298 with no gaps; concatenation reproduces the original
3
+ // byte-for-byte. No wrapper logic — just ordered concatenation.
4
+ import { emitTickBindingsPhase } from "./tick/bindings-phase.js";
5
+ import { emitTickTransitionsPhase } from "./tick/transitions-phase.js";
6
+ import { emitTickDirtyDrawPhase } from "./tick/dirty-draw-phase.js";
7
+ import { emitTickScrollCanvasPhase } from "./tick/scroll-canvas-phase.js";
8
+ import { emitTickFlushPhase } from "./tick/flush-phase.js";
9
+
10
+ export function emitTick(): string {
11
+ return [
12
+ emitTickBindingsPhase(),
13
+ emitTickTransitionsPhase(),
14
+ emitTickDirtyDrawPhase(),
15
+ emitTickScrollCanvasPhase(),
16
+ emitTickFlushPhase(),
17
+ ].join("");
18
+ }