@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.
Files changed (60) hide show
  1. package/README.md +1457 -1469
  2. package/dist/cli.js +22 -19
  3. package/dist/preview/host-ui-runtime.js +0 -1
  4. package/dist/ui-engine/model.js +7 -1
  5. package/dist/ui-engine/runtime-header/canvas-scrollbar.js +115 -115
  6. package/dist/ui-engine/runtime-header/dirty-scroll-mutators.js +182 -182
  7. package/dist/ui-engine/runtime-header/forward-decls.js +227 -216
  8. package/dist/ui-engine/runtime-header/init-press-input.js +138 -138
  9. package/dist/ui-engine/runtime-header/keyboard.js +676 -676
  10. package/dist/ui-engine/runtime-header/node-draw-body.js +1658 -1656
  11. package/dist/ui-engine/runtime-header/paint-order-coords.js +259 -259
  12. package/dist/ui-engine/runtime-header/paint-rects-repair.js +736 -736
  13. package/dist/ui-engine/runtime-header/scroll-physics.js +4 -4
  14. package/dist/ui-engine/runtime-header/structs.js +228 -228
  15. package/dist/ui-engine/runtime-header/text-rendering.js +888 -888
  16. package/dist/ui-engine/runtime-header/tick/bindings-phase.js +118 -118
  17. package/dist/ui-engine/runtime-header/tick/dirty-draw-phase.js +896 -896
  18. package/dist/ui-engine/runtime-header/tick/scroll-canvas-phase.js +24 -24
  19. package/dist/ui-engine/runtime-header/touch-keyboard-fwd.js +11 -11
  20. package/dist/ui-engine/runtime-header/types-defines.js +110 -110
  21. package/dist/ui-engine/shadcn-kit.d.ts +1 -1
  22. package/dist/ui-engine/shadcn-kit.js +4 -0
  23. package/dist/ui-engine/ui-lowering.d.ts +1 -1
  24. package/dist/ui-engine/ui-lowering.js +14 -5
  25. package/dist/ui-engine/ui-registry.js +9 -1
  26. package/dist/wizard/display-catalog.d.ts +0 -3
  27. package/dist/wizard/display-catalog.js +3 -12
  28. package/dist/wizard/index.d.ts +2 -0
  29. package/dist/wizard/index.js +1 -0
  30. package/dist/wizard/integration-wizard.d.ts +3 -0
  31. package/dist/wizard/integration-wizard.js +69 -20
  32. package/dist/wizard/package-writer.d.ts +20 -0
  33. package/dist/wizard/package-writer.js +79 -0
  34. package/package.json +4 -4
  35. package/src/cli.ts +90 -87
  36. package/src/preview/host-ui-runtime.ts +0 -1
  37. package/src/ui-engine/model.ts +6 -1
  38. package/src/ui-engine/runtime-header/canvas-scrollbar.ts +121 -121
  39. package/src/ui-engine/runtime-header/dirty-scroll-mutators.ts +188 -188
  40. package/src/ui-engine/runtime-header/forward-decls.ts +238 -227
  41. package/src/ui-engine/runtime-header/init-press-input.ts +144 -144
  42. package/src/ui-engine/runtime-header/keyboard.ts +689 -689
  43. package/src/ui-engine/runtime-header/node-draw-body.ts +1683 -1681
  44. package/src/ui-engine/runtime-header/paint-order-coords.ts +265 -265
  45. package/src/ui-engine/runtime-header/paint-rects-repair.ts +742 -742
  46. package/src/ui-engine/runtime-header/scroll-physics.ts +4 -4
  47. package/src/ui-engine/runtime-header/structs.ts +234 -234
  48. package/src/ui-engine/runtime-header/text-rendering.ts +894 -894
  49. package/src/ui-engine/runtime-header/tick/bindings-phase.ts +124 -124
  50. package/src/ui-engine/runtime-header/tick/dirty-draw-phase.ts +902 -902
  51. package/src/ui-engine/runtime-header/tick/scroll-canvas-phase.ts +30 -30
  52. package/src/ui-engine/runtime-header/touch-keyboard-fwd.ts +11 -11
  53. package/src/ui-engine/runtime-header/types-defines.ts +116 -116
  54. package/src/ui-engine/shadcn-kit.ts +4 -0
  55. package/src/ui-engine/ui-lowering.ts +12 -4
  56. package/src/ui-engine/ui-registry.ts +9 -1
  57. package/src/wizard/display-catalog.ts +261 -273
  58. package/src/wizard/index.ts +46 -38
  59. package/src/wizard/integration-wizard.ts +679 -619
  60. package/src/wizard/package-writer.ts +98 -0
