@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,121 +1,121 @@
|
|
|
1
|
-
// Slice of the C++ runtime header (original source lines 1449-1547).
|
|
2
|
-
// Canvas push/draw helpers, buffered scroll canvas push, scrollbar direct.
|
|
3
|
-
// See docs/superpowers/specs/2026-07-12-split-runtime-header-design.md.
|
|
4
|
-
export function emitCanvasScrollbar(): string {
|
|
5
|
-
return `
|
|
6
|
-
static inline void ui_push_canvas_rect(CuttlefishCanvas16* canvas, int16_t x, int16_t y, int16_t w, int16_t h) {
|
|
7
|
-
if (!canvas || !display_canvasBuffer(canvas)) return;
|
|
8
|
-
UI_COLOR_T* pixels = display_canvasBuffer(canvas);
|
|
9
|
-
int16_t stride = display_canvasWidth(canvas);
|
|
10
|
-
// The canvas is viewport-sized: buffer row 0 = the first row of the viewport.
|
|
11
|
-
// The display destination is (x, y) but the source buffer starts at (0, 0).
|
|
12
|
-
// When a full-screen framebuffer is the active draw target, composite into it
|
|
13
|
-
// via __ui_gfx (so the single bulk push at frame end captures everything).
|
|
14
|
-
// Otherwise write straight to the display in one SPI transaction.
|
|
15
|
-
if (__ui_fb) {
|
|
16
|
-
// The framebuffer is the composition target. Track this publish so a
|
|
17
|
-
// geometry repair or scroll-canvas repair that does not leave a node dirty
|
|
18
|
-
// still reaches the panel during the frame-end flush.
|
|
19
|
-
ui_fb_add_rect(x, y, w, h);
|
|
20
|
-
if (w == stride) {
|
|
21
|
-
ui_display_draw_rgb_bitmap(x, y, pixels, w, h);
|
|
22
|
-
return;
|
|
23
|
-
}
|
|
24
|
-
for (int16_t row = 0; row < h; row++) {
|
|
25
|
-
ui_display_draw_rgb_bitmap(x, y + row, pixels + static_cast<int32_t>(row) * stride, w, 1);
|
|
26
|
-
}
|
|
27
|
-
return;
|
|
28
|
-
}
|
|
29
|
-
display_startWrite();
|
|
30
|
-
display_setAddrWindow(x, y, w, h);
|
|
31
|
-
if (w == stride) {
|
|
32
|
-
// Full-width: contiguous in buffer, single write.
|
|
33
|
-
display_writePixels(pixels, static_cast<uint32_t>(w) * h);
|
|
34
|
-
display_endWrite();
|
|
35
|
-
return;
|
|
36
|
-
}
|
|
37
|
-
for (int16_t row = 0; row < h; row++) {
|
|
38
|
-
display_writePixels(pixels + static_cast<int32_t>(row) * stride, w);
|
|
39
|
-
}
|
|
40
|
-
display_endWrite();
|
|
41
|
-
}
|
|
42
|
-
|
|
43
|
-
static inline void ui_draw_canvas_rect(CuttlefishCanvas16* canvas, int16_t x, int16_t y, int16_t w, int16_t h) {
|
|
44
|
-
if (!canvas || !display_canvasBuffer(canvas)) return;
|
|
45
|
-
UI_COLOR_T* pixels = display_canvasBuffer(canvas);
|
|
46
|
-
int16_t stride = display_canvasWidth(canvas);
|
|
47
|
-
if (w == stride && h == display_canvasHeight(canvas)) {
|
|
48
|
-
ui_display_draw_rgb_bitmap(x, y, pixels, w, h);
|
|
49
|
-
return;
|
|
50
|
-
}
|
|
51
|
-
for (int16_t row = 0; row < h; row++) {
|
|
52
|
-
ui_display_draw_rgb_bitmap(x, y + row, pixels + static_cast<int32_t>(row) * stride, w, 1);
|
|
53
|
-
}
|
|
54
|
-
}
|
|
55
|
-
|
|
56
|
-
static inline void ui_push_buffered_scroll_canvas(CuttlefishCanvas16* bufferedScrollCanvas,
|
|
57
|
-
CuttlefishCanvas16* bufferedScrollRepaintCanvas,
|
|
58
|
-
int16_t bufferedScrollNode,
|
|
59
|
-
int16_t bufferedScrollVX,
|
|
60
|
-
int16_t bufferedScrollVY,
|
|
61
|
-
int16_t bufferedScrollRepaintY,
|
|
62
|
-
int16_t bufferedScrollRepaintH,
|
|
63
|
-
CuttlefishDisplayTarget* drawTarget) {
|
|
64
|
-
if (bufferedScrollNode < 0 || !bufferedScrollCanvas || !display_canvasBuffer(bufferedScrollCanvas)) return;
|
|
65
|
-
// Draw scrollbar into the canvas (canvas-local coords: 0,0 = viewport top-left).
|
|
66
|
-
ui_display_set_target(bufferedScrollCanvas);
|
|
67
|
-
int16_t si = bufferedScrollNode;
|
|
68
|
-
int16_t vw = __ui_nodes[si].box.w;
|
|
69
|
-
int16_t vh = __ui_nodes[si].box.h;
|
|
70
|
-
if (bufferedScrollRepaintCanvas && bufferedScrollRepaintH > 0) {
|
|
71
|
-
ui_draw_canvas_rect(bufferedScrollRepaintCanvas, 0, bufferedScrollRepaintY, vw, bufferedScrollRepaintH);
|
|
72
|
-
}
|
|
73
|
-
int16_t tx = vw - 4;
|
|
74
|
-
uint16_t thumbH = static_cast<uint32_t>(vh) * vh / __ui_nodes[si].contentHeight;
|
|
75
|
-
if (thumbH < 8) thumbH = 8;
|
|
76
|
-
// Clamp to vh so (vh - thumbH) below can't underflow on a degenerate sub-8px
|
|
77
|
-
// viewport (which would cast a negative value to a huge uint32_t and place the
|
|
78
|
-
// thumb off-screen). Matches the guard in ui_render_scroll_bands.
|
|
79
|
-
if (thumbH > static_cast<uint16_t>(vh)) thumbH = static_cast<uint16_t>(vh);
|
|
80
|
-
int16_t maxScroll = __ui_nodes[si].contentHeight - vh;
|
|
81
|
-
uint16_t thumbY = static_cast<uint32_t>(vh - thumbH) * __ui_nodes[si].scrollY / (maxScroll > 0 ? maxScroll : 1);
|
|
82
|
-
UI_COLOR_T dimFg = (UI_COLOR_T)((__ui_nodes[si].fg >> 1) & UI_DIM_MASK);
|
|
83
|
-
// Track covers the FULL reserved gutter (4px) so child decorations that
|
|
84
|
-
// poke past the content area (e.g. outset shadows, offset up to +6px)
|
|
85
|
-
// can't leave 1px ticks in the uncovered edge column. Thumb stays 3px.
|
|
86
|
-
ui_display_fill_rect(tx, 0, 4, vh, dimFg);
|
|
87
|
-
ui_display_fill_rect(tx, thumbY, 3, thumbH, __ui_nodes[si].fg);
|
|
88
|
-
// Record the scrollY this canvas now reflects, so the next scroll frame can
|
|
89
|
-
// compute its shift delta (Mode B) from scrollY - lastPaintedScrollY.
|
|
90
|
-
__ui_nodes[si].lastPaintedScrollY = __ui_nodes[si].scrollY;
|
|
91
|
-
// Push the canvas to the draw target at the viewport position (the
|
|
92
|
-
// framebuffer when active, else the display directly).
|
|
93
|
-
ui_display_set_target(drawTarget);
|
|
94
|
-
ui_push_canvas_rect(bufferedScrollCanvas,
|
|
95
|
-
bufferedScrollVX, bufferedScrollVY,
|
|
96
|
-
vw, vh);
|
|
97
|
-
}
|
|
98
|
-
|
|
99
|
-
// Draw a scroll container's scrollbar directly on the display (Mode C).
|
|
100
|
-
static inline void ui_draw_scrollbar_direct(int16_t si, int16_t vox, int16_t voy) {
|
|
101
|
-
if (si < 0 || si >= static_cast<int16_t>(__ui_node_count)) return;
|
|
102
|
-
int16_t vw = __ui_nodes[si].box.w;
|
|
103
|
-
int16_t vh = __ui_nodes[si].box.h;
|
|
104
|
-
if (__ui_nodes[si].contentHeight <= vh) return;
|
|
105
|
-
int16_t tx = vox + vw - 4;
|
|
106
|
-
uint16_t thumbH = static_cast<uint32_t>(vh) * vh / __ui_nodes[si].contentHeight;
|
|
107
|
-
if (thumbH < 8) thumbH = 8;
|
|
108
|
-
// Clamp to vh so (vh - thumbH) below can't underflow on a degenerate sub-8px
|
|
109
|
-
// viewport (which would cast a negative value to a huge uint32_t and place the
|
|
110
|
-
// thumb off-screen). Matches the guard in ui_render_scroll_bands.
|
|
111
|
-
if (thumbH > static_cast<uint16_t>(vh)) thumbH = static_cast<uint16_t>(vh);
|
|
112
|
-
int16_t maxScroll = __ui_nodes[si].contentHeight - vh;
|
|
113
|
-
uint16_t thumbY = static_cast<uint32_t>(vh - thumbH) * __ui_nodes[si].scrollY / (maxScroll > 0 ? maxScroll : 1);
|
|
114
|
-
UI_COLOR_T dimFg = (UI_COLOR_T)((__ui_nodes[si].fg >> 1) & UI_DIM_MASK);
|
|
115
|
-
// Full 4px gutter track (see the Mode B variant above).
|
|
116
|
-
ui_display_fill_rect(tx, voy, 4, vh, dimFg);
|
|
117
|
-
ui_display_fill_rect(tx, static_cast<int16_t>(voy + thumbY), 3, thumbH, __ui_nodes[si].fg);
|
|
118
|
-
}
|
|
119
|
-
|
|
120
|
-
// Per-node dirty marker (called by press handlers and binding evaluation).`;
|
|
121
|
-
}
|
|
1
|
+
// Slice of the C++ runtime header (original source lines 1449-1547).
|
|
2
|
+
// Canvas push/draw helpers, buffered scroll canvas push, scrollbar direct.
|
|
3
|
+
// See docs/superpowers/specs/2026-07-12-split-runtime-header-design.md.
|
|
4
|
+
export function emitCanvasScrollbar(): string {
|
|
5
|
+
return `
|
|
6
|
+
static inline void ui_push_canvas_rect(CuttlefishCanvas16* canvas, int16_t x, int16_t y, int16_t w, int16_t h) {
|
|
7
|
+
if (!canvas || !display_canvasBuffer(canvas)) return;
|
|
8
|
+
UI_COLOR_T* pixels = display_canvasBuffer(canvas);
|
|
9
|
+
int16_t stride = display_canvasWidth(canvas);
|
|
10
|
+
// The canvas is viewport-sized: buffer row 0 = the first row of the viewport.
|
|
11
|
+
// The display destination is (x, y) but the source buffer starts at (0, 0).
|
|
12
|
+
// When a full-screen framebuffer is the active draw target, composite into it
|
|
13
|
+
// via __ui_gfx (so the single bulk push at frame end captures everything).
|
|
14
|
+
// Otherwise write straight to the display in one SPI transaction.
|
|
15
|
+
if (__ui_fb) {
|
|
16
|
+
// The framebuffer is the composition target. Track this publish so a
|
|
17
|
+
// geometry repair or scroll-canvas repair that does not leave a node dirty
|
|
18
|
+
// still reaches the panel during the frame-end flush.
|
|
19
|
+
ui_fb_add_rect(x, y, w, h);
|
|
20
|
+
if (w == stride) {
|
|
21
|
+
ui_display_draw_rgb_bitmap(x, y, pixels, w, h);
|
|
22
|
+
return;
|
|
23
|
+
}
|
|
24
|
+
for (int16_t row = 0; row < h; row++) {
|
|
25
|
+
ui_display_draw_rgb_bitmap(x, y + row, pixels + static_cast<int32_t>(row) * stride, w, 1);
|
|
26
|
+
}
|
|
27
|
+
return;
|
|
28
|
+
}
|
|
29
|
+
display_startWrite();
|
|
30
|
+
display_setAddrWindow(x, y, w, h);
|
|
31
|
+
if (w == stride) {
|
|
32
|
+
// Full-width: contiguous in buffer, single write.
|
|
33
|
+
display_writePixels(pixels, static_cast<uint32_t>(w) * h);
|
|
34
|
+
display_endWrite();
|
|
35
|
+
return;
|
|
36
|
+
}
|
|
37
|
+
for (int16_t row = 0; row < h; row++) {
|
|
38
|
+
display_writePixels(pixels + static_cast<int32_t>(row) * stride, w);
|
|
39
|
+
}
|
|
40
|
+
display_endWrite();
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
static inline void ui_draw_canvas_rect(CuttlefishCanvas16* canvas, int16_t x, int16_t y, int16_t w, int16_t h) {
|
|
44
|
+
if (!canvas || !display_canvasBuffer(canvas)) return;
|
|
45
|
+
UI_COLOR_T* pixels = display_canvasBuffer(canvas);
|
|
46
|
+
int16_t stride = display_canvasWidth(canvas);
|
|
47
|
+
if (w == stride && h == display_canvasHeight(canvas)) {
|
|
48
|
+
ui_display_draw_rgb_bitmap(x, y, pixels, w, h);
|
|
49
|
+
return;
|
|
50
|
+
}
|
|
51
|
+
for (int16_t row = 0; row < h; row++) {
|
|
52
|
+
ui_display_draw_rgb_bitmap(x, y + row, pixels + static_cast<int32_t>(row) * stride, w, 1);
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
static inline void ui_push_buffered_scroll_canvas(CuttlefishCanvas16* bufferedScrollCanvas,
|
|
57
|
+
CuttlefishCanvas16* bufferedScrollRepaintCanvas,
|
|
58
|
+
int16_t bufferedScrollNode,
|
|
59
|
+
int16_t bufferedScrollVX,
|
|
60
|
+
int16_t bufferedScrollVY,
|
|
61
|
+
int16_t bufferedScrollRepaintY,
|
|
62
|
+
int16_t bufferedScrollRepaintH,
|
|
63
|
+
CuttlefishDisplayTarget* drawTarget) {
|
|
64
|
+
if (bufferedScrollNode < 0 || !bufferedScrollCanvas || !display_canvasBuffer(bufferedScrollCanvas)) return;
|
|
65
|
+
// Draw scrollbar into the canvas (canvas-local coords: 0,0 = viewport top-left).
|
|
66
|
+
ui_display_set_target(bufferedScrollCanvas);
|
|
67
|
+
int16_t si = bufferedScrollNode;
|
|
68
|
+
int16_t vw = __ui_nodes[si].box.w;
|
|
69
|
+
int16_t vh = __ui_nodes[si].box.h;
|
|
70
|
+
if (bufferedScrollRepaintCanvas && bufferedScrollRepaintH > 0) {
|
|
71
|
+
ui_draw_canvas_rect(bufferedScrollRepaintCanvas, 0, bufferedScrollRepaintY, vw, bufferedScrollRepaintH);
|
|
72
|
+
}
|
|
73
|
+
int16_t tx = vw - 4;
|
|
74
|
+
uint16_t thumbH = static_cast<uint32_t>(vh) * vh / __ui_nodes[si].contentHeight;
|
|
75
|
+
if (thumbH < 8) thumbH = 8;
|
|
76
|
+
// Clamp to vh so (vh - thumbH) below can't underflow on a degenerate sub-8px
|
|
77
|
+
// viewport (which would cast a negative value to a huge uint32_t and place the
|
|
78
|
+
// thumb off-screen). Matches the guard in ui_render_scroll_bands.
|
|
79
|
+
if (thumbH > static_cast<uint16_t>(vh)) thumbH = static_cast<uint16_t>(vh);
|
|
80
|
+
int16_t maxScroll = __ui_nodes[si].contentHeight - vh;
|
|
81
|
+
uint16_t thumbY = static_cast<uint32_t>(vh - thumbH) * __ui_nodes[si].scrollY / (maxScroll > 0 ? maxScroll : 1);
|
|
82
|
+
UI_COLOR_T dimFg = (UI_COLOR_T)((__ui_nodes[si].fg >> 1) & UI_DIM_MASK);
|
|
83
|
+
// Track covers the FULL reserved gutter (4px) so child decorations that
|
|
84
|
+
// poke past the content area (e.g. outset shadows, offset up to +6px)
|
|
85
|
+
// can't leave 1px ticks in the uncovered edge column. Thumb stays 3px.
|
|
86
|
+
ui_display_fill_rect(tx, 0, 4, vh, dimFg);
|
|
87
|
+
ui_display_fill_rect(tx, thumbY, 3, thumbH, __ui_nodes[si].fg);
|
|
88
|
+
// Record the scrollY this canvas now reflects, so the next scroll frame can
|
|
89
|
+
// compute its shift delta (Mode B) from scrollY - lastPaintedScrollY.
|
|
90
|
+
__ui_nodes[si].lastPaintedScrollY = __ui_nodes[si].scrollY;
|
|
91
|
+
// Push the canvas to the draw target at the viewport position (the
|
|
92
|
+
// framebuffer when active, else the display directly).
|
|
93
|
+
ui_display_set_target(drawTarget);
|
|
94
|
+
ui_push_canvas_rect(bufferedScrollCanvas,
|
|
95
|
+
bufferedScrollVX, bufferedScrollVY,
|
|
96
|
+
vw, vh);
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
// Draw a scroll container's scrollbar directly on the display (Mode C).
|
|
100
|
+
static inline void ui_draw_scrollbar_direct(int16_t si, int16_t vox, int16_t voy) {
|
|
101
|
+
if (si < 0 || si >= static_cast<int16_t>(__ui_node_count)) return;
|
|
102
|
+
int16_t vw = __ui_nodes[si].box.w;
|
|
103
|
+
int16_t vh = __ui_nodes[si].box.h;
|
|
104
|
+
if (__ui_nodes[si].contentHeight <= vh) return;
|
|
105
|
+
int16_t tx = vox + vw - 4;
|
|
106
|
+
uint16_t thumbH = static_cast<uint32_t>(vh) * vh / __ui_nodes[si].contentHeight;
|
|
107
|
+
if (thumbH < 8) thumbH = 8;
|
|
108
|
+
// Clamp to vh so (vh - thumbH) below can't underflow on a degenerate sub-8px
|
|
109
|
+
// viewport (which would cast a negative value to a huge uint32_t and place the
|
|
110
|
+
// thumb off-screen). Matches the guard in ui_render_scroll_bands.
|
|
111
|
+
if (thumbH > static_cast<uint16_t>(vh)) thumbH = static_cast<uint16_t>(vh);
|
|
112
|
+
int16_t maxScroll = __ui_nodes[si].contentHeight - vh;
|
|
113
|
+
uint16_t thumbY = static_cast<uint32_t>(vh - thumbH) * __ui_nodes[si].scrollY / (maxScroll > 0 ? maxScroll : 1);
|
|
114
|
+
UI_COLOR_T dimFg = (UI_COLOR_T)((__ui_nodes[si].fg >> 1) & UI_DIM_MASK);
|
|
115
|
+
// Full 4px gutter track (see the Mode B variant above).
|
|
116
|
+
ui_display_fill_rect(tx, voy, 4, vh, dimFg);
|
|
117
|
+
ui_display_fill_rect(tx, static_cast<int16_t>(voy + thumbY), 3, thumbH, __ui_nodes[si].fg);
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
// Per-node dirty marker (called by press handlers and binding evaluation).`;
|
|
121
|
+
}
|
|
@@ -1,188 +1,188 @@
|
|
|
1
|
-
// Slice of the C++ runtime header (original source lines 1548-1710).
|
|
2
|
-
// mark_dirty, subtree dirty, scroll direct prepare/compositor, scroll view dirty/overlap/invalidate.
|
|
3
|
-
// See docs/superpowers/specs/2026-07-12-split-runtime-header-design.md.
|
|
4
|
-
export function emitDirtyScrollMutators(): string {
|
|
5
|
-
return `
|
|
6
|
-
static inline void ui_mark_dirty(uint16_t nodeIdx) {
|
|
7
|
-
if (nodeIdx >= __ui_node_count) return;
|
|
8
|
-
__ui_nodes[nodeIdx].dirty = 1;
|
|
9
|
-
if (ui_is_effectively_visible(nodeIdx) &&
|
|
10
|
-
__ui_nodes[nodeIdx].screenId == __ui_active_screen) {
|
|
11
|
-
UIRect r;
|
|
12
|
-
ui_node_current_paint_rect(nodeIdx, &r);
|
|
13
|
-
if (r.w <= 0 || r.h <= 0) {
|
|
14
|
-
ui_mark_overlapping_higher_layers_dirty(nodeIdx);
|
|
15
|
-
return;
|
|
16
|
-
}
|
|
17
|
-
uint16_t p = __ui_nodes[nodeIdx].parent;
|
|
18
|
-
while (p != UI_NO_PARENT && p < __ui_node_count) {
|
|
19
|
-
if (__ui_nodes[p].scrollable &&
|
|
20
|
-
!__ui_nodes[p].virtualized &&
|
|
21
|
-
__ui_nodes[p].contentHeight > __ui_nodes[p].box.h) {
|
|
22
|
-
UIRect clip = {
|
|
23
|
-
__ui_nodes[p].box.x,
|
|
24
|
-
__ui_nodes[p].box.y,
|
|
25
|
-
__ui_nodes[p].box.w,
|
|
26
|
-
__ui_nodes[p].box.h
|
|
27
|
-
};
|
|
28
|
-
if (!ui_rects_intersect(r.x, r.y, r.w, r.h, clip.x, clip.y, clip.w, clip.h)) {
|
|
29
|
-
__ui_nodes[nodeIdx].dirty = 0;
|
|
30
|
-
return;
|
|
31
|
-
}
|
|
32
|
-
if (r.x < clip.x ||
|
|
33
|
-
r.x + r.w > clip.x + clip.w ||
|
|
34
|
-
r.y < clip.y ||
|
|
35
|
-
r.y + r.h > clip.y + clip.h) {
|
|
36
|
-
ui_mark_scroll_view_dirty(p);
|
|
37
|
-
return;
|
|
38
|
-
}
|
|
39
|
-
// Fully inside the viewport: leave the CHILD dirty. The dirty loop's
|
|
40
|
-
// scroll defer lets fully-contained children repaint through their own
|
|
41
|
-
// buffered paint (one atomic push; the retained scroll canvas is
|
|
42
|
-
// invalidated after the draw). Promoting here recomposed the whole
|
|
43
|
-
// viewport per dirty tick — fine for a rare :pressed button, but with
|
|
44
|
-
// keyframe animations marking children (and their overlapping higher
|
|
45
|
-
// layers) at frame rate it turned every animation frame into a
|
|
46
|
-
// full-viewport canvas push — constant tearing on no-TE SPI panels.
|
|
47
|
-
}
|
|
48
|
-
p = __ui_nodes[p].parent;
|
|
49
|
-
}
|
|
50
|
-
}
|
|
51
|
-
ui_mark_overlapping_higher_layers_dirty(nodeIdx);
|
|
52
|
-
}
|
|
53
|
-
|
|
54
|
-
// Set dirty=1 across a scroll subtree WITHOUT the per-child O(n) overlap repair.
|
|
55
|
-
// During scroll the subtree repaints into a freshly-cleared off-screen canvas, so
|
|
56
|
-
// intra-subtree overlap repair is pointless, and scroll children are draw-clipped
|
|
57
|
-
// to the container's viewport box — so all external higher-z neighbors of the
|
|
58
|
-
// viewport are covered by ONE overlap check at the container's paint rect (done by
|
|
59
|
-
// the caller). This drops scroll marking from O(K·n) to O(K + n).
|
|
60
|
-
static inline void ui_mark_subtree_dirty_local(uint16_t scrollNode) {
|
|
61
|
-
if (scrollNode >= __ui_node_count) return;
|
|
62
|
-
for (uint16_t c = scrollNode + 1; c < __ui_nodes[scrollNode].subtreeEnd; c++) {
|
|
63
|
-
__ui_nodes[c].dirty = 1;
|
|
64
|
-
}
|
|
65
|
-
__ui_nodes[scrollNode].dirty = 1;
|
|
66
|
-
}
|
|
67
|
-
|
|
68
|
-
// Mode C strip-only: direct-partial scroll when no viewport canvas fits and the
|
|
69
|
-
// scroll delta is small. Fills only the exposed strip on the display, then marks
|
|
70
|
-
// only the descendants that intersect that strip dirty for direct draw — mirroring
|
|
71
|
-
// the Mode B canvas repair path. Never clears the whole viewport, and never marks
|
|
72
|
-
// the whole subtree dirty (AGENTS.md: keep scroll drag invalidation small — a
|
|
73
|
-
// 1-pixel drag must not repaint every visible child).
|
|
74
|
-
static inline void ui_scroll_direct_prepare(uint16_t s, int16_t* outVX, int16_t* outVY) {
|
|
75
|
-
int16_t vw = __ui_nodes[s].box.w;
|
|
76
|
-
int16_t vh = __ui_nodes[s].box.h;
|
|
77
|
-
int16_t vox = __ui_nodes[s].box.x;
|
|
78
|
-
int16_t voy = __ui_nodes[s].box.y;
|
|
79
|
-
UI_COLOR_T scrollBg = __ui_nodes[s].hasBg ? __ui_nodes[s].bg : __ui_nodes[s].clearColor;
|
|
80
|
-
int16_t deltaY = __ui_nodes[s].scrollY - __ui_nodes[s].lastPaintedScrollY;
|
|
81
|
-
int16_t absDelta = deltaY < 0 ? -deltaY : deltaY;
|
|
82
|
-
int16_t stripY = deltaY > 0 ? static_cast<int16_t>(vh - absDelta) : 0;
|
|
83
|
-
ui_display_fill_rect(vox, static_cast<int16_t>(voy + stripY), vw, absDelta, scrollBg);
|
|
84
|
-
// Only descendants whose paint rect intersects the exposed strip need
|
|
85
|
-
// repainting; the rest keep their last-painted pixels (the strip background
|
|
86
|
-
// fill already covered the vacated band). Clear stale dirty flags on the
|
|
87
|
-
// others so a flag set by a prior frame (or a sibling promotion) doesn't
|
|
88
|
-
// trigger a needless repaint.
|
|
89
|
-
UIRect exposed = { vox, static_cast<int16_t>(voy + stripY), vw, absDelta };
|
|
90
|
-
for (uint16_t c = s + 1; c < __ui_nodes[s].subtreeEnd; c++) {
|
|
91
|
-
if (!ui_is_effectively_visible(c) || __ui_nodes[c].screenId != __ui_active_screen) {
|
|
92
|
-
__ui_nodes[c].dirty = 0;
|
|
93
|
-
continue;
|
|
94
|
-
}
|
|
95
|
-
UIRect cr;
|
|
96
|
-
ui_node_current_paint_rect(c, &cr);
|
|
97
|
-
if (cr.w > 0 && cr.h > 0 &&
|
|
98
|
-
ui_rects_intersect(cr.x, cr.y, cr.w, cr.h, exposed.x, exposed.y, exposed.w, exposed.h)) {
|
|
99
|
-
__ui_nodes[c].dirty = 1;
|
|
100
|
-
if (__ui_nodes[c].kind == NODE_PROGRESS || __ui_nodes[c].kind == NODE_RANGE) {
|
|
101
|
-
__ui_nodes[c].lastTextWidth = -1;
|
|
102
|
-
}
|
|
103
|
-
__ui_nodes[c].lastTextHeight = 0;
|
|
104
|
-
} else {
|
|
105
|
-
__ui_nodes[c].dirty = 0;
|
|
106
|
-
}
|
|
107
|
-
}
|
|
108
|
-
__ui_nodes[s].dirty = 0;
|
|
109
|
-
if (outVX) *outVX = vox;
|
|
110
|
-
if (outVY) *outVY = voy;
|
|
111
|
-
}
|
|
112
|
-
|
|
113
|
-
// Nearest overflow scroll container owning nodeIdx (or nodeIdx itself).
|
|
114
|
-
static inline int16_t ui_overflow_scroll_compositor(uint16_t nodeIdx) {
|
|
115
|
-
if (nodeIdx >= __ui_node_count) return -1;
|
|
116
|
-
if (__ui_nodes[nodeIdx].scrollable && !__ui_nodes[nodeIdx].virtualized &&
|
|
117
|
-
__ui_nodes[nodeIdx].contentHeight > __ui_nodes[nodeIdx].box.h) {
|
|
118
|
-
return static_cast<int16_t>(nodeIdx);
|
|
119
|
-
}
|
|
120
|
-
uint16_t p = __ui_nodes[nodeIdx].parent;
|
|
121
|
-
while (p != UI_NO_PARENT && p < __ui_node_count) {
|
|
122
|
-
if (__ui_nodes[p].scrollable && !__ui_nodes[p].virtualized &&
|
|
123
|
-
__ui_nodes[p].contentHeight > __ui_nodes[p].box.h) {
|
|
124
|
-
return static_cast<int16_t>(p);
|
|
125
|
-
}
|
|
126
|
-
p = __ui_nodes[p].parent;
|
|
127
|
-
}
|
|
128
|
-
return -1;
|
|
129
|
-
}
|
|
130
|
-
|
|
131
|
-
static inline void ui_mark_scroll_view_overlaps_dirty(uint16_t scrollNode) {
|
|
132
|
-
if (scrollNode >= __ui_node_count) return;
|
|
133
|
-
if (!ui_is_effectively_visible(scrollNode)) return;
|
|
134
|
-
if (__ui_nodes[scrollNode].screenId != __ui_active_screen) return;
|
|
135
|
-
UIRect r;
|
|
136
|
-
ui_node_current_paint_rect(scrollNode, &r);
|
|
137
|
-
if (r.w <= 0 || r.h <= 0) return;
|
|
138
|
-
for (uint16_t c = 0; c < __ui_node_count; c++) {
|
|
139
|
-
if (c == scrollNode) continue;
|
|
140
|
-
if (c > scrollNode && c < __ui_nodes[scrollNode].subtreeEnd) continue;
|
|
141
|
-
if (__ui_nodes[c].dirty) continue;
|
|
142
|
-
if (!ui_is_effectively_visible(c)) continue;
|
|
143
|
-
if (__ui_nodes[c].screenId != __ui_active_screen) continue;
|
|
144
|
-
if (!ui_node_draws_before(scrollNode, c)) continue;
|
|
145
|
-
UIRect cr;
|
|
146
|
-
ui_node_current_paint_rect(c, &cr);
|
|
147
|
-
if (cr.w <= 0 || cr.h <= 0) continue;
|
|
148
|
-
if (ui_rects_intersect(r.x, r.y, r.w, r.h, cr.x, cr.y, cr.w, cr.h)) {
|
|
149
|
-
__ui_nodes[c].dirty = 1;
|
|
150
|
-
}
|
|
151
|
-
}
|
|
152
|
-
}
|
|
153
|
-
|
|
154
|
-
static inline void ui_mark_scroll_subtree_dirty(uint16_t scrollNode) {
|
|
155
|
-
ui_mark_subtree_dirty_local(scrollNode);
|
|
156
|
-
// Single overlap check at the container's paint rect covers every external
|
|
157
|
-
// higher-z neighbor of the viewport. The container index is the right one to
|
|
158
|
-
// pass: scroll content lives within the container's stacking context, so
|
|
159
|
-
// ui_node_draws_before(scrollNode, c) selects exactly the external layers that
|
|
160
|
-
// should be repaired when the viewport is repainted.
|
|
161
|
-
ui_mark_overlapping_higher_layers_dirty(scrollNode);
|
|
162
|
-
}
|
|
163
|
-
|
|
164
|
-
static inline void ui_mark_scroll_view_dirty(uint16_t scrollNode) {
|
|
165
|
-
if (scrollNode >= __ui_node_count) return;
|
|
166
|
-
__ui_nodes[scrollNode].dirty = 1;
|
|
167
|
-
ui_mark_scroll_view_overlaps_dirty(scrollNode);
|
|
168
|
-
}
|
|
169
|
-
|
|
170
|
-
static inline int16_t ui_scroll_ancestor_for_node(uint16_t nodeIdx) {
|
|
171
|
-
uint16_t p = __ui_nodes[nodeIdx].parent;
|
|
172
|
-
while (p != UI_NO_PARENT && p < __ui_node_count) {
|
|
173
|
-
if (__ui_nodes[p].scrollable) return static_cast<int16_t>(p);
|
|
174
|
-
p = __ui_nodes[p].parent;
|
|
175
|
-
}
|
|
176
|
-
return -1;
|
|
177
|
-
}
|
|
178
|
-
|
|
179
|
-
static inline void ui_invalidate_scroll_canvas_for_node(uint16_t nodeIdx) {
|
|
180
|
-
int16_t scrollParent = ui_scroll_ancestor_for_node(nodeIdx);
|
|
181
|
-
if (scrollParent < 0) return;
|
|
182
|
-
if (__ui_nodes[scrollParent].virtualized) return;
|
|
183
|
-
if (__ui_nodes[scrollParent].contentHeight <= __ui_nodes[scrollParent].box.h) return;
|
|
184
|
-
int16_t span = __ui_nodes[scrollParent].box.h > 0 ? __ui_nodes[scrollParent].box.h : 1;
|
|
185
|
-
__ui_nodes[scrollParent].lastPaintedScrollY = __ui_nodes[scrollParent].scrollY - span;
|
|
186
|
-
}
|
|
187
|
-
`;
|
|
188
|
-
}
|
|
1
|
+
// Slice of the C++ runtime header (original source lines 1548-1710).
|
|
2
|
+
// mark_dirty, subtree dirty, scroll direct prepare/compositor, scroll view dirty/overlap/invalidate.
|
|
3
|
+
// See docs/superpowers/specs/2026-07-12-split-runtime-header-design.md.
|
|
4
|
+
export function emitDirtyScrollMutators(): string {
|
|
5
|
+
return `
|
|
6
|
+
static inline void ui_mark_dirty(uint16_t nodeIdx) {
|
|
7
|
+
if (nodeIdx >= __ui_node_count) return;
|
|
8
|
+
__ui_nodes[nodeIdx].dirty = 1;
|
|
9
|
+
if (ui_is_effectively_visible(nodeIdx) &&
|
|
10
|
+
__ui_nodes[nodeIdx].screenId == __ui_active_screen) {
|
|
11
|
+
UIRect r;
|
|
12
|
+
ui_node_current_paint_rect(nodeIdx, &r);
|
|
13
|
+
if (r.w <= 0 || r.h <= 0) {
|
|
14
|
+
ui_mark_overlapping_higher_layers_dirty(nodeIdx);
|
|
15
|
+
return;
|
|
16
|
+
}
|
|
17
|
+
uint16_t p = __ui_nodes[nodeIdx].parent;
|
|
18
|
+
while (p != UI_NO_PARENT && p < __ui_node_count) {
|
|
19
|
+
if (__ui_nodes[p].scrollable &&
|
|
20
|
+
!__ui_nodes[p].virtualized &&
|
|
21
|
+
__ui_nodes[p].contentHeight > __ui_nodes[p].box.h) {
|
|
22
|
+
UIRect clip = {
|
|
23
|
+
__ui_nodes[p].box.x,
|
|
24
|
+
__ui_nodes[p].box.y,
|
|
25
|
+
__ui_nodes[p].box.w,
|
|
26
|
+
__ui_nodes[p].box.h
|
|
27
|
+
};
|
|
28
|
+
if (!ui_rects_intersect(r.x, r.y, r.w, r.h, clip.x, clip.y, clip.w, clip.h)) {
|
|
29
|
+
__ui_nodes[nodeIdx].dirty = 0;
|
|
30
|
+
return;
|
|
31
|
+
}
|
|
32
|
+
if (r.x < clip.x ||
|
|
33
|
+
r.x + r.w > clip.x + clip.w ||
|
|
34
|
+
r.y < clip.y ||
|
|
35
|
+
r.y + r.h > clip.y + clip.h) {
|
|
36
|
+
ui_mark_scroll_view_dirty(p);
|
|
37
|
+
return;
|
|
38
|
+
}
|
|
39
|
+
// Fully inside the viewport: leave the CHILD dirty. The dirty loop's
|
|
40
|
+
// scroll defer lets fully-contained children repaint through their own
|
|
41
|
+
// buffered paint (one atomic push; the retained scroll canvas is
|
|
42
|
+
// invalidated after the draw). Promoting here recomposed the whole
|
|
43
|
+
// viewport per dirty tick — fine for a rare :pressed button, but with
|
|
44
|
+
// keyframe animations marking children (and their overlapping higher
|
|
45
|
+
// layers) at frame rate it turned every animation frame into a
|
|
46
|
+
// full-viewport canvas push — constant tearing on no-TE SPI panels.
|
|
47
|
+
}
|
|
48
|
+
p = __ui_nodes[p].parent;
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
ui_mark_overlapping_higher_layers_dirty(nodeIdx);
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
// Set dirty=1 across a scroll subtree WITHOUT the per-child O(n) overlap repair.
|
|
55
|
+
// During scroll the subtree repaints into a freshly-cleared off-screen canvas, so
|
|
56
|
+
// intra-subtree overlap repair is pointless, and scroll children are draw-clipped
|
|
57
|
+
// to the container's viewport box — so all external higher-z neighbors of the
|
|
58
|
+
// viewport are covered by ONE overlap check at the container's paint rect (done by
|
|
59
|
+
// the caller). This drops scroll marking from O(K·n) to O(K + n).
|
|
60
|
+
static inline void ui_mark_subtree_dirty_local(uint16_t scrollNode) {
|
|
61
|
+
if (scrollNode >= __ui_node_count) return;
|
|
62
|
+
for (uint16_t c = scrollNode + 1; c < __ui_nodes[scrollNode].subtreeEnd; c++) {
|
|
63
|
+
__ui_nodes[c].dirty = 1;
|
|
64
|
+
}
|
|
65
|
+
__ui_nodes[scrollNode].dirty = 1;
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
// Mode C strip-only: direct-partial scroll when no viewport canvas fits and the
|
|
69
|
+
// scroll delta is small. Fills only the exposed strip on the display, then marks
|
|
70
|
+
// only the descendants that intersect that strip dirty for direct draw — mirroring
|
|
71
|
+
// the Mode B canvas repair path. Never clears the whole viewport, and never marks
|
|
72
|
+
// the whole subtree dirty (AGENTS.md: keep scroll drag invalidation small — a
|
|
73
|
+
// 1-pixel drag must not repaint every visible child).
|
|
74
|
+
static inline void ui_scroll_direct_prepare(uint16_t s, int16_t* outVX, int16_t* outVY) {
|
|
75
|
+
int16_t vw = __ui_nodes[s].box.w;
|
|
76
|
+
int16_t vh = __ui_nodes[s].box.h;
|
|
77
|
+
int16_t vox = __ui_nodes[s].box.x;
|
|
78
|
+
int16_t voy = __ui_nodes[s].box.y;
|
|
79
|
+
UI_COLOR_T scrollBg = __ui_nodes[s].hasBg ? __ui_nodes[s].bg : __ui_nodes[s].clearColor;
|
|
80
|
+
int16_t deltaY = __ui_nodes[s].scrollY - __ui_nodes[s].lastPaintedScrollY;
|
|
81
|
+
int16_t absDelta = deltaY < 0 ? -deltaY : deltaY;
|
|
82
|
+
int16_t stripY = deltaY > 0 ? static_cast<int16_t>(vh - absDelta) : 0;
|
|
83
|
+
ui_display_fill_rect(vox, static_cast<int16_t>(voy + stripY), vw, absDelta, scrollBg);
|
|
84
|
+
// Only descendants whose paint rect intersects the exposed strip need
|
|
85
|
+
// repainting; the rest keep their last-painted pixels (the strip background
|
|
86
|
+
// fill already covered the vacated band). Clear stale dirty flags on the
|
|
87
|
+
// others so a flag set by a prior frame (or a sibling promotion) doesn't
|
|
88
|
+
// trigger a needless repaint.
|
|
89
|
+
UIRect exposed = { vox, static_cast<int16_t>(voy + stripY), vw, absDelta };
|
|
90
|
+
for (uint16_t c = s + 1; c < __ui_nodes[s].subtreeEnd; c++) {
|
|
91
|
+
if (!ui_is_effectively_visible(c) || __ui_nodes[c].screenId != __ui_active_screen) {
|
|
92
|
+
__ui_nodes[c].dirty = 0;
|
|
93
|
+
continue;
|
|
94
|
+
}
|
|
95
|
+
UIRect cr;
|
|
96
|
+
ui_node_current_paint_rect(c, &cr);
|
|
97
|
+
if (cr.w > 0 && cr.h > 0 &&
|
|
98
|
+
ui_rects_intersect(cr.x, cr.y, cr.w, cr.h, exposed.x, exposed.y, exposed.w, exposed.h)) {
|
|
99
|
+
__ui_nodes[c].dirty = 1;
|
|
100
|
+
if (__ui_nodes[c].kind == NODE_PROGRESS || __ui_nodes[c].kind == NODE_RANGE) {
|
|
101
|
+
__ui_nodes[c].lastTextWidth = -1;
|
|
102
|
+
}
|
|
103
|
+
__ui_nodes[c].lastTextHeight = 0;
|
|
104
|
+
} else {
|
|
105
|
+
__ui_nodes[c].dirty = 0;
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
__ui_nodes[s].dirty = 0;
|
|
109
|
+
if (outVX) *outVX = vox;
|
|
110
|
+
if (outVY) *outVY = voy;
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
// Nearest overflow scroll container owning nodeIdx (or nodeIdx itself).
|
|
114
|
+
static inline int16_t ui_overflow_scroll_compositor(uint16_t nodeIdx) {
|
|
115
|
+
if (nodeIdx >= __ui_node_count) return -1;
|
|
116
|
+
if (__ui_nodes[nodeIdx].scrollable && !__ui_nodes[nodeIdx].virtualized &&
|
|
117
|
+
__ui_nodes[nodeIdx].contentHeight > __ui_nodes[nodeIdx].box.h) {
|
|
118
|
+
return static_cast<int16_t>(nodeIdx);
|
|
119
|
+
}
|
|
120
|
+
uint16_t p = __ui_nodes[nodeIdx].parent;
|
|
121
|
+
while (p != UI_NO_PARENT && p < __ui_node_count) {
|
|
122
|
+
if (__ui_nodes[p].scrollable && !__ui_nodes[p].virtualized &&
|
|
123
|
+
__ui_nodes[p].contentHeight > __ui_nodes[p].box.h) {
|
|
124
|
+
return static_cast<int16_t>(p);
|
|
125
|
+
}
|
|
126
|
+
p = __ui_nodes[p].parent;
|
|
127
|
+
}
|
|
128
|
+
return -1;
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
static inline void ui_mark_scroll_view_overlaps_dirty(uint16_t scrollNode) {
|
|
132
|
+
if (scrollNode >= __ui_node_count) return;
|
|
133
|
+
if (!ui_is_effectively_visible(scrollNode)) return;
|
|
134
|
+
if (__ui_nodes[scrollNode].screenId != __ui_active_screen) return;
|
|
135
|
+
UIRect r;
|
|
136
|
+
ui_node_current_paint_rect(scrollNode, &r);
|
|
137
|
+
if (r.w <= 0 || r.h <= 0) return;
|
|
138
|
+
for (uint16_t c = 0; c < __ui_node_count; c++) {
|
|
139
|
+
if (c == scrollNode) continue;
|
|
140
|
+
if (c > scrollNode && c < __ui_nodes[scrollNode].subtreeEnd) continue;
|
|
141
|
+
if (__ui_nodes[c].dirty) continue;
|
|
142
|
+
if (!ui_is_effectively_visible(c)) continue;
|
|
143
|
+
if (__ui_nodes[c].screenId != __ui_active_screen) continue;
|
|
144
|
+
if (!ui_node_draws_before(scrollNode, c)) continue;
|
|
145
|
+
UIRect cr;
|
|
146
|
+
ui_node_current_paint_rect(c, &cr);
|
|
147
|
+
if (cr.w <= 0 || cr.h <= 0) continue;
|
|
148
|
+
if (ui_rects_intersect(r.x, r.y, r.w, r.h, cr.x, cr.y, cr.w, cr.h)) {
|
|
149
|
+
__ui_nodes[c].dirty = 1;
|
|
150
|
+
}
|
|
151
|
+
}
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
static inline void ui_mark_scroll_subtree_dirty(uint16_t scrollNode) {
|
|
155
|
+
ui_mark_subtree_dirty_local(scrollNode);
|
|
156
|
+
// Single overlap check at the container's paint rect covers every external
|
|
157
|
+
// higher-z neighbor of the viewport. The container index is the right one to
|
|
158
|
+
// pass: scroll content lives within the container's stacking context, so
|
|
159
|
+
// ui_node_draws_before(scrollNode, c) selects exactly the external layers that
|
|
160
|
+
// should be repaired when the viewport is repainted.
|
|
161
|
+
ui_mark_overlapping_higher_layers_dirty(scrollNode);
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
static inline void ui_mark_scroll_view_dirty(uint16_t scrollNode) {
|
|
165
|
+
if (scrollNode >= __ui_node_count) return;
|
|
166
|
+
__ui_nodes[scrollNode].dirty = 1;
|
|
167
|
+
ui_mark_scroll_view_overlaps_dirty(scrollNode);
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
static inline int16_t ui_scroll_ancestor_for_node(uint16_t nodeIdx) {
|
|
171
|
+
uint16_t p = __ui_nodes[nodeIdx].parent;
|
|
172
|
+
while (p != UI_NO_PARENT && p < __ui_node_count) {
|
|
173
|
+
if (__ui_nodes[p].scrollable) return static_cast<int16_t>(p);
|
|
174
|
+
p = __ui_nodes[p].parent;
|
|
175
|
+
}
|
|
176
|
+
return -1;
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
static inline void ui_invalidate_scroll_canvas_for_node(uint16_t nodeIdx) {
|
|
180
|
+
int16_t scrollParent = ui_scroll_ancestor_for_node(nodeIdx);
|
|
181
|
+
if (scrollParent < 0) return;
|
|
182
|
+
if (__ui_nodes[scrollParent].virtualized) return;
|
|
183
|
+
if (__ui_nodes[scrollParent].contentHeight <= __ui_nodes[scrollParent].box.h) return;
|
|
184
|
+
int16_t span = __ui_nodes[scrollParent].box.h > 0 ? __ui_nodes[scrollParent].box.h : 1;
|
|
185
|
+
__ui_nodes[scrollParent].lastPaintedScrollY = __ui_nodes[scrollParent].scrollY - span;
|
|
186
|
+
}
|
|
187
|
+
`;
|
|
188
|
+
}
|