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

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (135) hide show
  1. package/README.md +21 -1
  2. package/assets/fonts/dejavu/DejaVuSans-Bold.ttf +0 -0
  3. package/assets/fonts/dejavu/DejaVuSans-ExtraLight.ttf +0 -0
  4. package/assets/fonts/dejavu/DejaVuSans-Oblique.ttf +0 -0
  5. package/assets/fonts/dejavu/DejaVuSans.ttf +0 -0
  6. package/assets/fonts/dejavu/DejaVuSansMono-Bold.ttf +0 -0
  7. package/assets/fonts/dejavu/DejaVuSansMono.ttf +0 -0
  8. package/assets/fonts/dejavu/LICENSE +187 -0
  9. package/assets/fonts/dejavu/README.md +49 -0
  10. package/dist/cli.d.ts +2 -0
  11. package/dist/cli.js +74 -0
  12. package/dist/engine-index.js +2 -0
  13. package/dist/preview/build-program.d.ts +7 -1
  14. package/dist/preview/build-program.js +94 -15
  15. package/dist/preview/host-gfx.d.ts +3 -0
  16. package/dist/preview/host-gfx.js +30 -3
  17. package/dist/preview/host-ui-runtime.d.ts +115 -0
  18. package/dist/preview/host-ui-runtime.js +928 -46
  19. package/dist/ui-engine/block-layout.js +25 -11
  20. package/dist/ui-engine/color.d.ts +6 -0
  21. package/dist/ui-engine/color.js +80 -2
  22. package/dist/ui-engine/compat-report.d.ts +8 -0
  23. package/dist/ui-engine/compat-report.js +124 -0
  24. package/dist/ui-engine/css-imports.d.ts +4 -0
  25. package/dist/ui-engine/css-imports.js +70 -0
  26. package/dist/ui-engine/css-parser.js +58 -14
  27. package/dist/ui-engine/default-font.d.ts +25 -0
  28. package/dist/ui-engine/default-font.js +80 -0
  29. package/dist/ui-engine/font-assets.d.ts +2 -2
  30. package/dist/ui-engine/font-assets.js +22 -5
  31. package/dist/ui-engine/html-parser.d.ts +4 -0
  32. package/dist/ui-engine/html-parser.js +241 -26
  33. package/dist/ui-engine/image-assets.d.ts +8 -1
  34. package/dist/ui-engine/image-assets.js +35 -2
  35. package/dist/ui-engine/image-decode.d.ts +22 -0
  36. package/dist/ui-engine/image-decode.js +194 -0
  37. package/dist/ui-engine/inline-parser.js +1 -1
  38. package/dist/ui-engine/layout-engine.d.ts +19 -0
  39. package/dist/ui-engine/layout-engine.js +59 -3
  40. package/dist/ui-engine/model.d.ts +18 -0
  41. package/dist/ui-engine/model.js +76 -8
  42. package/dist/ui-engine/runtime-header/blend-bodies.js +29 -9
  43. package/dist/ui-engine/runtime-header/canvas-helpers.js +5 -2
  44. package/dist/ui-engine/runtime-header/canvas-scrollbar.js +115 -111
  45. package/dist/ui-engine/runtime-header/cuttlefish-gfx.js +21 -0
  46. package/dist/ui-engine/runtime-header/dirty-scroll-mutators.js +182 -179
  47. package/dist/ui-engine/runtime-header/forward-decls.js +215 -167
  48. package/dist/ui-engine/runtime-header/init-press-input.js +138 -135
  49. package/dist/ui-engine/runtime-header/keyboard.js +682 -380
  50. package/dist/ui-engine/runtime-header/node-draw-body.js +1656 -1377
  51. package/dist/ui-engine/runtime-header/paint-order-coords.js +259 -214
  52. package/dist/ui-engine/runtime-header/paint-rects-repair.js +736 -568
  53. package/dist/ui-engine/runtime-header/scroll-physics.js +25 -1
  54. package/dist/ui-engine/runtime-header/state-bindings-nav.js +9 -0
  55. package/dist/ui-engine/runtime-header/structs.js +228 -216
  56. package/dist/ui-engine/runtime-header/text-rendering.js +888 -770
  57. package/dist/ui-engine/runtime-header/tick/bindings-phase.js +118 -116
  58. package/dist/ui-engine/runtime-header/tick/dirty-draw-phase.js +897 -633
  59. package/dist/ui-engine/runtime-header/tick/scroll-canvas-phase.js +24 -25
  60. package/dist/ui-engine/runtime-header/touch-keyboard-fwd.js +79 -23
  61. package/dist/ui-engine/runtime-header/types-defines.js +110 -93
  62. package/dist/ui-engine/shadcn-kit.d.ts +1 -0
  63. package/dist/ui-engine/shadcn-kit.js +502 -0
  64. package/dist/ui-engine/style-resolver.d.ts +4 -0
  65. package/dist/ui-engine/style-resolver.js +58 -6
  66. package/dist/ui-engine/transpile-ui.js +12 -5
  67. package/dist/ui-engine/ua-stylesheet.d.ts +22 -1
  68. package/dist/ui-engine/ua-stylesheet.js +119 -32
  69. package/dist/ui-engine/ui-lowering.js +37 -3
  70. package/dist/ui-engine/ui-registry.js +32 -4
  71. package/dist/ui-engine/yoga-layout.js +57 -52
  72. package/dist/wizard/config-writer.d.ts +49 -0
  73. package/dist/wizard/config-writer.js +366 -0
  74. package/dist/wizard/display-catalog.d.ts +75 -0
  75. package/dist/wizard/display-catalog.js +207 -0
  76. package/dist/wizard/index.d.ts +7 -0
  77. package/dist/wizard/index.js +10 -0
  78. package/dist/wizard/integration-wizard.d.ts +15 -0
  79. package/dist/wizard/integration-wizard.js +486 -0
  80. package/dist/wizard/prompts.d.ts +32 -0
  81. package/dist/wizard/prompts.js +95 -0
  82. package/dist/wizard/starter-ui.d.ts +1 -0
  83. package/dist/wizard/starter-ui.js +56 -0
  84. package/package.json +18 -5
  85. package/src/cli.ts +87 -0
  86. package/src/engine-index.ts +53 -51
  87. package/src/preview/build-program.ts +810 -734
  88. package/src/preview/host-gfx.ts +30 -3
  89. package/src/preview/host-ui-runtime.ts +4133 -3289
  90. package/src/ui-engine/block-layout.ts +24 -11
  91. package/src/ui-engine/color.ts +77 -2
  92. package/src/ui-engine/compat-report.ts +139 -0
  93. package/src/ui-engine/css-imports.ts +69 -0
  94. package/src/ui-engine/css-parser.ts +64 -15
  95. package/src/ui-engine/default-font.ts +95 -0
  96. package/src/ui-engine/font-assets.ts +545 -525
  97. package/src/ui-engine/html-parser.ts +615 -397
  98. package/src/ui-engine/image-assets.ts +37 -2
  99. package/src/ui-engine/image-decode.ts +244 -0
  100. package/src/ui-engine/inline-parser.ts +1 -1
  101. package/src/ui-engine/layout-engine.ts +61 -3
  102. package/src/ui-engine/model.ts +1317 -1230
  103. package/src/ui-engine/runtime-header/blend-bodies.ts +29 -9
  104. package/src/ui-engine/runtime-header/canvas-helpers.ts +5 -2
  105. package/src/ui-engine/runtime-header/canvas-scrollbar.ts +121 -117
  106. package/src/ui-engine/runtime-header/cuttlefish-gfx.ts +21 -0
  107. package/src/ui-engine/runtime-header/dirty-scroll-mutators.ts +188 -185
  108. package/src/ui-engine/runtime-header/forward-decls.ts +227 -179
  109. package/src/ui-engine/runtime-header/init-press-input.ts +144 -141
  110. package/src/ui-engine/runtime-header/keyboard.ts +689 -386
  111. package/src/ui-engine/runtime-header/node-draw-body.ts +1681 -1402
  112. package/src/ui-engine/runtime-header/paint-order-coords.ts +265 -220
  113. package/src/ui-engine/runtime-header/paint-rects-repair.ts +742 -574
  114. package/src/ui-engine/runtime-header/scroll-physics.ts +25 -1
  115. package/src/ui-engine/runtime-header/state-bindings-nav.ts +9 -0
  116. package/src/ui-engine/runtime-header/structs.ts +234 -222
  117. package/src/ui-engine/runtime-header/text-rendering.ts +894 -776
  118. package/src/ui-engine/runtime-header/tick/bindings-phase.ts +124 -122
  119. package/src/ui-engine/runtime-header/tick/dirty-draw-phase.ts +902 -638
  120. package/src/ui-engine/runtime-header/tick/scroll-canvas-phase.ts +30 -31
  121. package/src/ui-engine/runtime-header/touch-keyboard-fwd.ts +79 -23
  122. package/src/ui-engine/runtime-header/types-defines.ts +116 -99
  123. package/src/ui-engine/shadcn-kit.ts +503 -0
  124. package/src/ui-engine/style-resolver.ts +471 -416
  125. package/src/ui-engine/transpile-ui.ts +12 -5
  126. package/src/ui-engine/ua-stylesheet.ts +137 -31
  127. package/src/ui-engine/ui-lowering.ts +486 -452
  128. package/src/ui-engine/ui-registry.ts +556 -524
  129. package/src/ui-engine/yoga-layout.ts +307 -309
  130. package/src/wizard/config-writer.ts +404 -0
  131. package/src/wizard/display-catalog.ts +273 -0
  132. package/src/wizard/index.ts +38 -0
  133. package/src/wizard/integration-wizard.ts +619 -0
  134. package/src/wizard/prompts.ts +145 -0
  135. package/src/wizard/starter-ui.ts +58 -0
