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

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (135) hide show
  1. package/README.md +21 -1
  2. package/assets/fonts/dejavu/DejaVuSans-Bold.ttf +0 -0
  3. package/assets/fonts/dejavu/DejaVuSans-ExtraLight.ttf +0 -0
  4. package/assets/fonts/dejavu/DejaVuSans-Oblique.ttf +0 -0
  5. package/assets/fonts/dejavu/DejaVuSans.ttf +0 -0
  6. package/assets/fonts/dejavu/DejaVuSansMono-Bold.ttf +0 -0
  7. package/assets/fonts/dejavu/DejaVuSansMono.ttf +0 -0
  8. package/assets/fonts/dejavu/LICENSE +187 -0
  9. package/assets/fonts/dejavu/README.md +49 -0
  10. package/dist/cli.d.ts +2 -0
  11. package/dist/cli.js +74 -0
  12. package/dist/engine-index.js +2 -0
  13. package/dist/preview/build-program.d.ts +7 -1
  14. package/dist/preview/build-program.js +94 -15
  15. package/dist/preview/host-gfx.d.ts +3 -0
  16. package/dist/preview/host-gfx.js +30 -3
  17. package/dist/preview/host-ui-runtime.d.ts +115 -0
  18. package/dist/preview/host-ui-runtime.js +928 -46
  19. package/dist/ui-engine/block-layout.js +25 -11
  20. package/dist/ui-engine/color.d.ts +6 -0
  21. package/dist/ui-engine/color.js +80 -2
  22. package/dist/ui-engine/compat-report.d.ts +8 -0
  23. package/dist/ui-engine/compat-report.js +124 -0
  24. package/dist/ui-engine/css-imports.d.ts +4 -0
  25. package/dist/ui-engine/css-imports.js +70 -0
  26. package/dist/ui-engine/css-parser.js +58 -14
  27. package/dist/ui-engine/default-font.d.ts +25 -0
  28. package/dist/ui-engine/default-font.js +80 -0
  29. package/dist/ui-engine/font-assets.d.ts +2 -2
  30. package/dist/ui-engine/font-assets.js +22 -5
  31. package/dist/ui-engine/html-parser.d.ts +4 -0
  32. package/dist/ui-engine/html-parser.js +241 -26
  33. package/dist/ui-engine/image-assets.d.ts +8 -1
  34. package/dist/ui-engine/image-assets.js +35 -2
  35. package/dist/ui-engine/image-decode.d.ts +22 -0
  36. package/dist/ui-engine/image-decode.js +194 -0
  37. package/dist/ui-engine/inline-parser.js +1 -1
  38. package/dist/ui-engine/layout-engine.d.ts +19 -0
  39. package/dist/ui-engine/layout-engine.js +59 -3
  40. package/dist/ui-engine/model.d.ts +18 -0
  41. package/dist/ui-engine/model.js +76 -8
  42. package/dist/ui-engine/runtime-header/blend-bodies.js +29 -9
  43. package/dist/ui-engine/runtime-header/canvas-helpers.js +5 -2
  44. package/dist/ui-engine/runtime-header/canvas-scrollbar.js +115 -111
  45. package/dist/ui-engine/runtime-header/cuttlefish-gfx.js +21 -0
  46. package/dist/ui-engine/runtime-header/dirty-scroll-mutators.js +182 -179
  47. package/dist/ui-engine/runtime-header/forward-decls.js +215 -167
  48. package/dist/ui-engine/runtime-header/init-press-input.js +138 -135
  49. package/dist/ui-engine/runtime-header/keyboard.js +682 -380
  50. package/dist/ui-engine/runtime-header/node-draw-body.js +1656 -1377
  51. package/dist/ui-engine/runtime-header/paint-order-coords.js +259 -214
  52. package/dist/ui-engine/runtime-header/paint-rects-repair.js +736 -568
  53. package/dist/ui-engine/runtime-header/scroll-physics.js +25 -1
  54. package/dist/ui-engine/runtime-header/state-bindings-nav.js +9 -0
  55. package/dist/ui-engine/runtime-header/structs.js +228 -216
  56. package/dist/ui-engine/runtime-header/text-rendering.js +888 -770
  57. package/dist/ui-engine/runtime-header/tick/bindings-phase.js +118 -116
  58. package/dist/ui-engine/runtime-header/tick/dirty-draw-phase.js +897 -633
  59. package/dist/ui-engine/runtime-header/tick/scroll-canvas-phase.js +24 -25
  60. package/dist/ui-engine/runtime-header/touch-keyboard-fwd.js +79 -23
  61. package/dist/ui-engine/runtime-header/types-defines.js +110 -93
  62. package/dist/ui-engine/shadcn-kit.d.ts +1 -0
  63. package/dist/ui-engine/shadcn-kit.js +502 -0
  64. package/dist/ui-engine/style-resolver.d.ts +4 -0
  65. package/dist/ui-engine/style-resolver.js +58 -6
  66. package/dist/ui-engine/transpile-ui.js +12 -5
  67. package/dist/ui-engine/ua-stylesheet.d.ts +22 -1
  68. package/dist/ui-engine/ua-stylesheet.js +119 -32
  69. package/dist/ui-engine/ui-lowering.js +37 -3
  70. package/dist/ui-engine/ui-registry.js +32 -4
  71. package/dist/ui-engine/yoga-layout.js +57 -52
  72. package/dist/wizard/config-writer.d.ts +49 -0
  73. package/dist/wizard/config-writer.js +366 -0
  74. package/dist/wizard/display-catalog.d.ts +75 -0
  75. package/dist/wizard/display-catalog.js +207 -0
  76. package/dist/wizard/index.d.ts +7 -0
  77. package/dist/wizard/index.js +10 -0
  78. package/dist/wizard/integration-wizard.d.ts +15 -0
  79. package/dist/wizard/integration-wizard.js +486 -0
  80. package/dist/wizard/prompts.d.ts +32 -0
  81. package/dist/wizard/prompts.js +95 -0
  82. package/dist/wizard/starter-ui.d.ts +1 -0
  83. package/dist/wizard/starter-ui.js +56 -0
  84. package/package.json +18 -5
  85. package/src/cli.ts +87 -0
  86. package/src/engine-index.ts +53 -51
  87. package/src/preview/build-program.ts +810 -734
  88. package/src/preview/host-gfx.ts +30 -3
  89. package/src/preview/host-ui-runtime.ts +4133 -3289
  90. package/src/ui-engine/block-layout.ts +24 -11
  91. package/src/ui-engine/color.ts +77 -2
  92. package/src/ui-engine/compat-report.ts +139 -0
  93. package/src/ui-engine/css-imports.ts +69 -0
  94. package/src/ui-engine/css-parser.ts +64 -15
  95. package/src/ui-engine/default-font.ts +95 -0
  96. package/src/ui-engine/font-assets.ts +545 -525
  97. package/src/ui-engine/html-parser.ts +615 -397
  98. package/src/ui-engine/image-assets.ts +37 -2
  99. package/src/ui-engine/image-decode.ts +244 -0
  100. package/src/ui-engine/inline-parser.ts +1 -1
  101. package/src/ui-engine/layout-engine.ts +61 -3
  102. package/src/ui-engine/model.ts +1317 -1230
  103. package/src/ui-engine/runtime-header/blend-bodies.ts +29 -9
  104. package/src/ui-engine/runtime-header/canvas-helpers.ts +5 -2
  105. package/src/ui-engine/runtime-header/canvas-scrollbar.ts +121 -117
  106. package/src/ui-engine/runtime-header/cuttlefish-gfx.ts +21 -0
  107. package/src/ui-engine/runtime-header/dirty-scroll-mutators.ts +188 -185
  108. package/src/ui-engine/runtime-header/forward-decls.ts +227 -179
  109. package/src/ui-engine/runtime-header/init-press-input.ts +144 -141
  110. package/src/ui-engine/runtime-header/keyboard.ts +689 -386
  111. package/src/ui-engine/runtime-header/node-draw-body.ts +1681 -1402
  112. package/src/ui-engine/runtime-header/paint-order-coords.ts +265 -220
  113. package/src/ui-engine/runtime-header/paint-rects-repair.ts +742 -574
  114. package/src/ui-engine/runtime-header/scroll-physics.ts +25 -1
  115. package/src/ui-engine/runtime-header/state-bindings-nav.ts +9 -0
  116. package/src/ui-engine/runtime-header/structs.ts +234 -222
  117. package/src/ui-engine/runtime-header/text-rendering.ts +894 -776
  118. package/src/ui-engine/runtime-header/tick/bindings-phase.ts +124 -122
  119. package/src/ui-engine/runtime-header/tick/dirty-draw-phase.ts +902 -638
  120. package/src/ui-engine/runtime-header/tick/scroll-canvas-phase.ts +30 -31
  121. package/src/ui-engine/runtime-header/touch-keyboard-fwd.ts +79 -23
  122. package/src/ui-engine/runtime-header/types-defines.ts +116 -99
  123. package/src/ui-engine/shadcn-kit.ts +503 -0
  124. package/src/ui-engine/style-resolver.ts +471 -416
  125. package/src/ui-engine/transpile-ui.ts +12 -5
  126. package/src/ui-engine/ua-stylesheet.ts +137 -31
  127. package/src/ui-engine/ui-lowering.ts +486 -452
  128. package/src/ui-engine/ui-registry.ts +556 -524
  129. package/src/ui-engine/yoga-layout.ts +307 -309
  130. package/src/wizard/config-writer.ts +404 -0
  131. package/src/wizard/display-catalog.ts +273 -0
  132. package/src/wizard/index.ts +38 -0
  133. package/src/wizard/integration-wizard.ts +619 -0
  134. package/src/wizard/prompts.ts +145 -0
  135. package/src/wizard/starter-ui.ts +58 -0
