@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,342 @@
1
+ // Slice of the C++ runtime header (original source lines 5434-5769).
2
+ // On-screen keyboard: add_key/insert/delete/compute_box/open/close/rect/touch/tick/tap/draw.
3
+ // See docs/superpowers/specs/2026-07-12-split-runtime-header-design.md.
4
+ export function emitKeyboard() {
5
+ return `
6
+ // ── On-screen keyboard subsystem ───────────────────────────────────────────
7
+ // (UIKey struct, UI_KB_* defines, __ui_kb_box, __ui_kb_visible, __ui_kb_bs_held,
8
+ // __ui_last_touch_x/y are declared earlier near the touch state machine so
9
+ // the touch functions can reference them.)
10
+
11
+ // Populated by the per-keyboard loader function (emitted by the lowering).
12
+ // (__ui_kb_keys is forward-declared earlier near the touch state machine.)
13
+ static uint8_t __ui_kb_keyCount;
14
+ static uint8_t __ui_kb_rows;
15
+ static uint8_t __ui_kb_cols;
16
+ static char __ui_kb_buffer[UI_TEXT_BUF + 1];
17
+ static uint8_t __ui_kb_len;
18
+ static uint8_t __ui_kb_maxlen;
19
+ static uint8_t __ui_kb_shift;
20
+ // __ui_kb_visible, __ui_kb_bs_held, __ui_last_touch_x/y are forward-declared
21
+ // earlier (near the touch state machine) because ui_touch_up references them.
22
+ // __ui_kb_target is also forward-declared there (the UI_HIDE_OSK caret/blink
23
+ // paths in ui_tick and the input draw reference it before this point).
24
+ static uint32_t __ui_kb_bs_repeat; // last auto-repeat deletion time
25
+ // __ui_kb_dirty is forward-declared earlier (near the touch state machine).
26
+ static void (*__ui_kb_onchange)();
27
+ // Dispatch table: one loader per input node. Indexed by input position.
28
+ extern void (*__ui_kb_loaders[])();
29
+ extern const uint16_t __ui_kb_loader_count;
30
+
31
+ static inline void ui_kb_add_key(char ch, uint8_t special, UI_COLOR_T bg, UI_COLOR_T fg, UI_COLOR_T borderColor) {
32
+ if (__ui_kb_keyCount >= UI_KB_MAX) return;
33
+ __ui_kb_keys[__ui_kb_keyCount] = { ch, special };
34
+ __ui_kb_styles[__ui_kb_keyCount] = { bg, fg, borderColor };
35
+ __ui_kb_keyCount++;
36
+ }
37
+
38
+ // Insert a character into the buffer (if space permits).
39
+ static inline void ui_kb_insert(char c) {
40
+ if (__ui_kb_maxlen > 0 && __ui_kb_len >= __ui_kb_maxlen) return;
41
+ if (__ui_kb_len >= UI_TEXT_BUF) return;
42
+ __ui_kb_buffer[__ui_kb_len++] = c;
43
+ __ui_kb_buffer[__ui_kb_len] = 0;
44
+ #if defined(UI_HIDE_OSK)
45
+ // Desktop target: the OSK grid isn't drawn, so the input field itself is the
46
+ // only place the in-progress text appears. Sync the buffer into the target
47
+ // node's textBuffer and mark it dirty so the field repaints on the next tick
48
+ // — without this, typing appears to do nothing until Enter commits at close.
49
+ if (__ui_kb_target >= 0) {
50
+ strncpy(__ui_nodes[__ui_kb_target].textBuffer, __ui_kb_buffer, UI_TEXT_BUF);
51
+ __ui_nodes[__ui_kb_target].textBuffer[UI_TEXT_BUF] = 0;
52
+ ui_mark_dirty((uint16_t)__ui_kb_target);
53
+ }
54
+ #endif
55
+ }
56
+
57
+ // Delete one character from the buffer.
58
+ static inline void ui_kb_delete() {
59
+ if (__ui_kb_len == 0) return;
60
+ __ui_kb_buffer[--__ui_kb_len] = 0;
61
+ #if defined(UI_HIDE_OSK)
62
+ if (__ui_kb_target >= 0) {
63
+ strncpy(__ui_nodes[__ui_kb_target].textBuffer, __ui_kb_buffer, UI_TEXT_BUF);
64
+ __ui_nodes[__ui_kb_target].textBuffer[UI_TEXT_BUF] = 0;
65
+ ui_mark_dirty((uint16_t)__ui_kb_target);
66
+ }
67
+ #endif
68
+ }
69
+
70
+ // Compute the keyboard box on open from display dimensions + grid shape.
71
+ // Alpha (wide grid) docks to the bottom 75%; number (narrow grid) centers at 60%.
72
+ // Uses the display profile dimensions if available, else 320×240.
73
+ #ifndef __ui_display_w
74
+ #define __ui_display_w 320
75
+ #endif
76
+ #ifndef __ui_display_h
77
+ #define __ui_display_h 240
78
+ #endif
79
+ static inline void ui_kb_compute_box() {
80
+ uint8_t isNumber = (__ui_kb_cols <= 4);
81
+ uint16_t h = isNumber ? (__ui_display_h * 60 / 100) : (__ui_display_h * 75 / 100);
82
+ __ui_kb_box.w = isNumber ? (__ui_display_w * 50 / 100) : __ui_display_w;
83
+ __ui_kb_box.h = h;
84
+ __ui_kb_box.x = isNumber ? (__ui_display_w - __ui_kb_box.w) / 2 : 0;
85
+ __ui_kb_box.y = __ui_display_h - h;
86
+ }
87
+
88
+ // Open the keyboard for an input node.
89
+ static inline void ui_kb_open(uint16_t nodeIdx, uint8_t inputPosition) {
90
+ __ui_kb_target = nodeIdx;
91
+ strncpy(__ui_kb_buffer, __ui_nodes[nodeIdx].textBuffer, UI_TEXT_BUF);
92
+ __ui_kb_buffer[UI_TEXT_BUF] = 0;
93
+ __ui_kb_len = strlen(__ui_kb_buffer);
94
+ uint16_t ml = __ui_nodes[nodeIdx].maxlen;
95
+ __ui_kb_maxlen = (ml > 0 && ml <= UI_TEXT_BUF) ? (uint8_t)ml : UI_TEXT_BUF;
96
+ __ui_kb_shift = 0;
97
+ __ui_kb_bs_held = 0;
98
+ // Load the key set via the dispatch table.
99
+ if (inputPosition < __ui_kb_loader_count) __ui_kb_loaders[inputPosition]();
100
+ __ui_kb_set_onchange();
101
+ ui_kb_compute_box();
102
+ __ui_kb_visible = 1;
103
+ __ui_kb_dirty = 1; // redraw on the first visible frame
104
+ // Do NOT pre-mark the tree dirty here. While the keyboard is visible the
105
+ // draw pass is skipped (its opaque background covers app nodes), so any
106
+ // dirty flags set now are never consumed/cleared — they survive until close,
107
+ // and the first post-close frame then repaints every dirty node = full-screen
108
+ // flash on SPI TFTs. The close path scopes the repaint to nodes whose paint
109
+ // rect intersects __ui_kb_box, which is the only region that needs restoring.
110
+ #if defined(UI_HIDE_OSK)
111
+ // Desktop target: the OSK isn't drawn, so the draw pass runs normally and the
112
+ // input field must repaint on focus to show the caret BEFORE the first
113
+ // keystroke. (The full-screen-flash concern above doesn't apply — there's no
114
+ // opaque overlay being skipped.) Seed the blink phase so the caret is ON for
115
+ // the first ~530ms (bit 0x20 set → visible), so focus feels immediate.
116
+ if (__ui_kb_target >= 0) ui_mark_dirty((uint16_t)__ui_kb_target);
117
+ __ui_kb_blink = 0x20;
118
+ #endif
119
+ }
120
+
121
+ // Close the keyboard: commit buffer back to the input node.
122
+ static inline void ui_kb_close() {
123
+ if (__ui_kb_target >= 0) {
124
+ strncpy(__ui_nodes[__ui_kb_target].textBuffer, __ui_kb_buffer, UI_TEXT_BUF);
125
+ __ui_nodes[__ui_kb_target].textBuffer[UI_TEXT_BUF] = 0;
126
+ if (__ui_kb_onchange) __ui_kb_onchange();
127
+ }
128
+ // Capture the keyboard box before clearing visibility — it identifies the
129
+ // screen region the opaque overlay covered and that now needs restoring.
130
+ UIRect kbBox = __ui_kb_box;
131
+ __ui_kb_visible = 0;
132
+ __ui_kb_target = -1;
133
+ __ui_kb_bs_held = 0;
134
+ // The <screen> root's paint rect spans the whole display, so it always
135
+ // intersects kbBox. Routing it through ui_mark_dirty would cascade via
136
+ // ui_mark_overlapping_higher_layers_dirty into marking every node on the
137
+ // active screen dirty (its rect overlaps everything), which is the
138
+ // full-screen flash this function exists to avoid. Repaint just the
139
+ // keyboard-box slice of its background directly instead.
140
+ int16_t screenRoot = -1;
141
+ for (uint16_t i = 0; i < __ui_node_count; i++) {
142
+ if (__ui_nodes[i].parent == UI_NO_PARENT && __ui_nodes[i].screenId == __ui_active_screen) {
143
+ screenRoot = (int16_t)i;
144
+ break;
145
+ }
146
+ }
147
+ if (screenRoot >= 0) {
148
+ UI_COLOR_T rootBg = (UI_COLOR_T)(__ui_nodes[screenRoot].hasBg
149
+ ? __ui_nodes[screenRoot].bg
150
+ : __ui_nodes[screenRoot].clearColor);
151
+ ui_display_fill_rect(kbBox.x, kbBox.y, kbBox.w, kbBox.h, rootBg);
152
+ }
153
+ // Repaint everything else the keyboard overlay actually overwrote: nodes
154
+ // whose paint rect intersects the keyboard box, plus the edited input
155
+ // itself (its text just changed). ui_mark_dirty handles overlap repair +
156
+ // scroll clipping — safe here since these nodes are bounded in size, unlike
157
+ // the screen root.
158
+ for (uint16_t i = 0; i < __ui_node_count; i++) {
159
+ if ((int16_t)i == screenRoot) continue;
160
+ UIRect r;
161
+ ui_node_current_paint_rect(i, &r);
162
+ if (r.w > 0 && r.h > 0 &&
163
+ ui_rects_intersect(r.x, r.y, r.w, r.h, kbBox.x, kbBox.y, kbBox.w, kbBox.h)) {
164
+ ui_mark_dirty(i);
165
+ }
166
+ }
167
+ }
168
+
169
+ // Compute a key's rect from its index, given the grid + box.
170
+ // The top UI_KB_TEXT_H pixels of the box are reserved for the preview text row;
171
+ // keys fill the area below it.
172
+ static inline void ui_kb_key_rect(uint8_t idx, UIRect* out) {
173
+ uint8_t col = idx % __ui_kb_cols;
174
+ uint8_t row = idx / __ui_kb_cols;
175
+ int16_t keysH = __ui_kb_box.h - UI_KB_TEXT_H; // key area height (below text row)
176
+ out->x = __ui_kb_box.x + (int16_t)col * __ui_kb_box.w / __ui_kb_cols;
177
+ out->y = __ui_kb_box.y + UI_KB_TEXT_H + (int16_t)row * keysH / __ui_kb_rows;
178
+ out->w = __ui_kb_box.w / __ui_kb_cols;
179
+ out->h = keysH / __ui_kb_rows;
180
+ }
181
+
182
+ // Handle a touch-down inside the keyboard box. tx,ty are display coords.
183
+ // Handle a touch-down inside the keyboard box. Only fires on the initial
184
+ // down edge (tracked by __ui_touch_state in the modal path), NOT every poll.
185
+ // Records WHICH key is under the finger — the same key is activated on release
186
+ // (ui_kb_handle_tap), avoiding mis-targeting from coordinate drift on release.
187
+ static inline void ui_kb_handle_touch(int16_t tx, int16_t ty) {
188
+ __ui_kb_pressed_key = -1;
189
+ for (uint8_t i = 0; i < __ui_kb_keyCount; i++) {
190
+ UIKey k = __ui_kb_keys[i];
191
+ if (k.special == 255) continue; // padding cell, skip
192
+ UIRect r;
193
+ ui_kb_key_rect(i, &r);
194
+ if (tx >= r.x && tx < r.x + r.w && ty >= r.y && ty < r.y + r.h) {
195
+ __ui_kb_pressed_key = (int8_t)i; // remember for release
196
+ __ui_kb_repaint_key = (int8_t)i;
197
+ __ui_kb_dirty = 2; // targeted: redraw just this key's highlight
198
+ // Backspace starts deleting immediately + arms auto-repeat.
199
+ if (k.special == 2) {
200
+ __ui_kb_bs_held = 1;
201
+ __ui_kb_bs_repeat = millis();
202
+ ui_kb_delete();
203
+ }
204
+ return;
205
+ }
206
+ }
207
+ }
208
+
209
+ // Called each frame while the keyboard is visible + a touch is held.
210
+ // Handles ⌫ auto-repeat.
211
+ static inline void ui_kb_tick(uint32_t now) {
212
+ if (!__ui_kb_bs_held) return;
213
+ if (now - __ui_kb_bs_repeat >= UI_KB_REPEAT_MS) {
214
+ ui_kb_delete();
215
+ __ui_kb_bs_repeat = now;
216
+ __ui_kb_repaint_key = __ui_kb_pressed_key; // ⌫ key stays highlighted
217
+ __ui_kb_dirty = 2; // targeted: text row + ⌫ key
218
+ }
219
+ }
220
+
221
+ // Handle a tap release. Activates the key that was under the finger on
222
+ // touch-down (__ui_kb_pressed_key) — NOT a fresh hit-test, which would
223
+ // mis-target due to coordinate drift on a resistive panel at release.
224
+ static inline void ui_kb_handle_tap(int16_t tx, int16_t ty) {
225
+ (void)tx; (void)ty; // key was recorded on touch-down; no re-hit-test
226
+ if (__ui_kb_pressed_key < 0) return;
227
+ UIKey k = __ui_kb_keys[__ui_kb_pressed_key];
228
+ int8_t releasedKey = __ui_kb_pressed_key;
229
+ __ui_kb_pressed_key = -1; // clear pressed state → highlight reverts
230
+ switch (k.special) {
231
+ case 0: { // char
232
+ char c = k.ch;
233
+ uint8_t wasShift = __ui_kb_shift;
234
+ if (wasShift && c >= 'a' && c <= 'z') c -= 32;
235
+ ui_kb_insert(c);
236
+ __ui_kb_shift = 0; // shift resets after one char
237
+ // Text row changes; if shift was active, repaint shift key too.
238
+ __ui_kb_repaint_key = wasShift ? -1 : releasedKey;
239
+ __ui_kb_dirty = 2;
240
+ break;
241
+ }
242
+ case 1: // shift toggle — all letter keys change case, full redraw
243
+ __ui_kb_shift = !__ui_kb_shift;
244
+ __ui_kb_dirty = 1;
245
+ break;
246
+ case 2: // backspace: handled on down + repeat; nothing on tap-up
247
+ __ui_kb_repaint_key = releasedKey; // revert highlight
248
+ __ui_kb_dirty = 2;
249
+ break;
250
+ case 3: // OK
251
+ ui_kb_close();
252
+ break;
253
+ case 4: { // page-swap — new key layout, full redraw
254
+ extern void __ui_kb_load_default_alpha();
255
+ extern void __ui_kb_load_default_number();
256
+ if (__ui_kb_cols <= 4) __ui_kb_load_default_alpha();
257
+ else __ui_kb_load_default_number();
258
+ ui_kb_compute_box();
259
+ __ui_kb_dirty = 1;
260
+ break;
261
+ }
262
+ }
263
+ }
264
+
265
+ // Draw a single key by index. Shared by the full draw + targeted redraw.
266
+ static inline void ui_kb_draw_key(uint8_t i) {
267
+ UIKey k = __ui_kb_keys[i];
268
+ UIKeyStyle ks = __ui_kb_styles[i];
269
+ UIRect r;
270
+ ui_kb_key_rect(i, &r);
271
+ UI_COLOR_T bg = ks.bg;
272
+ UI_COLOR_T fg = ks.fg;
273
+ UI_COLOR_T border = ks.borderColor;
274
+ // Shift-active highlight: brighten the shift key's background — but only
275
+ // when not pressed, so the press inversion stays high-contrast.
276
+ if (k.special == 1 && __ui_kb_shift && (int8_t)i != __ui_kb_pressed_key) {
277
+ #if UI_COLOR_DEPTH == 888
278
+ bg = 0xBDEFFF;
279
+ #else
280
+ bg = 0xBDF7;
281
+ #endif
282
+ }
283
+ // Pressed key: invert colors for clear tap feedback.
284
+ if ((int8_t)i == __ui_kb_pressed_key) { UI_COLOR_T t = bg; bg = fg; fg = t; }
285
+ ui_display_fill_rect(r.x + 1, r.y + 1, r.w - 2, r.h - 2, bg);
286
+ ui_display_draw_rect(r.x + 1, r.y + 1, r.w - 2, r.h - 2, border);
287
+ ui_display_set_text_color(fg, bg);
288
+ ui_display_set_text_size(1);
289
+ // Derive the label string + its length for centering.
290
+ const char* labelStr;
291
+ char single[2];
292
+ switch (k.special) {
293
+ case 1: labelStr = "Aa"; break;
294
+ case 2: labelStr = "DEL"; break;
295
+ case 3: labelStr = "OK"; break;
296
+ case 4: labelStr = __ui_kb_cols <= 4 ? "ABC" : "123"; break;
297
+ default:
298
+ single[0] = (__ui_kb_shift && k.ch >= 'a' && k.ch <= 'z') ? (char)(k.ch - 32) : k.ch;
299
+ single[1] = 0;
300
+ labelStr = single;
301
+ break;
302
+ }
303
+ // Center: textW = len * 6px, textH = 8px. Position inside the key rect.
304
+ uint8_t len = strlen(labelStr);
305
+ int16_t textW = (int16_t)len * 6;
306
+ int16_t textH = 8;
307
+ int16_t cx = r.x + (r.w - textW) / 2;
308
+ int16_t cy = r.y + (r.h - textH) / 2;
309
+ if (cx < r.x + 1) cx = r.x + 1; // clamp if label wider than key
310
+ ui_display_set_cursor(cx, cy);
311
+ ui_display_print(labelStr);
312
+ }
313
+
314
+ // Redraw only the text display row (top of keyboard box). Used when a char is
315
+ // inserted/deleted without changing key highlights.
316
+ static inline void ui_kb_draw_text_row() {
317
+ // Clear the text row area (top UI_KB_TEXT_H px of the keyboard box).
318
+ ui_display_fill_rect(__ui_kb_box.x, __ui_kb_box.y, __ui_kb_box.w, UI_KB_TEXT_H, __ui_kb_bg);
319
+ ui_display_set_cursor(__ui_kb_box.x + 4, __ui_kb_box.y + 4);
320
+ #if UI_COLOR_DEPTH == 888
321
+ ui_display_set_text_color(0xFFFFFF, 0x000000);
322
+ #else
323
+ ui_display_set_text_color(0xFFFF, 0x0000);
324
+ #endif
325
+ ui_display_set_text_size(2);
326
+ ui_display_print(__ui_kb_buffer);
327
+ ui_display_print("_"); // cursor
328
+ }
329
+
330
+ // Draw the full keyboard overlay (background + text row + all keys).
331
+ static inline void ui_kb_draw() {
332
+ // Opaque background over the keyboard box.
333
+ ui_display_fill_rect(__ui_kb_box.x, __ui_kb_box.y, __ui_kb_box.w, __ui_kb_box.h, __ui_kb_bg);
334
+ ui_kb_draw_text_row();
335
+ // Keys: one rect per key, label centered.
336
+ for (uint8_t i = 0; i < __ui_kb_keyCount; i++) {
337
+ if (__ui_kb_keys[i].special == 255) continue; // padding cell, skip
338
+ ui_kb_draw_key(i);
339
+ }
340
+ }
341
+ `;
342
+ }
@@ -0,0 +1 @@
1
+ export declare function emitNodeDecoration(): string;
@@ -0,0 +1,212 @@
1
+ // Slice of the C++ runtime header (original source lines 3704-3909).
2
+ // Gradient, shadow, round-rect, border, outline drawing.
3
+ // See docs/superpowers/specs/2026-07-12-split-runtime-header-design.md.
4
+ export function emitNodeDecoration() {
5
+ return `
6
+ static inline void ui_draw_gradient_fill(uint16_t i, int16_t drawY) {
7
+ int16_t bx = __ui_nodes[i].box.x;
8
+ int16_t by = drawY;
9
+ int16_t bw = __ui_nodes[i].box.w;
10
+ int16_t bh = __ui_nodes[i].box.h;
11
+ int16_t clipX = bx;
12
+ int16_t clipY = by;
13
+ int16_t clipW = bw;
14
+ int16_t clipH = bh;
15
+ if (!ui_clip_rect_to_display_target(&clipX, &clipY, &clipW, &clipH)) return;
16
+ UI_COLOR_T c1 = __ui_nodes[i].gradientColor1;
17
+ UI_COLOR_T c2 = __ui_nodes[i].gradientColor2;
18
+ uint8_t dir = __ui_nodes[i].gradientEnabled; // 1=vertical, 2=horizontal
19
+ if (dir == 1) {
20
+ // Vertical: top=c1, bottom=c2. Draw row by row.
21
+ int16_t yStart = (int16_t)(clipY - by);
22
+ int16_t yEnd = (int16_t)(clipY + clipH - by);
23
+ for (int16_t y = yStart; y < yEnd; y++) {
24
+ uint8_t op = (uint8_t)((uint16_t)y * 100 / (bh > 1 ? bh - 1 : 1));
25
+ uint32_t col = ui_blend(c1, c2, op);
26
+ ui_display_draw_fast_hline(clipX, by + y, clipW, col);
27
+ }
28
+ } else {
29
+ // Horizontal: left=c1, right=c2. Draw column by column.
30
+ int16_t xStart = (int16_t)(clipX - bx);
31
+ int16_t xEnd = (int16_t)(clipX + clipW - bx);
32
+ for (int16_t x = xStart; x < xEnd; x++) {
33
+ uint8_t op = (uint8_t)((uint16_t)x * 100 / (bw > 1 ? bw - 1 : 1));
34
+ uint32_t col = ui_blend(c1, c2, op);
35
+ ui_display_draw_fast_vline(bx + x, clipY, clipH, col);
36
+ }
37
+ }
38
+ }
39
+
40
+ static inline void ui_draw_shadow(uint16_t i, int16_t drawY, uint8_t insetOnly) {
41
+ if (__ui_nodes[i].shadowCount == 0) return;
42
+ int16_t bx = __ui_nodes[i].box.x;
43
+ int16_t by = drawY;
44
+ int16_t bw = __ui_nodes[i].box.w;
45
+ int16_t bh = __ui_nodes[i].box.h;
46
+ UI_COLOR_T clearCol = __ui_nodes[i].clearColor;
47
+ uint8_t radius = __ui_nodes[i].borderRadius;
48
+
49
+ for (uint8_t s = 0; s < __ui_nodes[i].shadowCount && s < 4; s++) {
50
+ if (s >= __ui_nodes[i].shadowCount) continue; // use count, not color check (0 is valid black)
51
+ UI_COLOR_T shadowCol = __ui_nodes[i].shadowColor[s];
52
+ int8_t ox = __ui_nodes[i].shadowOffsetX[s];
53
+ int8_t oy = __ui_nodes[i].shadowOffsetY[s];
54
+ uint8_t rawBlur = __ui_nodes[i].shadowBlur[s];
55
+ uint8_t blur = rawBlur;
56
+ if (blur == 0) blur = 1; // at least 1 pass for a hard shadow
57
+ uint8_t baseAlpha = __ui_nodes[i].shadowAlpha[s];
58
+ uint8_t inset = __ui_nodes[i].shadowInset[s];
59
+ if (insetOnly && !inset) continue;
60
+ if (!insetOnly && inset) continue;
61
+
62
+ if (inset && rawBlur == 0) {
63
+ UI_COLOR_T insetBg = __ui_nodes[i].hasBg ? __ui_nodes[i].bg : __ui_nodes[i].clearColor;
64
+ uint32_t col = ui_blend(shadowCol, insetBg, baseAlpha);
65
+ if (oy > 0) {
66
+ ui_display_fill_rect(bx, by, bw, oy, col);
67
+ } else if (oy < 0) {
68
+ ui_display_fill_rect(bx, by + bh + oy, bw, -oy, col);
69
+ }
70
+ if (ox > 0) {
71
+ ui_display_fill_rect(bx, by, ox, bh, col);
72
+ } else if (ox < 0) {
73
+ ui_display_fill_rect(bx + bw + ox, by, -ox, bh, col);
74
+ }
75
+ if (ox == 0 && oy == 0) {
76
+ ui_display_draw_rect(bx, by, bw, bh, col);
77
+ }
78
+ continue;
79
+ }
80
+
81
+ for (int8_t pass = blur; pass >= 1; pass--) {
82
+ uint8_t opacity = (uint8_t)((uint16_t)baseAlpha / (pass + 1));
83
+ uint32_t col = ui_blend(shadowCol, inset ? (__ui_nodes[i].hasBg ? __ui_nodes[i].bg : clearCol) : clearCol, opacity);
84
+ if (inset) {
85
+ // Inset: draw inside the element, shrinking inward by pass.
86
+ int16_t ix = bx + pass;
87
+ int16_t iy = by + pass;
88
+ int16_t iw = bw - 2 * pass;
89
+ int16_t ih = bh - 2 * pass;
90
+ if (iw <= 0 || ih <= 0) continue;
91
+ // Offset the inset by the shadow's x/y (e.g. top highlight).
92
+ iy += oy;
93
+ ix += ox;
94
+ // Draw only the edge ring (4 thin rects), not a full fill — the
95
+ // element's own background will cover the center anyway.
96
+ ui_display_fill_rect(ix, iy, iw, 1, col); // top edge
97
+ ui_display_fill_rect(ix, iy + ih - 1, iw, 1, col); // bottom edge
98
+ ui_display_fill_rect(ix, iy, 1, ih, col); // left edge
99
+ ui_display_fill_rect(ix + iw - 1, iy, 1, ih, col); // right edge
100
+ } else {
101
+ // Outset: expand outward from box + offset.
102
+ int16_t sx = bx + ox - pass;
103
+ int16_t sy = by + oy - pass;
104
+ int16_t sw = bw + 2 * pass;
105
+ int16_t sh_ = bh + 2 * pass;
106
+ if (radius > 0) {
107
+ uint8_t r = radius + (uint8_t)pass;
108
+ if (r > sw / 2) r = sw / 2;
109
+ if (r > sh_ / 2) r = sh_ / 2;
110
+ ui_display_fill_round_rect(sx, sy, sw, sh_, r, col);
111
+ } else {
112
+ ui_display_fill_rect(sx, sy, sw, sh_, col);
113
+ }
114
+ }
115
+ }
116
+ }
117
+ }
118
+
119
+ static inline void ui_draw_closed_round_rect(int16_t x, int16_t y, int16_t w, int16_t h, uint8_t radius, UI_COLOR_T color) {
120
+ if (w <= 0 || h <= 0) return;
121
+ uint8_t r = radius;
122
+ if (r > w / 2) r = w / 2;
123
+ if (r > h / 2) r = h / 2;
124
+ if (r == 0) {
125
+ ui_display_draw_rect(x, y, w, h, color);
126
+ return;
127
+ }
128
+ ui_display_draw_round_rect(x, y, w, h, r, color);
129
+ // Adafruit_GFX's circle helper omits the cardinal tangent pixels. Fill them
130
+ // so straight edges and corner arcs meet without visible pinholes.
131
+ ui_display_draw_pixel(x + r, y, color);
132
+ ui_display_draw_pixel(x + w - r - 1, y, color);
133
+ ui_display_draw_pixel(x + r, y + h - 1, color);
134
+ ui_display_draw_pixel(x + w - r - 1, y + h - 1, color);
135
+ ui_display_draw_pixel(x, y + r, color);
136
+ ui_display_draw_pixel(x + w - 1, y + r, color);
137
+ ui_display_draw_pixel(x, y + h - r - 1, color);
138
+ ui_display_draw_pixel(x + w - 1, y + h - r - 1, color);
139
+ }
140
+
141
+ static inline void ui_draw_rect_outline(int16_t x, int16_t y, int16_t w, int16_t h, uint8_t radius, uint8_t style, uint8_t width, UI_COLOR_T color) {
142
+ if (style == 0 || width == 0 || w <= 0 || h <= 0) return;
143
+ for (uint8_t b = 0; b < width; b++) {
144
+ int16_t rx = x + b;
145
+ int16_t ry = y + b;
146
+ int16_t rw = w - 2 * b;
147
+ int16_t rh = h - 2 * b;
148
+ if (rw <= 0 || rh <= 0) return;
149
+ uint8_t r = radius > b ? radius - b : 0;
150
+ if (style == 1) {
151
+ if (r > 0) ui_draw_closed_round_rect(rx, ry, rw, rh, r, color);
152
+ else ui_display_draw_rect(rx, ry, rw, rh, color);
153
+ } else {
154
+ for (int16_t dx = 0; dx < rw; dx += 8) {
155
+ int16_t seg = (dx + 4 <= rw) ? 4 : (rw - dx);
156
+ if (seg > 0) {
157
+ ui_display_draw_fast_hline(rx + dx, ry, seg, color);
158
+ ui_display_draw_fast_hline(rx + dx, ry + rh - 1, seg, color);
159
+ }
160
+ }
161
+ for (int16_t dy = 0; dy < rh; dy += 8) {
162
+ int16_t seg = (dy + 4 <= rh) ? 4 : (rh - dy);
163
+ if (seg > 0) {
164
+ ui_display_draw_fast_vline(rx, ry + dy, seg, color);
165
+ ui_display_draw_fast_vline(rx + rw - 1, ry + dy, seg, color);
166
+ }
167
+ }
168
+ }
169
+ }
170
+ }
171
+
172
+ static inline void ui_draw_node_border(uint16_t i, int16_t drawX, int16_t drawY, UI_COLOR_T color) {
173
+ // Per-side borders: when any side's width differs from the uniform width,
174
+ // draw each side as an independent filled rect. The uniform path (one
175
+ // ui_draw_rect_outline call) is the common case and stays unchanged.
176
+ if (__ui_nodes[i].hasPerSideBorder) {
177
+ int16_t w = __ui_nodes[i].box.w;
178
+ int16_t h = __ui_nodes[i].box.h;
179
+ uint8_t st = __ui_nodes[i].borderStyle;
180
+ // Top edge
181
+ if (__ui_nodes[i].borderTopWidth > 0) {
182
+ ui_display_fill_rect(drawX, drawY, w, __ui_nodes[i].borderTopWidth, color);
183
+ }
184
+ // Bottom edge
185
+ if (__ui_nodes[i].borderBottomWidth > 0) {
186
+ ui_display_fill_rect(drawX, drawY + h - __ui_nodes[i].borderBottomWidth, w, __ui_nodes[i].borderBottomWidth, color);
187
+ }
188
+ // Left edge (between top and bottom borders)
189
+ if (__ui_nodes[i].borderLeftWidth > 0) {
190
+ ui_display_fill_rect(drawX, drawY + __ui_nodes[i].borderTopWidth, __ui_nodes[i].borderLeftWidth,
191
+ h - __ui_nodes[i].borderTopWidth - __ui_nodes[i].borderBottomWidth, color);
192
+ }
193
+ // Right edge
194
+ if (__ui_nodes[i].borderRightWidth > 0) {
195
+ ui_display_fill_rect(drawX + w - __ui_nodes[i].borderRightWidth, drawY + __ui_nodes[i].borderTopWidth,
196
+ __ui_nodes[i].borderRightWidth, h - __ui_nodes[i].borderTopWidth - __ui_nodes[i].borderBottomWidth, color);
197
+ }
198
+ return;
199
+ }
200
+ ui_draw_rect_outline(drawX, drawY, __ui_nodes[i].box.w, __ui_nodes[i].box.h,
201
+ __ui_nodes[i].borderRadius, __ui_nodes[i].borderStyle, __ui_nodes[i].borderWidth, color);
202
+ }
203
+
204
+ static inline void ui_draw_node_outline(uint16_t i, int16_t drawX, int16_t drawY) {
205
+ if (__ui_nodes[i].outlineStyle == 0 || __ui_nodes[i].outlineWidth == 0) return;
206
+ uint8_t w = __ui_nodes[i].outlineWidth;
207
+ ui_draw_rect_outline(drawX - w, drawY - w,
208
+ __ui_nodes[i].box.w + 2 * w, __ui_nodes[i].box.h + 2 * w,
209
+ __ui_nodes[i].borderRadius + w, __ui_nodes[i].outlineStyle, w, __ui_nodes[i].outlineColor);
210
+ }
211
+ `;
212
+ }
@@ -0,0 +1 @@
1
+ export declare function emitPaintOrderCoords(): string;