@@ -2,219 +2,264 @@
2
2
  // rects_intersect, visibility, draw order, owner table, screen bg, coordinate helpers, shadow extents, expand_rect.
3
3
  // See docs/superpowers/specs/2026-07-12-split-runtime-header-design.md.
4
4
  export function emitPaintOrderCoords() {
5
- return `
6
- static inline uint8_t ui_rects_intersect(int16_t ax, int16_t ay, int16_t aw, int16_t ah,
7
- int16_t bx, int16_t by, int16_t bw, int16_t bh) {
8
- return ax + aw > bx && ax < bx + bw && ay + ah > by && ay < by + bh;
9
- }
10
-
11
- static inline uint8_t ui_is_effectively_visible(uint16_t nodeIdx) {
12
- if (nodeIdx >= __ui_node_count) return 0;
13
- if (!__ui_nodes[nodeIdx].visible) return 0;
14
- uint16_t p = __ui_nodes[nodeIdx].parent;
15
- while (p != UI_NO_PARENT && p < __ui_node_count) {
16
- if (!__ui_nodes[p].visible) return 0;
17
- p = __ui_nodes[p].parent;
18
- }
19
- return 1;
20
- }
21
-
22
- static inline uint8_t ui_node_draws_before(uint16_t a, uint16_t b) {
23
- if (__ui_nodes[a].zIndex != __ui_nodes[b].zIndex) {
24
- return __ui_nodes[a].zIndex < __ui_nodes[b].zIndex;
25
- }
26
- return a < b;
27
- }
28
-
29
- // Sort node indices once at startup so the dirty draw pass is O(N) instead of
30
- // re-scanning all nodes for every dirty repaint (O(D·N)).
31
- static inline void ui_build_draw_order() {
32
- if (__ui_draw_order || __ui_node_count == 0) return;
33
- __ui_draw_order = new (std::nothrow) uint16_t[__ui_node_count];
34
- if (!__ui_draw_order) return;
35
- for (uint16_t i = 0; i < __ui_node_count; i++) __ui_draw_order[i] = i;
36
- for (uint16_t a = 1; a < __ui_node_count; a++) {
37
- uint16_t key = __ui_draw_order[a];
38
- int16_t j = static_cast<int16_t>(a) - 1;
39
- while (j >= 0 && ui_node_draws_before(key, __ui_draw_order[j])) {
40
- __ui_draw_order[j + 1] = __ui_draw_order[j];
41
- j--;
42
- }
43
- __ui_draw_order[j + 1] = key;
44
- }
45
- }
46
-
47
- // Index scroll containers once so touch/wheel ownership doesn't scan unrelated
48
- // nodes each frame.
49
- static inline void ui_build_scroll_owner_table() {
50
- if (__ui_scroll_owners || __ui_node_count == 0) return;
51
- uint16_t count = 0;
52
- for (uint16_t i = 0; i < __ui_node_count; i++) {
53
- if (__ui_nodes[i].scrollable) count++;
54
- }
55
- __ui_scroll_owner_count = count;
56
- if (!count) return;
57
- __ui_scroll_owners = new (std::nothrow) uint16_t[count];
58
- if (!__ui_scroll_owners) {
59
- __ui_scroll_owner_count = 0;
60
- return;
61
- }
62
- uint16_t w = 0;
63
- for (uint16_t i = 0; i < __ui_node_count; i++) {
64
- if (__ui_nodes[i].scrollable) {
65
- __ui_scroll_owners[w++] = i;
66
- }
67
- }
68
- }
69
-
70
- static inline void ui_refresh_active_screen_bg_node() {
71
- __ui_active_screen_bg_node = 0xFFFF;
72
- for (uint16_t i = 0; i < __ui_node_count; i++) {
73
- if (__ui_nodes[i].screenId == __ui_active_screen && __ui_nodes[i].kind == NODE_FILL) {
74
- __ui_active_screen_bg_node = i;
75
- return;
76
- }
77
- }
78
- }
79
-
80
- static inline uint8_t ui_scroll_subtree_has_dirty(uint16_t scrollNode) {
81
- if (scrollNode >= __ui_node_count) return 0;
82
- uint16_t end = __ui_nodes[scrollNode].subtreeEnd;
83
- if (end > __ui_node_count) end = __ui_node_count;
84
- for (uint16_t c = scrollNode + 1; c < end; c++) {
85
- if (__ui_nodes[c].dirty &&
86
- ui_is_effectively_visible(c) &&
87
- __ui_nodes[c].screenId == __ui_active_screen) {
88
- return 1;
89
- }
90
- }
91
- return 0;
92
- }
93
-
94
- static inline uint8_t ui_is_ancestor_of(uint16_t candidate, uint16_t nodeIdx) {
95
- uint16_t p = __ui_nodes[nodeIdx].parent;
96
- while (p != UI_NO_PARENT && p < __ui_node_count) {
97
- if (p == candidate) return 1;
98
- p = __ui_nodes[p].parent;
99
- }
100
- return 0;
101
- }
102
-
103
- static inline int16_t ui_pressed_offset_x_for_node(uint16_t nodeIdx) {
104
- return __ui_nodes[nodeIdx].value > 0 ? __ui_nodes[nodeIdx].pressedOffsetX : 0;
105
- }
106
-
107
- static inline int16_t ui_pressed_offset_y_for_node(uint16_t nodeIdx) {
108
- return __ui_nodes[nodeIdx].value > 0 ? __ui_nodes[nodeIdx].pressedOffsetY : 0;
109
- }
110
-
111
- static inline int16_t ui_base_draw_x_for_node(uint16_t nodeIdx) {
112
- return __ui_nodes[nodeIdx].box.x + __ui_nodes[nodeIdx].transformOffsetX;
113
- }
114
-
115
- static inline int16_t ui_draw_x_for_node(uint16_t nodeIdx) {
116
- return ui_base_draw_x_for_node(nodeIdx) + ui_pressed_offset_x_for_node(nodeIdx);
117
- }
118
-
119
- static inline int16_t ui_base_draw_y_for_node(uint16_t nodeIdx) {
120
- int16_t y = __ui_nodes[nodeIdx].box.y + __ui_nodes[nodeIdx].transformOffsetY;
121
- uint16_t p = __ui_nodes[nodeIdx].parent;
122
- while (p != UI_NO_PARENT && p < __ui_node_count) {
123
- if (__ui_nodes[p].scrollable) y -= __ui_nodes[p].scrollY;
124
- p = __ui_nodes[p].parent;
125
- }
126
- return y;
127
- }
128
-
129
- static inline int16_t ui_draw_y_for_node(uint16_t nodeIdx) {
130
- return ui_base_draw_y_for_node(nodeIdx) + ui_pressed_offset_y_for_node(nodeIdx);
131
- }
132
-
133
- // Find the scrollable container (list or generic scroll view) whose box contains
134
- // a point, on the active screen, with content overflowing the viewport. Mirrors
135
- // the touch path's scroll-scan (ui_touch_down) so the wheel handler finds the
136
- // SAME owner a drag would — robust against the cursor resting on a non-child
137
- // node (text, a sibling, padding) where the hit-test + ancestor-walk approach
138
- // misses. Returns the topmost such node by draw order, or -1 if none.
139
- static inline int16_t ui_scroll_node_at(int16_t tx, int16_t ty) {
140
- int16_t bestScroll = -1;
141
- if (__ui_scroll_owners) {
142
- for (uint16_t oi = 0; oi < __ui_scroll_owner_count; oi++) {
143
- uint16_t i = __ui_scroll_owners[oi];
144
- if (!ui_is_effectively_visible(i)) continue;
145
- if (__ui_nodes[i].screenId != __ui_active_screen) continue;
146
- if (__ui_nodes[i].contentHeight <= __ui_nodes[i].box.h) continue;
147
- int16_t drawX = ui_draw_x_for_node(i);
148
- int16_t drawY = ui_draw_y_for_node(i);
149
- if (tx >= drawX && tx < drawX + __ui_nodes[i].box.w &&
150
- ty >= drawY && ty < drawY + __ui_nodes[i].box.h &&
151
- (bestScroll < 0 || ui_node_draws_before(static_cast<uint16_t>(bestScroll), i))) {
152
- bestScroll = static_cast<int16_t>(i);
153
- }
154
- }
155
- } else {
156
- for (uint16_t i = 0; i < __ui_node_count; i++) {
157
- if (!__ui_nodes[i].scrollable || !ui_is_effectively_visible(i)) continue;
158
- if (__ui_nodes[i].screenId != __ui_active_screen) continue;
159
- if (__ui_nodes[i].contentHeight <= __ui_nodes[i].box.h) continue;
160
- int16_t drawX = ui_draw_x_for_node(i);
161
- int16_t drawY = ui_draw_y_for_node(i);
162
- if (tx >= drawX && tx < drawX + __ui_nodes[i].box.w &&
163
- ty >= drawY && ty < drawY + __ui_nodes[i].box.h &&
164
- (bestScroll < 0 || ui_node_draws_before(static_cast<uint16_t>(bestScroll), i))) {
165
- bestScroll = static_cast<int16_t>(i);
166
- }
167
- }
168
- }
169
- return bestScroll;
170
- }
171
-
172
- // Resolve the actual backdrop for a node by walking painted ancestors. A direct
173
- // parent may be transparent; using its clearColor there can blend against the
174
- // wrong layer when siblings or an opaque grandparent are behind the node.
175
- static inline UI_COLOR_T ui_parent_clear_color(uint16_t nodeIdx) {
176
- uint16_t p = __ui_nodes[nodeIdx].parent;
177
- while (p != UI_NO_PARENT && p < __ui_node_count) {
178
- if (__ui_nodes[p].hasBg) {
179
- UI_COLOR_T backdrop = __ui_nodes[p].bg;
180
- if (__ui_nodes[p].opacity < 100) {
181
- uint16_t pp = __ui_nodes[p].parent;
182
- UI_COLOR_T under = __ui_nodes[p].clearColor;
183
- if (pp != UI_NO_PARENT && pp < __ui_node_count) under = ui_parent_clear_color(p);
184
- backdrop = ui_blend(backdrop, under, __ui_nodes[p].opacity);
185
- }
186
- return backdrop;
187
- }
188
- p = __ui_nodes[p].parent;
189
- }
190
- return __ui_nodes[nodeIdx].clearColor;
191
- }
192
-
193
- static inline void ui_shadow_extents(uint16_t nodeIdx, int16_t* left, int16_t* top, int16_t* right, int16_t* bottom) {
194
- *left = 0; *top = 0; *right = 0; *bottom = 0;
195
- for (uint8_t s = 0; s < __ui_nodes[nodeIdx].shadowCount && s < 4; s++) {
196
- if (__ui_nodes[nodeIdx].shadowInset[s]) continue;
197
- int16_t blur = __ui_nodes[nodeIdx].shadowBlur[s];
198
- if (blur == 0) blur = 1;
199
- int16_t ox = __ui_nodes[nodeIdx].shadowOffsetX[s];
200
- int16_t oy = __ui_nodes[nodeIdx].shadowOffsetY[s];
201
- int16_t l = blur - ox;
202
- int16_t t = blur - oy;
203
- int16_t r = blur + ox;
204
- int16_t b = blur + oy;
205
- if (l > *left) *left = l;
206
- if (t > *top) *top = t;
207
- if (r > *right) *right = r;
208
- if (b > *bottom) *bottom = b;
209
- }
210
- }
211
-
212
- static inline void ui_expand_rect(int16_t* x0, int16_t* y0, int16_t* x1, int16_t* y1,
213
- int16_t rx0, int16_t ry0, int16_t rx1, int16_t ry1) {
214
- if (rx0 < *x0) *x0 = rx0;
215
- if (ry0 < *y0) *y0 = ry0;
216
- if (rx1 > *x1) *x1 = rx1;
217
- if (ry1 > *y1) *y1 = ry1;
218
- }
5
+ return `
6
+ static inline uint8_t ui_rects_intersect(int16_t ax, int16_t ay, int16_t aw, int16_t ah,
7
+ int16_t bx, int16_t by, int16_t bw, int16_t bh) {
8
+ return ax + aw > bx && ax < bx + bw && ay + ah > by && ay < by + bh;
9
+ }
10
+
11
+ static inline uint8_t ui_is_effectively_visible(uint16_t nodeIdx) {
12
+ if (nodeIdx >= __ui_node_count) return 0;
13
+ if (!__ui_nodes[nodeIdx].visible) return 0;
14
+ uint16_t p = __ui_nodes[nodeIdx].parent;
15
+ while (p != UI_NO_PARENT && p < __ui_node_count) {
16
+ if (!__ui_nodes[p].visible) return 0;
17
+ p = __ui_nodes[p].parent;
18
+ }
19
+ // Centered overlay panels (<dialog>): offsets cannot hide the panel or its
20
+ // subtree (travel is 0 by design), so the closed state gates visibility
21
+ // directly — the root via its own slot, descendants via a side-4 ancestor
22
+ // walk. Without the ancestor walk, dialog children (scrim, card, buttons)
23
+ // have drawerSide -1 and no offsets: they painted while the dialog was
24
+ // closed (an "Are you sure?" card on screen entry, buttons that close an
25
+ // already-closed slot and appear dead). Edge drawers (<drawer>, <toast
26
+ // side>) keep offset-only hiding — the bottom "peek" of an edge-anchored
27
+ // panel must stay visible.
28
+ if (__ui_nodes[nodeIdx].drawerSide == 4) {
29
+ int8_t centerSlot = __ui_drawer_slot_of(nodeIdx);
30
+ if (centerSlot < 0 || (!__ui_drawer_open[centerSlot] && __ui_drawer_progress[centerSlot] == 0)) return 0;
31
+ } else {
32
+ uint16_t a = __ui_nodes[nodeIdx].parent;
33
+ while (a != UI_NO_PARENT && a < __ui_node_count) {
34
+ if (__ui_nodes[a].drawerSide == 4) {
35
+ int8_t centerSlot = __ui_drawer_slot_of(a);
36
+ if (centerSlot < 0 || (!__ui_drawer_open[centerSlot] && __ui_drawer_progress[centerSlot] == 0)) return 0;
37
+ break;
38
+ }
39
+ a = __ui_nodes[a].parent;
40
+ }
41
+ }
42
+ // No closed-drawer gating here for edge drawers: they hide by transform
43
+ // offsets alone (seeded at full travel by ui_drawer_discover), so the
44
+ // bottom "peek" of a bottom-anchored panel stays visible.
45
+ return 1;
46
+ }
47
+
48
+ // Effective stacking z, CSS-style: a non-zero zIndex raises the node AND its
49
+ // subtree a z-raised panel forms a stacking context, so its descendants
50
+ // stack WITH it (above lower-z siblings), never independently UNDER it. A
51
+ // flat (zIndex, index) sort painted a dialog's scrim (z30) and card (z31)
52
+ // over the card's own z0 children (title/description/buttons): the band
53
+ // compositor erased the content it had just drawn (only the topmost child
54
+ // survived), and hit-testing saw the card as "topmost" so its buttons could
55
+ // never receive taps. 0 = "auto" (no context) — keep walking ancestors.
56
+ static inline int16_t ui_stacking_z(uint16_t nodeIdx) {
57
+ uint16_t n = nodeIdx;
58
+ while (n != UI_NO_PARENT && n < __ui_node_count) {
59
+ if (__ui_nodes[n].zIndex != 0) return __ui_nodes[n].zIndex;
60
+ n = __ui_nodes[n].parent;
61
+ }
62
+ return 0;
63
+ }
64
+
65
+ static inline uint8_t ui_node_draws_before(uint16_t a, uint16_t b) {
66
+ int16_t za = ui_stacking_z(a);
67
+ int16_t zb = ui_stacking_z(b);
68
+ if (za != zb) return za < zb;
69
+ // Same stacking level: source order. Sibling stacking contexts occupy
70
+ // disjoint index ranges, so this also keeps a context's subtree contiguous.
71
+ return a < b;
72
+ }
73
+
74
+ // Sort node indices once at startup so the dirty draw pass is O(N) instead of
75
+ // re-scanning all nodes for every dirty repaint (O(D·N)).
76
+ static inline void ui_build_draw_order() {
77
+ if (__ui_draw_order || __ui_node_count == 0) return;
78
+ __ui_draw_order = new (std::nothrow) uint16_t[__ui_node_count];
79
+ if (!__ui_draw_order) return;
80
+ for (uint16_t i = 0; i < __ui_node_count; i++) __ui_draw_order[i] = i;
81
+ for (uint16_t a = 1; a < __ui_node_count; a++) {
82
+ uint16_t key = __ui_draw_order[a];
83
+ int16_t j = static_cast<int16_t>(a) - 1;
84
+ while (j >= 0 && ui_node_draws_before(key, __ui_draw_order[j])) {
85
+ __ui_draw_order[j + 1] = __ui_draw_order[j];
86
+ j--;
87
+ }
88
+ __ui_draw_order[j + 1] = key;
89
+ }
90
+ }
91
+
92
+ // Index scroll containers once so touch/wheel ownership doesn't scan unrelated
93
+ // nodes each frame.
94
+ static inline void ui_build_scroll_owner_table() {
95
+ if (__ui_scroll_owners || __ui_node_count == 0) return;
96
+ uint16_t count = 0;
97
+ for (uint16_t i = 0; i < __ui_node_count; i++) {
98
+ if (__ui_nodes[i].scrollable) count++;
99
+ }
100
+ __ui_scroll_owner_count = count;
101
+ if (!count) return;
102
+ __ui_scroll_owners = new (std::nothrow) uint16_t[count];
103
+ if (!__ui_scroll_owners) {
104
+ __ui_scroll_owner_count = 0;
105
+ return;
106
+ }
107
+ uint16_t w = 0;
108
+ for (uint16_t i = 0; i < __ui_node_count; i++) {
109
+ if (__ui_nodes[i].scrollable) {
110
+ __ui_scroll_owners[w++] = i;
111
+ }
112
+ }
113
+ }
114
+
115
+ static inline void ui_refresh_active_screen_bg_node() {
116
+ __ui_active_screen_bg_node = 0xFFFF;
117
+ for (uint16_t i = 0; i < __ui_node_count; i++) {
118
+ if (__ui_nodes[i].screenId == __ui_active_screen && __ui_nodes[i].kind == NODE_FILL) {
119
+ __ui_active_screen_bg_node = i;
120
+ return;
121
+ }
122
+ }
123
+ }
124
+
125
+ static inline uint8_t ui_scroll_subtree_has_dirty(uint16_t scrollNode) {
126
+ if (scrollNode >= __ui_node_count) return 0;
127
+ uint16_t end = __ui_nodes[scrollNode].subtreeEnd;
128
+ if (end > __ui_node_count) end = __ui_node_count;
129
+ for (uint16_t c = scrollNode + 1; c < end; c++) {
130
+ if (__ui_nodes[c].dirty &&
131
+ ui_is_effectively_visible(c) &&
132
+ __ui_nodes[c].screenId == __ui_active_screen) {
133
+ return 1;
134
+ }
135
+ }
136
+ return 0;
137
+ }
138
+
139
+ static inline uint8_t ui_is_ancestor_of(uint16_t candidate, uint16_t nodeIdx) {
140
+ uint16_t p = __ui_nodes[nodeIdx].parent;
141
+ while (p != UI_NO_PARENT && p < __ui_node_count) {
142
+ if (p == candidate) return 1;
143
+ p = __ui_nodes[p].parent;
144
+ }
145
+ return 0;
146
+ }
147
+
148
+ static inline int16_t ui_pressed_offset_x_for_node(uint16_t nodeIdx) {
149
+ return __ui_nodes[nodeIdx].value > 0 ? __ui_nodes[nodeIdx].pressedOffsetX : 0;
150
+ }
151
+
152
+ static inline int16_t ui_pressed_offset_y_for_node(uint16_t nodeIdx) {
153
+ return __ui_nodes[nodeIdx].value > 0 ? __ui_nodes[nodeIdx].pressedOffsetY : 0;
154
+ }
155
+
156
+ static inline int16_t ui_base_draw_x_for_node(uint16_t nodeIdx) {
157
+ return __ui_nodes[nodeIdx].box.x + __ui_nodes[nodeIdx].transformOffsetX;
158
+ }
159
+
160
+ static inline int16_t ui_draw_x_for_node(uint16_t nodeIdx) {
161
+ return ui_base_draw_x_for_node(nodeIdx) + ui_pressed_offset_x_for_node(nodeIdx);
162
+ }
163
+
164
+ static inline int16_t ui_base_draw_y_for_node(uint16_t nodeIdx) {
165
+ int16_t y = __ui_nodes[nodeIdx].box.y + __ui_nodes[nodeIdx].transformOffsetY;
166
+ uint16_t p = __ui_nodes[nodeIdx].parent;
167
+ while (p != UI_NO_PARENT && p < __ui_node_count) {
168
+ if (__ui_nodes[p].scrollable) y -= __ui_nodes[p].scrollY;
169
+ p = __ui_nodes[p].parent;
170
+ }
171
+ return y;
172
+ }
173
+
174
+ static inline int16_t ui_draw_y_for_node(uint16_t nodeIdx) {
175
+ return ui_base_draw_y_for_node(nodeIdx) + ui_pressed_offset_y_for_node(nodeIdx);
176
+ }
177
+
178
+ // Find the scrollable container (list or generic scroll view) whose box contains
179
+ // a point, on the active screen, with content overflowing the viewport. Mirrors
180
+ // the touch path's scroll-scan (ui_touch_down) so the wheel handler finds the
181
+ // SAME owner a drag would — robust against the cursor resting on a non-child
182
+ // node (text, a sibling, padding) where the hit-test + ancestor-walk approach
183
+ // misses. Returns the topmost such node by draw order, or -1 if none.
184
+ static inline int16_t ui_scroll_node_at(int16_t tx, int16_t ty) {
185
+ int16_t bestScroll = -1;
186
+ if (__ui_scroll_owners) {
187
+ for (uint16_t oi = 0; oi < __ui_scroll_owner_count; oi++) {
188
+ uint16_t i = __ui_scroll_owners[oi];
189
+ if (!ui_is_effectively_visible(i)) continue;
190
+ if (__ui_nodes[i].screenId != __ui_active_screen) continue;
191
+ if (__ui_nodes[i].contentHeight <= __ui_nodes[i].box.h) continue;
192
+ int16_t drawX = ui_draw_x_for_node(i);
193
+ int16_t drawY = ui_draw_y_for_node(i);
194
+ if (tx >= drawX && tx < drawX + __ui_nodes[i].box.w &&
195
+ ty >= drawY && ty < drawY + __ui_nodes[i].box.h &&
196
+ (bestScroll < 0 || ui_node_draws_before(static_cast<uint16_t>(bestScroll), i))) {
197
+ bestScroll = static_cast<int16_t>(i);
198
+ }
199
+ }
200
+ } else {
201
+ for (uint16_t i = 0; i < __ui_node_count; i++) {
202
+ if (!__ui_nodes[i].scrollable || !ui_is_effectively_visible(i)) continue;
203
+ if (__ui_nodes[i].screenId != __ui_active_screen) continue;
204
+ if (__ui_nodes[i].contentHeight <= __ui_nodes[i].box.h) continue;
205
+ int16_t drawX = ui_draw_x_for_node(i);
206
+ int16_t drawY = ui_draw_y_for_node(i);
207
+ if (tx >= drawX && tx < drawX + __ui_nodes[i].box.w &&
208
+ ty >= drawY && ty < drawY + __ui_nodes[i].box.h &&
209
+ (bestScroll < 0 || ui_node_draws_before(static_cast<uint16_t>(bestScroll), i))) {
210
+ bestScroll = static_cast<int16_t>(i);
211
+ }
212
+ }
213
+ }
214
+ return bestScroll;
215
+ }
216
+
217
+ // Resolve the actual backdrop for a node by walking painted ancestors. A direct
218
+ // parent may be transparent; using its clearColor there can blend against the
219
+ // wrong layer when siblings or an opaque grandparent are behind the node.
220
+ static inline UI_COLOR_T ui_parent_clear_color(uint16_t nodeIdx) {
221
+ uint16_t p = __ui_nodes[nodeIdx].parent;
222
+ while (p != UI_NO_PARENT && p < __ui_node_count) {
223
+ if (__ui_nodes[p].hasBg) {
224
+ UI_COLOR_T backdrop = __ui_nodes[p].bg;
225
+ if (__ui_nodes[p].opacity < 100) {
226
+ uint16_t pp = __ui_nodes[p].parent;
227
+ UI_COLOR_T under = __ui_nodes[p].clearColor;
228
+ if (pp != UI_NO_PARENT && pp < __ui_node_count) under = ui_parent_clear_color(p);
229
+ backdrop = ui_blend(backdrop, under, __ui_nodes[p].opacity);
230
+ }
231
+ return backdrop;
232
+ }
233
+ p = __ui_nodes[p].parent;
234
+ }
235
+ return __ui_nodes[nodeIdx].clearColor;
236
+ }
237
+
238
+ static inline void ui_shadow_extents(uint16_t nodeIdx, int16_t* left, int16_t* top, int16_t* right, int16_t* bottom) {
239
+ *left = 0; *top = 0; *right = 0; *bottom = 0;
240
+ for (uint8_t s = 0; s < __ui_nodes[nodeIdx].shadowCount && s < 4; s++) {
241
+ if (__ui_nodes[nodeIdx].shadowInset[s]) continue;
242
+ int16_t blur = __ui_nodes[nodeIdx].shadowBlur[s];
243
+ if (blur == 0) blur = 1;
244
+ int16_t ox = __ui_nodes[nodeIdx].shadowOffsetX[s];
245
+ int16_t oy = __ui_nodes[nodeIdx].shadowOffsetY[s];
246
+ int16_t l = blur - ox;
247
+ int16_t t = blur - oy;
248
+ int16_t r = blur + ox;
249
+ int16_t b = blur + oy;
250
+ if (l > *left) *left = l;
251
+ if (t > *top) *top = t;
252
+ if (r > *right) *right = r;
253
+ if (b > *bottom) *bottom = b;
254
+ }
255
+ }
256
+
257
+ static inline void ui_expand_rect(int16_t* x0, int16_t* y0, int16_t* x1, int16_t* y1,
258
+ int16_t rx0, int16_t ry0, int16_t rx1, int16_t ry1) {
259
+ if (rx0 < *x0) *x0 = rx0;
260
+ if (ry0 < *y0) *y0 = ry0;
261
+ if (rx1 > *x1) *x1 = rx1;
262
+ if (ry1 > *y1) *y1 = ry1;
263
+ }
219
264
  `;
220
265
  }