@typecad/ui 1.0.0-alpha.7 → 1.0.0-alpha.9
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
|
@@ -5,9 +5,13 @@ export function emitTickFlushPhase() {
|
|
|
5
5
|
return `
|
|
6
6
|
}
|
|
7
7
|
// ── Framebuffer bulk push ────────────────────────────────────────────────
|
|
8
|
-
// When a framebuffer was used this frame,
|
|
9
|
-
//
|
|
10
|
-
|
|
8
|
+
// When a framebuffer was used this frame, publish only its changed bounds.
|
|
9
|
+
// Local touch updates therefore avoid a full-screen SPI burst (which appears
|
|
10
|
+
// as a brightness dip on panels without a synchronized frame latch).
|
|
11
|
+
// Repairs performed before the dirty-node pass (for example transform
|
|
12
|
+
// geometry cleanup) may update the retained buffer without leaving a node
|
|
13
|
+
// dirty. The dirty-bounds accumulator is the authoritative publish signal.
|
|
14
|
+
if (__ui_fb && (__ui_fb_frame_dirty || __ui_fb_dirty)) {
|
|
11
15
|
ui_push_framebuffer();
|
|
12
16
|
}
|
|
13
17
|
ui_display_use_default_target();
|
|
@@ -9,7 +9,23 @@ export function emitTickScrollCanvasPhase() {
|
|
|
9
9
|
ui_push_buffered_scroll_canvas(bufferedScrollCanvas, bufferedScrollRepaintCanvas,
|
|
10
10
|
bufferedScrollNode, bufferedScrollVX, bufferedScrollVY,
|
|
11
11
|
bufferedScrollRepaintY, bufferedScrollRepaintH, __ui_draw_target);
|
|
12
|
-
} else if (bufferedScrollNode >= 0 &&
|
|
13
|
-
|
|
14
|
-
|
|
12
|
+
} else if (bufferedScrollNode >= 0 && bufferedScrollBands) {
|
|
13
|
+
// Band render fallback: the main loop didn't trigger it at the owner's
|
|
14
|
+
// z-slot (e.g. the owner was filtered out after dispatch). Render now so
|
|
15
|
+
// the subtree is never left unpainted. ui_render_scroll_bands sets
|
|
16
|
+
// lastPaintedScrollY itself on success; only the failure fallback (band
|
|
17
|
+
// canvas wouldn't allocate) needs to record the scrollY here so the next
|
|
18
|
+
// frame's delta is correct.
|
|
19
|
+
if (!ui_render_scroll_bands(static_cast<uint16_t>(bufferedScrollNode))) {
|
|
20
|
+
// No band canvas means there is no coherent frame to submit. Retain the
|
|
21
|
+
// previous viewport rather than drawing the scrollbar/direct subtree to
|
|
22
|
+
// the live SPI target, which would reintroduce tearing.
|
|
23
|
+
if (__ui_scroll_render_locked) __ui_scroll_render_locked[static_cast<uint16_t>(bufferedScrollNode)] = 1;
|
|
24
|
+
__ui_nodes[bufferedScrollNode].dirty = 0;
|
|
25
|
+
}
|
|
26
|
+
} else if (bufferedScrollNode >= 0 && bufferedScrollDirectFull) {
|
|
27
|
+
// The direct-full path is intentionally disabled for tear-sensitive scroll
|
|
28
|
+
// rendering. Keep the retained pixels when no compositor is available.
|
|
29
|
+
if (__ui_scroll_render_locked) __ui_scroll_render_locked[static_cast<uint16_t>(bufferedScrollNode)] = 1;
|
|
30
|
+
__ui_nodes[bufferedScrollNode].dirty = 0;`;
|
|
15
31
|
}
|
|
@@ -10,9 +10,10 @@ export function emitTickTransitionsPhase() {
|
|
|
10
10
|
__ui_trans[i].elapsed += deltaMs;
|
|
11
11
|
uint16_t k = __ui_trans[i].durationMs == 0
|
|
12
12
|
? 100
|
|
13
|
-
:
|
|
13
|
+
: static_cast<uint16_t>(static_cast<uint32_t>(__ui_trans[i].elapsed > __ui_trans[i].durationMs
|
|
14
|
+
? __ui_trans[i].durationMs : __ui_trans[i].elapsed) * 100 / __ui_trans[i].durationMs);
|
|
14
15
|
if (__ui_trans[i].durationMs > 0 && __ui_trans[i].durationMs <= UI_TRANSITION_SNAP_MS) k = 100;
|
|
15
|
-
uint32_t v = UI_LERP_COLOR(__ui_trans[i].prevValue, __ui_trans[i].targetValue, (
|
|
16
|
+
uint32_t v = UI_LERP_COLOR(__ui_trans[i].prevValue, __ui_trans[i].targetValue, static_cast<uint8_t>(k));
|
|
16
17
|
if (__ui_trans[i].prop == PROP_FG) {
|
|
17
18
|
__ui_nodes[__ui_trans[i].node].fg = v;
|
|
18
19
|
} else {
|
|
@@ -44,14 +45,20 @@ export function emitTickTransitionsPhase() {
|
|
|
44
45
|
ui_keyframe_set_has_scroll_sensitive_geometry(__ui_anims[i].keyframeSet)) {
|
|
45
46
|
continue;
|
|
46
47
|
}
|
|
47
|
-
__ui_anims[i].elapsed += (
|
|
48
|
+
__ui_anims[i].elapsed += static_cast<uint32_t>(deltaMs);
|
|
48
49
|
uint32_t elapsedNoDelay = __ui_anims[i].elapsed;
|
|
49
50
|
if (elapsedNoDelay < __ui_anims[i].delayMs) continue;
|
|
50
51
|
elapsedNoDelay -= __ui_anims[i].delayMs;
|
|
51
52
|
// Check iteration limit (finite).
|
|
52
53
|
uint8_t completing = 0;
|
|
53
54
|
if (__ui_anims[i].iterations > 0) {
|
|
54
|
-
|
|
55
|
+
// Saturate the multiplication so a long duration × iteration count
|
|
56
|
+
// cannot wrap and complete immediately after ~49 days of uptime.
|
|
57
|
+
uint32_t totalDuration = static_cast<uint32_t>(__ui_anims[i].iterations) * static_cast<uint32_t>(__ui_anims[i].durationMs);
|
|
58
|
+
if (__ui_anims[i].durationMs != 0 &&
|
|
59
|
+
static_cast<uint32_t>(__ui_anims[i].iterations) > 0xFFFFFFFFUL / static_cast<uint32_t>(__ui_anims[i].durationMs)) {
|
|
60
|
+
totalDuration = 0xFFFFFFFFUL;
|
|
61
|
+
}
|
|
55
62
|
if (elapsedNoDelay >= totalDuration) {
|
|
56
63
|
elapsedNoDelay = totalDuration;
|
|
57
64
|
completing = 1;
|
|
@@ -61,7 +68,7 @@ export function emitTickTransitionsPhase() {
|
|
|
61
68
|
uint8_t pct = 100;
|
|
62
69
|
if (!completing && __ui_anims[i].durationMs > 0) {
|
|
63
70
|
uint32_t cycleMs = elapsedNoDelay % __ui_anims[i].durationMs;
|
|
64
|
-
pct =
|
|
71
|
+
pct = static_cast<uint8_t>(static_cast<uint32_t>(cycleMs) * 100 / __ui_anims[i].durationMs);
|
|
65
72
|
}
|
|
66
73
|
// Find surrounding keyframe stops.
|
|
67
74
|
if (__ui_anims[i].keyframeSet >= __ui_keyframe_set_count) continue;
|
|
@@ -77,7 +84,7 @@ export function emitTickTransitionsPhase() {
|
|
|
77
84
|
const UIKeyframeStop* sHi = &ks->stops[hi];
|
|
78
85
|
// Lerp factor between lo and hi.
|
|
79
86
|
uint8_t range = sHi->percent - sLo->percent;
|
|
80
|
-
uint8_t lerpK = range > 0 ?
|
|
87
|
+
uint8_t lerpK = range > 0 ? static_cast<uint8_t>(static_cast<uint16_t>(pct - sLo->percent) * 100 / range) : 0;
|
|
81
88
|
// Shape the lerp by the animation's timing function (ease-in-out, etc.).
|
|
82
89
|
// CSS attaches it to the animation and applies it between stops.
|
|
83
90
|
lerpK = ui_ease_lerp_k(__ui_anims[i].timingFunction, lerpK);
|
|
@@ -95,7 +102,7 @@ export function emitTickTransitionsPhase() {
|
|
|
95
102
|
}
|
|
96
103
|
if ((sLo->props & UI_KF_OPACITY) && (sHi->props & UI_KF_OPACITY)) {
|
|
97
104
|
uint8_t newOp = range > 0
|
|
98
|
-
?
|
|
105
|
+
? static_cast<uint8_t>(static_cast<int16_t>(sLo->opacity) + (static_cast<int16_t>(sHi->opacity) - static_cast<int16_t>(sLo->opacity)) * lerpK / 100)
|
|
99
106
|
: sLo->opacity;
|
|
100
107
|
if (newOp != __ui_nodes[n].opacity) { __ui_nodes[n].opacity = newOp; changed = 1; }
|
|
101
108
|
}
|
|
@@ -108,10 +115,10 @@ export function emitTickTransitionsPhase() {
|
|
|
108
115
|
uint8_t hasSizeFrame = (sLo->props & UI_KF_SIZE) && (sHi->props & UI_KF_SIZE);
|
|
109
116
|
if (hasSizeFrame) {
|
|
110
117
|
nextWidth = range > 0
|
|
111
|
-
?
|
|
118
|
+
? static_cast<int16_t>(static_cast<int32_t>(sLo->width) + (static_cast<int32_t>(sHi->width) - static_cast<int32_t>(sLo->width)) * lerpK / 100)
|
|
112
119
|
: sLo->width;
|
|
113
120
|
nextHeight = range > 0
|
|
114
|
-
?
|
|
121
|
+
? static_cast<int16_t>(static_cast<int32_t>(sLo->height) + (static_cast<int32_t>(sHi->height) - static_cast<int32_t>(sLo->height)) * lerpK / 100)
|
|
115
122
|
: sLo->height;
|
|
116
123
|
if (nextWidth < 0) nextWidth = 0;
|
|
117
124
|
if (nextHeight < 0) nextHeight = 0;
|
|
@@ -122,25 +129,25 @@ export function emitTickTransitionsPhase() {
|
|
|
122
129
|
}
|
|
123
130
|
if ((sLo->props & UI_KF_TRANSFORM) && (sHi->props & UI_KF_TRANSFORM)) {
|
|
124
131
|
int16_t pxX = range > 0
|
|
125
|
-
?
|
|
132
|
+
? static_cast<int16_t>(static_cast<int32_t>(sLo->transformOffsetX) + (static_cast<int32_t>(sHi->transformOffsetX) - static_cast<int32_t>(sLo->transformOffsetX)) * lerpK / 100)
|
|
126
133
|
: sLo->transformOffsetX;
|
|
127
134
|
int16_t pxY = range > 0
|
|
128
|
-
?
|
|
135
|
+
? static_cast<int16_t>(static_cast<int32_t>(sLo->transformOffsetY) + (static_cast<int32_t>(sHi->transformOffsetY) - static_cast<int32_t>(sLo->transformOffsetY)) * lerpK / 100)
|
|
129
136
|
: sLo->transformOffsetY;
|
|
130
137
|
int16_t pctX = range > 0
|
|
131
|
-
?
|
|
138
|
+
? static_cast<int16_t>(static_cast<int32_t>(sLo->translatePctX) + (static_cast<int32_t>(sHi->translatePctX) - static_cast<int32_t>(sLo->translatePctX)) * lerpK / 100)
|
|
132
139
|
: sLo->translatePctX;
|
|
133
140
|
int16_t pctY = range > 0
|
|
134
|
-
?
|
|
141
|
+
? static_cast<int16_t>(static_cast<int32_t>(sLo->translatePctY) + (static_cast<int32_t>(sHi->translatePctY) - static_cast<int32_t>(sLo->translatePctY)) * lerpK / 100)
|
|
135
142
|
: sLo->translatePctY;
|
|
136
143
|
int16_t scaleX = range > 0
|
|
137
|
-
?
|
|
144
|
+
? static_cast<int16_t>(static_cast<int32_t>(sLo->scaleX) + (static_cast<int32_t>(sHi->scaleX) - static_cast<int32_t>(sLo->scaleX)) * lerpK / 100)
|
|
138
145
|
: sLo->scaleX;
|
|
139
146
|
int16_t scaleY = range > 0
|
|
140
|
-
?
|
|
147
|
+
? static_cast<int16_t>(static_cast<int32_t>(sLo->scaleY) + (static_cast<int32_t>(sHi->scaleY) - static_cast<int32_t>(sLo->scaleY)) * lerpK / 100)
|
|
141
148
|
: sLo->scaleY;
|
|
142
149
|
nextRotateDeg = range > 0
|
|
143
|
-
?
|
|
150
|
+
? static_cast<int16_t>(static_cast<int32_t>(sLo->rotateDeg) + (static_cast<int32_t>(sHi->rotateDeg) - static_cast<int32_t>(sLo->rotateDeg)) * lerpK / 100)
|
|
144
151
|
: sLo->rotateDeg;
|
|
145
152
|
if (scaleX < 0) scaleX = 0;
|
|
146
153
|
if (scaleY < 0) scaleY = 0;
|
|
@@ -148,14 +155,14 @@ export function emitTickTransitionsPhase() {
|
|
|
148
155
|
int16_t refH = hasSizeFrame ? nextHeight : __ui_anims[i].baseHeight;
|
|
149
156
|
if (refW <= 0) refW = __ui_nodes[n].box.w;
|
|
150
157
|
if (refH <= 0) refH = __ui_nodes[n].box.h;
|
|
151
|
-
int16_t originPxX =
|
|
152
|
-
int16_t originPxY =
|
|
153
|
-
int16_t scaledW =
|
|
154
|
-
int16_t scaledH =
|
|
155
|
-
int16_t scaleOffsetX = originPxX -
|
|
156
|
-
int16_t scaleOffsetY = originPxY -
|
|
157
|
-
nextTransformX = pxX +
|
|
158
|
-
nextTransformY = pxY +
|
|
158
|
+
int16_t originPxX = static_cast<int16_t>(static_cast<int32_t>(refW) * __ui_anims[i].originX / 100);
|
|
159
|
+
int16_t originPxY = static_cast<int16_t>(static_cast<int32_t>(refH) * __ui_anims[i].originY / 100);
|
|
160
|
+
int16_t scaledW = static_cast<int16_t>(static_cast<int32_t>(refW) * scaleX / 100);
|
|
161
|
+
int16_t scaledH = static_cast<int16_t>(static_cast<int32_t>(refH) * scaleY / 100);
|
|
162
|
+
int16_t scaleOffsetX = originPxX - static_cast<int16_t>(static_cast<int32_t>(originPxX) * scaleX / 100);
|
|
163
|
+
int16_t scaleOffsetY = originPxY - static_cast<int16_t>(static_cast<int32_t>(originPxY) * scaleY / 100);
|
|
164
|
+
nextTransformX = pxX + static_cast<int16_t>(static_cast<int32_t>(refW) * pctX / 100) + scaleOffsetX;
|
|
165
|
+
nextTransformY = pxY + static_cast<int16_t>(static_cast<int32_t>(refH) * pctY / 100) + scaleOffsetY;
|
|
159
166
|
nextWidth = scaledW < 0 ? 0 : scaledW;
|
|
160
167
|
nextHeight = scaledH < 0 ? 0 : scaledH;
|
|
161
168
|
if (nextTransformX != __ui_nodes[n].transformOffsetX ||
|
|
@@ -92,15 +92,15 @@ static int16_t ui_hit_test(int16_t tx, int16_t ty) {
|
|
|
92
92
|
for (uint16_t i = 0; i < __ui_node_count; i++) {
|
|
93
93
|
if (!ui_is_effectively_visible(i)) continue;
|
|
94
94
|
if (__ui_nodes[i].screenId != __ui_active_screen) continue;
|
|
95
|
-
int16_t drawX = ui_draw_x_for_node((
|
|
96
|
-
int16_t drawY = ui_draw_y_for_node((
|
|
95
|
+
int16_t drawX = ui_draw_x_for_node(static_cast<uint16_t>(i));
|
|
96
|
+
int16_t drawY = ui_draw_y_for_node(static_cast<uint16_t>(i));
|
|
97
97
|
if (tx >= drawX && tx < drawX + __ui_nodes[i].box.w &&
|
|
98
98
|
ty >= drawY && ty < drawY + __ui_nodes[i].box.h) {
|
|
99
99
|
// The node's box contains the tap. For nodes inside a scroll container,
|
|
100
100
|
// the tap point (not the whole box) must lie within the visible viewport:
|
|
101
101
|
// a tall wrapped paragraph can overflow below the fold yet still have a
|
|
102
102
|
// tappable link segment in its visible portion.
|
|
103
|
-
if (ui_is_point_clipped_by_scroll((
|
|
103
|
+
if (ui_is_point_clipped_by_scroll(static_cast<uint16_t>(i), tx, ty)) continue;
|
|
104
104
|
// Skip nodes without any click handler — they're containers, not targets.
|
|
105
105
|
// Exceptions: NODE_RANGE (horizontal drag), NODE_INPUT (opens keyboard),
|
|
106
106
|
// NODE_LIST (virtualized item tap), and NODE_BUTTON — the last so a button
|
|
@@ -121,7 +121,7 @@ static int16_t ui_hit_test(int16_t tx, int16_t ty) {
|
|
|
121
121
|
|
|
122
122
|
// Dispatch a handler from the given table if registered for the node.
|
|
123
123
|
static void ui_dispatch(void (**table)(), uint16_t count, int16_t node) {
|
|
124
|
-
if (node >= 0 && (
|
|
124
|
+
if (node >= 0 && static_cast<uint16_t>(node) < count && table[node]) {
|
|
125
125
|
table[node]();
|
|
126
126
|
}
|
|
127
127
|
}
|
|
@@ -165,28 +165,13 @@ static void ui_touch_down(int16_t tx, int16_t ty) {
|
|
|
165
165
|
// One owner per gesture; the double-delta bug class (node in both a container
|
|
166
166
|
// and a list) is gone because there's no second mechanism.
|
|
167
167
|
#if UI_SCROLL_HAS_TOUCH
|
|
168
|
-
|
|
169
|
-
for (uint16_t i = 0; i < __ui_node_count; i++) {
|
|
170
|
-
if (!__ui_nodes[i].scrollable || !ui_is_effectively_visible(i)) continue;
|
|
171
|
-
if (__ui_nodes[i].screenId != __ui_active_screen) continue;
|
|
172
|
-
// Only scrollable if content overflows the viewport.
|
|
173
|
-
if (__ui_nodes[i].contentHeight <= __ui_nodes[i].box.h) continue;
|
|
174
|
-
int16_t drawX = ui_draw_x_for_node((uint16_t)i);
|
|
175
|
-
int16_t drawY = ui_draw_y_for_node((uint16_t)i);
|
|
176
|
-
if (tx >= drawX && tx < drawX + __ui_nodes[i].box.w &&
|
|
177
|
-
ty >= drawY && ty < drawY + __ui_nodes[i].box.h) {
|
|
178
|
-
if (bestScroll < 0 || ui_node_draws_before(bestScroll, i)) bestScroll = i;
|
|
179
|
-
}
|
|
180
|
-
}
|
|
181
|
-
__ui_scroll_node = bestScroll;
|
|
168
|
+
__ui_scroll_node = ui_scroll_node_at(tx, ty);
|
|
182
169
|
#else
|
|
183
170
|
(void)tx; (void)ty;
|
|
184
171
|
#endif
|
|
185
172
|
if (node >= 0) {
|
|
186
|
-
uint8_t handledTouchTarget = 0;
|
|
187
173
|
if (__ui_nodes[node].kind == NODE_BUTTON) {
|
|
188
|
-
ui_set_pressed((
|
|
189
|
-
handledTouchTarget = 1;
|
|
174
|
+
ui_set_pressed(static_cast<uint16_t>(node), 1);
|
|
190
175
|
}
|
|
191
176
|
// Track range nodes for horizontal drag
|
|
192
177
|
if (__ui_nodes[node].kind == NODE_RANGE) {
|
|
@@ -199,20 +184,22 @@ static void ui_touch_down(int16_t tx, int16_t ty) {
|
|
|
199
184
|
int16_t rMax = __ui_nodes[node].rangeMax;
|
|
200
185
|
int16_t range = rMax - rMin;
|
|
201
186
|
if (range <= 0) range = 100;
|
|
202
|
-
int16_t relX = tx - ui_draw_x_for_node((
|
|
187
|
+
int16_t relX = tx - ui_draw_x_for_node(static_cast<uint16_t>(node)) - 4;
|
|
203
188
|
int16_t usable = __ui_nodes[node].box.w - 8;
|
|
204
189
|
if (usable <= 0) usable = 1;
|
|
205
|
-
int16_t nextVal = rMin + ((
|
|
190
|
+
int16_t nextVal = rMin + (static_cast<int32_t>(relX) * range) / usable;
|
|
206
191
|
nextVal = constrain(nextVal, rMin, rMax);
|
|
207
192
|
if (nextVal != __ui_nodes[node].value) {
|
|
208
193
|
__ui_nodes[node].value = nextVal;
|
|
209
194
|
ui_mark_dirty(node);
|
|
210
195
|
}
|
|
211
|
-
handledTouchTarget = 1;
|
|
212
196
|
}
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
197
|
+
// Only controls with a visual state change need a dirty repaint on touch-down:
|
|
198
|
+
// buttons were marked by ui_set_pressed() above and ranges were marked when
|
|
199
|
+
// their value changed. Links, inputs, checks, radios, selects, and list
|
|
200
|
+
// containers do not have a pressed state; repainting them here is redundant
|
|
201
|
+
// and can expose a clear/redraw flash on direct SPI targets. They repaint
|
|
202
|
+
// after the click/value change (or when scrolling actually begins).
|
|
216
203
|
}
|
|
217
204
|
}
|
|
218
205
|
|
|
@@ -241,21 +228,29 @@ static void ui_touch_up() {
|
|
|
241
228
|
int16_t clickedNode = __ui_touch_node;
|
|
242
229
|
if (elapsed < UI_TOUCH_HOLD_MS) {
|
|
243
230
|
if (__ui_nodes[clickedNode].kind == NODE_INPUT) {
|
|
244
|
-
ui_open_keyboard_for_input((
|
|
231
|
+
ui_open_keyboard_for_input(static_cast<uint16_t>(clickedNode));
|
|
245
232
|
}
|
|
246
233
|
ui_dispatch(__ui_click_handlers, __ui_click_handler_count, __ui_touch_node);
|
|
247
234
|
// Rich-text inline link: if the tapped node has link runs, find which link
|
|
248
235
|
// segment the tap falls within and navigate to its target screen. Copies
|
|
249
236
|
// the list-item subdivision precedent with measured run rects.
|
|
250
237
|
if (__ui_nodes[clickedNode].runCount > 0) {
|
|
251
|
-
int16_t ndx = __ui_last_touch_x - ui_draw_x_for_node((
|
|
252
|
-
int16_t ndy = __ui_last_touch_y - ui_draw_y_for_node((
|
|
253
|
-
int8_t target = ui_rich_link_hit((
|
|
254
|
-
if (target >= 0) ui_navigate((
|
|
238
|
+
int16_t ndx = __ui_last_touch_x - ui_draw_x_for_node(static_cast<uint16_t>(clickedNode));
|
|
239
|
+
int16_t ndy = __ui_last_touch_y - ui_draw_y_for_node(static_cast<uint16_t>(clickedNode));
|
|
240
|
+
int8_t target = ui_rich_link_hit(static_cast<uint16_t>(clickedNode), ndx, ndy);
|
|
241
|
+
if (target >= 0) ui_navigate(static_cast<uint8_t>(target));
|
|
255
242
|
}
|
|
256
243
|
}
|
|
257
244
|
ui_dispatch(__ui_release_handlers, __ui_click_handler_count, __ui_touch_node);
|
|
258
|
-
|
|
245
|
+
// Built-in check/radio/select handlers mutate the node value during click
|
|
246
|
+
// dispatch and need one repaint. Buttons are already dirty from releasing
|
|
247
|
+
// :pressed; links, lists, inputs, and ordinary callbacks are either handled
|
|
248
|
+
// by navigation/keyboard state or observed by bindings on the next tick.
|
|
249
|
+
if (__ui_nodes[clickedNode].kind == NODE_CHECK ||
|
|
250
|
+
__ui_nodes[clickedNode].kind == NODE_RADIO ||
|
|
251
|
+
__ui_nodes[clickedNode].kind == NODE_SELECT) {
|
|
252
|
+
ui_mark_dirty(static_cast<uint16_t>(clickedNode));
|
|
253
|
+
}
|
|
259
254
|
}
|
|
260
255
|
// Release the pressed button's :pressed state unconditionally — even when a
|
|
261
256
|
// drag/scroll hijacked the gesture (the click above is correctly gated on
|
|
@@ -263,7 +258,7 @@ static void ui_touch_up() {
|
|
|
263
258
|
// otherwise a touch-down on a button followed by a scroll leaves it stuck).
|
|
264
259
|
if (__ui_touch_node >= 0 && __ui_touch_node < __ui_node_count &&
|
|
265
260
|
__ui_nodes[__ui_touch_node].kind == NODE_BUTTON && __ui_nodes[__ui_touch_node].value != 0) {
|
|
266
|
-
ui_set_pressed((
|
|
261
|
+
ui_set_pressed(static_cast<uint16_t>(__ui_touch_node), 0);
|
|
267
262
|
}
|
|
268
263
|
// Resume any \`await ui.onTap()\` awaiter. Runs for EVERY completed tap —
|
|
269
264
|
// including holds (released above) and taps on empty space (__ui_touch_node
|
|
@@ -283,9 +278,9 @@ static void ui_touch_up() {
|
|
|
283
278
|
int16_t relY = __ui_last_touch_y - drawY;
|
|
284
279
|
int16_t totalMove = abs(__ui_last_touch_y - __ui_touch_down_y_pos);
|
|
285
280
|
uint16_t ih = __ui_nodes[n].listItemHeight > 0 ? __ui_nodes[n].listItemHeight : 24;
|
|
286
|
-
if (totalMove <
|
|
281
|
+
if (totalMove < static_cast<int16_t>(ih / 2) &&
|
|
287
282
|
relY >= 0 && relY < __ui_nodes[n].box.h) {
|
|
288
|
-
uint16_t itemIdx =
|
|
283
|
+
uint16_t itemIdx = static_cast<uint16_t>((relY + __ui_nodes[n].scrollY) / ih);
|
|
289
284
|
if (itemIdx < __ui_nodes[n].listCount) {
|
|
290
285
|
__ui_nodes[n].listTapFn(itemIdx);
|
|
291
286
|
}
|
|
@@ -349,16 +344,16 @@ static inline void ui_handle_touch(int16_t tx, int16_t ty) {
|
|
|
349
344
|
int16_t rMax = __ui_nodes[__ui_range_node].rangeMax;
|
|
350
345
|
int16_t range = rMax - rMin;
|
|
351
346
|
if (range <= 0) range = 100;
|
|
352
|
-
int16_t relX = tx - ui_draw_x_for_node((
|
|
347
|
+
int16_t relX = tx - ui_draw_x_for_node(static_cast<uint16_t>(__ui_range_node)) - 4;
|
|
353
348
|
int16_t usable = __ui_nodes[__ui_range_node].box.w - 8;
|
|
354
349
|
if (usable <= 0) usable = 1;
|
|
355
|
-
int16_t newVal = rMin + ((
|
|
350
|
+
int16_t newVal = rMin + (static_cast<int32_t>(relX) * range) / usable;
|
|
356
351
|
newVal = constrain(newVal, rMin, rMax);
|
|
357
352
|
if (newVal != __ui_nodes[__ui_range_node].value) {
|
|
358
353
|
__ui_nodes[__ui_range_node].value = newVal;
|
|
359
354
|
ui_mark_dirty(__ui_range_node);
|
|
360
355
|
// Fire the onChange callback (if any) — every value change during drag.
|
|
361
|
-
if (__ui_range_node < (
|
|
356
|
+
if (__ui_range_node < static_cast<int16_t>(__ui_rangechange_handler_count) &&
|
|
362
357
|
__ui_rangechange_handlers[__ui_range_node]) {
|
|
363
358
|
__ui_rangechange_handlers[__ui_range_node]();
|
|
364
359
|
}
|
|
@@ -376,10 +371,17 @@ static inline void ui_handle_touch(int16_t tx, int16_t ty) {
|
|
|
376
371
|
ui_apply_scroll_delta(__ui_scroll_node, dy);
|
|
377
372
|
__ui_drag_start_y = ty;
|
|
378
373
|
#if UI_SCROLL_DEBUG
|
|
374
|
+
#if defined(ESP32) && defined(ARDUINO)
|
|
379
375
|
Serial.printf("scroll dy=%d sy=%d ov=%d virt=%d\\n",
|
|
380
376
|
dy, __ui_nodes[__ui_scroll_node].scrollY,
|
|
381
377
|
__ui_nodes[__ui_scroll_node].overscrollPx,
|
|
382
|
-
(
|
|
378
|
+
static_cast<int>(__ui_nodes[__ui_scroll_node].virtualized));
|
|
379
|
+
#else
|
|
380
|
+
printk("scroll dy=%d sy=%d ov=%d virt=%d\\n",
|
|
381
|
+
dy, __ui_nodes[__ui_scroll_node].scrollY,
|
|
382
|
+
__ui_nodes[__ui_scroll_node].overscrollPx,
|
|
383
|
+
static_cast<int>(__ui_nodes[__ui_scroll_node].virtualized));
|
|
384
|
+
#endif
|
|
383
385
|
#endif
|
|
384
386
|
}
|
|
385
387
|
}
|
|
@@ -7,7 +7,8 @@ export function emitTypesDefines() {
|
|
|
7
7
|
#ifndef __TC_UI_RUNTIME
|
|
8
8
|
#define __TC_UI_RUNTIME
|
|
9
9
|
#include <stdint.h>
|
|
10
|
-
#include <
|
|
10
|
+
#include <cstring>
|
|
11
|
+
#include <new>
|
|
11
12
|
#define UI_TEXT_BUF 32 // max stored UI text chars, excluding the trailing NUL
|
|
12
13
|
#define UI_TEXT_LINE_BUF 96
|
|
13
14
|
#define UI_WS_NORMAL 0
|
|
@@ -17,6 +18,17 @@ export function emitTypesDefines() {
|
|
|
17
18
|
#ifndef UI_MAX_BUFFERED_PAINT_PIXELS
|
|
18
19
|
#define UI_MAX_BUFFERED_PAINT_PIXELS 20000
|
|
19
20
|
#endif
|
|
21
|
+
// Above this many pixels, prefer the band renderer (small ~10KB SRAM canvas,
|
|
22
|
+
// strip-at-a-time) over the repair canvas even when the repair canvas could
|
|
23
|
+
// allocate (e.g. in PSRAM). A large repair canvas (a full-width button is
|
|
24
|
+
// ~15-30KB) has higher alloc/seed/push latency than banding — the per-band
|
|
25
|
+
// push is smaller and the band canvas lives in fast internal SRAM. Set below
|
|
26
|
+
// UI_MAX_BUFFERED_PAINT_PIXELS so large rects band while small repaints keep
|
|
27
|
+
// the fast single-canvas composite. Tune per target; 0 = always prefer the
|
|
28
|
+
// repair canvas when it allocates (the pre-PSRAM behavior).
|
|
29
|
+
#ifndef UI_BAND_PREFER_PIXELS
|
|
30
|
+
#define UI_BAND_PREFER_PIXELS 8000
|
|
31
|
+
#endif
|
|
20
32
|
#ifndef UI_USE_FULL_FRAMEBUFFER
|
|
21
33
|
#define UI_USE_FULL_FRAMEBUFFER 0
|
|
22
34
|
#endif
|
|
@@ -69,8 +81,16 @@ export function emitTypesDefines() {
|
|
|
69
81
|
#ifndef UI_SCROLL_DEBUG
|
|
70
82
|
#define UI_SCROLL_DEBUG 0
|
|
71
83
|
#endif
|
|
72
|
-
|
|
73
|
-
|
|
84
|
+
// Band renderer: height (px) of the horizontal band canvas used to composite
|
|
85
|
+
// scroll subtrees tear-free when no viewport canvas fits (no PSRAM / over
|
|
86
|
+
// budget). Each band is vw × UI_STRIP_BAND_HEIGHT. Must be tall enough that a
|
|
87
|
+
// single line of AA text (glyph height ~lineHeight, typically 16-22px at ts=2)
|
|
88
|
+
// fits within one band — if a glyph straddles a band boundary, its anti-
|
|
89
|
+
// aliased edge pixels blend toward the wrong bg in the adjacent band, visible
|
|
90
|
+
// as a clipped/faded text bottom. 32 ensures most fonts fit; at 480px wide
|
|
91
|
+
// that's ~30KB (fits SRAM on most ESP32 targets).
|
|
92
|
+
#ifndef UI_STRIP_BAND_HEIGHT
|
|
93
|
+
#define UI_STRIP_BAND_HEIGHT 32
|
|
74
94
|
#endif
|
|
75
95
|
#if defined(ESP32) || defined(ESP8266)
|
|
76
96
|
#include <Esp.h>
|
|
@@ -35,6 +35,7 @@ import { emitTouchKeyboardFwd } from "./runtime-header/touch-keyboard-fwd.js";
|
|
|
35
35
|
import { emitBlendBodies } from "./runtime-header/blend-bodies.js";
|
|
36
36
|
import { emitTextRendering } from "./runtime-header/text-rendering.js";
|
|
37
37
|
import { emitNodeDecoration } from "./runtime-header/node-decoration.js";
|
|
38
|
+
import { emitNodeDrawBody } from "./runtime-header/node-draw-body.js";
|
|
38
39
|
import { emitTickMotionHelpers } from "./runtime-header/tick-motion-helpers.js";
|
|
39
40
|
import { emitTick } from "./runtime-header/tick.js";
|
|
40
41
|
import { emitAntialiasing } from "./runtime-header/antialiasing.js";
|
|
@@ -64,6 +65,7 @@ export function emitRuntimeHeader(opts) {
|
|
|
64
65
|
emitBlendBodies(),
|
|
65
66
|
emitTextRendering(),
|
|
66
67
|
emitNodeDecoration(),
|
|
68
|
+
emitNodeDrawBody(),
|
|
67
69
|
emitTickMotionHelpers(),
|
|
68
70
|
emitTick(),
|
|
69
71
|
emitAntialiasing(),
|
|
@@ -13,7 +13,7 @@ import { parseHtml } from "./html-parser.js";
|
|
|
13
13
|
import { parseCss, parseFontFaces, parseKeyframes } from "./css-parser.js";
|
|
14
14
|
import { resolveStyles } from "./style-resolver.js";
|
|
15
15
|
import { selectEngine } from "./select-engine.js";
|
|
16
|
-
import {
|
|
16
|
+
import { measureWithFonts } from "./layout-engine.js";
|
|
17
17
|
import { lowerUIToCpp } from "./ui-lowering.js";
|
|
18
18
|
import { buildUIFontAssets } from "./font-assets.js";
|
|
19
19
|
import { buildKeyframeSets } from "./keyframes.js";
|
|
@@ -27,7 +27,7 @@ export function transpileUI(html, css, opts) {
|
|
|
27
27
|
// Select layout engine: Yoga for flexbox, BlockLayout as fallback.
|
|
28
28
|
const engine = selectEngine(styled);
|
|
29
29
|
const viewport = { x: 0, y: 0, w: opts.viewport.width, h: opts.viewport.height };
|
|
30
|
-
const boxes = engine.arrange(styled, viewport,
|
|
30
|
+
const boxes = engine.arrange(styled, viewport, measureWithFonts(fontAssets));
|
|
31
31
|
const keyframeSets = buildKeyframeSets(rawKeyframes, opts.colorFormat);
|
|
32
32
|
return lowerUIToCpp(styled, boxes, opts.colorFormat, opts.storage, [], rules, undefined, fontAssets, [], new Map(), keyframeSets);
|
|
33
33
|
}
|
|
@@ -6,9 +6,15 @@
|
|
|
6
6
|
// ---------------------------------------------------------------------------
|
|
7
7
|
import { parseCss } from "./css-parser.js";
|
|
8
8
|
const UA_CSS = `
|
|
9
|
+
/* Browser-like light defaults: dark text on a near-white background.
|
|
10
|
+
A dark theme overrides these via user CSS (e.g. screen { color: #f0f0f0;
|
|
11
|
+
background: #1a1a1a }). Without these, screen fg defaults to white
|
|
12
|
+
(0xFFFF at the model level), making text invisible on light backgrounds. */
|
|
9
13
|
screen {
|
|
10
14
|
display: flex;
|
|
11
15
|
flex-direction: column;
|
|
16
|
+
color: #1a1a1a;
|
|
17
|
+
background: #ffffff;
|
|
12
18
|
}
|
|
13
19
|
body {
|
|
14
20
|
display: flex;
|
|
@@ -20,14 +26,41 @@ view {
|
|
|
20
26
|
display: flex;
|
|
21
27
|
flex-direction: column;
|
|
22
28
|
}
|
|
29
|
+
/* Scrollable content area — the standard pattern for multi-screen apps.
|
|
30
|
+
align-items:center + gap give readable card-style layouts out of the box. */
|
|
31
|
+
.scrollBody {
|
|
32
|
+
overflow: scroll;
|
|
33
|
+
flex: 1;
|
|
34
|
+
align-items: center;
|
|
35
|
+
gap: 10px;
|
|
36
|
+
}
|
|
37
|
+
/* Header bar (back link + title). flex-direction:row is the expected layout;
|
|
38
|
+
without it the back link and title stack vertically. */
|
|
39
|
+
.screenHeader {
|
|
40
|
+
flex-direction: row;
|
|
41
|
+
align-items: center;
|
|
42
|
+
gap: 8px;
|
|
43
|
+
}
|
|
23
44
|
button {
|
|
24
|
-
padding:
|
|
45
|
+
padding: 10px 16px;
|
|
25
46
|
border: 1px solid;
|
|
47
|
+
min-height: 48px;
|
|
26
48
|
text-align: center;
|
|
27
49
|
}
|
|
28
50
|
input {
|
|
29
|
-
padding:
|
|
51
|
+
padding: 8px;
|
|
52
|
+
border: 1px solid;
|
|
53
|
+
}
|
|
54
|
+
select {
|
|
55
|
+
padding: 10px 12px;
|
|
30
56
|
border: 1px solid;
|
|
57
|
+
min-height: 48px;
|
|
58
|
+
}
|
|
59
|
+
check, radio {
|
|
60
|
+
min-height: 48px;
|
|
61
|
+
}
|
|
62
|
+
range {
|
|
63
|
+
height: 40px;
|
|
31
64
|
}
|
|
32
65
|
list {
|
|
33
66
|
overflow: scroll;
|
|
@@ -39,13 +72,23 @@ a {
|
|
|
39
72
|
label {
|
|
40
73
|
text-align: left;
|
|
41
74
|
}
|
|
75
|
+
/* h3 is non-bold by default: the runtime bumps textSize for bold text
|
|
76
|
+
(roughly doubling per-char advance), which wraps multi-word captions
|
|
77
|
+
like "width / height / min / max" to 3 lines. Normal weight keeps
|
|
78
|
+
them readable. Users can override with font-weight:bold if needed. */
|
|
42
79
|
h1 { font-size: 24px; font-weight: bold; }
|
|
43
80
|
h2 { font-size: 20px; font-weight: bold; }
|
|
44
|
-
h3 { font-size: 18px; font-weight:
|
|
81
|
+
h3 { font-size: 18px; font-weight: normal; }
|
|
45
82
|
h4 { font-size: 16px; font-weight: bold; }
|
|
46
83
|
h5 { font-size: 14px; }
|
|
47
84
|
h6 { font-size: 12px; }
|
|
48
85
|
p { font-size: 16px; }
|
|
86
|
+
/* Keyboard structural defaults (the OSK is runtime-generated, not
|
|
87
|
+
user-authored HTML). Border keeps keys visually separated. Colors
|
|
88
|
+
are design choices left to user CSS. */
|
|
89
|
+
.ui-key {
|
|
90
|
+
border: 1px solid;
|
|
91
|
+
}
|
|
49
92
|
`;
|
|
50
93
|
let UA_RULES = null;
|
|
51
94
|
/** Get the parsed UA default rules. Lazily computed, cached. */
|
|
@@ -21,13 +21,11 @@ export interface LoweredUI {
|
|
|
21
21
|
imageTables: string;
|
|
22
22
|
/** C++ keyframe data arrays + animation table. */
|
|
23
23
|
keyframeTables: string;
|
|
24
|
-
/** Compile-time warnings for scroll viewports that exceed the canvas budget. */
|
|
25
|
-
scrollMemoryDiagnostics: Diagnostic[];
|
|
26
24
|
/** Layout-time diagnostics (viewport overflow, text overflow). Populated by
|
|
27
25
|
* lowerOnMount after the boxes are computed; empty until then. */
|
|
28
26
|
diagnostics: Diagnostic[];
|
|
29
27
|
}
|
|
30
28
|
type ColorFormat = "rgb565" | "rgb666" | "rgb888" | "mono";
|
|
31
29
|
type Storage = "progmem" | "flash";
|
|
32
|
-
export declare function lowerUIToCpp(root: StyledNode, boxes: Box[], colorFormat: ColorFormat, storage: Storage, keyboards?: KeyboardTemplate[], rules?: CSSRule[], display?: DisplayProfile, fontAssets?: UIFontAssetModel[], allScreens?: StyledNode[], imageAssetIds?: Map<string, number>, keyframeSets?: KeyframeSetModel[]
|
|
30
|
+
export declare function lowerUIToCpp(root: StyledNode, boxes: Box[], colorFormat: ColorFormat, storage: Storage, keyboards?: KeyboardTemplate[], rules?: CSSRule[], display?: DisplayProfile, fontAssets?: UIFontAssetModel[], allScreens?: StyledNode[], imageAssetIds?: Map<string, number>, keyframeSets?: KeyframeSetModel[]): LoweredUI;
|
|
33
31
|
export {};
|
|
@@ -14,16 +14,14 @@
|
|
|
14
14
|
import { lowerUIToModel } from "./model.js";
|
|
15
15
|
import { resolveColor } from "./color.js";
|
|
16
16
|
import { DEFAULT_ALPHA_KEYBOARD, DEFAULT_NUMBER_KEYBOARD } from "./default-keyboards.js";
|
|
17
|
-
import { analyzeScrollMemory } from "./scroll-memory-diagnostics.js";
|
|
18
17
|
import { getListBindings } from "@typecad/cuttlefish/ir/transformers/ui-reactive";
|
|
19
|
-
export function lowerUIToCpp(root, boxes, colorFormat, storage, keyboards = [], rules = [], display, fontAssets = [], allScreens = [], imageAssetIds = new Map(), keyframeSets = []
|
|
18
|
+
export function lowerUIToCpp(root, boxes, colorFormat, storage, keyboards = [], rules = [], display, fontAssets = [], allScreens = [], imageAssetIds = new Map(), keyframeSets = []) {
|
|
20
19
|
void storage;
|
|
21
20
|
const model = lowerUIToModel(root, boxes, colorFormat, display, fontAssets, allScreens, imageAssetIds, keyframeSets);
|
|
22
|
-
const scrollMemoryDiagnostics = analyzeScrollMemory(model.nodes, scrollCanvasBudgetBytes);
|
|
23
21
|
// Tables are mutable RAM (ui_tick updates bg/dirty/elapsed/active each
|
|
24
22
|
// frame), so no PROGMEM/flash storage keyword — those imply read-only.
|
|
25
23
|
const fontTables = emitFontTables(model);
|
|
26
|
-
const nodeTable = emitNodeTable(model)
|
|
24
|
+
const nodeTable = emitNodeTable(model);
|
|
27
25
|
const transitionTable = emitTransitionTable(model);
|
|
28
26
|
const typeDecl = emitTypeDecl(root);
|
|
29
27
|
// Keyboard loaders + dispatch: collect input nodes in tree order, resolve
|
|
@@ -63,7 +61,7 @@ export function lowerUIToCpp(root, boxes, colorFormat, storage, keyboards = [],
|
|
|
63
61
|
const screenCount = model.nodes.length > 0 ? Math.max(...model.nodes.map(n => n.screenId)) + 1 : 1;
|
|
64
62
|
const imageTables = "const UIImage __ui_images[] = {};\nconst uint16_t __ui_image_count = 0;";
|
|
65
63
|
const keyframeTables = emitKeyframeTables(model);
|
|
66
|
-
return { fontTables, nodeTable, transitionTable, typeDecl, keyboardLoaders, keyboardDispatch, screenCount, imageTables, keyframeTables,
|
|
64
|
+
return { fontTables, nodeTable, transitionTable, typeDecl, keyboardLoaders, keyboardDispatch, screenCount, imageTables, keyframeTables, diagnostics: [] };
|
|
67
65
|
}
|
|
68
66
|
function sanitizedId(id) {
|
|
69
67
|
return id.replace(/[^a-zA-Z0-9_]/g, "_");
|
|
@@ -177,6 +175,7 @@ function cppKind(kind) {
|
|
|
177
175
|
case "img": return "NODE_IMG";
|
|
178
176
|
case "list": return "NODE_LIST";
|
|
179
177
|
case "canvas": return "NODE_CANVAS";
|
|
178
|
+
case "select": return "NODE_SELECT";
|
|
180
179
|
case "text": return "NODE_TEXT";
|
|
181
180
|
}
|
|
182
181
|
}
|
|
@@ -226,24 +225,6 @@ function emitFontTables(model) {
|
|
|
226
225
|
lines.push(`const uint16_t __ui_font_face_count = ${assets.length};`);
|
|
227
226
|
return lines.join("\n");
|
|
228
227
|
}
|
|
229
|
-
function emitScrollNodeIdLookup(model) {
|
|
230
|
-
const cases = [];
|
|
231
|
-
for (const n of model.nodes) {
|
|
232
|
-
if (!n.scrollable || n.contentHeight <= n.box.h || !n.id)
|
|
233
|
-
continue;
|
|
234
|
-
const id = n.id.replace(/\\/g, "\\\\").replace(/"/g, '\\"');
|
|
235
|
-
cases.push(` if (idx == ${n.index}) return "${id}";`);
|
|
236
|
-
}
|
|
237
|
-
if (cases.length === 0) {
|
|
238
|
-
return "static inline const char* __ui_scroll_node_id(uint16_t idx) { (void)idx; return nullptr; }";
|
|
239
|
-
}
|
|
240
|
-
return [
|
|
241
|
-
"static inline const char* __ui_scroll_node_id(uint16_t idx) {",
|
|
242
|
-
...cases,
|
|
243
|
-
" return nullptr;",
|
|
244
|
-
"}",
|
|
245
|
-
].join("\n");
|
|
246
|
-
}
|
|
247
228
|
function emitNodeTable(model) {
|
|
248
229
|
// Map node index → list binding so virtualized nodes carry their count/item/
|
|
249
230
|
// tap function pointers on-node (no UIListBinding side table at runtime).
|
|
@@ -25,7 +25,6 @@ import { selectEngine } from "./select-engine.js";
|
|
|
25
25
|
import { measureWithFonts } from "./layout-engine.js";
|
|
26
26
|
import { lowerUIToCpp } from "./ui-lowering.js";
|
|
27
27
|
import { getDisplayProfile } from "@typecad/cuttlefish/stores/display-profile-store";
|
|
28
|
-
import { resolveScrollConfig } from "@typecad/cuttlefish/api/shared";
|
|
29
28
|
import { buildUIFontAssets } from "./font-assets.js";
|
|
30
29
|
import { emitImageTables, loadImageAssets } from "./image-assets.js";
|
|
31
30
|
import { splitUiFile } from "./ui-file-splitter.js";
|
|
@@ -145,12 +144,7 @@ export function lowerOnMount(htmlPath, opts) {
|
|
|
145
144
|
const keyframeSets = buildKeyframeSets(mod.rawKeyframes || [], opts.colorFormat);
|
|
146
145
|
// Load image assets before lowering so imgDataId can be set.
|
|
147
146
|
const imageAssets = loadImageAssets(allStyled.length > 0 ? allStyled : [mod.styled], path.dirname(abs));
|
|
148
|
-
const
|
|
149
|
-
const scrollBudget = resolveScrollConfig(_p, { buildTarget: _p._buildTarget, psram: _p._psram }).scrollCanvasBudgetBytes;
|
|
150
|
-
const result = lowerUIToCpp(mod.styled, allBoxes, opts.colorFormat, opts.storage, mod.keyboards, mod.rules, getDisplayProfile(), mod.fontAssets, allStyled, imageAssets.nodeIdToAssetIndex, keyframeSets, scrollBudget);
|
|
151
|
-
for (const d of result.scrollMemoryDiagnostics) {
|
|
152
|
-
mod.mountDiagnostics.push({ ...d, source: d.source ?? path.basename(mod.htmlPath) });
|
|
153
|
-
}
|
|
147
|
+
const result = lowerUIToCpp(mod.styled, allBoxes, opts.colorFormat, opts.storage, mod.keyboards, mod.rules, getDisplayProfile(), mod.fontAssets, allStyled, imageAssets.nodeIdToAssetIndex, keyframeSets);
|
|
154
148
|
// Layout diagnostics: viewport-overflow (node bottom past the viewport) and
|
|
155
149
|
// text-overflow (node right edge past its parent). Both reference the node by
|
|
156
150
|
// a human-label (#id / <tag "text"> / <tag>) so the author can find it in
|