@typecad/ui 1.0.0-alpha.13 → 1.0.0-alpha.15
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.
- package/README.md +1457 -1469
- package/dist/cli.js +22 -19
- package/dist/preview/host-ui-runtime.js +0 -1
- package/dist/ui-engine/model.js +7 -1
- package/dist/ui-engine/runtime-header/canvas-scrollbar.js +115 -115
- package/dist/ui-engine/runtime-header/dirty-scroll-mutators.js +182 -182
- package/dist/ui-engine/runtime-header/forward-decls.js +227 -216
- package/dist/ui-engine/runtime-header/init-press-input.js +138 -138
- package/dist/ui-engine/runtime-header/keyboard.js +676 -676
- package/dist/ui-engine/runtime-header/node-draw-body.js +1658 -1656
- package/dist/ui-engine/runtime-header/paint-order-coords.js +259 -259
- package/dist/ui-engine/runtime-header/paint-rects-repair.js +736 -736
- package/dist/ui-engine/runtime-header/scroll-physics.js +4 -4
- package/dist/ui-engine/runtime-header/structs.js +228 -228
- package/dist/ui-engine/runtime-header/text-rendering.js +888 -888
- package/dist/ui-engine/runtime-header/tick/bindings-phase.js +118 -118
- package/dist/ui-engine/runtime-header/tick/dirty-draw-phase.js +896 -896
- package/dist/ui-engine/runtime-header/tick/scroll-canvas-phase.js +24 -24
- package/dist/ui-engine/runtime-header/touch-keyboard-fwd.js +11 -11
- package/dist/ui-engine/runtime-header/types-defines.js +110 -110
- package/dist/ui-engine/shadcn-kit.d.ts +1 -1
- package/dist/ui-engine/shadcn-kit.js +4 -0
- package/dist/ui-engine/ui-lowering.d.ts +1 -1
- package/dist/ui-engine/ui-lowering.js +14 -5
- package/dist/ui-engine/ui-registry.js +9 -1
- package/dist/wizard/display-catalog.d.ts +0 -3
- package/dist/wizard/display-catalog.js +3 -12
- package/dist/wizard/index.d.ts +2 -0
- package/dist/wizard/index.js +1 -0
- package/dist/wizard/integration-wizard.d.ts +3 -0
- package/dist/wizard/integration-wizard.js +69 -20
- package/dist/wizard/package-writer.d.ts +20 -0
- package/dist/wizard/package-writer.js +79 -0
- package/package.json +4 -4
- package/src/cli.ts +90 -87
- package/src/preview/host-ui-runtime.ts +0 -1
- package/src/ui-engine/model.ts +6 -1
- package/src/ui-engine/runtime-header/canvas-scrollbar.ts +121 -121
- package/src/ui-engine/runtime-header/dirty-scroll-mutators.ts +188 -188
- package/src/ui-engine/runtime-header/forward-decls.ts +238 -227
- package/src/ui-engine/runtime-header/init-press-input.ts +144 -144
- package/src/ui-engine/runtime-header/keyboard.ts +689 -689
- package/src/ui-engine/runtime-header/node-draw-body.ts +1683 -1681
- package/src/ui-engine/runtime-header/paint-order-coords.ts +265 -265
- package/src/ui-engine/runtime-header/paint-rects-repair.ts +742 -742
- package/src/ui-engine/runtime-header/scroll-physics.ts +4 -4
- package/src/ui-engine/runtime-header/structs.ts +234 -234
- package/src/ui-engine/runtime-header/text-rendering.ts +894 -894
- package/src/ui-engine/runtime-header/tick/bindings-phase.ts +124 -124
- package/src/ui-engine/runtime-header/tick/dirty-draw-phase.ts +902 -902
- package/src/ui-engine/runtime-header/tick/scroll-canvas-phase.ts +30 -30
- package/src/ui-engine/runtime-header/touch-keyboard-fwd.ts +11 -11
- package/src/ui-engine/runtime-header/types-defines.ts +116 -116
- package/src/ui-engine/shadcn-kit.ts +4 -0
- package/src/ui-engine/ui-lowering.ts +12 -4
- package/src/ui-engine/ui-registry.ts +9 -1
- package/src/wizard/display-catalog.ts +261 -273
- package/src/wizard/index.ts +46 -38
- package/src/wizard/integration-wizard.ts +679 -619
- package/src/wizard/package-writer.ts +98 -0
|
@@ -1,265 +1,265 @@
|
|
|
1
|
-
// Slice of the C++ runtime header (original source lines 1711-1894).
|
|
2
|
-
// rects_intersect, visibility, draw order, owner table, screen bg, coordinate helpers, shadow extents, expand_rect.
|
|
3
|
-
// See docs/superpowers/specs/2026-07-12-split-runtime-header-design.md.
|
|
4
|
-
export function emitPaintOrderCoords(): string {
|
|
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
|
-
}
|
|
264
|
-
`;
|
|
265
|
-
}
|
|
1
|
+
// Slice of the C++ runtime header (original source lines 1711-1894).
|
|
2
|
+
// rects_intersect, visibility, draw order, owner table, screen bg, coordinate helpers, shadow extents, expand_rect.
|
|
3
|
+
// See docs/superpowers/specs/2026-07-12-split-runtime-header-design.md.
|
|
4
|
+
export function emitPaintOrderCoords(): string {
|
|
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
|
+
}
|
|
264
|
+
`;
|
|
265
|
+
}
|