@@ -1,185 +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, but overflow scroll content is canvas-
40
- // composited. Promote to a scroll repaint otherwise the defer path
41
- // drops the child's dirty flag without drawing (e.g. :pressed buttons).
42
- ui_mark_scroll_view_dirty(p);
43
- return;
44
- }
45
- p = __ui_nodes[p].parent;
46
- }
47
- }
48
- ui_mark_overlapping_higher_layers_dirty(nodeIdx);
49
- }
50
-
51
- // Set dirty=1 across a scroll subtree WITHOUT the per-child O(n) overlap repair.
52
- // During scroll the subtree repaints into a freshly-cleared off-screen canvas, so
53
- // intra-subtree overlap repair is pointless, and scroll children are draw-clipped
54
- // to the container's viewport box so all external higher-z neighbors of the
55
- // viewport are covered by ONE overlap check at the container's paint rect (done by
56
- // the caller). This drops scroll marking from O(K·n) to O(K + n).
57
- static inline void ui_mark_subtree_dirty_local(uint16_t scrollNode) {
58
- if (scrollNode >= __ui_node_count) return;
59
- for (uint16_t c = scrollNode + 1; c < __ui_nodes[scrollNode].subtreeEnd; c++) {
60
- __ui_nodes[c].dirty = 1;
61
- }
62
- __ui_nodes[scrollNode].dirty = 1;
63
- }
64
-
65
- // Mode C strip-only: direct-partial scroll when no viewport canvas fits and the
66
- // scroll delta is small. Fills only the exposed strip on the display, then marks
67
- // only the descendants that intersect that strip dirty for direct draw — mirroring
68
- // the Mode B canvas repair path. Never clears the whole viewport, and never marks
69
- // the whole subtree dirty (AGENTS.md: keep scroll drag invalidation small a
70
- // 1-pixel drag must not repaint every visible child).
71
- static inline void ui_scroll_direct_prepare(uint16_t s, int16_t* outVX, int16_t* outVY) {
72
- int16_t vw = __ui_nodes[s].box.w;
73
- int16_t vh = __ui_nodes[s].box.h;
74
- int16_t vox = __ui_nodes[s].box.x;
75
- int16_t voy = __ui_nodes[s].box.y;
76
- UI_COLOR_T scrollBg = __ui_nodes[s].hasBg ? __ui_nodes[s].bg : __ui_nodes[s].clearColor;
77
- int16_t deltaY = __ui_nodes[s].scrollY - __ui_nodes[s].lastPaintedScrollY;
78
- int16_t absDelta = deltaY < 0 ? -deltaY : deltaY;
79
- int16_t stripY = deltaY > 0 ? static_cast<int16_t>(vh - absDelta) : 0;
80
- ui_display_fill_rect(vox, static_cast<int16_t>(voy + stripY), vw, absDelta, scrollBg);
81
- // Only descendants whose paint rect intersects the exposed strip need
82
- // repainting; the rest keep their last-painted pixels (the strip background
83
- // fill already covered the vacated band). Clear stale dirty flags on the
84
- // others so a flag set by a prior frame (or a sibling promotion) doesn't
85
- // trigger a needless repaint.
86
- UIRect exposed = { vox, static_cast<int16_t>(voy + stripY), vw, absDelta };
87
- for (uint16_t c = s + 1; c < __ui_nodes[s].subtreeEnd; c++) {
88
- if (!ui_is_effectively_visible(c) || __ui_nodes[c].screenId != __ui_active_screen) {
89
- __ui_nodes[c].dirty = 0;
90
- continue;
91
- }
92
- UIRect cr;
93
- ui_node_current_paint_rect(c, &cr);
94
- if (cr.w > 0 && cr.h > 0 &&
95
- ui_rects_intersect(cr.x, cr.y, cr.w, cr.h, exposed.x, exposed.y, exposed.w, exposed.h)) {
96
- __ui_nodes[c].dirty = 1;
97
- if (__ui_nodes[c].kind == NODE_PROGRESS || __ui_nodes[c].kind == NODE_RANGE) {
98
- __ui_nodes[c].lastTextWidth = -1;
99
- }
100
- __ui_nodes[c].lastTextHeight = 0;
101
- } else {
102
- __ui_nodes[c].dirty = 0;
103
- }
104
- }
105
- __ui_nodes[s].dirty = 0;
106
- if (outVX) *outVX = vox;
107
- if (outVY) *outVY = voy;
108
- }
109
-
110
- // Nearest overflow scroll container owning nodeIdx (or nodeIdx itself).
111
- static inline int16_t ui_overflow_scroll_compositor(uint16_t nodeIdx) {
112
- if (nodeIdx >= __ui_node_count) return -1;
113
- if (__ui_nodes[nodeIdx].scrollable && !__ui_nodes[nodeIdx].virtualized &&
114
- __ui_nodes[nodeIdx].contentHeight > __ui_nodes[nodeIdx].box.h) {
115
- return static_cast<int16_t>(nodeIdx);
116
- }
117
- uint16_t p = __ui_nodes[nodeIdx].parent;
118
- while (p != UI_NO_PARENT && p < __ui_node_count) {
119
- if (__ui_nodes[p].scrollable && !__ui_nodes[p].virtualized &&
120
- __ui_nodes[p].contentHeight > __ui_nodes[p].box.h) {
121
- return static_cast<int16_t>(p);
122
- }
123
- p = __ui_nodes[p].parent;
124
- }
125
- return -1;
126
- }
127
-
128
- static inline void ui_mark_scroll_view_overlaps_dirty(uint16_t scrollNode) {
129
- if (scrollNode >= __ui_node_count) return;
130
- if (!ui_is_effectively_visible(scrollNode)) return;
131
- if (__ui_nodes[scrollNode].screenId != __ui_active_screen) return;
132
- UIRect r;
133
- ui_node_current_paint_rect(scrollNode, &r);
134
- if (r.w <= 0 || r.h <= 0) return;
135
- for (uint16_t c = 0; c < __ui_node_count; c++) {
136
- if (c == scrollNode) continue;
137
- if (c > scrollNode && c < __ui_nodes[scrollNode].subtreeEnd) continue;
138
- if (__ui_nodes[c].dirty) continue;
139
- if (!ui_is_effectively_visible(c)) continue;
140
- if (__ui_nodes[c].screenId != __ui_active_screen) continue;
141
- if (!ui_node_draws_before(scrollNode, c)) continue;
142
- UIRect cr;
143
- ui_node_current_paint_rect(c, &cr);
144
- if (cr.w <= 0 || cr.h <= 0) continue;
145
- if (ui_rects_intersect(r.x, r.y, r.w, r.h, cr.x, cr.y, cr.w, cr.h)) {
146
- __ui_nodes[c].dirty = 1;
147
- }
148
- }
149
- }
150
-
151
- static inline void ui_mark_scroll_subtree_dirty(uint16_t scrollNode) {
152
- ui_mark_subtree_dirty_local(scrollNode);
153
- // Single overlap check at the container's paint rect covers every external
154
- // higher-z neighbor of the viewport. The container index is the right one to
155
- // pass: scroll content lives within the container's stacking context, so
156
- // ui_node_draws_before(scrollNode, c) selects exactly the external layers that
157
- // should be repaired when the viewport is repainted.
158
- ui_mark_overlapping_higher_layers_dirty(scrollNode);
159
- }
160
-
161
- static inline void ui_mark_scroll_view_dirty(uint16_t scrollNode) {
162
- if (scrollNode >= __ui_node_count) return;
163
- __ui_nodes[scrollNode].dirty = 1;
164
- ui_mark_scroll_view_overlaps_dirty(scrollNode);
165
- }
166
-
167
- static inline int16_t ui_scroll_ancestor_for_node(uint16_t nodeIdx) {
168
- uint16_t p = __ui_nodes[nodeIdx].parent;
169
- while (p != UI_NO_PARENT && p < __ui_node_count) {
170
- if (__ui_nodes[p].scrollable) return static_cast<int16_t>(p);
171
- p = __ui_nodes[p].parent;
172
- }
173
- return -1;
174
- }
175
-
176
- static inline void ui_invalidate_scroll_canvas_for_node(uint16_t nodeIdx) {
177
- int16_t scrollParent = ui_scroll_ancestor_for_node(nodeIdx);
178
- if (scrollParent < 0) return;
179
- if (__ui_nodes[scrollParent].virtualized) return;
180
- if (__ui_nodes[scrollParent].contentHeight <= __ui_nodes[scrollParent].box.h) return;
181
- int16_t span = __ui_nodes[scrollParent].box.h > 0 ? __ui_nodes[scrollParent].box.h : 1;
182
- __ui_nodes[scrollParent].lastPaintedScrollY = __ui_nodes[scrollParent].scrollY - span;
183
- }
184
- `;
185
- }
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
+ }