@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,137 @@
1
+ // Slice of the C++ runtime header (original source lines 622-752).
2
+ // navigate, canvas/scroll/text/paint/clip/AA/image forward declarations, persistent canvas globals.
3
+ // See docs/superpowers/specs/2026-07-12-split-runtime-header-design.md.
4
+ export function emitForwardDecls() {
5
+ return `
6
+ static uint8_t __ui_fade_opacity = 100; // fade-in animation (0=transparent, 100=full)
7
+ static uint16_t __ui_fade_elapsed = 0;
8
+ static uint16_t __ui_fade_duration = 200; // ms
9
+
10
+ // Early forward declaration: ui_navigate (below) calls ui_release_canvas_state
11
+ // and ui_set_pressed (defined later) during screen changes. Needed on native
12
+ // (single TU, no Arduino auto-prototyper).
13
+ static inline void ui_release_canvas_state();
14
+ static inline void ui_set_pressed(uint16_t nodeIdx, uint8_t pressed);
15
+ static inline void ui_refresh_active_screen_bg_node();
16
+
17
+ // Navigate to a screen by index. Marks the new screen's nodes dirty + starts fade.
18
+ static inline void ui_navigate(uint8_t screenIdx) {
19
+ if (screenIdx >= __ui_screen_count || screenIdx == __ui_active_screen) return;
20
+ __ui_active_screen = screenIdx;
21
+ ui_refresh_active_screen_bg_node();
22
+ // Reset scroll/touch state so the old screen's scroll container doesn't
23
+ // interfere with the new screen. Release any currently-pressed button FIRST:
24
+ // navigation fires from a button's click handler during touch-down, so the
25
+ // matching touch-up release would otherwise be suppressed by the reset below
26
+ // and the button would stay stuck in its :pressed color.
27
+ if (__ui_touch_node >= 0 && __ui_touch_node < __ui_node_count &&
28
+ __ui_nodes[__ui_touch_node].kind == NODE_BUTTON && __ui_nodes[__ui_touch_node].value != 0) {
29
+ ui_set_pressed((uint16_t)__ui_touch_node, 0);
30
+ }
31
+ __ui_scroll_node = -1;
32
+ __ui_touch_node = -1;
33
+ __ui_touch_state = 0;
34
+ __ui_kb_visible = 0;
35
+ // Free every persistent canvas so the new screen allocates into a clean,
36
+ // unfragmented heap. Without this, the previous screen's canvas buffer stays
37
+ // resident and fragments the heap, so the new screen's buffer can't get a
38
+ // contiguous block (the "works first, then blanks until reset" symptom).
39
+ ui_release_canvas_state();
40
+ // Clear the entire display so old screen content doesn't show. On deferred-
41
+ // refresh panels (e-ink) a full clear flashes, so skip it — the all-nodes-
42
+ // dirty marking below drives a full repaint via partial refresh instead.
43
+ #ifndef UI_REFRESH_DEFERRED
44
+ display_fillScreen(0x0000);
45
+ #endif
46
+ // Mark all nodes dirty so the new screen fully redraws.
47
+ for (uint16_t i = 0; i < __ui_node_count; i++) {
48
+ __ui_nodes[i].dirty = 1;
49
+ __ui_nodes[i].lastTextHeight = 0;
50
+ __ui_nodes[i].layoutCacheKey = 0;
51
+ if (__ui_nodes[i].kind == NODE_PROGRESS || __ui_nodes[i].kind == NODE_RANGE) __ui_nodes[i].lastTextWidth = -1;
52
+ else __ui_nodes[i].lastTextWidth = 0;
53
+ }
54
+ }
55
+ extern const uint16_t __ui_font_face_count;
56
+
57
+ #define UI_NO_PARENT 0xFFFF // sentinel: out of uint16_t node-index range
58
+
59
+ // Forward declarations suppress Arduino's auto-prototyper, which would insert
60
+ // prototypes before UIFontFace/UIFontGlyph are declared.
61
+ static inline const UIFontFace* ui_font_face(uint8_t id);
62
+ static inline const UIFontGlyph* ui_font_glyph(const UIFontFace* face, uint16_t codepoint);
63
+ static inline uint8_t ui_font_alpha_at(const UIFontFace* face, const UIFontGlyph* glyph, uint16_t pixelIndex);
64
+ static inline uint16_t ui_asset_text_width(const char* text, const UIFontFace* face);
65
+ static inline uint8_t ui_asset_text_height(const UIFontFace* face);
66
+ static inline uint8_t ui_draw_asset_text(const char* text, int16_t x, int16_t y, UI_COLOR_T fg, UI_COLOR_T bg, uint8_t antialias, uint8_t fontFace);
67
+ static inline uint16_t ui_text_width(const char* text, uint8_t ts, uint8_t fontFace, int8_t letterSpacing);
68
+ struct UITextLine;
69
+ static inline uint8_t ui_text_next_line(const char** cursor, uint16_t maxWidth, uint8_t whiteSpaceMode, uint8_t ts, uint8_t fontFace, int8_t letterSpacing, UITextLine* out);
70
+ static inline void ui_text_layout_metrics(const char* text, uint16_t maxWidth, uint8_t whiteSpaceMode, uint8_t ts, uint8_t fontFace, int8_t letterSpacing, uint8_t lineHeight, uint16_t* outW, uint16_t* outH);
71
+ static inline void ui_invalidate_text_layout_cache(uint16_t nodeIdx);
72
+ static inline uint16_t ui_node_text_max_width(uint16_t nodeIdx);
73
+ static inline void ui_node_text_layout_metrics(uint16_t nodeIdx, uint16_t textMaxW, uint16_t* outW, uint16_t* outH);
74
+ static inline uint8_t ui_rects_intersect(int16_t ax, int16_t ay, int16_t aw, int16_t ah, int16_t bx, int16_t by, int16_t bw, int16_t bh);
75
+ static inline uint8_t ui_is_effectively_visible(uint16_t nodeIdx);
76
+ static inline uint8_t ui_node_draws_before(uint16_t a, uint16_t b);
77
+ static inline void ui_node_paint_rect(uint16_t nodeIdx, int16_t baseX, int16_t baseY, int16_t drawX, int16_t drawY, uint16_t textW, uint16_t textH, UIRect* out);
78
+ static inline void ui_node_current_paint_rect(uint16_t nodeIdx, UIRect* out);
79
+ static inline uint8_t ui_subtree_current_paint_rect(uint16_t nodeIdx, UIRect* out);
80
+ static inline void ui_mark_overlapping_higher_layers_dirty(uint16_t nodeIdx);
81
+ static inline void ui_mark_overlapping_higher_layers_dirty_for_rect(uint16_t nodeIdx, const UIRect* r);
82
+ static inline void ui_mark_scroll_view_dirty(uint16_t scrollNode);
83
+ static inline void ui_release_canvas_state();
84
+ static inline void ui_set_visible(uint16_t nodeIdx, uint8_t visible);
85
+ static inline void ui_invalidate_scroll_canvas_for_node(uint16_t nodeIdx);
86
+ static inline uint8_t ui_clip_rect_to_rect(UIRect* r, const UIRect* clip);
87
+ static inline void ui_fill_rect_clipped(int16_t x, int16_t y, int16_t w, int16_t h, const UIRect* clip, UI_COLOR_T color);
88
+ static inline void ui_hline_clipped(int16_t x, int16_t y, int16_t w, const UIRect* clip, UI_COLOR_T color);
89
+ static inline void ui_vline_clipped(int16_t x, int16_t y, int16_t h, const UIRect* clip, UI_COLOR_T color);
90
+ static inline void ui_draw_rect_outline_clipped(int16_t x, int16_t y, int16_t w, int16_t h, uint8_t style, uint8_t width, const UIRect* clip, UI_COLOR_T color);
91
+ static inline void ui_draw_node_decoration_clipped(uint16_t nodeIdx, int16_t drawY, const UIRect* clip);
92
+ // Forward declarations for functions used before their definition in the
93
+ // single native translation unit (Arduino's auto-prototyper hides this;
94
+ // native emits one TU so explicit forwards are needed).
95
+ static inline int8_t ui_rich_link_hit(uint16_t nodeIdx, int16_t px, int16_t py);
96
+ static inline CuttlefishCanvas16* ui_aa_begin(int16_t w, int16_t h, UI_COLOR_T bg);
97
+ static inline void ui_aa_end(CuttlefishCanvas16* c);
98
+ static inline void ui_aa_push(CuttlefishCanvas16* c, int16_t dx, int16_t dy);
99
+ static inline void ui_aa_line(CuttlefishCanvas16* c, float x0, float y0, float x1, float y1, UI_COLOR_T color);
100
+ static inline void ui_aa_circle(CuttlefishCanvas16* c, int16_t cx, int16_t cy, float r, UI_COLOR_T color);
101
+ static inline void ui_aa_fill_circle(CuttlefishCanvas16* c, int16_t cx, int16_t cy, float r, UI_COLOR_T color);
102
+ static inline uint8_t ui_repair_current_node_paint_with_parent(uint16_t nodeIdx, UIRect* r);
103
+ static inline void ui_clear_node_paint_rect(uint16_t nodeIdx, const UIRect* paintRect);
104
+ static inline uint8_t ui_try_repair_geometry_fill(uint16_t nodeIdx, const UIRect* oldRect);
105
+ static inline void ui_draw_node_border(uint16_t i, int16_t drawX, int16_t drawY, UI_COLOR_T color);
106
+ static inline void ui_draw_node_outline(uint16_t i, int16_t drawX, int16_t drawY);
107
+ static inline void ui_draw_gradient_fill(uint16_t i, int16_t drawY);
108
+ static inline uint8_t ui_rotation_quadrant(int16_t deg);
109
+ static inline int16_t ui_rotated_face_w(uint16_t nodeIdx, int16_t w, int16_t h);
110
+ static inline int16_t ui_rotated_face_h(uint16_t nodeIdx, int16_t w, int16_t h);
111
+ static inline void ui_draw_image_rotated(const UIImage* img, int16_t x, int16_t y, int16_t rotateDeg);
112
+ static inline void ui_draw_image_with_fit(const UIImage* img, int16_t x, int16_t y, int16_t rotateDeg,
113
+ uint8_t fitMode, int16_t targetW, int16_t targetH);
114
+ static inline void ui_draw_scaled_image(const UIImage* img, int16_t x, int16_t y, int16_t rotateDeg,
115
+ int16_t drawW, int16_t drawH);
116
+
117
+ static CuttlefishDisplayTarget* __ui_gfx = display_defaultTarget();
118
+ // Persistent canvas slots, all freed on screen change (ui_navigate) so each
119
+ // screen starts with a clean heap. Without this, the first screen's canvas
120
+ // buffer stays resident and fragments the heap, so a later screen's buffer
121
+ // can't get a contiguous block — manifesting as that element blanking until a
122
+ // hard reset. File-scoped (not function-static) so ui_release_canvas_state can
123
+ // reach them.
124
+ static CuttlefishCanvas16* __ui_container_canvas = nullptr; // Mode B shift-and-repair
125
+ static CuttlefishCanvas16* __ui_list_canvas = nullptr; // virtualized <list> viewport
126
+ static int16_t __ui_list_canvas_node = -1; // node currently represented by __ui_list_canvas
127
+ static CuttlefishCanvas16* __ui_node_canvas = nullptr; // <canvas> element offscreen
128
+ static CuttlefishCanvas16* __ui_repair_canvas = nullptr; // buffered-paint / exposed-strip
129
+ static int16_t __ui_canvas_fallback_w = 0; // dimensions for direct <canvas> fallback
130
+ static int16_t __ui_canvas_fallback_h = 0;
131
+ // Draw offset: normally zero. The <canvas> allocation fallback sets it so
132
+ // callback-local coordinates draw into the node's current display/canvas target.
133
+ static int16_t __ui_draw_off_x = 0;
134
+ static int16_t __ui_draw_off_y = 0;
135
+
136
+ // Release every persistent canvas so the next screen allocates into a clean`;
137
+ }
@@ -0,0 +1 @@
1
+ export declare function emitGuardClose(): string;
@@ -0,0 +1,8 @@
1
+ // Slice of the C++ runtime header (original source lines 5770-5770).
2
+ // Closing #endif of the __TC_UI_RUNTIME guard.
3
+ // See docs/superpowers/specs/2026-07-12-split-runtime-header-design.md.
4
+ export function emitGuardClose() {
5
+ return `
6
+ #endif
7
+ `;
8
+ }
@@ -0,0 +1 @@
1
+ export declare function emitImageDrawing(): string;
@@ -0,0 +1,300 @@
1
+ // Slice of the C++ runtime header (original source lines 1155-1448).
2
+ // Tail of framebuffer block + image rotation/fit/scaled/rotated drawing.
3
+ // See docs/superpowers/specs/2026-07-12-split-runtime-header-design.md.
4
+ export function emitImageDrawing() {
5
+ return `
6
+ // ── Full-screen framebuffer (opt-in PSRAM perf experiment) ───────────────────
7
+ // Disabled by default: pushing a full 320×240 frame for a tiny dirty button can
8
+ // look like a global brightness flash on SPI TFTs. Enable only when a target
9
+ // has enough PSRAM and a full-frame repaint is preferable to partial updates.
10
+ static CuttlefishCanvas16* __ui_fb = nullptr;
11
+ static uint8_t __ui_fb_tried = 0; // 0 = not yet attempted, 1 = alloc attempted
12
+
13
+ // Get the framebuffer, allocating once (in PSRAM when available). Returns null
14
+ // when PSRAM is absent or the allocation failed — callers fall back to direct.
15
+ static inline CuttlefishCanvas16* ui_get_framebuffer() {
16
+ if (__ui_fb_tried) return __ui_fb; // one-time attempt; null means "none"
17
+ __ui_fb_tried = 1;
18
+ #if UI_USE_FULL_FRAMEBUFFER && defined(ESP32) && defined(BOARD_HAS_PSRAM)
19
+ if (psramFound()) {
20
+ int16_t fw = display_width();
21
+ int16_t fh = display_height();
22
+ // GFXcanvas16 uses ps_malloc when ESP32 PSRAM malloc is hooked in via the
23
+ // Arduino core; the allocation succeeds only if PSRAM is really present.
24
+ __ui_fb = display_createCanvasPsram(fw, fh);
25
+ if (!__ui_fb || !display_canvasBuffer(__ui_fb)) {
26
+ // Allocation failed (PSRAM too small / not really mapped) — give up.
27
+ __ui_fb = nullptr;
28
+ }
29
+ }
30
+ #endif
31
+ return __ui_fb;
32
+ }
33
+
34
+ // Push the entire framebuffer to the display in one bulk SPI transaction.
35
+ static inline void ui_push_framebuffer() {
36
+ if (!__ui_fb || !display_canvasBuffer(__ui_fb)) return;
37
+ int16_t w = display_canvasWidth(__ui_fb);
38
+ int16_t h = display_canvasHeight(__ui_fb);
39
+ display_startWrite();
40
+ display_setAddrWindow(0, 0, w, h);
41
+ display_writePixels(display_canvasBuffer(__ui_fb), (uint32_t)w * h);
42
+ display_endWrite();
43
+ }
44
+
45
+ static inline uint8_t ui_rotation_quadrant(int16_t deg) {
46
+ int16_t normalized = deg % 360;
47
+ if (normalized < 0) normalized += 360;
48
+ if (normalized == 90) return 1;
49
+ if (normalized == 180) return 2;
50
+ if (normalized == 270) return 3;
51
+ return 0;
52
+ }
53
+
54
+ static inline int16_t ui_rotated_face_w(uint16_t nodeIdx, int16_t w, int16_t h) {
55
+ if (__ui_nodes[nodeIdx].kind != NODE_IMG &&
56
+ !(__ui_nodes[nodeIdx].kind == NODE_FILL && __ui_nodes[nodeIdx].gradientEnabled == 0)) return w;
57
+ uint8_t q = ui_rotation_quadrant(__ui_nodes[nodeIdx].rotateDeg);
58
+ return (q == 1 || q == 3) ? h : w;
59
+ }
60
+
61
+ static inline int16_t ui_rotated_face_h(uint16_t nodeIdx, int16_t w, int16_t h) {
62
+ if (__ui_nodes[nodeIdx].kind != NODE_IMG &&
63
+ !(__ui_nodes[nodeIdx].kind == NODE_FILL && __ui_nodes[nodeIdx].gradientEnabled == 0)) return h;
64
+ uint8_t q = ui_rotation_quadrant(__ui_nodes[nodeIdx].rotateDeg);
65
+ return (q == 1 || q == 3) ? w : h;
66
+ }
67
+
68
+ // Draw image with object-fit: 0=none, 1=fill, 2=contain, 3=cover, 4=scale-down.
69
+ // The sampler is bounded to the target box first, then quarter-turn rotated.
70
+ // This keeps cover cropped inside the element instead of overpainting siblings.
71
+ static inline void ui_draw_image_with_fit(const UIImage* img, int16_t x, int16_t y, int16_t rotateDeg,
72
+ uint8_t fitMode, int16_t targetW, int16_t targetH) {
73
+ if (!img || !img->data || img->w == 0 || img->h == 0 || targetW <= 0 || targetH <= 0) return;
74
+ int16_t srcW = img->w, srcH = img->h;
75
+ int16_t drawW = srcW, drawH = srcH;
76
+ int16_t offX = (targetW - drawW) / 2;
77
+ int16_t offY = (targetH - drawH) / 2;
78
+
79
+ if (fitMode == 1) {
80
+ drawW = targetW;
81
+ drawH = targetH;
82
+ offX = 0;
83
+ offY = 0;
84
+ } else if (fitMode == 2 || fitMode == 3 || fitMode == 4) {
85
+ int32_t scaleX = ((int32_t)targetW * 1000) / srcW;
86
+ int32_t scaleY = ((int32_t)targetH * 1000) / srcH;
87
+ if (scaleX < 1) scaleX = 1;
88
+ if (scaleY < 1) scaleY = 1;
89
+ int32_t scale = scaleX;
90
+ if (fitMode == 2) {
91
+ if (scaleY < scaleX) scale = scaleY;
92
+ } else if (fitMode == 3) {
93
+ if (scaleY > scaleX) scale = scaleY;
94
+ } else {
95
+ if (scaleY < scaleX) scale = scaleY;
96
+ if (scale > 1000) scale = 1000;
97
+ }
98
+ drawW = (int16_t)((int32_t)srcW * scale / 1000);
99
+ drawH = (int16_t)((int32_t)srcH * scale / 1000);
100
+ if (drawW < 1) drawW = 1;
101
+ if (drawH < 1) drawH = 1;
102
+ if (fitMode == 3) {
103
+ if (drawW < targetW) drawW = targetW;
104
+ if (drawH < targetH) drawH = targetH;
105
+ }
106
+ offX = (targetW - drawW) / 2;
107
+ offY = (targetH - drawH) / 2;
108
+ }
109
+
110
+ uint8_t q = ui_rotation_quadrant(rotateDeg);
111
+ int16_t clipX = x;
112
+ int16_t clipY = y;
113
+ int16_t clipW = (q == 1 || q == 3) ? targetH : targetW;
114
+ int16_t clipH = (q == 1 || q == 3) ? targetW : targetH;
115
+ if (!ui_clip_rect_to_display_target(&clipX, &clipY, &clipW, &clipH)) return;
116
+ int16_t txStart = 0, txEnd = targetW, tyStart = 0, tyEnd = targetH;
117
+ if (q == 0) {
118
+ txStart = ui_clamp_i16((int16_t)(clipX - x), 0, targetW);
119
+ txEnd = ui_clamp_i16((int16_t)(clipX + clipW - x), 0, targetW);
120
+ tyStart = ui_clamp_i16((int16_t)(clipY - y), 0, targetH);
121
+ tyEnd = ui_clamp_i16((int16_t)(clipY + clipH - y), 0, targetH);
122
+ } else if (q == 1) {
123
+ txStart = ui_clamp_i16((int16_t)(clipY - y), 0, targetW);
124
+ txEnd = ui_clamp_i16((int16_t)(clipY + clipH - y), 0, targetW);
125
+ tyStart = ui_clamp_i16((int16_t)(x + targetH - (clipX + clipW)), 0, targetH);
126
+ tyEnd = ui_clamp_i16((int16_t)(x + targetH - clipX), 0, targetH);
127
+ } else if (q == 2) {
128
+ txStart = ui_clamp_i16((int16_t)(x + targetW - (clipX + clipW)), 0, targetW);
129
+ txEnd = ui_clamp_i16((int16_t)(x + targetW - clipX), 0, targetW);
130
+ tyStart = ui_clamp_i16((int16_t)(y + targetH - (clipY + clipH)), 0, targetH);
131
+ tyEnd = ui_clamp_i16((int16_t)(y + targetH - clipY), 0, targetH);
132
+ } else {
133
+ txStart = ui_clamp_i16((int16_t)(y + targetW - (clipY + clipH)), 0, targetW);
134
+ txEnd = ui_clamp_i16((int16_t)(y + targetW - clipY), 0, targetW);
135
+ tyStart = ui_clamp_i16((int16_t)(clipX - x), 0, targetH);
136
+ tyEnd = ui_clamp_i16((int16_t)(clipX + clipW - x), 0, targetH);
137
+ }
138
+ if (txStart >= txEnd || tyStart >= tyEnd) return;
139
+ for (int16_t ty = tyStart; ty < tyEnd; ty++) {
140
+ int16_t localY = ty - offY;
141
+ if (localY < 0 || localY >= drawH) continue;
142
+ int16_t srcY = ((int32_t)localY * srcH) / drawH;
143
+ if (srcY < 0) srcY = 0;
144
+ if (srcY >= srcH) srcY = srcH - 1;
145
+ for (int16_t tx = txStart; tx < txEnd; tx++) {
146
+ int16_t localX = tx - offX;
147
+ if (localX < 0 || localX >= drawW) continue;
148
+ int16_t srcX = ((int32_t)localX * srcW) / drawW;
149
+ if (srcX < 0) srcX = 0;
150
+ if (srcX >= srcW) srcX = srcW - 1;
151
+ UI_COLOR_T color = img->data[(int32_t)srcY * srcW + srcX];
152
+ int16_t dx = tx;
153
+ int16_t dy = ty;
154
+ if (q == 1) {
155
+ dx = targetH - 1 - ty;
156
+ dy = tx;
157
+ } else if (q == 2) {
158
+ dx = targetW - 1 - tx;
159
+ dy = targetH - 1 - ty;
160
+ } else if (q == 3) {
161
+ dx = ty;
162
+ dy = targetW - 1 - tx;
163
+ }
164
+ ui_display_draw_pixel(x + dx, y + dy, color);
165
+ }
166
+ }
167
+ }
168
+
169
+ static inline void ui_draw_scaled_image(const UIImage* img, int16_t x, int16_t y, int16_t rotateDeg,
170
+ int16_t drawW, int16_t drawH) {
171
+ if (!img || !img->data || img->w == 0 || img->h == 0 || drawW <= 0 || drawH <= 0) return;
172
+ uint8_t q = ui_rotation_quadrant(rotateDeg);
173
+ int16_t clipX = x;
174
+ int16_t clipY = y;
175
+ int16_t clipW = (q == 1 || q == 3) ? drawH : drawW;
176
+ int16_t clipH = (q == 1 || q == 3) ? drawW : drawH;
177
+ if (!ui_clip_rect_to_display_target(&clipX, &clipY, &clipW, &clipH)) return;
178
+ int16_t dxStart = 0, dxEnd = drawW, dyStart = 0, dyEnd = drawH;
179
+ if (q == 0) {
180
+ dxStart = ui_clamp_i16((int16_t)(clipX - x), 0, drawW);
181
+ dxEnd = ui_clamp_i16((int16_t)(clipX + clipW - x), 0, drawW);
182
+ dyStart = ui_clamp_i16((int16_t)(clipY - y), 0, drawH);
183
+ dyEnd = ui_clamp_i16((int16_t)(clipY + clipH - y), 0, drawH);
184
+ } else if (q == 1) {
185
+ dxStart = ui_clamp_i16((int16_t)(clipY - y), 0, drawW);
186
+ dxEnd = ui_clamp_i16((int16_t)(clipY + clipH - y), 0, drawW);
187
+ dyStart = ui_clamp_i16((int16_t)(x + drawH - (clipX + clipW)), 0, drawH);
188
+ dyEnd = ui_clamp_i16((int16_t)(x + drawH - clipX), 0, drawH);
189
+ } else if (q == 2) {
190
+ dxStart = ui_clamp_i16((int16_t)(x + drawW - (clipX + clipW)), 0, drawW);
191
+ dxEnd = ui_clamp_i16((int16_t)(x + drawW - clipX), 0, drawW);
192
+ dyStart = ui_clamp_i16((int16_t)(y + drawH - (clipY + clipH)), 0, drawH);
193
+ dyEnd = ui_clamp_i16((int16_t)(y + drawH - clipY), 0, drawH);
194
+ } else {
195
+ dxStart = ui_clamp_i16((int16_t)(y + drawW - (clipY + clipH)), 0, drawW);
196
+ dxEnd = ui_clamp_i16((int16_t)(y + drawW - clipY), 0, drawW);
197
+ dyStart = ui_clamp_i16((int16_t)(clipX - x), 0, drawH);
198
+ dyEnd = ui_clamp_i16((int16_t)(clipX + clipW - x), 0, drawH);
199
+ }
200
+ if (dxStart >= dxEnd || dyStart >= dyEnd) return;
201
+ if (q == 0) {
202
+ // Simple case: no rotation, draw with scaling
203
+ if (drawW == img->w && drawH == img->h) {
204
+ int16_t rows = (int16_t)(dyEnd - dyStart);
205
+ int16_t cols = (int16_t)(dxEnd - dxStart);
206
+ for (int16_t row = 0; row < rows; row++) {
207
+ ui_display_draw_rgb_bitmap((int16_t)(x + dxStart), (int16_t)(y + dyStart + row),
208
+ img->data + (int32_t)(dyStart + row) * img->w + dxStart, cols, 1);
209
+ }
210
+ } else {
211
+ // Scale using nearest-neighbor
212
+ for (int16_t dy = dyStart; dy < dyEnd; dy++) {
213
+ int16_t srcY = ((int32_t)dy * img->h) / drawH;
214
+ for (int16_t dx = dxStart; dx < dxEnd; dx++) {
215
+ int16_t srcX = ((int32_t)dx * img->w) / drawW;
216
+ UI_COLOR_T color = img->data[(int32_t)srcY * img->w + srcX];
217
+ ui_display_draw_pixel(x + dx, y + dy, color);
218
+ }
219
+ }
220
+ }
221
+ return;
222
+ }
223
+ for (int16_t dy = dyStart; dy < dyEnd; dy++) {
224
+ int16_t srcY = ((int32_t)dy * img->h) / drawH;
225
+ for (int16_t dx = dxStart; dx < dxEnd; dx++) {
226
+ int16_t srcX = ((int32_t)dx * img->w) / drawW;
227
+ UI_COLOR_T color = img->data[(int32_t)srcY * img->w + srcX];
228
+ int16_t rdx = 0, rdy = 0;
229
+ if (q == 1) {
230
+ rdx = drawH - 1 - dy;
231
+ rdy = dx;
232
+ } else if (q == 2) {
233
+ rdx = drawW - 1 - dx;
234
+ rdy = drawH - 1 - dy;
235
+ } else {
236
+ rdx = dy;
237
+ rdy = drawW - 1 - dx;
238
+ }
239
+ ui_display_draw_pixel(x + rdx, y + rdy, color);
240
+ }
241
+ }
242
+ }
243
+
244
+ static inline void ui_draw_image_rotated(const UIImage* img, int16_t x, int16_t y, int16_t rotateDeg) {
245
+ uint8_t q = ui_rotation_quadrant(rotateDeg);
246
+ int16_t clipX = x;
247
+ int16_t clipY = y;
248
+ int16_t clipW = (q == 1 || q == 3) ? img->h : img->w;
249
+ int16_t clipH = (q == 1 || q == 3) ? img->w : img->h;
250
+ if (!ui_clip_rect_to_display_target(&clipX, &clipY, &clipW, &clipH)) return;
251
+ if (q == 0) {
252
+ int16_t sx0 = ui_clamp_i16((int16_t)(clipX - x), 0, (int16_t)img->w);
253
+ int16_t sy0 = ui_clamp_i16((int16_t)(clipY - y), 0, (int16_t)img->h);
254
+ int16_t sw = ui_clamp_i16(clipW, 0, (int16_t)(img->w - sx0));
255
+ int16_t sh = ui_clamp_i16(clipH, 0, (int16_t)(img->h - sy0));
256
+ for (int16_t row = 0; row < sh; row++) {
257
+ ui_display_draw_rgb_bitmap((int16_t)(x + sx0), (int16_t)(y + sy0 + row),
258
+ img->data + (int32_t)(sy0 + row) * img->w + sx0, sw, 1);
259
+ }
260
+ return;
261
+ }
262
+ uint16_t sxStart = 0, sxEnd = img->w, syStart = 0, syEnd = img->h;
263
+ if (q == 1) {
264
+ sxStart = (uint16_t)ui_clamp_i16((int16_t)(clipY - y), 0, (int16_t)img->w);
265
+ sxEnd = (uint16_t)ui_clamp_i16((int16_t)(clipY + clipH - y), 0, (int16_t)img->w);
266
+ syStart = (uint16_t)ui_clamp_i16((int16_t)(x + img->h - (clipX + clipW)), 0, (int16_t)img->h);
267
+ syEnd = (uint16_t)ui_clamp_i16((int16_t)(x + img->h - clipX), 0, (int16_t)img->h);
268
+ } else if (q == 2) {
269
+ sxStart = (uint16_t)ui_clamp_i16((int16_t)(x + img->w - (clipX + clipW)), 0, (int16_t)img->w);
270
+ sxEnd = (uint16_t)ui_clamp_i16((int16_t)(x + img->w - clipX), 0, (int16_t)img->w);
271
+ syStart = (uint16_t)ui_clamp_i16((int16_t)(y + img->h - (clipY + clipH)), 0, (int16_t)img->h);
272
+ syEnd = (uint16_t)ui_clamp_i16((int16_t)(y + img->h - clipY), 0, (int16_t)img->h);
273
+ } else {
274
+ sxStart = (uint16_t)ui_clamp_i16((int16_t)(y + img->w - (clipY + clipH)), 0, (int16_t)img->w);
275
+ sxEnd = (uint16_t)ui_clamp_i16((int16_t)(y + img->w - clipY), 0, (int16_t)img->w);
276
+ syStart = (uint16_t)ui_clamp_i16((int16_t)(clipX - x), 0, (int16_t)img->h);
277
+ syEnd = (uint16_t)ui_clamp_i16((int16_t)(clipX + clipW - x), 0, (int16_t)img->h);
278
+ }
279
+ if (sxStart >= sxEnd || syStart >= syEnd) return;
280
+ for (uint16_t sy = syStart; sy < syEnd; sy++) {
281
+ for (uint16_t sx = sxStart; sx < sxEnd; sx++) {
282
+ UI_COLOR_T color = img->data[(uint32_t)sy * img->w + sx];
283
+ int16_t dx = 0;
284
+ int16_t dy = 0;
285
+ if (q == 1) {
286
+ dx = (int16_t)(img->h - 1 - sy);
287
+ dy = (int16_t)sx;
288
+ } else if (q == 2) {
289
+ dx = (int16_t)(img->w - 1 - sx);
290
+ dy = (int16_t)(img->h - 1 - sy);
291
+ } else {
292
+ dx = (int16_t)sy;
293
+ dy = (int16_t)(img->w - 1 - sx);
294
+ }
295
+ ui_display_draw_pixel(x + dx, y + dy, color);
296
+ }
297
+ }
298
+ }
299
+ `;
300
+ }
@@ -0,0 +1 @@
1
+ export declare function emitInitPressInput(): string;
@@ -0,0 +1,126 @@
1
+ // Slice of the C++ runtime header (original source lines 2442-2561).
2
+ // ui_init, press/release entry points, pin-watch polling.
3
+ // See docs/superpowers/specs/2026-07-12-split-runtime-header-design.md.
4
+ export function emitInitPressInput() {
5
+ return `
6
+ // Initial draw: mark all nodes dirty so the first ui_tick renders everything.
7
+ // Called once in setup() before the loop begins.
8
+ // Also seed each text-bound node's buffer from its flash literal so the first
9
+ // strcmp in ui_tick has a valid baseline (no spurious redraw on frame 1).
10
+ static inline void ui_init(void) {
11
+ // Allocate the per-node scroll-canvas-OK flag array (once; __ui_node_count
12
+ // is a compile-time constant known by this point). calloc zeroes it — all
13
+ // containers start locked until the scroll-container loop proves their
14
+ // canvas fits.
15
+ if (!__ui_scroll_canvas_ok && __ui_node_count > 0) {
16
+ __ui_scroll_canvas_ok = (uint8_t*)calloc(__ui_node_count, sizeof(uint8_t));
17
+ }
18
+ if (!__ui_scroll_mem_warned && __ui_node_count > 0) {
19
+ __ui_scroll_mem_warned = (uint8_t*)calloc(__ui_node_count, sizeof(uint8_t));
20
+ }
21
+ for (uint16_t i = 0; i < __ui_node_count; i++) {
22
+ __ui_nodes[i].dirty = 1;
23
+ __ui_nodes[i].lastTextHeight = 0;
24
+ __ui_nodes[i].layoutCacheKey = 0;
25
+ if (__ui_nodes[i].kind == NODE_PROGRESS || __ui_nodes[i].kind == NODE_RANGE) {
26
+ __ui_nodes[i].lastTextWidth = -1;
27
+ }
28
+ }
29
+ for (uint16_t i = 0; i < __ui_binding_count; i++) {
30
+ if (__ui_bindings[i].prop == PROP_TEXT && __ui_bindings[i].textFn) {
31
+ uint16_t n = __ui_bindings[i].node;
32
+ __ui_nodes[n].hasTextBinding = 1;
33
+ strncpy(__ui_nodes[n].textBuffer, __ui_nodes[n].text ? __ui_nodes[n].text : "", UI_TEXT_BUF);
34
+ __ui_nodes[n].textBuffer[UI_TEXT_BUF] = '\\0';
35
+ }
36
+ }
37
+ // Seed virtualized-list runtime state. The fn pointers can't be baked into
38
+ // the static node initializer: ui.bindList is resolved AFTER ui.mount lowers
39
+ // the HTML, so the lowering can't see the binding yet. Instead the lowering
40
+ // emits the UIListBinding table (the binding's fn bodies) and ui_init copies
41
+ // the pointers onto each <list> node here, then computes the initial count
42
+ // and contentHeight so the first paint and the scroll clamp bound are correct.
43
+ for (uint16_t b = 0; b < __ui_list_binding_count; b++) {
44
+ uint16_t n = __ui_list_bindings[b].node;
45
+ if (n >= __ui_node_count || !__ui_nodes[n].virtualized) continue;
46
+ __ui_nodes[n].listCountFn = __ui_list_bindings[b].countFn;
47
+ __ui_nodes[n].listItemFn = __ui_list_bindings[b].itemFn;
48
+ __ui_nodes[n].listTapFn = __ui_list_bindings[b].tapFn;
49
+ }
50
+ for (uint16_t i = 0; i < __ui_node_count; i++) {
51
+ if (!__ui_nodes[i].virtualized || !__ui_nodes[i].listCountFn) continue;
52
+ uint16_t ih = __ui_nodes[i].listItemHeight > 0 ? __ui_nodes[i].listItemHeight : 24;
53
+ uint16_t cnt = __ui_nodes[i].listCountFn();
54
+ __ui_nodes[i].listCount = cnt;
55
+ __ui_nodes[i].contentHeight = (int16_t)((uint32_t)cnt * ih);
56
+ __ui_nodes[i].scrollY = 0;
57
+ __ui_nodes[i].overscrollPx = 0;
58
+ __ui_nodes[i].settling = 0;
59
+ __ui_nodes[i].lastPaintedScrollY = -(__ui_nodes[i].box.h > 0 ? __ui_nodes[i].box.h : 1);
60
+ }
61
+ ui_build_draw_order();
62
+ ui_build_scroll_owner_table();
63
+ ui_refresh_active_screen_bg_node();
64
+ }
65
+
66
+ // Debounce: ignore press/release events within 50ms of the last edge.
67
+ // Mechanical switches bounce (multiple edges in ~5-20ms); without this, the
68
+ // transition gets armed/interrupted dozens of times per physical press.
69
+ static volatile uint32_t __ui_last_edge_time = 0;
70
+ #define UI_DEBOUNCE_MS 50
71
+
72
+ static inline void ui_set_pressed(uint16_t nodeIdx, uint8_t pressed) {
73
+ __ui_nodes[nodeIdx].value = pressed ? 1 : 0;
74
+ ui_mark_dirty(nodeIdx);
75
+ for (uint16_t i = 0; i < __ui_trans_count; i++) {
76
+ if (__ui_trans[i].node == nodeIdx) {
77
+ __ui_trans[i].prevValue = __ui_trans[i].prop == PROP_FG ? __ui_nodes[nodeIdx].fg : __ui_nodes[nodeIdx].bg;
78
+ __ui_trans[i].targetValue = pressed ? __ui_trans[i].pressedTarget : __ui_trans[i].baseTarget;
79
+ __ui_trans[i].elapsed = 0;
80
+ __ui_trans[i].active = 1;
81
+ }
82
+ }
83
+ }
84
+
85
+ // Press / release entry points that node.onPress(pin) lowers to.
86
+ // On press, arm transitions toward the :pressed target color; on release,
87
+ // arm them back toward the base color (interrupt-and-re-lerp from current).
88
+ static inline void ui_on_press(uint16_t nodeIdx) {
89
+ uint32_t now = millis();
90
+ if (now - __ui_last_edge_time < UI_DEBOUNCE_MS) return;
91
+ __ui_last_edge_time = now;
92
+ ui_set_pressed(nodeIdx, 1);
93
+ }
94
+ static inline void ui_on_release(uint16_t nodeIdx) {
95
+ uint32_t now = millis();
96
+ if (now - __ui_last_edge_time < UI_DEBOUNCE_MS) return;
97
+ __ui_last_edge_time = now;
98
+ ui_set_pressed(nodeIdx, 0);
99
+ }
100
+
101
+ // Pin-watch callback type: void fn(void)
102
+ typedef void (*PinWatchCallback)(void);
103
+
104
+ struct UIPinWatch {
105
+ uint8_t pin;
106
+ uint8_t lastState; // for edge detection
107
+ PinWatchCallback cb; // fires on falling edge
108
+ };
109
+
110
+ // Populated by the emit layer from ui.watchPin() calls.
111
+ extern UIPinWatch __ui_pin_watches[];
112
+ extern const uint16_t __ui_pin_watch_count;
113
+
114
+ // Poll all configured pin-watchers. Called at the start of ui_tick each frame.
115
+ // Detects falling edges with natural debounce from the ~16ms frame rate.
116
+ static inline void ui_poll_inputs() {
117
+ for (uint16_t i = 0; i < __ui_pin_watch_count; i++) {
118
+ uint8_t val = digitalRead(__ui_pin_watches[i].pin);
119
+ if (val == LOW && __ui_pin_watches[i].lastState == HIGH) {
120
+ if (__ui_pin_watches[i].cb) __ui_pin_watches[i].cb();
121
+ }
122
+ __ui_pin_watches[i].lastState = val;
123
+ }
124
+ }
125
+ `;
126
+ }
@@ -0,0 +1 @@
1
+ export declare function emitKeyboard(): string;