@typecad/ui 1.0.0-alpha.7 → 1.0.0-alpha.8
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 +6 -0
- package/dist/engine-index.js +0 -2
- package/dist/preview/host-ui-runtime.d.ts +1 -0
- package/dist/preview/host-ui-runtime.js +56 -11
- package/dist/ui-engine/font-assets.d.ts +8 -0
- package/dist/ui-engine/font-assets.js +13 -0
- package/dist/ui-engine/layout-engine.js +19 -6
- package/dist/ui-engine/model.d.ts +1 -1
- package/dist/ui-engine/model.js +10 -4
- package/dist/ui-engine/runtime-header/antialiasing.js +52 -32
- package/dist/ui-engine/runtime-header/blend-bodies.js +4 -4
- package/dist/ui-engine/runtime-header/canvas-helpers.js +32 -65
- package/dist/ui-engine/runtime-header/canvas-scrollbar.js +22 -10
- package/dist/ui-engine/runtime-header/color-mono-refresh.js +10 -10
- package/dist/ui-engine/runtime-header/cuttlefish-gfx.js +59 -29
- package/dist/ui-engine/runtime-header/dirty-scroll-mutators.js +26 -10
- package/dist/ui-engine/runtime-header/display-shim.js +8 -8
- package/dist/ui-engine/runtime-header/forward-decls.js +48 -6
- package/dist/ui-engine/runtime-header/image-drawing.js +154 -84
- package/dist/ui-engine/runtime-header/init-press-input.js +20 -5
- package/dist/ui-engine/runtime-header/keyboard.js +19 -16
- package/dist/ui-engine/runtime-header/node-decoration.js +8 -8
- package/dist/ui-engine/runtime-header/node-draw-body.d.ts +1 -0
- package/dist/ui-engine/runtime-header/node-draw-body.js +1402 -0
- package/dist/ui-engine/runtime-header/paint-order-coords.js +48 -18
- package/dist/ui-engine/runtime-header/paint-rects-repair.js +38 -17
- package/dist/ui-engine/runtime-header/scroll-physics.js +15 -14
- package/dist/ui-engine/runtime-header/state-bindings-nav.js +23 -13
- package/dist/ui-engine/runtime-header/structs.js +12 -10
- package/dist/ui-engine/runtime-header/text-rendering.js +132 -71
- package/dist/ui-engine/runtime-header/tick/bindings-phase.js +29 -10
- package/dist/ui-engine/runtime-header/tick/dirty-draw-phase.js +299 -747
- package/dist/ui-engine/runtime-header/tick/flush-phase.js +7 -3
- package/dist/ui-engine/runtime-header/tick/scroll-canvas-phase.js +19 -3
- package/dist/ui-engine/runtime-header/tick/transitions-phase.js +31 -24
- package/dist/ui-engine/runtime-header/touch-keyboard-fwd.js +42 -40
- package/dist/ui-engine/runtime-header/types-defines.js +23 -3
- package/dist/ui-engine/runtime-header.js +2 -0
- package/dist/ui-engine/transpile-ui.js +2 -2
- package/dist/ui-engine/ua-stylesheet.js +46 -3
- package/dist/ui-engine/ui-lowering.d.ts +1 -3
- package/dist/ui-engine/ui-lowering.js +4 -23
- package/dist/ui-engine/ui-registry.js +1 -7
- package/package.json +2 -2
- package/src/engine-index.ts +0 -2
- package/src/preview/host-ui-runtime.ts +56 -12
- package/src/ui-engine/font-assets.ts +13 -0
- package/src/ui-engine/layout-engine.ts +19 -6
- package/src/ui-engine/model.ts +10 -5
- package/src/ui-engine/runtime-header/antialiasing.ts +52 -32
- package/src/ui-engine/runtime-header/blend-bodies.ts +4 -4
- package/src/ui-engine/runtime-header/canvas-helpers.ts +32 -65
- package/src/ui-engine/runtime-header/canvas-scrollbar.ts +22 -10
- package/src/ui-engine/runtime-header/color-mono-refresh.ts +10 -10
- package/src/ui-engine/runtime-header/cuttlefish-gfx.ts +59 -29
- package/src/ui-engine/runtime-header/dirty-scroll-mutators.ts +26 -10
- package/src/ui-engine/runtime-header/display-shim.ts +8 -8
- package/src/ui-engine/runtime-header/forward-decls.ts +48 -6
- package/src/ui-engine/runtime-header/image-drawing.ts +154 -84
- package/src/ui-engine/runtime-header/init-press-input.ts +20 -5
- package/src/ui-engine/runtime-header/keyboard.ts +19 -16
- package/src/ui-engine/runtime-header/node-decoration.ts +8 -8
- package/src/ui-engine/runtime-header/node-draw-body.ts +1402 -0
- package/src/ui-engine/runtime-header/paint-order-coords.ts +48 -18
- package/src/ui-engine/runtime-header/paint-rects-repair.ts +38 -17
- package/src/ui-engine/runtime-header/scroll-physics.ts +15 -14
- package/src/ui-engine/runtime-header/state-bindings-nav.ts +23 -13
- package/src/ui-engine/runtime-header/structs.ts +12 -10
- package/src/ui-engine/runtime-header/text-rendering.ts +132 -71
- package/src/ui-engine/runtime-header/tick/bindings-phase.ts +29 -10
- package/src/ui-engine/runtime-header/tick/dirty-draw-phase.ts +299 -747
- package/src/ui-engine/runtime-header/tick/flush-phase.ts +7 -3
- package/src/ui-engine/runtime-header/tick/scroll-canvas-phase.ts +19 -3
- package/src/ui-engine/runtime-header/tick/transitions-phase.ts +31 -24
- package/src/ui-engine/runtime-header/touch-keyboard-fwd.ts +42 -40
- package/src/ui-engine/runtime-header/types-defines.ts +23 -3
- package/src/ui-engine/runtime-header.ts +2 -0
- package/src/ui-engine/transpile-ui.ts +2 -2
- package/src/ui-engine/ua-stylesheet.ts +46 -3
- package/src/ui-engine/ui-lowering.ts +3 -25
- package/src/ui-engine/ui-registry.ts +1 -8
- package/dist/ui-engine/scroll-memory-diagnostics.d.ts +0 -16
- package/dist/ui-engine/scroll-memory-diagnostics.js +0 -58
- package/src/ui-engine/scroll-memory-diagnostics.ts +0 -79
|
@@ -30,12 +30,12 @@ static inline uint8_t ui_node_draws_before(uint16_t a, uint16_t b) {
|
|
|
30
30
|
// re-scanning all nodes for every dirty repaint (O(D·N)).
|
|
31
31
|
static inline void ui_build_draw_order() {
|
|
32
32
|
if (__ui_draw_order || __ui_node_count == 0) return;
|
|
33
|
-
__ui_draw_order = (
|
|
33
|
+
__ui_draw_order = new (std::nothrow) uint16_t[__ui_node_count];
|
|
34
34
|
if (!__ui_draw_order) return;
|
|
35
35
|
for (uint16_t i = 0; i < __ui_node_count; i++) __ui_draw_order[i] = i;
|
|
36
36
|
for (uint16_t a = 1; a < __ui_node_count; a++) {
|
|
37
37
|
uint16_t key = __ui_draw_order[a];
|
|
38
|
-
int16_t j = (
|
|
38
|
+
int16_t j = static_cast<int16_t>(a) - 1;
|
|
39
39
|
while (j >= 0 && ui_node_draws_before(key, __ui_draw_order[j])) {
|
|
40
40
|
__ui_draw_order[j + 1] = __ui_draw_order[j];
|
|
41
41
|
j--;
|
|
@@ -44,24 +44,24 @@ static inline void ui_build_draw_order() {
|
|
|
44
44
|
}
|
|
45
45
|
}
|
|
46
46
|
|
|
47
|
-
// Index
|
|
48
|
-
//
|
|
47
|
+
// Index scroll containers once so touch/wheel ownership doesn't scan unrelated
|
|
48
|
+
// nodes each frame.
|
|
49
49
|
static inline void ui_build_scroll_owner_table() {
|
|
50
50
|
if (__ui_scroll_owners || __ui_node_count == 0) return;
|
|
51
51
|
uint16_t count = 0;
|
|
52
52
|
for (uint16_t i = 0; i < __ui_node_count; i++) {
|
|
53
|
-
if (__ui_nodes[i].scrollable
|
|
53
|
+
if (__ui_nodes[i].scrollable) count++;
|
|
54
54
|
}
|
|
55
55
|
__ui_scroll_owner_count = count;
|
|
56
56
|
if (!count) return;
|
|
57
|
-
__ui_scroll_owners = (
|
|
57
|
+
__ui_scroll_owners = new (std::nothrow) uint16_t[count];
|
|
58
58
|
if (!__ui_scroll_owners) {
|
|
59
59
|
__ui_scroll_owner_count = 0;
|
|
60
60
|
return;
|
|
61
61
|
}
|
|
62
62
|
uint16_t w = 0;
|
|
63
63
|
for (uint16_t i = 0; i < __ui_node_count; i++) {
|
|
64
|
-
if (__ui_nodes[i].scrollable
|
|
64
|
+
if (__ui_nodes[i].scrollable) {
|
|
65
65
|
__ui_scroll_owners[w++] = i;
|
|
66
66
|
}
|
|
67
67
|
}
|
|
@@ -138,24 +138,54 @@ static inline int16_t ui_draw_y_for_node(uint16_t nodeIdx) {
|
|
|
138
138
|
// misses. Returns the topmost such node by draw order, or -1 if none.
|
|
139
139
|
static inline int16_t ui_scroll_node_at(int16_t tx, int16_t ty) {
|
|
140
140
|
int16_t bestScroll = -1;
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
if (
|
|
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
|
+
}
|
|
150
167
|
}
|
|
151
168
|
}
|
|
152
169
|
return bestScroll;
|
|
153
170
|
}
|
|
154
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.
|
|
155
175
|
static inline UI_COLOR_T ui_parent_clear_color(uint16_t nodeIdx) {
|
|
156
176
|
uint16_t p = __ui_nodes[nodeIdx].parent;
|
|
157
|
-
|
|
158
|
-
|
|
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;
|
|
159
189
|
}
|
|
160
190
|
return __ui_nodes[nodeIdx].clearColor;
|
|
161
191
|
}
|
|
@@ -9,11 +9,11 @@ static inline void ui_node_paint_rect(uint16_t nodeIdx, int16_t baseX, int16_t b
|
|
|
9
9
|
|
|
10
10
|
int16_t faceW = __ui_nodes[nodeIdx].box.w;
|
|
11
11
|
int16_t faceH = __ui_nodes[nodeIdx].box.h;
|
|
12
|
-
if (__ui_nodes[nodeIdx].kind == NODE_TEXT || __ui_nodes[nodeIdx].kind == NODE_CHECK || __ui_nodes[nodeIdx].kind == NODE_RADIO) {
|
|
12
|
+
if (__ui_nodes[nodeIdx].kind == NODE_TEXT || __ui_nodes[nodeIdx].kind == NODE_CHECK || __ui_nodes[nodeIdx].kind == NODE_RADIO || __ui_nodes[nodeIdx].kind == NODE_SELECT) {
|
|
13
13
|
if (__ui_nodes[nodeIdx].lastTextWidth > faceW) faceW = __ui_nodes[nodeIdx].lastTextWidth;
|
|
14
14
|
if (__ui_nodes[nodeIdx].lastTextHeight > faceH) faceH = __ui_nodes[nodeIdx].lastTextHeight;
|
|
15
|
-
if ((
|
|
16
|
-
if ((
|
|
15
|
+
if (static_cast<int16_t>(textW) > faceW) faceW = static_cast<int16_t>(textW);
|
|
16
|
+
if (static_cast<int16_t>(textH) > faceH) faceH = static_cast<int16_t>(textH);
|
|
17
17
|
}
|
|
18
18
|
int16_t unrotatedFaceW = faceW;
|
|
19
19
|
int16_t unrotatedFaceH = faceH;
|
|
@@ -137,11 +137,18 @@ static inline void ui_clear_press_offset_area(uint16_t nodeIdx, int16_t baseX, i
|
|
|
137
137
|
|
|
138
138
|
// Generated-font / AA text and images draw per-pixel when sent straight to SPI.
|
|
139
139
|
// Prefer the RAM paint canvas for these kinds (within the pixel budget).
|
|
140
|
+
// NOTE: ALL NODE_TEXT is treated as pixel-heavy. Even the classic GFX bitmap
|
|
141
|
+
// font renders via drawPixel per font pixel, and on an SPI TFT each drawPixel
|
|
142
|
+
// is a full set_window+RAMWR SPI transaction (~30us) — a single 460x8 text band
|
|
143
|
+
// costs >100ms direct-drawn. Forcing every text node through the paint canvas
|
|
144
|
+
// turns that into one RAM render + one SPI push (~3ms). AA/generated-font text
|
|
145
|
+
// additionally requires getPixel readback for coverage blending, which is only
|
|
146
|
+
// correct against a RAM target.
|
|
140
147
|
static inline uint8_t ui_pixel_heavy_node(uint16_t nodeIdx) {
|
|
141
148
|
if (nodeIdx >= __ui_node_count) return 0;
|
|
142
149
|
if (__ui_nodes[nodeIdx].kind == NODE_IMG) return 1;
|
|
143
|
-
if (__ui_nodes[nodeIdx].kind == NODE_TEXT
|
|
144
|
-
|
|
150
|
+
if (__ui_nodes[nodeIdx].kind == NODE_TEXT) return 1;
|
|
151
|
+
if (__ui_nodes[nodeIdx].kind == NODE_SELECT) return 1;
|
|
145
152
|
return 0;
|
|
146
153
|
}
|
|
147
154
|
|
|
@@ -150,7 +157,7 @@ static inline uint8_t ui_should_buffer_paint(uint16_t nodeIdx, int16_t w, int16_
|
|
|
150
157
|
if (__ui_nodes[nodeIdx].kind == NODE_LIST) return 0;
|
|
151
158
|
// <canvas> elements batch via __ui_node_canvas when drawing to the display.
|
|
152
159
|
if (__ui_nodes[nodeIdx].kind == NODE_CANVAS) return 0;
|
|
153
|
-
if ((
|
|
160
|
+
if (static_cast<uint32_t>(w) * static_cast<uint32_t>(h) > UI_MAX_BUFFERED_PAINT_PIXELS) return 0;
|
|
154
161
|
if (ui_pixel_heavy_node(nodeIdx)) return 1;
|
|
155
162
|
if (__ui_nodes[nodeIdx].kind == NODE_FILL &&
|
|
156
163
|
__ui_nodes[nodeIdx].hasBg &&
|
|
@@ -167,7 +174,7 @@ static inline uint8_t ui_should_buffer_paint(uint16_t nodeIdx, int16_t w, int16_
|
|
|
167
174
|
return 1;
|
|
168
175
|
}
|
|
169
176
|
|
|
170
|
-
static inline void ui_seed_paint_canvas_for_node(uint16_t nodeIdx, CuttlefishCanvas16* canvas, int16_t canvasX, int16_t canvasY) {
|
|
177
|
+
static inline void ui_seed_paint_canvas_for_node(uint16_t nodeIdx, CuttlefishCanvas16* canvas, int16_t canvasX, int16_t canvasY, int16_t bandTop) {
|
|
171
178
|
if (!canvas || !display_canvasBuffer(canvas)) return;
|
|
172
179
|
uint16_t p = __ui_nodes[nodeIdx].parent;
|
|
173
180
|
if (p == UI_NO_PARENT || p >= __ui_node_count) {
|
|
@@ -191,7 +198,11 @@ static inline void ui_seed_paint_canvas_for_node(uint16_t nodeIdx, CuttlefishCan
|
|
|
191
198
|
CuttlefishDisplayTarget* previousGfx = ui_display_get_target();
|
|
192
199
|
ui_display_set_target(canvas);
|
|
193
200
|
__ui_nodes[p].box.x = parentDrawX - canvasX;
|
|
194
|
-
|
|
201
|
+
// Shift the parent's draw Y up by bandTop so the band canvas shows the
|
|
202
|
+
// correct slice of the parent fill/border for this band's Y range. bandTop=0
|
|
203
|
+
// (the main-loop caller) is a no-op shift. canvasY already carries the paint
|
|
204
|
+
// rect's display-space origin; bandTop further offsets within the rect.
|
|
205
|
+
int16_t localY = parentDrawY - canvasY - bandTop;
|
|
195
206
|
|
|
196
207
|
// Parent fill color: blended toward the parent's backdrop when the parent is
|
|
197
208
|
// translucent (matching the main NODE_FILL draw, which uses fillBg). Without
|
|
@@ -222,12 +233,17 @@ static inline void ui_seed_paint_canvas_for_node(uint16_t nodeIdx, CuttlefishCan
|
|
|
222
233
|
|
|
223
234
|
static inline uint8_t ui_repair_current_node_paint_with_parent(uint16_t nodeIdx, UIRect* r) {
|
|
224
235
|
if (!r || r->w <= 0 || r->h <= 0) return 0;
|
|
225
|
-
if ((
|
|
236
|
+
if (static_cast<uint32_t>(r->w) * static_cast<uint32_t>(r->h) > UI_MAX_BUFFERED_PAINT_PIXELS) return 0;
|
|
226
237
|
CuttlefishCanvas16* repairCanvas = ui_get_repair_canvas(r->w, r->h);
|
|
227
238
|
if (!repairCanvas) return 0;
|
|
228
|
-
ui_seed_paint_canvas_for_node(nodeIdx, repairCanvas, r->x, r->y);
|
|
229
|
-
|
|
239
|
+
ui_seed_paint_canvas_for_node(nodeIdx, repairCanvas, r->x, r->y, 0);
|
|
240
|
+
// During a retained-framebuffer tick, publish the repair into the RAM
|
|
241
|
+
// composition surface. Sending it to the live panel here creates the visible
|
|
242
|
+
// erase/redraw flash seen on animated transforms.
|
|
243
|
+
if (__ui_fb) ui_display_set_target((CuttlefishDisplayTarget*)__ui_fb);
|
|
244
|
+
else ui_display_use_default_target();
|
|
230
245
|
ui_push_canvas_rect(repairCanvas, r->x, r->y, r->w, r->h);
|
|
246
|
+
ui_display_use_default_target();
|
|
231
247
|
return 1;
|
|
232
248
|
}
|
|
233
249
|
|
|
@@ -375,7 +391,8 @@ static inline void ui_clear_node_paint_rect(uint16_t nodeIdx, const UIRect* pain
|
|
|
375
391
|
if (nodeIdx >= __ui_node_count || !paintRect || paintRect->w <= 0 || paintRect->h <= 0) return;
|
|
376
392
|
int16_t scrollParent = ui_scroll_ancestor_for_node(nodeIdx);
|
|
377
393
|
UIRect r = *paintRect;
|
|
378
|
-
|
|
394
|
+
if (__ui_fb) ui_display_set_target((CuttlefishDisplayTarget*)__ui_fb);
|
|
395
|
+
else ui_display_use_default_target();
|
|
379
396
|
if (scrollParent >= 0) {
|
|
380
397
|
UIRect clip = {
|
|
381
398
|
__ui_nodes[scrollParent].box.x,
|
|
@@ -439,7 +456,7 @@ static inline uint8_t ui_try_repair_geometry_fill(uint16_t nodeIdx, const UIRect
|
|
|
439
456
|
int16_t y0 = oldRect->y < newRect.y ? oldRect->y : newRect.y;
|
|
440
457
|
int16_t x1 = oldRect->x + oldRect->w > newRect.x + newRect.w ? oldRect->x + oldRect->w : newRect.x + newRect.w;
|
|
441
458
|
int16_t y1 = oldRect->y + oldRect->h > newRect.y + newRect.h ? oldRect->y + oldRect->h : newRect.y + newRect.h;
|
|
442
|
-
UIRect repair = { x0, y0,
|
|
459
|
+
UIRect repair = { x0, y0, static_cast<int16_t>(x1 - x0), static_cast<int16_t>(y1 - y0) };
|
|
443
460
|
int16_t scrollParent = ui_scroll_ancestor_for_node(nodeIdx);
|
|
444
461
|
if (scrollParent >= 0) {
|
|
445
462
|
UIRect clip = {
|
|
@@ -456,11 +473,11 @@ static inline uint8_t ui_try_repair_geometry_fill(uint16_t nodeIdx, const UIRect
|
|
|
456
473
|
return 0;
|
|
457
474
|
}
|
|
458
475
|
if (repair.w <= 0 || repair.h <= 0) return 0;
|
|
459
|
-
if ((
|
|
476
|
+
if (static_cast<uint32_t>(repair.w) * static_cast<uint32_t>(repair.h) > UI_MAX_BUFFERED_PAINT_PIXELS) return 0;
|
|
460
477
|
CuttlefishCanvas16* repairCanvas = ui_get_repair_canvas(repair.w, repair.h);
|
|
461
478
|
if (!repairCanvas) return 0;
|
|
462
479
|
|
|
463
|
-
ui_seed_paint_canvas_for_node(nodeIdx, repairCanvas, repair.x, repair.y);
|
|
480
|
+
ui_seed_paint_canvas_for_node(nodeIdx, repairCanvas, repair.x, repair.y, 0);
|
|
464
481
|
CuttlefishDisplayTarget* previousGfx = ui_display_get_target();
|
|
465
482
|
ui_display_set_target(repairCanvas);
|
|
466
483
|
UI_COLOR_T fillBg = __ui_nodes[nodeIdx].bg;
|
|
@@ -473,8 +490,12 @@ static inline uint8_t ui_try_repair_geometry_fill(uint16_t nodeIdx, const UIRect
|
|
|
473
490
|
int16_t fillH = ui_rotated_face_h(nodeIdx, __ui_nodes[nodeIdx].box.w, __ui_nodes[nodeIdx].box.h);
|
|
474
491
|
ui_display_fill_rect(drawX, drawY, fillW, fillH, fillBg);
|
|
475
492
|
ui_display_set_target(previousGfx);
|
|
476
|
-
|
|
493
|
+
// Keep geometry repair off the physical panel when a retained framebuffer is
|
|
494
|
+
// active; the final dirty-bounds publish will send the old+new union once.
|
|
495
|
+
if (__ui_fb) ui_display_set_target((CuttlefishDisplayTarget*)__ui_fb);
|
|
496
|
+
else ui_display_use_default_target();
|
|
477
497
|
ui_push_canvas_rect(repairCanvas, repair.x, repair.y, repair.w, repair.h);
|
|
498
|
+
ui_display_use_default_target();
|
|
478
499
|
if (scrollParent >= 0) ui_invalidate_scroll_canvas_for_node(nodeIdx);
|
|
479
500
|
ui_mark_overlapping_higher_layers_dirty_for_rect(nodeIdx, &repair);
|
|
480
501
|
return 1;
|
|
@@ -530,7 +551,7 @@ static inline void ui_set_visible(uint16_t nodeIdx, uint8_t visible) {
|
|
|
530
551
|
ui_clear_subtree_current_paint(nodeIdx);
|
|
531
552
|
int16_t end = __ui_nodes[nodeIdx].subtreeEnd;
|
|
532
553
|
if (end > __ui_node_count) end = __ui_node_count;
|
|
533
|
-
for (int16_t c = end - 1; c >= (
|
|
554
|
+
for (int16_t c = end - 1; c >= static_cast<int16_t>(nodeIdx); c--) {
|
|
534
555
|
if (__ui_nodes[c].screenId != __ui_active_screen) continue;
|
|
535
556
|
__ui_nodes[c].dirty = 0;
|
|
536
557
|
}
|
|
@@ -9,15 +9,15 @@ export function emitScrollPhysics() {
|
|
|
9
9
|
// 'none' tier compiles drag scroll out entirely.
|
|
10
10
|
|
|
11
11
|
static inline int16_t ui_scroll_scale_dy(int16_t dy) {
|
|
12
|
-
int32_t scaled = (
|
|
13
|
-
return
|
|
12
|
+
int32_t scaled = static_cast<int32_t>(dy) * static_cast<int32_t>(UI_SCROLL_DRAG_SCALE_X10);
|
|
13
|
+
return static_cast<int16_t>(scaled >= 0 ? (scaled + 5) / 10 : (scaled - 5) / 10);
|
|
14
14
|
}
|
|
15
15
|
|
|
16
16
|
static inline int16_t ui_scroll_smooth_dy(int16_t dy) {
|
|
17
17
|
#if UI_SCROLL_INPUT_TIER_CAPACITIVE
|
|
18
18
|
return ui_scroll_scale_dy(dy);
|
|
19
19
|
#elif UI_SCROLL_INPUT_TIER_RESISTIVE
|
|
20
|
-
int16_t db = (
|
|
20
|
+
int16_t db = static_cast<int16_t>(UI_SCROLL_DEADBAND_PX);
|
|
21
21
|
if (dy >= -db && dy <= db) {
|
|
22
22
|
// Deadband: kill per-sample jitter around zero. Steady drag (|dy|>db) below
|
|
23
23
|
// passes through unchanged, so steady-state is 1:1 (spec Q1).
|
|
@@ -44,11 +44,11 @@ static inline int16_t ui_scroll_max(int16_t node) {
|
|
|
44
44
|
// r = maxOverscroll * d / (d + stiffness). stiffness held as X10 fixed-point.
|
|
45
45
|
static inline int16_t ui_scroll_overscroll_for(int16_t d) {
|
|
46
46
|
if (d <= 0) return 0;
|
|
47
|
-
int16_t maxOv = (
|
|
48
|
-
int16_t stiffX10 = (
|
|
47
|
+
int16_t maxOv = static_cast<int16_t>(UI_SCROLL_MAX_OVERSCROLL);
|
|
48
|
+
int16_t stiffX10 = static_cast<int16_t>(UI_SCROLL_STIFFNESS_X10);
|
|
49
49
|
if (stiffX10 <= 0) stiffX10 = 1;
|
|
50
|
-
int32_t r = ((
|
|
51
|
-
return r > maxOv ? maxOv : (
|
|
50
|
+
int32_t r = (static_cast<int32_t>(maxOv) * static_cast<int32_t>(d)) / (static_cast<int32_t>(d) + static_cast<int32_t>(stiffX10));
|
|
51
|
+
return r > maxOv ? maxOv : static_cast<int16_t>(r);
|
|
52
52
|
}
|
|
53
53
|
|
|
54
54
|
// Apply a smoothed drag delta to the owning scroll node. Returns 1 if the view
|
|
@@ -56,6 +56,7 @@ static inline int16_t ui_scroll_overscroll_for(int16_t d) {
|
|
|
56
56
|
// itself never leaves [0, maxScroll] so the committed position stays valid.
|
|
57
57
|
static inline uint8_t ui_apply_scroll_delta(int16_t node, int16_t dy) {
|
|
58
58
|
if (node < 0 || dy == 0) return 0;
|
|
59
|
+
if (__ui_scroll_render_locked && __ui_scroll_render_locked[node]) return 0;
|
|
59
60
|
int16_t sy = __ui_nodes[node].scrollY;
|
|
60
61
|
int16_t maxS = ui_scroll_max(node);
|
|
61
62
|
int16_t nextY = sy - dy;
|
|
@@ -88,7 +89,7 @@ static inline uint8_t ui_apply_scroll_delta(int16_t node, int16_t dy) {
|
|
|
88
89
|
}
|
|
89
90
|
__ui_nodes[node].overscrollPx = nextOv;
|
|
90
91
|
uint8_t changed = (__ui_nodes[node].scrollY != sy) || (nextOv != prevOv);
|
|
91
|
-
if (changed) ui_mark_scroll_view_dirty((
|
|
92
|
+
if (changed) ui_mark_scroll_view_dirty(static_cast<uint16_t>(node));
|
|
92
93
|
return changed;
|
|
93
94
|
}
|
|
94
95
|
|
|
@@ -106,7 +107,7 @@ static inline uint8_t ui_scroll_release(int16_t node) {
|
|
|
106
107
|
} else {
|
|
107
108
|
int16_t sy = __ui_nodes[node].scrollY;
|
|
108
109
|
int16_t maxS = ui_scroll_max(node);
|
|
109
|
-
int16_t snap = (
|
|
110
|
+
int16_t snap = static_cast<int16_t>(UI_SCROLL_EDGE_SNAP_PX);
|
|
110
111
|
if (sy > 0 && sy <= snap) {
|
|
111
112
|
__ui_nodes[node].settling = 1;
|
|
112
113
|
__ui_settle_from_scrollY = sy; // positive → snap toward 0
|
|
@@ -126,27 +127,27 @@ static inline uint8_t ui_scroll_release(int16_t node) {
|
|
|
126
127
|
|
|
127
128
|
// Advance the settle animation for a node (called from ui_tick). Ease-out over
|
|
128
129
|
// UI_SCROLL_SETTLE_MS, terminating at the boundary. Bounded — always ends.
|
|
129
|
-
static inline void ui_scroll_advance_settle(
|
|
130
|
+
static inline void ui_scroll_advance_settle(uint16_t node, uint16_t deltaMs) {
|
|
130
131
|
(void)deltaMs;
|
|
131
132
|
if (node >= __ui_node_count || !__ui_nodes[node].settling) return;
|
|
132
133
|
uint32_t elapsed = millis() - __ui_settle_start_ms;
|
|
133
|
-
uint16_t dur = (
|
|
134
|
+
uint16_t dur = static_cast<uint16_t>(UI_SCROLL_SETTLE_MS);
|
|
134
135
|
// ease-out: k = 1 - (1 - t)^2, t in [0,1]
|
|
135
136
|
uint32_t t = elapsed >= dur ? 100 : (elapsed * 100) / dur;
|
|
136
137
|
uint32_t k = 100 - ((100 - t) * (100 - t)) / 100;
|
|
137
138
|
if (__ui_nodes[node].overscrollPx != 0) {
|
|
138
139
|
int16_t from = __ui_settle_from_overscroll;
|
|
139
|
-
__ui_nodes[node].overscrollPx =
|
|
140
|
+
__ui_nodes[node].overscrollPx = static_cast<int16_t>(from - static_cast<int32_t>(from * k) / 100);
|
|
140
141
|
if (t >= 100) __ui_nodes[node].overscrollPx = 0;
|
|
141
142
|
} else if (__ui_settle_from_scrollY != 0) {
|
|
142
143
|
int16_t from = __ui_settle_from_scrollY; // +toward 0, -toward max
|
|
143
144
|
int16_t maxS = ui_scroll_max(node);
|
|
144
145
|
if (from > 0) {
|
|
145
|
-
__ui_nodes[node].scrollY =
|
|
146
|
+
__ui_nodes[node].scrollY = static_cast<int16_t>(from - static_cast<int32_t>(from * k) / 100);
|
|
146
147
|
if (t >= 100) __ui_nodes[node].scrollY = 0;
|
|
147
148
|
} else { // from < 0: snap toward maxS
|
|
148
149
|
int16_t target = maxS;
|
|
149
|
-
__ui_nodes[node].scrollY = target +
|
|
150
|
+
__ui_nodes[node].scrollY = target + static_cast<int16_t>(static_cast<int32_t>(from) * (100 - k) / 100);
|
|
150
151
|
if (t >= 100) __ui_nodes[node].scrollY = target;
|
|
151
152
|
}
|
|
152
153
|
}
|
|
@@ -17,14 +17,24 @@ static int16_t __ui_scroll_node = -1; // owning scroll container (int
|
|
|
17
17
|
// frozen-but-not-torn rather than allowed to scroll with unclipped children.
|
|
18
18
|
// Sized at runtime via __ui_node_count; defaults to all-zero (lock until proven).
|
|
19
19
|
static uint8_t* __ui_scroll_canvas_ok = nullptr;
|
|
20
|
-
// One-shot scroll memory warnings (indexed by node).
|
|
21
|
-
static uint8_t* __ui_scroll_mem_warned = nullptr;
|
|
22
20
|
// Precomputed draw order (lower z-index first, then source index). Built once in
|
|
23
21
|
// ui_init; zIndex is static after mount so this stays valid for the app lifetime.
|
|
24
22
|
static uint16_t* __ui_draw_order = nullptr;
|
|
25
|
-
//
|
|
23
|
+
// Scroll-container node indices (built once in ui_init). Includes lists for
|
|
24
|
+
// hit-testing; the generic scroll compositor filters virtualized lists out.
|
|
26
25
|
static uint16_t* __ui_scroll_owners = nullptr;
|
|
27
26
|
static uint16_t __ui_scroll_owner_count = 0;
|
|
27
|
+
// Set when neither a viewport nor a band canvas can be allocated. The current
|
|
28
|
+
// pixels are retained and future drag deltas are rejected rather than exposing
|
|
29
|
+
// a direct per-primitive SPI fallback (which tears).
|
|
30
|
+
static uint8_t* __ui_scroll_render_locked = nullptr;
|
|
31
|
+
struct UIScrollPaintCandidate {
|
|
32
|
+
uint16_t node;
|
|
33
|
+
int16_t screenY;
|
|
34
|
+
int16_t faceH;
|
|
35
|
+
};
|
|
36
|
+
static UIScrollPaintCandidate* __ui_scroll_candidates = nullptr;
|
|
37
|
+
static uint16_t __ui_scroll_candidate_count = 0;
|
|
28
38
|
// First NODE_FILL on the active screen (for framebuffer bg seed).
|
|
29
39
|
static uint16_t __ui_active_screen_bg_node = 0xFFFF;
|
|
30
40
|
static uint32_t __ui_settle_start_ms = 0; // when the active settle animation began
|
|
@@ -112,24 +122,24 @@ static inline uint8_t ui_ease_lerp_k(uint8_t timing, uint8_t k) {
|
|
|
112
122
|
// converges. Products of four /1000 values are /1e12; t³ is /1e9 (×1000 to align).
|
|
113
123
|
// int64 accumulation avoids overflow (3e12 > INT32_MAX). Identical algorithm +
|
|
114
124
|
// control points to easeCurveLerpK in model.ts — preview and device must agree.
|
|
115
|
-
int32_t targetX = (
|
|
125
|
+
int32_t targetX = static_cast<int32_t>(k) * 10; // input on the /1000 x axis
|
|
116
126
|
int32_t lo = 0, hi = 1000;
|
|
117
127
|
for (uint8_t i = 0; i < 14; i++) {
|
|
118
128
|
int32_t t = (lo + hi) >> 1;
|
|
119
129
|
int32_t mt = 1000 - t;
|
|
120
|
-
int64_t termX1 = (
|
|
121
|
-
int64_t termX2 = (
|
|
122
|
-
int64_t termX3 = (
|
|
123
|
-
int32_t X =
|
|
130
|
+
int64_t termX1 = static_cast<int64_t>(3) * mt * mt * t * x1; // /1e12
|
|
131
|
+
int64_t termX2 = static_cast<int64_t>(3) * mt * t * t * x2; // /1e12
|
|
132
|
+
int64_t termX3 = static_cast<int64_t>(t) * t * t * 1000; // /1e12 (t³ was /1e9)
|
|
133
|
+
int32_t X = static_cast<int32_t>((termX1 + termX2 + termX3) / 1000000000LL); // back to /1000
|
|
124
134
|
if (X < targetX) lo = t; else hi = t;
|
|
125
135
|
}
|
|
126
136
|
int32_t t = (lo + hi) >> 1;
|
|
127
137
|
int32_t mt = 1000 - t;
|
|
128
|
-
int64_t termY1 = (
|
|
129
|
-
int64_t termY2 = (
|
|
130
|
-
int64_t termY3 = (
|
|
131
|
-
int32_t Y =
|
|
132
|
-
return
|
|
138
|
+
int64_t termY1 = static_cast<int64_t>(3) * mt * mt * t * y1;
|
|
139
|
+
int64_t termY2 = static_cast<int64_t>(3) * mt * t * t * y2;
|
|
140
|
+
int64_t termY3 = static_cast<int64_t>(t) * t * t * 1000;
|
|
141
|
+
int32_t Y = static_cast<int32_t>((termY1 + termY2 + termY3) / 1000000000LL); // /1000
|
|
142
|
+
return static_cast<uint8_t>(Y / 10); // back to /100
|
|
133
143
|
}
|
|
134
144
|
extern const UIKeyframeSet __ui_keyframe_sets[];
|
|
135
145
|
extern const uint16_t __ui_keyframe_set_count;
|
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
// See docs/superpowers/specs/2026-07-12-split-runtime-header-design.md.
|
|
4
4
|
export function emitStructs() {
|
|
5
5
|
return `
|
|
6
|
-
enum UINodeKind { NODE_FILL, NODE_TEXT, NODE_BUTTON, NODE_CHECK, NODE_RADIO, NODE_PROGRESS, NODE_RANGE, NODE_INPUT, NODE_IMG, NODE_LIST, NODE_CANVAS };
|
|
6
|
+
enum UINodeKind { NODE_FILL, NODE_TEXT, NODE_BUTTON, NODE_CHECK, NODE_RADIO, NODE_PROGRESS, NODE_RANGE, NODE_INPUT, NODE_IMG, NODE_LIST, NODE_CANVAS, NODE_SELECT };
|
|
7
7
|
enum UIProperty { PROP_BG, PROP_FG, PROP_TEXT, PROP_VISIBLE, PROP_BORDER_COLOR, PROP_VALUE };
|
|
8
8
|
|
|
9
9
|
struct UIRect { int16_t x, y, w, h; };
|
|
@@ -160,7 +160,7 @@ struct UITransition {
|
|
|
160
160
|
uint32_t pressedTarget;
|
|
161
161
|
uint32_t baseTarget;
|
|
162
162
|
// runtime
|
|
163
|
-
|
|
163
|
+
uint32_t elapsed;
|
|
164
164
|
uint32_t prevValue;
|
|
165
165
|
uint32_t targetValue;
|
|
166
166
|
uint8_t active;
|
|
@@ -170,6 +170,8 @@ struct UIBinding {
|
|
|
170
170
|
UIProperty prop;
|
|
171
171
|
uint32_t (*fn)(void); // for color/numeric bindings
|
|
172
172
|
void (*textFn)(char* buf, uint8_t size); // for text bindings (PROP_TEXT): fills buf
|
|
173
|
+
uint32_t lastValue; // numeric binding cache; avoids redundant dirty work
|
|
174
|
+
uint8_t initialized; // first evaluation establishes the baseline
|
|
173
175
|
};
|
|
174
176
|
|
|
175
177
|
// Color lerp for transitions (rgb565). For mono, this collapses to a snap.
|
|
@@ -181,10 +183,10 @@ static inline uint16_t lerp_color(uint16_t a, uint16_t b, uint8_t k100) {
|
|
|
181
183
|
uint8_t br5 = (b >> 11) & 0x1f, bg6 = (b >> 5) & 0x3f, bb5 = b & 0x1f;
|
|
182
184
|
uint16_t ar8 = (ar5 << 3) | (ar5 >> 2), ag8 = (ag6 << 2) | (ag6 >> 4), ab8 = (ab5 << 3) | (ab5 >> 2);
|
|
183
185
|
uint16_t br8 = (br5 << 3) | (br5 >> 2), bg8 = (bg6 << 2) | (bg6 >> 4), bb8 = (bb5 << 3) | (bb5 >> 2);
|
|
184
|
-
int16_t r =
|
|
185
|
-
int16_t g =
|
|
186
|
-
int16_t bl =
|
|
187
|
-
return (
|
|
186
|
+
int16_t r = static_cast<int16_t>(ar8 + static_cast<int16_t>((br8 - ar8) * k100 / 100));
|
|
187
|
+
int16_t g = static_cast<int16_t>(ag8 + static_cast<int16_t>((bg8 - ag8) * k100 / 100));
|
|
188
|
+
int16_t bl = static_cast<int16_t>(ab8 + static_cast<int16_t>((bb8 - ab8) * k100 / 100));
|
|
189
|
+
return (static_cast<uint16_t>((r >> 3) & 0x1f) << 11) | (static_cast<uint16_t>((g >> 2) & 0x3f) << 5) | static_cast<uint16_t>((bl >> 3) & 0x1f);
|
|
188
190
|
}
|
|
189
191
|
|
|
190
192
|
// RGB888 lerp — for transitions on RGB888/RGB666 targets (Phase 2+). Unused in
|
|
@@ -194,10 +196,10 @@ static inline uint32_t lerp_color_888(uint32_t a, uint32_t b, uint8_t k100) {
|
|
|
194
196
|
if (k100 >= 100) return b;
|
|
195
197
|
uint8_t ar = (a >> 16) & 0xff, ag = (a >> 8) & 0xff, ab = a & 0xff;
|
|
196
198
|
uint8_t br = (b >> 16) & 0xff, bg = (b >> 8) & 0xff, bb = b & 0xff;
|
|
197
|
-
int16_t r = ar +
|
|
198
|
-
int16_t g = ag +
|
|
199
|
-
int16_t bl = ab +
|
|
200
|
-
return (
|
|
199
|
+
int16_t r = ar + static_cast<int16_t>((static_cast<int16_t>(br) - static_cast<int16_t>(ar)) * k100 / 100);
|
|
200
|
+
int16_t g = ag + static_cast<int16_t>((static_cast<int16_t>(bg) - static_cast<int16_t>(ag)) * k100 / 100);
|
|
201
|
+
int16_t bl = ab + static_cast<int16_t>((static_cast<int16_t>(bb) - static_cast<int16_t>(ab)) * k100 / 100);
|
|
202
|
+
return (static_cast<uint32_t>(r & 0xff) << 16) | (static_cast<uint32_t>(g & 0xff) << 8) | static_cast<uint32_t>(bl & 0xff);
|
|
201
203
|
}
|
|
202
204
|
|
|
203
205
|
// Declared by the lowering output (the tables). Matches the mutable (non-const)
|