@@ -1,227 +1,238 @@
1
- // Slice of the C++ runtime header (original source lines 622-752).
2
- // navigate, canvas/scroll/text/paint/clip/AA/image forward declarations, persistent canvas globals.
3
- // See docs/superpowers/specs/2026-07-12-split-runtime-header-design.md.
4
- //
5
- // EMIT BOUNDARY: This file is the structural head of the UI runtime header
6
- // surface (C) — every partial emitter in this directory produces bytes that are
7
- // assembled into the C++ header inlined into user sketches. The emitted bytes
8
- // are covered by the TypeCAD Runtime Exception (see RUNTIME_EXCEPTION.md at the
9
- // repository root) and are not subject to the license of this tool source.
10
- export function emitForwardDecls(): string {
11
- return `
12
- // ── Modal <select> + <drawer> declarations ────────────────────────────────
13
- // Declared here (the earliest runtime-header module) because ui_navigate,
14
- // ui_is_effectively_visible, ui_init, and ui_tick all reference them;
15
- // definitions live in the touch/keyboard modules below.
16
- // ── Modal <select> option list ────────────────────────────────────────────
17
- // Tapping a select opens a centered list of its options; tapping a row sets
18
- // the value, tapping outside dismisses. Mirrors the preview's select modal.
19
- static int16_t __ui_select_menu = -1; // node index while open, -1 closed
20
- static uint8_t __ui_select_menu_dirty = 0; // overlay needs stamping this frame
21
- static inline void ui_select_menu_open(uint16_t nodeIdx);
22
- static inline void ui_select_menu_close(uint8_t repaint);
23
- static inline void ui_select_menu_geom(uint16_t nodeIdx, UIRect* out, int16_t* rowH);
24
- static inline void ui_select_menu_draw();
25
- static inline void ui_select_menu_tap(int16_t tx, int16_t ty);
26
-
27
- // ── <drawer> slide-in panels ──────────────────────────────────────────────
28
- // Author-styled absolute panels; the runtime slides the subtree in from the
29
- // drawer's edge (transform offsets), hides it while closed, and closes on
30
- // outside taps. Mirrors the preview's drawer implementation.
31
- #define UI_DRAWER_MAX 4
32
- static int16_t __ui_drawer_idx[UI_DRAWER_MAX]; // node index per slot, -1 free; int16 — node indices exceed 127 in real apps
33
- static uint8_t __ui_drawer_open[UI_DRAWER_MAX]; // target state (0 closed, 1 open)
34
- static uint8_t __ui_drawer_progress[UI_DRAWER_MAX]; // 0 closed .. 1 open
35
- static uint8_t __ui_drawer_slots = 0; // discovered drawers (init scan)
36
- static int16_t __ui_drawer_last_dx[UI_DRAWER_MAX]; // last applied slide dx (delta bookkeeping)
37
- static int16_t __ui_drawer_last_dy[UI_DRAWER_MAX]; // last applied slide dy
38
- static uint32_t __ui_toast_elapsed[UI_DRAWER_MAX]; // <toast>: ms since fully open
39
- // Compose-region inflation for an open drawer's subtree compose: nodes the
40
- // overlap classification skipped (their own ladder would paint their FULL
41
- // rect over the open panel) still owe pixels — their uncovered sliver. The
42
- // dirty pass unions their paint rect here; the drawer's subtree compose
43
- // consumes it and clears the flag. Per-frame, reset at pass start.
44
- static UIRect __ui_drawer_inflate_rect[UI_DRAWER_MAX];
45
- static uint8_t __ui_drawer_inflate_used[UI_DRAWER_MAX];
46
- static int8_t __ui_drawer_slot_of(uint16_t nodeIdx);
47
- static void ui_drawer_discover();
48
- static void ui_drawer_open(uint16_t nodeIdx);
49
- static void ui_drawer_close(uint16_t nodeIdx);
50
- static void ui_drawer_close_all();
51
- static void ui_drawer_apply(uint8_t slot, uint8_t progress);
52
- static void ui_drawer_tick(uint32_t deltaMs);
53
-
54
- // Early forward declaration: ui_navigate (below) calls ui_release_canvas_state
55
- // and ui_set_pressed (defined later) during screen changes. Needed on native
56
- // (single TU, no Arduino auto-prototyper).
57
- static inline void ui_release_canvas_state();
58
- static inline void ui_set_pressed(uint16_t nodeIdx, uint8_t pressed);
59
- static inline void ui_refresh_active_screen_bg_node();
60
- static inline CuttlefishCanvas16* ui_get_framebuffer();
61
- // Set when the next frame must be composed from the active screen background;
62
- // declared before ui_navigate because navigation can request that composition.
63
- static uint8_t __ui_fb_needs_compose = 1;
64
-
65
- // Navigate to a screen by index. Marks the new screen's nodes dirty, releases
66
- // persistent canvas state, and requests a complete off-screen composition when
67
- // the full-frame path is available. The physical panel is never cleared here on
68
- // that path: navigation must not expose an intermediate frame while the next
69
- // frame is built. Without a full-frame buffer, the fallback remains best-effort;
70
- // no-TE TFT hardware cannot provide a strict tear-free guarantee.
71
- static inline void ui_navigate(uint8_t screenIdx) {
72
- if (screenIdx >= __ui_screen_count || screenIdx == __ui_active_screen) return;
73
- __ui_active_screen = screenIdx;
74
- ui_refresh_active_screen_bg_node();
75
- // Reset scroll/touch state so the old screen's scroll container doesn't
76
- // interfere with the new screen. Release any currently-pressed button FIRST:
77
- // navigation fires from a button's click handler during touch-down, so the
78
- // matching touch-up release would otherwise be suppressed by the reset below
79
- // and the button would stay stuck in its :pressed color.
80
- if (__ui_touch_node >= 0 && __ui_touch_node < __ui_node_count &&
81
- __ui_nodes[__ui_touch_node].kind == NODE_BUTTON && __ui_nodes[__ui_touch_node].value != 0) {
82
- ui_set_pressed(static_cast<uint16_t>(__ui_touch_node), 0);
83
- }
84
- __ui_scroll_node = -1;
85
- __ui_touch_node = -1;
86
- __ui_touch_state = 0;
87
- __ui_kb_visible = 0;
88
- __ui_select_menu = -1;
89
- __ui_select_menu_dirty = 0;
90
- // Drawers reset closed on navigation (device parity with the preview).
91
- ui_drawer_close_all();
92
- for (uint8_t s = 0; s < __ui_drawer_slots; s++) ui_drawer_apply(s, 0);
93
- // Free every persistent canvas so the new screen allocates into a clean,
94
- // unfragmented heap. Without this, the previous screen's canvas buffer stays
95
- // resident and fragments the heap, so the new screen's buffer can't get a
96
- // contiguous block (the "works first, then blanks until reset" symptom).
97
- ui_release_canvas_state();
98
- // A framebuffer-capable target keeps the old frame visible until the new one
99
- // is complete. Targets without enough memory retain the legacy clear fallback
100
- // so stale pixels from the old screen cannot remain; this constrained path is
101
- // best-effort because the panel has no TE/vblank synchronization.
102
- if (ui_get_framebuffer()) {
103
- __ui_fb_needs_compose = 1;
104
- } else {
105
- #ifndef UI_REFRESH_DEFERRED
106
- UI_COLOR_T navBg = (UI_COLOR_T)0;
107
- if (__ui_active_screen_bg_node < __ui_node_count) {
108
- navBg = __ui_nodes[__ui_active_screen_bg_node].hasBg
109
- ? __ui_nodes[__ui_active_screen_bg_node].bg
110
- : __ui_nodes[__ui_active_screen_bg_node].clearColor;
111
- }
112
- display_fillScreen(navBg);
113
- #endif
114
- }
115
- // Mark all nodes dirty so the new screen fully redraws.
116
- for (uint16_t i = 0; i < __ui_node_count; i++) {
117
- __ui_nodes[i].dirty = 1;
118
- __ui_nodes[i].lastTextHeight = 0;
119
- __ui_nodes[i].layoutCacheKey = 0;
120
- if (__ui_nodes[i].scrollable) {
121
- // The next screen needs a complete scroll-canvas seed; subsequent local
122
- // child updates can use retained-canvas repairs.
123
- __ui_nodes[i].lastPaintedScrollY = static_cast<int16_t>(
124
- __ui_nodes[i].scrollY - (__ui_nodes[i].box.h > 0 ? __ui_nodes[i].box.h : 1));
125
- }
126
- if (__ui_nodes[i].kind == NODE_PROGRESS || __ui_nodes[i].kind == NODE_RANGE) __ui_nodes[i].lastTextWidth = -1;
127
- else __ui_nodes[i].lastTextWidth = 0;
128
- }
129
- }
130
- extern const uint16_t __ui_font_face_count;
131
-
132
- #define UI_NO_PARENT 0xFFFF // sentinel: out of uint16_t node-index range
133
-
134
- // Forward declarations suppress Arduino's auto-prototyper, which would insert
135
- // prototypes before UIFontFace/UIFontGlyph are declared.
136
- static inline const UIFontFace* ui_font_face(uint8_t id);
137
- static inline const UIFontGlyph* ui_font_glyph(const UIFontFace* face, uint16_t codepoint);
138
- static inline uint8_t ui_font_alpha_at(const UIFontFace* face, const UIFontGlyph* glyph, uint16_t pixelIndex);
139
- static inline uint16_t ui_asset_text_width(const char* text, const UIFontFace* face);
140
- static inline uint8_t ui_asset_text_height(const UIFontFace* face);
141
- static inline uint8_t ui_draw_asset_text(const char* text, int16_t x, int16_t y, UI_COLOR_T fg, UI_COLOR_T bg, uint8_t antialias, uint8_t fontFace);
142
- static inline uint16_t ui_text_width(const char* text, uint8_t ts, uint8_t fontFace, int8_t letterSpacing);
143
- struct UITextLine;
144
- static inline uint8_t ui_text_next_line(const char** cursor, uint16_t maxWidth, uint8_t whiteSpaceMode, uint8_t ts, uint8_t fontFace, int8_t letterSpacing, UITextLine* out);
145
- static inline void ui_text_layout_metrics(const char* text, uint16_t maxWidth, uint8_t whiteSpaceMode, uint8_t ts, uint8_t fontFace, int8_t letterSpacing, uint8_t lineHeight, uint16_t* outW, uint16_t* outH);
146
- static inline void ui_invalidate_text_layout_cache(uint16_t nodeIdx);
147
- static inline uint16_t ui_node_text_max_width(uint16_t nodeIdx);
148
- static inline void ui_node_text_layout_metrics(uint16_t nodeIdx, uint16_t textMaxW, uint16_t* outW, uint16_t* outH);
149
- static inline uint8_t ui_rects_intersect(int16_t ax, int16_t ay, int16_t aw, int16_t ah, int16_t bx, int16_t by, int16_t bw, int16_t bh);
150
- static inline uint8_t ui_is_effectively_visible(uint16_t nodeIdx);
151
- static inline uint8_t ui_node_draws_before(uint16_t a, uint16_t b);
152
- static inline void ui_node_paint_rect(uint16_t nodeIdx, int16_t baseX, int16_t baseY, int16_t drawX, int16_t drawY, uint16_t textW, uint16_t textH, UIRect* out);
153
- static inline void ui_node_current_paint_rect(uint16_t nodeIdx, UIRect* out);
154
- static inline uint8_t ui_subtree_current_paint_rect(uint16_t nodeIdx, UIRect* out);
155
- static inline void ui_mark_overlapping_higher_layers_dirty(uint16_t nodeIdx);
156
- static inline void ui_mark_overlapping_higher_layers_dirty_for_rect(uint16_t nodeIdx, const UIRect* r);
157
- static inline void ui_mark_scroll_view_dirty(uint16_t scrollNode);
158
- static inline UI_COLOR_T ui_parent_clear_color(uint16_t nodeIdx);
159
- static inline void ui_release_canvas_state();
160
- static inline void ui_set_visible(uint16_t nodeIdx, uint8_t visible);
161
- static inline void ui_invalidate_scroll_canvas_for_node(uint16_t nodeIdx);
162
- static inline uint8_t ui_clip_rect_to_rect(UIRect* r, const UIRect* clip);
163
- static inline void ui_fill_rect_clipped(int16_t x, int16_t y, int16_t w, int16_t h, const UIRect* clip, UI_COLOR_T color);
164
- static inline void ui_hline_clipped(int16_t x, int16_t y, int16_t w, const UIRect* clip, UI_COLOR_T color);
165
- static inline void ui_vline_clipped(int16_t x, int16_t y, int16_t h, const UIRect* clip, UI_COLOR_T color);
166
- static inline void ui_draw_rect_outline_clipped(int16_t x, int16_t y, int16_t w, int16_t h, uint8_t style, uint8_t width, const UIRect* clip, UI_COLOR_T color);
167
- static inline void ui_draw_node_decoration_clipped(uint16_t nodeIdx, int16_t drawY, const UIRect* clip);
168
- // Forward declarations for functions used before their definition in the
169
- // single native translation unit (Arduino's auto-prototyper hides this;
170
- // native emits one TU so explicit forwards are needed).
171
- static inline int8_t ui_rich_link_hit(uint16_t nodeIdx, int16_t px, int16_t py);
172
- #ifdef UI_AA
173
- static inline CuttlefishCanvas16* ui_aa_begin(int16_t w, int16_t h, UI_COLOR_T bg);
174
- static inline void ui_aa_end(CuttlefishCanvas16* c);
175
- static inline void ui_aa_push(CuttlefishCanvas16* c, int16_t dx, int16_t dy);
176
- static inline void ui_aa_line(CuttlefishCanvas16* c, float x0, float y0, float x1, float y1, UI_COLOR_T color);
177
- static inline void ui_aa_circle(CuttlefishCanvas16* c, int16_t cx, int16_t cy, float r, UI_COLOR_T color);
178
- static inline void ui_aa_fill_circle(CuttlefishCanvas16* c, int16_t cx, int16_t cy, float r, UI_COLOR_T color);
179
- #endif
180
- static inline uint8_t ui_repair_current_node_paint_with_parent(uint16_t nodeIdx, UIRect* r);
181
- static inline void ui_clear_node_paint_rect(uint16_t nodeIdx, const UIRect* paintRect);
182
- static inline uint8_t ui_try_repair_geometry_fill(uint16_t nodeIdx, const UIRect* oldRect);
183
- // Band renderers (defined in node-draw-body; forward-declared so the NODE_LIST
184
- // case can fall back to ui_render_list_bands before its definition site).
185
- static inline uint8_t ui_render_node_bands(uint16_t nodeIdx, int16_t prX, int16_t prY, int16_t prW, int16_t prH);
186
- static inline uint8_t ui_render_screen_bands(int16_t rx, int16_t ry, int16_t rw, int16_t rh);
187
- static inline uint8_t ui_render_list_bands(uint16_t i);
188
- static inline uint8_t ui_render_list_direct(uint16_t i);
189
- static inline void ui_draw_node_border(uint16_t i, int16_t drawX, int16_t drawY, UI_COLOR_T color);
190
- static inline void ui_draw_node_outline(uint16_t i, int16_t drawX, int16_t drawY);
191
- static inline void ui_draw_gradient_fill(uint16_t i, int16_t drawY);
192
- static inline uint8_t ui_rotation_quadrant(int16_t deg);
193
- static inline int16_t ui_rotated_face_w(uint16_t nodeIdx, int16_t w, int16_t h);
194
- static inline int16_t ui_rotated_face_h(uint16_t nodeIdx, int16_t w, int16_t h);
195
- static inline void ui_draw_image_rotated(const UIImage* img, int16_t x, int16_t y, int16_t rotateDeg);
196
- static inline void ui_draw_image_with_fit(const UIImage* img, int16_t x, int16_t y, int16_t rotateDeg,
197
- uint8_t fitMode, int16_t targetW, int16_t targetH);
198
- static inline void ui_draw_scaled_image(const UIImage* img, int16_t x, int16_t y, int16_t rotateDeg,
199
- int16_t drawW, int16_t drawH);
200
-
201
- static CuttlefishDisplayTarget* __ui_gfx = display_defaultTarget();
202
- // Full-frame composition is required for the first frame and after navigation.
203
- // Once composed, subsequent dirty updates can safely modify the retained
204
- // framebuffer without rebuilding unchanged nodes. The flags are declared
205
- // before ui_navigate above because navigation sets the compose request.
206
- // Persistent canvas slots, all freed on screen change (ui_navigate) so each
207
- // screen starts with a clean heap. Without this, the first screen's canvas
208
- // buffer stays resident and fragments the heap, so a later screen's buffer
209
- // can't get a contiguous block manifesting as that element blanking until a
210
- // hard reset. File-scoped (not function-static) so ui_release_canvas_state can
211
- // reach them.
212
- static CuttlefishCanvas16* __ui_container_canvas = nullptr; // Mode B shift-and-repair
213
- static CuttlefishCanvas16* __ui_list_canvas = nullptr; // virtualized <list> viewport
214
- static int16_t __ui_list_canvas_node = -1; // node currently represented by __ui_list_canvas
215
- static CuttlefishCanvas16* __ui_node_canvas = nullptr; // <canvas> element offscreen
216
- static CuttlefishCanvas16* __ui_repair_canvas = nullptr; // buffered-paint / exposed-strip
217
- static CuttlefishCanvas16* __ui_band_canvas = nullptr; // band renderer (no-PSRAM scroll)
218
- static CuttlefishCanvas16* __ui_kb_canvas = nullptr; // on-screen keyboard overlay
219
- static int16_t __ui_canvas_fallback_w = 0; // dimensions for direct <canvas> fallback
220
- static int16_t __ui_canvas_fallback_h = 0;
221
- // Draw offset: normally zero. The <canvas> allocation fallback sets it so
222
- // callback-local coordinates draw into the node's current display/canvas target.
223
- static int16_t __ui_draw_off_x = 0;
224
- static int16_t __ui_draw_off_y = 0;
225
-
226
- // Release every persistent canvas so the next screen allocates into a clean`;
227
- }
1
+ // Slice of the C++ runtime header (original source lines 622-752).
2
+ // navigate, canvas/scroll/text/paint/clip/AA/image forward declarations, persistent canvas globals.
3
+ // See docs/superpowers/specs/2026-07-12-split-runtime-header-design.md.
4
+ //
5
+ // EMIT BOUNDARY: This file is the structural head of the UI runtime header
6
+ // surface (C) — every partial emitter in this directory produces bytes that are
7
+ // assembled into the C++ header inlined into user programs. The emitted bytes
8
+ // are covered by the TypeCAD Runtime Exception (see RUNTIME_EXCEPTION.md at the
9
+ // repository root) and are not subject to the license of this tool source.
10
+ export function emitForwardDecls(): string {
11
+ return `
12
+ // ── Runtime clock + clamp (thin-HAL contract) ────────────────────────────
13
+ // The UI runtime's timing contracts (debounce, press/repeat, toasts,
14
+ // animations, scroll physics) need a monotonic millisecond clock. The
15
+ // framework strategy provides __tc_now_ms() (Zephyr: kernel uptime,
16
+ // native: steady_clock) the same clock the async runtime and Time.now()
17
+ // lower onto. All comparisons are subtraction-based, so the 32-bit wrap
18
+ // (~49.7 days) is harmless.
19
+ extern uint32_t __tc_now_ms(void);
20
+ // Integer clamp used by progress/range draws and the keyboard value steps.
21
+ static inline long __ui_constrain(long x, long lo, long hi) { return (x < lo) ? lo : ((x > hi) ? hi : x); }
22
+
23
+ // ── Modal <select> + <drawer> declarations ────────────────────────────────
24
+ // Declared here (the earliest runtime-header module) because ui_navigate,
25
+ // ui_is_effectively_visible, ui_init, and ui_tick all reference them;
26
+ // definitions live in the touch/keyboard modules below.
27
+ // ── Modal <select> option list ────────────────────────────────────────────
28
+ // Tapping a select opens a centered list of its options; tapping a row sets
29
+ // the value, tapping outside dismisses. Mirrors the preview's select modal.
30
+ static int16_t __ui_select_menu = -1; // node index while open, -1 closed
31
+ static uint8_t __ui_select_menu_dirty = 0; // overlay needs stamping this frame
32
+ static inline void ui_select_menu_open(uint16_t nodeIdx);
33
+ static inline void ui_select_menu_close(uint8_t repaint);
34
+ static inline void ui_select_menu_geom(uint16_t nodeIdx, UIRect* out, int16_t* rowH);
35
+ static inline void ui_select_menu_draw();
36
+ static inline void ui_select_menu_tap(int16_t tx, int16_t ty);
37
+
38
+ // ── <drawer> slide-in panels ──────────────────────────────────────────────
39
+ // Author-styled absolute panels; the runtime slides the subtree in from the
40
+ // drawer's edge (transform offsets), hides it while closed, and closes on
41
+ // outside taps. Mirrors the preview's drawer implementation.
42
+ #define UI_DRAWER_MAX 4
43
+ static int16_t __ui_drawer_idx[UI_DRAWER_MAX]; // node index per slot, -1 free; int16 node indices exceed 127 in real apps
44
+ static uint8_t __ui_drawer_open[UI_DRAWER_MAX]; // target state (0 closed, 1 open)
45
+ static uint8_t __ui_drawer_progress[UI_DRAWER_MAX]; // 0 closed .. 1 open
46
+ static uint8_t __ui_drawer_slots = 0; // discovered drawers (init scan)
47
+ static int16_t __ui_drawer_last_dx[UI_DRAWER_MAX]; // last applied slide dx (delta bookkeeping)
48
+ static int16_t __ui_drawer_last_dy[UI_DRAWER_MAX]; // last applied slide dy
49
+ static uint32_t __ui_toast_elapsed[UI_DRAWER_MAX]; // <toast>: ms since fully open
50
+ // Compose-region inflation for an open drawer's subtree compose: nodes the
51
+ // overlap classification skipped (their own ladder would paint their FULL
52
+ // rect over the open panel) still owe pixels — their uncovered sliver. The
53
+ // dirty pass unions their paint rect here; the drawer's subtree compose
54
+ // consumes it and clears the flag. Per-frame, reset at pass start.
55
+ static UIRect __ui_drawer_inflate_rect[UI_DRAWER_MAX];
56
+ static uint8_t __ui_drawer_inflate_used[UI_DRAWER_MAX];
57
+ static int8_t __ui_drawer_slot_of(uint16_t nodeIdx);
58
+ static void ui_drawer_discover();
59
+ static void ui_drawer_open(uint16_t nodeIdx);
60
+ static void ui_drawer_close(uint16_t nodeIdx);
61
+ static void ui_drawer_close_all();
62
+ static void ui_drawer_apply(uint8_t slot, uint8_t progress);
63
+ static void ui_drawer_tick(uint32_t deltaMs);
64
+
65
+ // Early forward declaration: ui_navigate (below) calls ui_release_canvas_state
66
+ // and ui_set_pressed (defined later) during screen changes. Needed on native
67
+ // (single TU, no Arduino auto-prototyper).
68
+ static inline void ui_release_canvas_state();
69
+ static inline void ui_set_pressed(uint16_t nodeIdx, uint8_t pressed);
70
+ static inline void ui_refresh_active_screen_bg_node();
71
+ static inline CuttlefishCanvas16* ui_get_framebuffer();
72
+ // Set when the next frame must be composed from the active screen background;
73
+ // declared before ui_navigate because navigation can request that composition.
74
+ static uint8_t __ui_fb_needs_compose = 1;
75
+
76
+ // Navigate to a screen by index. Marks the new screen's nodes dirty, releases
77
+ // persistent canvas state, and requests a complete off-screen composition when
78
+ // the full-frame path is available. The physical panel is never cleared here on
79
+ // that path: navigation must not expose an intermediate frame while the next
80
+ // frame is built. Without a full-frame buffer, the fallback remains best-effort;
81
+ // no-TE TFT hardware cannot provide a strict tear-free guarantee.
82
+ static inline void ui_navigate(uint8_t screenIdx) {
83
+ if (screenIdx >= __ui_screen_count || screenIdx == __ui_active_screen) return;
84
+ __ui_active_screen = screenIdx;
85
+ ui_refresh_active_screen_bg_node();
86
+ // Reset scroll/touch state so the old screen's scroll container doesn't
87
+ // interfere with the new screen. Release any currently-pressed button FIRST:
88
+ // navigation fires from a button's click handler during touch-down, so the
89
+ // matching touch-up release would otherwise be suppressed by the reset below
90
+ // and the button would stay stuck in its :pressed color.
91
+ if (__ui_touch_node >= 0 && __ui_touch_node < __ui_node_count &&
92
+ __ui_nodes[__ui_touch_node].kind == NODE_BUTTON && __ui_nodes[__ui_touch_node].value != 0) {
93
+ ui_set_pressed(static_cast<uint16_t>(__ui_touch_node), 0);
94
+ }
95
+ __ui_scroll_node = -1;
96
+ __ui_touch_node = -1;
97
+ __ui_touch_state = 0;
98
+ __ui_kb_visible = 0;
99
+ __ui_select_menu = -1;
100
+ __ui_select_menu_dirty = 0;
101
+ // Drawers reset closed on navigation (device parity with the preview).
102
+ ui_drawer_close_all();
103
+ for (uint8_t s = 0; s < __ui_drawer_slots; s++) ui_drawer_apply(s, 0);
104
+ // Free every persistent canvas so the new screen allocates into a clean,
105
+ // unfragmented heap. Without this, the previous screen's canvas buffer stays
106
+ // resident and fragments the heap, so the new screen's buffer can't get a
107
+ // contiguous block (the "works first, then blanks until reset" symptom).
108
+ ui_release_canvas_state();
109
+ // A framebuffer-capable target keeps the old frame visible until the new one
110
+ // is complete. Targets without enough memory retain the legacy clear fallback
111
+ // so stale pixels from the old screen cannot remain; this constrained path is
112
+ // best-effort because the panel has no TE/vblank synchronization.
113
+ if (ui_get_framebuffer()) {
114
+ __ui_fb_needs_compose = 1;
115
+ } else {
116
+ #ifndef UI_REFRESH_DEFERRED
117
+ UI_COLOR_T navBg = (UI_COLOR_T)0;
118
+ if (__ui_active_screen_bg_node < __ui_node_count) {
119
+ navBg = __ui_nodes[__ui_active_screen_bg_node].hasBg
120
+ ? __ui_nodes[__ui_active_screen_bg_node].bg
121
+ : __ui_nodes[__ui_active_screen_bg_node].clearColor;
122
+ }
123
+ display_fillScreen(navBg);
124
+ #endif
125
+ }
126
+ // Mark all nodes dirty so the new screen fully redraws.
127
+ for (uint16_t i = 0; i < __ui_node_count; i++) {
128
+ __ui_nodes[i].dirty = 1;
129
+ __ui_nodes[i].lastTextHeight = 0;
130
+ __ui_nodes[i].layoutCacheKey = 0;
131
+ if (__ui_nodes[i].scrollable) {
132
+ // The next screen needs a complete scroll-canvas seed; subsequent local
133
+ // child updates can use retained-canvas repairs.
134
+ __ui_nodes[i].lastPaintedScrollY = static_cast<int16_t>(
135
+ __ui_nodes[i].scrollY - (__ui_nodes[i].box.h > 0 ? __ui_nodes[i].box.h : 1));
136
+ }
137
+ if (__ui_nodes[i].kind == NODE_PROGRESS || __ui_nodes[i].kind == NODE_RANGE) __ui_nodes[i].lastTextWidth = -1;
138
+ else __ui_nodes[i].lastTextWidth = 0;
139
+ }
140
+ }
141
+ extern const uint16_t __ui_font_face_count;
142
+
143
+ #define UI_NO_PARENT 0xFFFF // sentinel: out of uint16_t node-index range
144
+
145
+ // Forward declarations suppress Arduino's auto-prototyper, which would insert
146
+ // prototypes before UIFontFace/UIFontGlyph are declared.
147
+ static inline const UIFontFace* ui_font_face(uint8_t id);
148
+ static inline const UIFontGlyph* ui_font_glyph(const UIFontFace* face, uint16_t codepoint);
149
+ static inline uint8_t ui_font_alpha_at(const UIFontFace* face, const UIFontGlyph* glyph, uint16_t pixelIndex);
150
+ static inline uint16_t ui_asset_text_width(const char* text, const UIFontFace* face);
151
+ static inline uint8_t ui_asset_text_height(const UIFontFace* face);
152
+ static inline uint8_t ui_draw_asset_text(const char* text, int16_t x, int16_t y, UI_COLOR_T fg, UI_COLOR_T bg, uint8_t antialias, uint8_t fontFace);
153
+ static inline uint16_t ui_text_width(const char* text, uint8_t ts, uint8_t fontFace, int8_t letterSpacing);
154
+ struct UITextLine;
155
+ static inline uint8_t ui_text_next_line(const char** cursor, uint16_t maxWidth, uint8_t whiteSpaceMode, uint8_t ts, uint8_t fontFace, int8_t letterSpacing, UITextLine* out);
156
+ static inline void ui_text_layout_metrics(const char* text, uint16_t maxWidth, uint8_t whiteSpaceMode, uint8_t ts, uint8_t fontFace, int8_t letterSpacing, uint8_t lineHeight, uint16_t* outW, uint16_t* outH);
157
+ static inline void ui_invalidate_text_layout_cache(uint16_t nodeIdx);
158
+ static inline uint16_t ui_node_text_max_width(uint16_t nodeIdx);
159
+ static inline void ui_node_text_layout_metrics(uint16_t nodeIdx, uint16_t textMaxW, uint16_t* outW, uint16_t* outH);
160
+ static inline uint8_t ui_rects_intersect(int16_t ax, int16_t ay, int16_t aw, int16_t ah, int16_t bx, int16_t by, int16_t bw, int16_t bh);
161
+ static inline uint8_t ui_is_effectively_visible(uint16_t nodeIdx);
162
+ static inline uint8_t ui_node_draws_before(uint16_t a, uint16_t b);
163
+ static inline void ui_node_paint_rect(uint16_t nodeIdx, int16_t baseX, int16_t baseY, int16_t drawX, int16_t drawY, uint16_t textW, uint16_t textH, UIRect* out);
164
+ static inline void ui_node_current_paint_rect(uint16_t nodeIdx, UIRect* out);
165
+ static inline uint8_t ui_subtree_current_paint_rect(uint16_t nodeIdx, UIRect* out);
166
+ static inline void ui_mark_overlapping_higher_layers_dirty(uint16_t nodeIdx);
167
+ static inline void ui_mark_overlapping_higher_layers_dirty_for_rect(uint16_t nodeIdx, const UIRect* r);
168
+ static inline void ui_mark_scroll_view_dirty(uint16_t scrollNode);
169
+ static inline UI_COLOR_T ui_parent_clear_color(uint16_t nodeIdx);
170
+ static inline void ui_release_canvas_state();
171
+ static inline void ui_set_visible(uint16_t nodeIdx, uint8_t visible);
172
+ static inline void ui_invalidate_scroll_canvas_for_node(uint16_t nodeIdx);
173
+ static inline uint8_t ui_clip_rect_to_rect(UIRect* r, const UIRect* clip);
174
+ static inline void ui_fill_rect_clipped(int16_t x, int16_t y, int16_t w, int16_t h, const UIRect* clip, UI_COLOR_T color);
175
+ static inline void ui_hline_clipped(int16_t x, int16_t y, int16_t w, const UIRect* clip, UI_COLOR_T color);
176
+ static inline void ui_vline_clipped(int16_t x, int16_t y, int16_t h, const UIRect* clip, UI_COLOR_T color);
177
+ static inline void ui_draw_rect_outline_clipped(int16_t x, int16_t y, int16_t w, int16_t h, uint8_t style, uint8_t width, const UIRect* clip, UI_COLOR_T color);
178
+ static inline void ui_draw_node_decoration_clipped(uint16_t nodeIdx, int16_t drawY, const UIRect* clip);
179
+ // Forward declarations for functions used before their definition in the
180
+ // single native translation unit (Arduino's auto-prototyper hides this;
181
+ // native emits one TU so explicit forwards are needed).
182
+ static inline int8_t ui_rich_link_hit(uint16_t nodeIdx, int16_t px, int16_t py);
183
+ #ifdef UI_AA
184
+ static inline CuttlefishCanvas16* ui_aa_begin(int16_t w, int16_t h, UI_COLOR_T bg);
185
+ static inline void ui_aa_end(CuttlefishCanvas16* c);
186
+ static inline void ui_aa_push(CuttlefishCanvas16* c, int16_t dx, int16_t dy);
187
+ static inline void ui_aa_line(CuttlefishCanvas16* c, float x0, float y0, float x1, float y1, UI_COLOR_T color);
188
+ static inline void ui_aa_circle(CuttlefishCanvas16* c, int16_t cx, int16_t cy, float r, UI_COLOR_T color);
189
+ static inline void ui_aa_fill_circle(CuttlefishCanvas16* c, int16_t cx, int16_t cy, float r, UI_COLOR_T color);
190
+ #endif
191
+ static inline uint8_t ui_repair_current_node_paint_with_parent(uint16_t nodeIdx, UIRect* r);
192
+ static inline void ui_clear_node_paint_rect(uint16_t nodeIdx, const UIRect* paintRect);
193
+ static inline uint8_t ui_try_repair_geometry_fill(uint16_t nodeIdx, const UIRect* oldRect);
194
+ // Band renderers (defined in node-draw-body; forward-declared so the NODE_LIST
195
+ // case can fall back to ui_render_list_bands before its definition site).
196
+ static inline uint8_t ui_render_node_bands(uint16_t nodeIdx, int16_t prX, int16_t prY, int16_t prW, int16_t prH);
197
+ static inline uint8_t ui_render_screen_bands(int16_t rx, int16_t ry, int16_t rw, int16_t rh);
198
+ static inline uint8_t ui_render_list_bands(uint16_t i);
199
+ static inline uint8_t ui_render_list_direct(uint16_t i);
200
+ static inline void ui_draw_node_border(uint16_t i, int16_t drawX, int16_t drawY, UI_COLOR_T color);
201
+ static inline void ui_draw_node_outline(uint16_t i, int16_t drawX, int16_t drawY);
202
+ static inline void ui_draw_gradient_fill(uint16_t i, int16_t drawY);
203
+ static inline uint8_t ui_rotation_quadrant(int16_t deg);
204
+ static inline int16_t ui_rotated_face_w(uint16_t nodeIdx, int16_t w, int16_t h);
205
+ static inline int16_t ui_rotated_face_h(uint16_t nodeIdx, int16_t w, int16_t h);
206
+ static inline void ui_draw_image_rotated(const UIImage* img, int16_t x, int16_t y, int16_t rotateDeg);
207
+ static inline void ui_draw_image_with_fit(const UIImage* img, int16_t x, int16_t y, int16_t rotateDeg,
208
+ uint8_t fitMode, int16_t targetW, int16_t targetH);
209
+ static inline void ui_draw_scaled_image(const UIImage* img, int16_t x, int16_t y, int16_t rotateDeg,
210
+ int16_t drawW, int16_t drawH);
211
+
212
+ static CuttlefishDisplayTarget* __ui_gfx = display_defaultTarget();
213
+ // Full-frame composition is required for the first frame and after navigation.
214
+ // Once composed, subsequent dirty updates can safely modify the retained
215
+ // framebuffer without rebuilding unchanged nodes. The flags are declared
216
+ // before ui_navigate above because navigation sets the compose request.
217
+ // Persistent canvas slots, all freed on screen change (ui_navigate) so each
218
+ // screen starts with a clean heap. Without this, the first screen's canvas
219
+ // buffer stays resident and fragments the heap, so a later screen's buffer
220
+ // can't get a contiguous block — manifesting as that element blanking until a
221
+ // hard reset. File-scoped (not function-static) so ui_release_canvas_state can
222
+ // reach them.
223
+ static CuttlefishCanvas16* __ui_container_canvas = nullptr; // Mode B shift-and-repair
224
+ static CuttlefishCanvas16* __ui_list_canvas = nullptr; // virtualized <list> viewport
225
+ static int16_t __ui_list_canvas_node = -1; // node currently represented by __ui_list_canvas
226
+ static CuttlefishCanvas16* __ui_node_canvas = nullptr; // <canvas> element offscreen
227
+ static CuttlefishCanvas16* __ui_repair_canvas = nullptr; // buffered-paint / exposed-strip
228
+ static CuttlefishCanvas16* __ui_band_canvas = nullptr; // band renderer (no-PSRAM scroll)
229
+ static CuttlefishCanvas16* __ui_kb_canvas = nullptr; // on-screen keyboard overlay
230
+ static int16_t __ui_canvas_fallback_w = 0; // dimensions for direct <canvas> fallback
231
+ static int16_t __ui_canvas_fallback_h = 0;
232
+ // Draw offset: normally zero. The <canvas> allocation fallback sets it so
233
+ // callback-local coordinates draw into the node's current display/canvas target.
234
+ static int16_t __ui_draw_off_x = 0;
235
+ static int16_t __ui_draw_off_y = 0;
236
+
237
+ // Release every persistent canvas so the next screen allocates into a clean`;
238
+ }