@typecad/ui 0.1.0-alpha.1
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/LICENSE +21 -0
- package/README.md +1443 -0
- package/dist/engine-index.d.ts +7 -0
- package/dist/engine-index.js +41 -0
- package/dist/index.d.ts +165 -0
- package/dist/index.js +42 -0
- package/dist/preview/build-program.d.ts +7 -0
- package/dist/preview/build-program.js +665 -0
- package/dist/preview/host-gfx.d.ts +90 -0
- package/dist/preview/host-gfx.js +552 -0
- package/dist/preview/host-ui-runtime.d.ts +241 -0
- package/dist/preview/host-ui-runtime.js +3236 -0
- package/dist/preview/types.d.ts +83 -0
- package/dist/preview/types.js +1 -0
- package/dist/types.d.ts +108 -0
- package/dist/types.js +10 -0
- package/dist/ui-engine/block-layout.d.ts +8 -0
- package/dist/ui-engine/block-layout.js +95 -0
- package/dist/ui-engine/color.d.ts +49 -0
- package/dist/ui-engine/color.js +273 -0
- package/dist/ui-engine/css-parser.d.ts +155 -0
- package/dist/ui-engine/css-parser.js +1058 -0
- package/dist/ui-engine/default-keyboards.d.ts +3 -0
- package/dist/ui-engine/default-keyboards.js +53 -0
- package/dist/ui-engine/easing.d.ts +12 -0
- package/dist/ui-engine/easing.js +76 -0
- package/dist/ui-engine/font-assets.d.ts +50 -0
- package/dist/ui-engine/font-assets.js +431 -0
- package/dist/ui-engine/html-parser.d.ts +116 -0
- package/dist/ui-engine/html-parser.js +284 -0
- package/dist/ui-engine/image-assets.d.ts +21 -0
- package/dist/ui-engine/image-assets.js +125 -0
- package/dist/ui-engine/inline-parser.d.ts +30 -0
- package/dist/ui-engine/inline-parser.js +66 -0
- package/dist/ui-engine/keyframes.d.ts +3 -0
- package/dist/ui-engine/keyframes.js +39 -0
- package/dist/ui-engine/layout-engine.d.ts +35 -0
- package/dist/ui-engine/layout-engine.js +203 -0
- package/dist/ui-engine/model.d.ts +239 -0
- package/dist/ui-engine/model.js +987 -0
- package/dist/ui-engine/rich-layout.d.ts +51 -0
- package/dist/ui-engine/rich-layout.js +220 -0
- package/dist/ui-engine/run-types.d.ts +13 -0
- package/dist/ui-engine/run-types.js +1 -0
- package/dist/ui-engine/runtime-header/antialiasing.d.ts +1 -0
- package/dist/ui-engine/runtime-header/antialiasing.js +141 -0
- package/dist/ui-engine/runtime-header/blend-bodies.d.ts +1 -0
- package/dist/ui-engine/runtime-header/blend-bodies.js +39 -0
- package/dist/ui-engine/runtime-header/canvas-helpers.d.ts +1 -0
- package/dist/ui-engine/runtime-header/canvas-helpers.js +155 -0
- package/dist/ui-engine/runtime-header/canvas-scrollbar.d.ts +1 -0
- package/dist/ui-engine/runtime-header/canvas-scrollbar.js +105 -0
- package/dist/ui-engine/runtime-header/color-mono-refresh.d.ts +1 -0
- package/dist/ui-engine/runtime-header/color-mono-refresh.js +154 -0
- package/dist/ui-engine/runtime-header/dirty-scroll-mutators.d.ts +1 -0
- package/dist/ui-engine/runtime-header/dirty-scroll-mutators.js +169 -0
- package/dist/ui-engine/runtime-header/display-shim.d.ts +1 -0
- package/dist/ui-engine/runtime-header/display-shim.js +104 -0
- package/dist/ui-engine/runtime-header/forward-decls.d.ts +1 -0
- package/dist/ui-engine/runtime-header/forward-decls.js +137 -0
- package/dist/ui-engine/runtime-header/guard-close.d.ts +1 -0
- package/dist/ui-engine/runtime-header/guard-close.js +8 -0
- package/dist/ui-engine/runtime-header/image-drawing.d.ts +1 -0
- package/dist/ui-engine/runtime-header/image-drawing.js +300 -0
- package/dist/ui-engine/runtime-header/init-press-input.d.ts +1 -0
- package/dist/ui-engine/runtime-header/init-press-input.js +126 -0
- package/dist/ui-engine/runtime-header/keyboard.d.ts +1 -0
- package/dist/ui-engine/runtime-header/keyboard.js +342 -0
- package/dist/ui-engine/runtime-header/node-decoration.d.ts +1 -0
- package/dist/ui-engine/runtime-header/node-decoration.js +212 -0
- package/dist/ui-engine/runtime-header/paint-order-coords.d.ts +1 -0
- package/dist/ui-engine/runtime-header/paint-order-coords.js +190 -0
- package/dist/ui-engine/runtime-header/paint-rects-repair.d.ts +1 -0
- package/dist/ui-engine/runtime-header/paint-rects-repair.js +553 -0
- package/dist/ui-engine/runtime-header/scroll-physics.d.ts +1 -0
- package/dist/ui-engine/runtime-header/scroll-physics.js +161 -0
- package/dist/ui-engine/runtime-header/state-bindings-nav.d.ts +1 -0
- package/dist/ui-engine/runtime-header/state-bindings-nav.js +176 -0
- package/dist/ui-engine/runtime-header/structs.d.ts +1 -0
- package/dist/ui-engine/runtime-header/structs.js +220 -0
- package/dist/ui-engine/runtime-header/text-rendering.d.ts +1 -0
- package/dist/ui-engine/runtime-header/text-rendering.js +715 -0
- package/dist/ui-engine/runtime-header/tick/bindings-phase.d.ts +1 -0
- package/dist/ui-engine/runtime-header/tick/bindings-phase.js +103 -0
- package/dist/ui-engine/runtime-header/tick/dirty-draw-phase.d.ts +1 -0
- package/dist/ui-engine/runtime-header/tick/dirty-draw-phase.js +1051 -0
- package/dist/ui-engine/runtime-header/tick/flush-phase.d.ts +1 -0
- package/dist/ui-engine/runtime-header/tick/flush-phase.js +17 -0
- package/dist/ui-engine/runtime-header/tick/scroll-canvas-phase.d.ts +1 -0
- package/dist/ui-engine/runtime-header/tick/scroll-canvas-phase.js +15 -0
- package/dist/ui-engine/runtime-header/tick/transitions-phase.d.ts +1 -0
- package/dist/ui-engine/runtime-header/tick/transitions-phase.js +213 -0
- package/dist/ui-engine/runtime-header/tick-motion-helpers.d.ts +1 -0
- package/dist/ui-engine/runtime-header/tick-motion-helpers.js +26 -0
- package/dist/ui-engine/runtime-header/tick.d.ts +1 -0
- package/dist/ui-engine/runtime-header/tick.js +17 -0
- package/dist/ui-engine/runtime-header/touch-keyboard-fwd.d.ts +1 -0
- package/dist/ui-engine/runtime-header/touch-keyboard-fwd.js +406 -0
- package/dist/ui-engine/runtime-header/types-defines.d.ts +1 -0
- package/dist/ui-engine/runtime-header/types-defines.js +79 -0
- package/dist/ui-engine/runtime-header.d.ts +1 -0
- package/dist/ui-engine/runtime-header.js +69 -0
- package/dist/ui-engine/scroll-memory-diagnostics.d.ts +16 -0
- package/dist/ui-engine/scroll-memory-diagnostics.js +58 -0
- package/dist/ui-engine/select-engine.d.ts +4 -0
- package/dist/ui-engine/select-engine.js +19 -0
- package/dist/ui-engine/style-resolver.d.ts +77 -0
- package/dist/ui-engine/style-resolver.js +341 -0
- package/dist/ui-engine/text-layout.d.ts +19 -0
- package/dist/ui-engine/text-layout.js +92 -0
- package/dist/ui-engine/transpile-ui.d.ts +11 -0
- package/dist/ui-engine/transpile-ui.js +33 -0
- package/dist/ui-engine/ua-stylesheet.d.ts +3 -0
- package/dist/ui-engine/ua-stylesheet.js +56 -0
- package/dist/ui-engine/ui-file-splitter.d.ts +14 -0
- package/dist/ui-engine/ui-file-splitter.js +47 -0
- package/dist/ui-engine/ui-lowering.d.ts +33 -0
- package/dist/ui-engine/ui-lowering.js +390 -0
- package/dist/ui-engine/ui-registry.d.ts +81 -0
- package/dist/ui-engine/ui-registry.js +429 -0
- package/dist/ui-engine/yoga-layout.d.ts +12 -0
- package/dist/ui-engine/yoga-layout.js +314 -0
- package/package.json +86 -0
- package/src/engine-index.ts +51 -0
- package/src/index.ts +232 -0
- package/src/preview/build-program.ts +734 -0
- package/src/preview/host-gfx.ts +580 -0
- package/src/preview/host-ui-runtime.ts +3245 -0
- package/src/preview/types.ts +96 -0
- package/src/types.ts +127 -0
- package/src/ui-engine/block-layout.ts +104 -0
- package/src/ui-engine/color.ts +263 -0
- package/src/ui-engine/css-parser.ts +1003 -0
- package/src/ui-engine/default-keyboards.ts +58 -0
- package/src/ui-engine/easing.ts +53 -0
- package/src/ui-engine/font-assets.ts +512 -0
- package/src/ui-engine/html-parser.ts +397 -0
- package/src/ui-engine/image-assets.ts +170 -0
- package/src/ui-engine/inline-parser.ts +78 -0
- package/src/ui-engine/keyframes.ts +47 -0
- package/src/ui-engine/layout-engine.ts +228 -0
- package/src/ui-engine/model.ts +1225 -0
- package/src/ui-engine/rich-layout.ts +257 -0
- package/src/ui-engine/run-types.ts +14 -0
- package/src/ui-engine/runtime-header/antialiasing.ts +141 -0
- package/src/ui-engine/runtime-header/blend-bodies.ts +39 -0
- package/src/ui-engine/runtime-header/canvas-helpers.ts +155 -0
- package/src/ui-engine/runtime-header/canvas-scrollbar.ts +105 -0
- package/src/ui-engine/runtime-header/color-mono-refresh.ts +154 -0
- package/src/ui-engine/runtime-header/dirty-scroll-mutators.ts +169 -0
- package/src/ui-engine/runtime-header/display-shim.ts +104 -0
- package/src/ui-engine/runtime-header/forward-decls.ts +137 -0
- package/src/ui-engine/runtime-header/guard-close.ts +8 -0
- package/src/ui-engine/runtime-header/image-drawing.ts +300 -0
- package/src/ui-engine/runtime-header/init-press-input.ts +126 -0
- package/src/ui-engine/runtime-header/keyboard.ts +342 -0
- package/src/ui-engine/runtime-header/node-decoration.ts +212 -0
- package/src/ui-engine/runtime-header/paint-order-coords.ts +190 -0
- package/src/ui-engine/runtime-header/paint-rects-repair.ts +553 -0
- package/src/ui-engine/runtime-header/scroll-physics.ts +161 -0
- package/src/ui-engine/runtime-header/state-bindings-nav.ts +176 -0
- package/src/ui-engine/runtime-header/structs.ts +220 -0
- package/src/ui-engine/runtime-header/text-rendering.ts +715 -0
- package/src/ui-engine/runtime-header/tick/bindings-phase.ts +103 -0
- package/src/ui-engine/runtime-header/tick/dirty-draw-phase.ts +1051 -0
- package/src/ui-engine/runtime-header/tick/flush-phase.ts +17 -0
- package/src/ui-engine/runtime-header/tick/scroll-canvas-phase.ts +15 -0
- package/src/ui-engine/runtime-header/tick/transitions-phase.ts +213 -0
- package/src/ui-engine/runtime-header/tick-motion-helpers.ts +26 -0
- package/src/ui-engine/runtime-header/tick.ts +18 -0
- package/src/ui-engine/runtime-header/touch-keyboard-fwd.ts +406 -0
- package/src/ui-engine/runtime-header/types-defines.ts +79 -0
- package/src/ui-engine/runtime-header.ts +71 -0
- package/src/ui-engine/scroll-memory-diagnostics.ts +79 -0
- package/src/ui-engine/select-engine.ts +21 -0
- package/src/ui-engine/style-resolver.ts +416 -0
- package/src/ui-engine/text-layout.ts +114 -0
- package/src/ui-engine/transpile-ui.ts +45 -0
- package/src/ui-engine/ua-stylesheet.ts +60 -0
- package/src/ui-engine/ui-file-splitter.ts +59 -0
- package/src/ui-engine/ui-lowering.ts +459 -0
- package/src/ui-engine/ui-registry.ts +530 -0
- package/src/ui-engine/yoga-layout.ts +309 -0
|
@@ -0,0 +1,406 @@
|
|
|
1
|
+
// Slice of the C++ runtime header (original source lines 2562-2961).
|
|
2
|
+
// Touch hit-test/dispatch/state machine + keyboard forward-decls & state defines.
|
|
3
|
+
// See docs/superpowers/specs/2026-07-12-split-runtime-header-design.md.
|
|
4
|
+
export function emitTouchKeyboardFwd() {
|
|
5
|
+
return `
|
|
6
|
+
// ── Touch hit-testing + click dispatch ─────────────────────────────────────
|
|
7
|
+
// Radio groups for mutual exclusion
|
|
8
|
+
struct UIRadioGroup {
|
|
9
|
+
uint16_t nodeIndices[8];
|
|
10
|
+
uint8_t count;
|
|
11
|
+
};
|
|
12
|
+
extern UIRadioGroup __ui_radio_groups[];
|
|
13
|
+
extern const uint16_t __ui_radio_group_count;
|
|
14
|
+
|
|
15
|
+
// Forward-declare the click handler type + tables (defined by the emit layer).
|
|
16
|
+
extern void (*__ui_click_handlers[])();
|
|
17
|
+
extern void (*__ui_hold_handlers[])();
|
|
18
|
+
extern void (*__ui_release_handlers[])();
|
|
19
|
+
extern void (*__ui_rangechange_handlers[])();
|
|
20
|
+
extern const uint16_t __ui_click_handler_count;
|
|
21
|
+
extern const uint16_t __ui_rangechange_handler_count;
|
|
22
|
+
|
|
23
|
+
// Touch state machine: tracks down → hold → up → click lifecycle
|
|
24
|
+
// Touch node/scroll node state is defined near navigation because ui_navigate resets it.
|
|
25
|
+
static uint32_t __ui_touch_down_time = 0; // millis() when touch started
|
|
26
|
+
static int16_t __ui_touch_down_y_pos = 0; // Y position when touch started (for tap vs drag detection)
|
|
27
|
+
static uint32_t __ui_last_touch_time = 0; // for debounce (updated on touch down only)
|
|
28
|
+
static uint32_t __ui_last_release_time = 0; // for release debounce
|
|
29
|
+
static int16_t __ui_drag_start_x = 0;
|
|
30
|
+
static int16_t __ui_drag_start_y = 0;
|
|
31
|
+
static uint8_t __ui_is_dragging = 0; // 1 once movement exceeds threshold
|
|
32
|
+
static int16_t __ui_range_node = -1; // range slider being dragged (int16: node index can exceed 127)
|
|
33
|
+
|
|
34
|
+
// ── Awaitable tap source (for \`await ui.onTap()\`) ─────────────────────────
|
|
35
|
+
// __ui_tap_seq increments on every completed tap (after click/release dispatch);
|
|
36
|
+
// async awaiters poll it for change. __ui_tap_node records the node hit by the
|
|
37
|
+
// last tap (-1 = empty space / non-interactive area) for per-element awaiters.
|
|
38
|
+
// volatile: written in the touch path, read from task .step() polls.
|
|
39
|
+
static volatile uint32_t __ui_tap_seq = 0;
|
|
40
|
+
static volatile int16_t __ui_tap_node = -1; // int16: node index can exceed 127
|
|
41
|
+
// Keyboard overlay state.
|
|
42
|
+
#define UI_KB_MAX 48 // max key cells (4 rows × 11 padded cols + margin)
|
|
43
|
+
#define UI_KB_HOLD_MS 600
|
|
44
|
+
#define UI_KB_REPEAT_MS 100
|
|
45
|
+
#define UI_KB_TEXT_H 24 // height reserved for the preview text row at the top
|
|
46
|
+
struct UIKey { char ch; uint8_t special; }; // special: 0=char,1=shift,2=bs,3=ok,4=page
|
|
47
|
+
struct UIKeyStyle { uint32_t bg, fg, borderColor; };
|
|
48
|
+
static UIRect __ui_kb_box;
|
|
49
|
+
static UIKey __ui_kb_keys[UI_KB_MAX];
|
|
50
|
+
static UIKeyStyle __ui_kb_styles[UI_KB_MAX];
|
|
51
|
+
static UI_COLOR_T __ui_kb_bg = 0x0000; // keyboard background (resolved from CSS)
|
|
52
|
+
static uint8_t __ui_kb_bs_held = 0;
|
|
53
|
+
static uint8_t __ui_kb_dirty = 0; // 0=clean, 1=full redraw, 2=text row + single key
|
|
54
|
+
// Forward-declared here (defined in the keyboard subsystem block below) so the
|
|
55
|
+
// UI_HIDE_OSK caret/blink paths in ui_tick and the input draw can reference it.
|
|
56
|
+
static int16_t __ui_kb_target;
|
|
57
|
+
#if defined(UI_HIDE_OSK)
|
|
58
|
+
// Caret blink phase for the desktop target (no OSK grid → no focus indicator).
|
|
59
|
+
// Incremented each tick; the input draw path blinks a caret on the active edit
|
|
60
|
+
// target every ~530ms (32 ticks ≈ 512ms at 60fps, even-power-of-2 mask).
|
|
61
|
+
static uint16_t __ui_kb_blink = 0;
|
|
62
|
+
#endif
|
|
63
|
+
static int8_t __ui_kb_pressed_key = -1; // key index under the current touch (-1=none)
|
|
64
|
+
static int8_t __ui_kb_repaint_key = -1; // key to repaint on a targeted (mode 2) redraw
|
|
65
|
+
static int16_t __ui_last_touch_x = 0;
|
|
66
|
+
static int16_t __ui_last_touch_y = 0;
|
|
67
|
+
// Keyboard function forward declarations (defined in the subsystem block below;
|
|
68
|
+
// needed because ui_touch_down/up/handle_touch reference them, AND to suppress
|
|
69
|
+
// Arduino's auto-prototyper which would inject prototypes before UIRect is defined).
|
|
70
|
+
static inline void ui_kb_insert(char c);
|
|
71
|
+
static inline void ui_kb_delete();
|
|
72
|
+
static inline void ui_kb_open(uint16_t nodeIdx, uint8_t inputPosition);
|
|
73
|
+
static inline void ui_kb_close();
|
|
74
|
+
static inline void ui_kb_tick(uint32_t now);
|
|
75
|
+
static inline void ui_kb_handle_touch(int16_t tx, int16_t ty);
|
|
76
|
+
static inline void ui_kb_handle_tap(int16_t tx, int16_t ty);
|
|
77
|
+
static inline void ui_kb_key_rect(uint8_t idx, UIRect* out);
|
|
78
|
+
static inline void ui_kb_draw();
|
|
79
|
+
static inline void ui_kb_draw_key(uint8_t i);
|
|
80
|
+
static inline void ui_kb_draw_text_row();
|
|
81
|
+
static inline void ui_kb_compute_box();
|
|
82
|
+
#define UI_TOUCH_DEBOUNCE_MS 50
|
|
83
|
+
#define UI_TOUCH_HOLD_MS 600
|
|
84
|
+
#define UI_DRAG_THRESHOLD 10
|
|
85
|
+
|
|
86
|
+
// Hit-test a touch point against all visible nodes (topmost first).
|
|
87
|
+
// Returns the node index of the topmost node that BOTH contains the point
|
|
88
|
+
// AND has a click handler registered. Returns -1 if none.
|
|
89
|
+
static int16_t ui_hit_test(int16_t tx, int16_t ty) {
|
|
90
|
+
int16_t best = -1;
|
|
91
|
+
for (uint16_t i = 0; i < __ui_node_count; i++) {
|
|
92
|
+
if (!ui_is_effectively_visible(i)) continue;
|
|
93
|
+
if (__ui_nodes[i].screenId != __ui_active_screen) continue;
|
|
94
|
+
int16_t drawX = ui_draw_x_for_node((uint16_t)i);
|
|
95
|
+
int16_t drawY = ui_draw_y_for_node((uint16_t)i);
|
|
96
|
+
if (tx >= drawX && tx < drawX + __ui_nodes[i].box.w &&
|
|
97
|
+
ty >= drawY && ty < drawY + __ui_nodes[i].box.h) {
|
|
98
|
+
// The node's box contains the tap. For nodes inside a scroll container,
|
|
99
|
+
// the tap point (not the whole box) must lie within the visible viewport:
|
|
100
|
+
// a tall wrapped paragraph can overflow below the fold yet still have a
|
|
101
|
+
// tappable link segment in its visible portion.
|
|
102
|
+
if (ui_is_point_clipped_by_scroll((uint16_t)i, tx, ty)) continue;
|
|
103
|
+
// Skip nodes without any click handler — they're containers, not targets.
|
|
104
|
+
// Exceptions: NODE_RANGE (horizontal drag), NODE_INPUT (opens keyboard),
|
|
105
|
+
// NODE_LIST (virtualized item tap), and NODE_BUTTON — the last so a button
|
|
106
|
+
// gets :pressed/transition visual feedback even with no JS onClick wired
|
|
107
|
+
// (a pure-CSS button like a demo "tap to transition" control).
|
|
108
|
+
if (__ui_nodes[i].kind == NODE_RANGE || __ui_nodes[i].kind == NODE_INPUT || __ui_nodes[i].kind == NODE_LIST || __ui_nodes[i].kind == NODE_BUTTON || __ui_nodes[i].kind == NODE_CHECK || __ui_nodes[i].kind == NODE_RADIO) {
|
|
109
|
+
if (best < 0 || ui_node_draws_before(best, i)) best = i;
|
|
110
|
+
continue;
|
|
111
|
+
}
|
|
112
|
+
if (i < __ui_click_handler_count &&
|
|
113
|
+
(__ui_click_handlers[i] || __ui_hold_handlers[i] || __ui_release_handlers[i])) {
|
|
114
|
+
if (best < 0 || ui_node_draws_before(best, i)) best = i;
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
return best;
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
// Dispatch a handler from the given table if registered for the node.
|
|
122
|
+
static void ui_dispatch(void (**table)(), uint16_t count, int16_t node) {
|
|
123
|
+
if (node >= 0 && (uint16_t)node < count && table[node]) {
|
|
124
|
+
table[node]();
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
static inline void ui_open_keyboard_for_input(uint16_t nodeIdx) {
|
|
129
|
+
if (__ui_nodes[nodeIdx].kind != NODE_INPUT) return;
|
|
130
|
+
#if defined(UI_HIDE_OSK)
|
|
131
|
+
// Desktop target: the OSK grid isn't shown, so inputs remain visible and
|
|
132
|
+
// tappable while another is being edited. Commit any open session before
|
|
133
|
+
// opening for the new target, so focus follows the tap instead of being
|
|
134
|
+
// locked to the first input. (ui_kb_open is a no-op if already closed.)
|
|
135
|
+
if (__ui_kb_visible) ui_kb_close();
|
|
136
|
+
#endif
|
|
137
|
+
if (__ui_kb_visible) return;
|
|
138
|
+
// Resolve the input's position in the loader dispatch table by scanning
|
|
139
|
+
// for the Nth NODE_INPUT. (The loader table is indexed by input order.)
|
|
140
|
+
// Counter is uint16_t: nodeIdx is uint16_t and inputs can live past node
|
|
141
|
+
// 255 (demo-ui's are at 263/265/309). A uint8_t counter wraps 255→0 and
|
|
142
|
+
// never reaches nodeIdx, deadlocking the device on input tap.
|
|
143
|
+
uint8_t inputPos = 0;
|
|
144
|
+
for (uint16_t j = 0; j < nodeIdx; j++) {
|
|
145
|
+
if (__ui_nodes[j].kind == NODE_INPUT) inputPos++;
|
|
146
|
+
}
|
|
147
|
+
ui_kb_open(nodeIdx, inputPos);
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
// Touch down: called when screen is first touched.
|
|
151
|
+
static void ui_touch_down(int16_t tx, int16_t ty) {
|
|
152
|
+
int16_t node = ui_hit_test(tx, ty);
|
|
153
|
+
__ui_touch_node = node;
|
|
154
|
+
__ui_touch_state = 1;
|
|
155
|
+
__ui_touch_down_time = millis();
|
|
156
|
+
__ui_touch_down_y_pos = ty;
|
|
157
|
+
__ui_drag_start_x = tx;
|
|
158
|
+
__ui_drag_start_y = ty;
|
|
159
|
+
__ui_is_dragging = 0;
|
|
160
|
+
__ui_scroll_node = -1;
|
|
161
|
+
__ui_range_node = -1;
|
|
162
|
+
// Unified scroll-scan: one pass over scrollable nodes (containers AND lists —
|
|
163
|
+
// lists are scrollable via the UA stylesheet) finds the owning container.
|
|
164
|
+
// One owner per gesture; the double-delta bug class (node in both a container
|
|
165
|
+
// and a list) is gone because there's no second mechanism.
|
|
166
|
+
#if UI_SCROLL_HAS_TOUCH
|
|
167
|
+
int16_t bestScroll = -1;
|
|
168
|
+
for (uint16_t i = 0; i < __ui_node_count; i++) {
|
|
169
|
+
if (!__ui_nodes[i].scrollable || !ui_is_effectively_visible(i)) continue;
|
|
170
|
+
if (__ui_nodes[i].screenId != __ui_active_screen) continue;
|
|
171
|
+
// Only scrollable if content overflows the viewport.
|
|
172
|
+
if (__ui_nodes[i].contentHeight <= __ui_nodes[i].box.h) continue;
|
|
173
|
+
int16_t drawX = ui_draw_x_for_node((uint16_t)i);
|
|
174
|
+
int16_t drawY = ui_draw_y_for_node((uint16_t)i);
|
|
175
|
+
if (tx >= drawX && tx < drawX + __ui_nodes[i].box.w &&
|
|
176
|
+
ty >= drawY && ty < drawY + __ui_nodes[i].box.h) {
|
|
177
|
+
if (bestScroll < 0 || ui_node_draws_before(bestScroll, i)) bestScroll = i;
|
|
178
|
+
}
|
|
179
|
+
}
|
|
180
|
+
__ui_scroll_node = bestScroll;
|
|
181
|
+
#else
|
|
182
|
+
(void)tx; (void)ty;
|
|
183
|
+
#endif
|
|
184
|
+
if (node >= 0) {
|
|
185
|
+
uint8_t handledTouchTarget = 0;
|
|
186
|
+
if (__ui_nodes[node].kind == NODE_BUTTON) {
|
|
187
|
+
ui_set_pressed((uint16_t)node, 1);
|
|
188
|
+
handledTouchTarget = 1;
|
|
189
|
+
}
|
|
190
|
+
// Track range nodes for horizontal drag
|
|
191
|
+
if (__ui_nodes[node].kind == NODE_RANGE) {
|
|
192
|
+
__ui_range_node = node;
|
|
193
|
+
// Range owns this gesture. Do not let vertical touch jitter also scroll
|
|
194
|
+
// the containing view, which would dirty and repaint the whole viewport.
|
|
195
|
+
__ui_scroll_node = -1;
|
|
196
|
+
// Immediately set value from touch position
|
|
197
|
+
int16_t rMin = __ui_nodes[node].rangeMin;
|
|
198
|
+
int16_t rMax = __ui_nodes[node].rangeMax;
|
|
199
|
+
int16_t range = rMax - rMin;
|
|
200
|
+
if (range <= 0) range = 100;
|
|
201
|
+
int16_t relX = tx - ui_draw_x_for_node((uint16_t)node) - 4;
|
|
202
|
+
int16_t usable = __ui_nodes[node].box.w - 8;
|
|
203
|
+
if (usable <= 0) usable = 1;
|
|
204
|
+
int16_t nextVal = rMin + ((int32_t)relX * range) / usable;
|
|
205
|
+
nextVal = constrain(nextVal, rMin, rMax);
|
|
206
|
+
if (nextVal != __ui_nodes[node].value) {
|
|
207
|
+
__ui_nodes[node].value = nextVal;
|
|
208
|
+
ui_mark_dirty(node);
|
|
209
|
+
}
|
|
210
|
+
handledTouchTarget = 1;
|
|
211
|
+
}
|
|
212
|
+
uint8_t touchStartsScrollableView =
|
|
213
|
+
(__ui_scroll_node >= 0 && node == __ui_scroll_node);
|
|
214
|
+
if (!handledTouchTarget && !touchStartsScrollableView) ui_mark_dirty(node);
|
|
215
|
+
}
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
// Touch up: called when touch is released. Determines click vs hold.
|
|
219
|
+
static void ui_touch_up() {
|
|
220
|
+
// Modal keyboard: route tap-up to the keyboard; swallow normal click logic.
|
|
221
|
+
// (Skipped on UI_HIDE_OSK desktop targets — touches pass through to the app,
|
|
222
|
+
// since the editing session is driven by the real keyboard, not the grid.)
|
|
223
|
+
#if !defined(UI_HIDE_OSK)
|
|
224
|
+
if (__ui_kb_visible) {
|
|
225
|
+
ui_kb_handle_tap(__ui_last_touch_x, __ui_last_touch_y);
|
|
226
|
+
__ui_kb_bs_held = 0;
|
|
227
|
+
__ui_touch_state = 0;
|
|
228
|
+
__ui_last_touch_time = millis();
|
|
229
|
+
return;
|
|
230
|
+
}
|
|
231
|
+
#endif
|
|
232
|
+
uint32_t elapsed = millis() - __ui_touch_down_time;
|
|
233
|
+
// Release the scroll owner: arm a bounded settle (bounce-back / edge-snap).
|
|
234
|
+
// No fling — motion ends with the finger (the settle animation is the only
|
|
235
|
+
// post-lift motion, terminating within UI_SCROLL_SETTLE_MS).
|
|
236
|
+
if (__ui_scroll_node >= 0) {
|
|
237
|
+
ui_scroll_release(__ui_scroll_node);
|
|
238
|
+
}
|
|
239
|
+
if (__ui_touch_node >= 0 && !__ui_is_dragging) {
|
|
240
|
+
int16_t clickedNode = __ui_touch_node;
|
|
241
|
+
if (elapsed < UI_TOUCH_HOLD_MS) {
|
|
242
|
+
if (__ui_nodes[clickedNode].kind == NODE_INPUT) {
|
|
243
|
+
ui_open_keyboard_for_input((uint16_t)clickedNode);
|
|
244
|
+
}
|
|
245
|
+
ui_dispatch(__ui_click_handlers, __ui_click_handler_count, __ui_touch_node);
|
|
246
|
+
// Rich-text inline link: if the tapped node has link runs, find which link
|
|
247
|
+
// segment the tap falls within and navigate to its target screen. Copies
|
|
248
|
+
// the list-item subdivision precedent with measured run rects.
|
|
249
|
+
if (__ui_nodes[clickedNode].runCount > 0) {
|
|
250
|
+
int16_t ndx = __ui_last_touch_x - ui_draw_x_for_node((uint16_t)clickedNode);
|
|
251
|
+
int16_t ndy = __ui_last_touch_y - ui_draw_y_for_node((uint16_t)clickedNode);
|
|
252
|
+
int8_t target = ui_rich_link_hit((uint16_t)clickedNode, ndx, ndy);
|
|
253
|
+
if (target >= 0) ui_navigate((uint8_t)target);
|
|
254
|
+
}
|
|
255
|
+
}
|
|
256
|
+
ui_dispatch(__ui_release_handlers, __ui_click_handler_count, __ui_touch_node);
|
|
257
|
+
ui_mark_dirty(clickedNode);
|
|
258
|
+
}
|
|
259
|
+
// Release the pressed button's :pressed state unconditionally — even when a
|
|
260
|
+
// drag/scroll hijacked the gesture (the click above is correctly gated on
|
|
261
|
+
// !__ui_is_dragging, but the visual press state should always reset on lift,
|
|
262
|
+
// otherwise a touch-down on a button followed by a scroll leaves it stuck).
|
|
263
|
+
if (__ui_touch_node >= 0 && __ui_touch_node < __ui_node_count &&
|
|
264
|
+
__ui_nodes[__ui_touch_node].kind == NODE_BUTTON && __ui_nodes[__ui_touch_node].value != 0) {
|
|
265
|
+
ui_set_pressed((uint16_t)__ui_touch_node, 0);
|
|
266
|
+
}
|
|
267
|
+
// Resume any \`await ui.onTap()\` awaiter. Runs for EVERY completed tap —
|
|
268
|
+
// including holds (released above) and taps on empty space (__ui_touch_node
|
|
269
|
+
// == -1), which is what makes "wake on any touch" work for display-sleep.
|
|
270
|
+
// Placed AFTER the click/release dispatch so onClick always fires first.
|
|
271
|
+
__ui_tap_seq++;
|
|
272
|
+
__ui_tap_node = __ui_touch_node;
|
|
273
|
+
// Virtualized list item tap: if the touch was inside a list, compute the item
|
|
274
|
+
// index from the touch Y. Use total movement (not drag flag) to distinguish
|
|
275
|
+
// tap from scroll: a tap moves < itemHeight/2 total; a scroll moves more.
|
|
276
|
+
if (__ui_scroll_node >= 0 && __ui_nodes[__ui_scroll_node].virtualized) {
|
|
277
|
+
int16_t n = __ui_scroll_node;
|
|
278
|
+
if (__ui_nodes[n].listTapFn) {
|
|
279
|
+
int16_t drawY = ui_draw_y_for_node(n);
|
|
280
|
+
int16_t relY = __ui_last_touch_y - drawY;
|
|
281
|
+
int16_t totalMove = abs(__ui_last_touch_y - __ui_touch_down_y_pos);
|
|
282
|
+
uint16_t ih = __ui_nodes[n].listItemHeight > 0 ? __ui_nodes[n].listItemHeight : 24;
|
|
283
|
+
if (totalMove < (int16_t)(ih / 2) &&
|
|
284
|
+
relY >= 0 && relY < __ui_nodes[n].box.h) {
|
|
285
|
+
uint16_t itemIdx = (uint16_t)((relY + __ui_nodes[n].scrollY) / ih);
|
|
286
|
+
if (itemIdx < __ui_nodes[n].listCount) {
|
|
287
|
+
__ui_nodes[n].listTapFn(itemIdx);
|
|
288
|
+
}
|
|
289
|
+
}
|
|
290
|
+
}
|
|
291
|
+
}
|
|
292
|
+
__ui_touch_state = 0;
|
|
293
|
+
__ui_touch_node = -1;
|
|
294
|
+
__ui_is_dragging = 0;
|
|
295
|
+
__ui_scroll_node = -1;
|
|
296
|
+
__ui_range_node = -1;
|
|
297
|
+
}
|
|
298
|
+
|
|
299
|
+
// Called each frame from ui_poll_touch when touch is detected.
|
|
300
|
+
// Implements debounce + the down/hold/up/click state machine.
|
|
301
|
+
static inline void ui_handle_touch(int16_t tx, int16_t ty) {
|
|
302
|
+
uint32_t now = millis();
|
|
303
|
+
// Track last touch coords for tap-up routing.
|
|
304
|
+
__ui_last_touch_x = tx;
|
|
305
|
+
__ui_last_touch_y = ty;
|
|
306
|
+
|
|
307
|
+
// Modal keyboard: if visible, route touch to the keyboard only.
|
|
308
|
+
// (Skipped on UI_HIDE_OSK desktop targets — touches pass through to the app,
|
|
309
|
+
// since the editing session is driven by the real keyboard, not the grid.)
|
|
310
|
+
#if !defined(UI_HIDE_OSK)
|
|
311
|
+
if (__ui_kb_visible) {
|
|
312
|
+
// Only process the down-edge for key actions (insert/delete-on-down).
|
|
313
|
+
// Repeat is handled by ui_kb_tick; release by ui_touch_up → ui_kb_handle_tap.
|
|
314
|
+
if (__ui_touch_state == 0) {
|
|
315
|
+
__ui_touch_state = 1;
|
|
316
|
+
__ui_touch_down_time = now;
|
|
317
|
+
if (tx >= __ui_kb_box.x && tx < __ui_kb_box.x + __ui_kb_box.w &&
|
|
318
|
+
ty >= __ui_kb_box.y && ty < __ui_kb_box.y + __ui_kb_box.h) {
|
|
319
|
+
ui_kb_handle_touch(tx, ty);
|
|
320
|
+
}
|
|
321
|
+
} else {
|
|
322
|
+
// Held: run auto-repeat (backspace).
|
|
323
|
+
ui_kb_tick(now);
|
|
324
|
+
}
|
|
325
|
+
__ui_last_touch_time = now;
|
|
326
|
+
return; // swallow all other touches while modal
|
|
327
|
+
}
|
|
328
|
+
#endif
|
|
329
|
+
|
|
330
|
+
if (__ui_touch_state == 0) {
|
|
331
|
+
// Idle: check debounce, then start touch
|
|
332
|
+
if (now - __ui_last_touch_time < UI_TOUCH_DEBOUNCE_MS) return;
|
|
333
|
+
ui_touch_down(tx, ty);
|
|
334
|
+
} else {
|
|
335
|
+
// Already touching: check for drag or hold
|
|
336
|
+
if (__ui_range_node < 0 && !__ui_is_dragging && __ui_scroll_node >= 0) {
|
|
337
|
+
// Check if movement exceeds drag threshold
|
|
338
|
+
int16_t dy = ty - __ui_drag_start_y;
|
|
339
|
+
if (abs(dy) >= UI_DRAG_THRESHOLD) {
|
|
340
|
+
__ui_is_dragging = 1;
|
|
341
|
+
}
|
|
342
|
+
}
|
|
343
|
+
// Range slider: update value from horizontal touch position
|
|
344
|
+
if (__ui_range_node >= 0) {
|
|
345
|
+
int16_t rMin = __ui_nodes[__ui_range_node].rangeMin;
|
|
346
|
+
int16_t rMax = __ui_nodes[__ui_range_node].rangeMax;
|
|
347
|
+
int16_t range = rMax - rMin;
|
|
348
|
+
if (range <= 0) range = 100;
|
|
349
|
+
int16_t relX = tx - ui_draw_x_for_node((uint16_t)__ui_range_node) - 4;
|
|
350
|
+
int16_t usable = __ui_nodes[__ui_range_node].box.w - 8;
|
|
351
|
+
if (usable <= 0) usable = 1;
|
|
352
|
+
int16_t newVal = rMin + ((int32_t)relX * range) / usable;
|
|
353
|
+
newVal = constrain(newVal, rMin, rMax);
|
|
354
|
+
if (newVal != __ui_nodes[__ui_range_node].value) {
|
|
355
|
+
__ui_nodes[__ui_range_node].value = newVal;
|
|
356
|
+
ui_mark_dirty(__ui_range_node);
|
|
357
|
+
// Fire the onChange callback (if any) — every value change during drag.
|
|
358
|
+
if (__ui_range_node < (int16_t)__ui_rangechange_handler_count &&
|
|
359
|
+
__ui_rangechange_handlers[__ui_range_node]) {
|
|
360
|
+
__ui_rangechange_handlers[__ui_range_node]();
|
|
361
|
+
}
|
|
362
|
+
}
|
|
363
|
+
}
|
|
364
|
+
// Unified scroll drag: one owning node, immediate-apply each frame (the
|
|
365
|
+
// list's proven model, now used for all scroll containers). No accumulator,
|
|
366
|
+
// no cadence gate, no pending buffer — the smoothed delta is applied via the
|
|
367
|
+
// physics layer (1:1 in-bounds, rubber-band at edges). Telemetry optional.
|
|
368
|
+
if (__ui_range_node < 0 && __ui_is_dragging && __ui_scroll_node >= 0) {
|
|
369
|
+
int16_t rawDy = ty - __ui_drag_start_y;
|
|
370
|
+
if (rawDy != 0) {
|
|
371
|
+
int16_t dy = ui_scroll_smooth_dy(rawDy);
|
|
372
|
+
if (dy != 0) {
|
|
373
|
+
ui_apply_scroll_delta(__ui_scroll_node, dy);
|
|
374
|
+
__ui_drag_start_y = ty;
|
|
375
|
+
#if UI_SCROLL_DEBUG
|
|
376
|
+
Serial.printf("scroll dy=%d sy=%d ov=%d virt=%d\\n",
|
|
377
|
+
dy, __ui_nodes[__ui_scroll_node].scrollY,
|
|
378
|
+
__ui_nodes[__ui_scroll_node].overscrollPx,
|
|
379
|
+
(int)__ui_nodes[__ui_scroll_node].virtualized);
|
|
380
|
+
#endif
|
|
381
|
+
}
|
|
382
|
+
}
|
|
383
|
+
}
|
|
384
|
+
if (__ui_touch_state == 1 && __ui_touch_node >= 0 && !__ui_is_dragging) {
|
|
385
|
+
if (now - __ui_touch_down_time >= UI_TOUCH_HOLD_MS) {
|
|
386
|
+
__ui_touch_state = 2;
|
|
387
|
+
ui_dispatch(__ui_hold_handlers, __ui_click_handler_count, __ui_touch_node);
|
|
388
|
+
}
|
|
389
|
+
}
|
|
390
|
+
}
|
|
391
|
+
__ui_last_touch_time = now;
|
|
392
|
+
}
|
|
393
|
+
|
|
394
|
+
// Called each frame when no touch is detected.
|
|
395
|
+
static inline void ui_handle_no_touch() {
|
|
396
|
+
if (__ui_touch_state != 0) {
|
|
397
|
+
// Debounce: require a gap since the last release before processing another.
|
|
398
|
+
// This prevents crash from rapid touch/no-touch flicker on resistive screens.
|
|
399
|
+
if (millis() - __ui_last_release_time < UI_TOUCH_DEBOUNCE_MS) return;
|
|
400
|
+
ui_touch_up();
|
|
401
|
+
__ui_last_release_time = millis();
|
|
402
|
+
}
|
|
403
|
+
}
|
|
404
|
+
|
|
405
|
+
// Blend two RGB565 colors by opacity (0-100). Returns fg faded toward bg.`;
|
|
406
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export declare function emitTypesDefines(): string;
|
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
// Slice of the C++ runtime header (original source lines 17-89).
|
|
2
|
+
// Guard open, includes, and #define knobs.
|
|
3
|
+
// See docs/superpowers/specs/2026-07-12-split-runtime-header-design.md.
|
|
4
|
+
export function emitTypesDefines() {
|
|
5
|
+
return `
|
|
6
|
+
// ── Cuttlefish UI runtime (emit once per TU) ──────────────────────────────
|
|
7
|
+
#ifndef __TC_UI_RUNTIME
|
|
8
|
+
#define __TC_UI_RUNTIME
|
|
9
|
+
#include <stdint.h>
|
|
10
|
+
#include <string.h>
|
|
11
|
+
#define UI_TEXT_BUF 32 // max stored UI text chars, excluding the trailing NUL
|
|
12
|
+
#define UI_TEXT_LINE_BUF 96
|
|
13
|
+
#define UI_WS_NORMAL 0
|
|
14
|
+
#define UI_WS_NOWRAP 1
|
|
15
|
+
#define UI_WS_PRE 2
|
|
16
|
+
#define UI_WS_PRE_LINE 3
|
|
17
|
+
#ifndef UI_MAX_BUFFERED_PAINT_PIXELS
|
|
18
|
+
#define UI_MAX_BUFFERED_PAINT_PIXELS 20000
|
|
19
|
+
#endif
|
|
20
|
+
#ifndef UI_USE_FULL_FRAMEBUFFER
|
|
21
|
+
#define UI_USE_FULL_FRAMEBUFFER 0
|
|
22
|
+
#endif
|
|
23
|
+
#ifndef UI_TRANSITION_SNAP_MS
|
|
24
|
+
#define UI_TRANSITION_SNAP_MS 100
|
|
25
|
+
#endif
|
|
26
|
+
// Scroll physics + capability defaults. The per-TU #define overrides emitted
|
|
27
|
+
// by the UI emitter (from resolveScrollConfig) hit BEFORE this header, so these
|
|
28
|
+
// #ifndef guards adopt the configured values. Spec: 2026-06-28-scroll-engine-rewrite-design.md
|
|
29
|
+
#ifndef UI_SCROLL_MAX_OVERSCROLL
|
|
30
|
+
#define UI_SCROLL_MAX_OVERSCROLL 40
|
|
31
|
+
#endif
|
|
32
|
+
#ifndef UI_SCROLL_STIFFNESS_X10
|
|
33
|
+
#define UI_SCROLL_STIFFNESS_X10 5
|
|
34
|
+
#endif
|
|
35
|
+
#ifndef UI_SCROLL_EDGE_SNAP_PX
|
|
36
|
+
#define UI_SCROLL_EDGE_SNAP_PX 12
|
|
37
|
+
#endif
|
|
38
|
+
#ifndef UI_SCROLL_DRAG_SCALE_X10
|
|
39
|
+
#define UI_SCROLL_DRAG_SCALE_X10 10
|
|
40
|
+
#endif
|
|
41
|
+
#ifndef UI_SCROLL_SETTLE_MS
|
|
42
|
+
#define UI_SCROLL_SETTLE_MS 180
|
|
43
|
+
#endif
|
|
44
|
+
#ifndef UI_SCROLL_DEADBAND_PX
|
|
45
|
+
#define UI_SCROLL_DEADBAND_PX 2
|
|
46
|
+
#endif
|
|
47
|
+
// Capability tier flags (emitted per-TU before this header; defaults = full).
|
|
48
|
+
#ifndef UI_SCROLL_INPUT_TIER_CAPACITIVE
|
|
49
|
+
#define UI_SCROLL_INPUT_TIER_CAPACITIVE 0
|
|
50
|
+
#endif
|
|
51
|
+
#ifndef UI_SCROLL_INPUT_TIER_RESISTIVE
|
|
52
|
+
#define UI_SCROLL_INPUT_TIER_RESISTIVE 0
|
|
53
|
+
#endif
|
|
54
|
+
#ifndef UI_SCROLL_INPUT_TIER_NONE
|
|
55
|
+
#define UI_SCROLL_INPUT_TIER_NONE 0
|
|
56
|
+
#endif
|
|
57
|
+
#ifndef UI_SCROLL_RENDER_TIER_FULL
|
|
58
|
+
#define UI_SCROLL_RENDER_TIER_FULL 1
|
|
59
|
+
#endif
|
|
60
|
+
#ifndef UI_SCROLL_RENDER_TIER_CONSTRAINED
|
|
61
|
+
#define UI_SCROLL_RENDER_TIER_CONSTRAINED 0
|
|
62
|
+
#endif
|
|
63
|
+
#if (UI_SCROLL_INPUT_TIER_CAPACITIVE + UI_SCROLL_INPUT_TIER_RESISTIVE + UI_SCROLL_INPUT_TIER_NONE) == 0
|
|
64
|
+
#define UI_SCROLL_INPUT_TIER_RESISTIVE 1
|
|
65
|
+
#endif
|
|
66
|
+
#define UI_SCROLL_HAS_TOUCH (UI_SCROLL_INPUT_TIER_CAPACITIVE || UI_SCROLL_INPUT_TIER_RESISTIVE)
|
|
67
|
+
#define UI_SCROLL_ELASTIC (UI_SCROLL_RENDER_TIER_FULL)
|
|
68
|
+
// Telemetry: emits per-frame scrollY/overscrollPx/dy over Serial when defined.
|
|
69
|
+
#ifndef UI_SCROLL_DEBUG
|
|
70
|
+
#define UI_SCROLL_DEBUG 0
|
|
71
|
+
#endif
|
|
72
|
+
#ifndef UI_SCROLL_CANVAS_BUDGET_BYTES
|
|
73
|
+
#define UI_SCROLL_CANVAS_BUDGET_BYTES 88000
|
|
74
|
+
#endif
|
|
75
|
+
#if defined(ESP32) || defined(ESP8266)
|
|
76
|
+
#include <Esp.h>
|
|
77
|
+
#endif
|
|
78
|
+
`;
|
|
79
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export declare function emitRuntimeHeader(): string;
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
// ---------------------------------------------------------------------------
|
|
2
|
+
// C++ reactive runtime header — the driver code that walks the node/binding/
|
|
3
|
+
// transition tables each frame.
|
|
4
|
+
//
|
|
5
|
+
// This is emitted once per translation unit (guarded) so the static tables
|
|
6
|
+
// produced by the lowering transformer have something to drive them. It
|
|
7
|
+
// implements the three-phase frame from spec §7:
|
|
8
|
+
// 1. Advance transitions (lerp toward target)
|
|
9
|
+
// 2. Draw traversal (dirty nodes only)
|
|
10
|
+
// 3. Flush dirty rects
|
|
11
|
+
//
|
|
12
|
+
// Plus the press/release entry points that node.onPress(pin) lowers to.
|
|
13
|
+
//
|
|
14
|
+
// The C++ source is split into contiguous slice modules under
|
|
15
|
+
// ./runtime-header/, concatenated here in source order. Each slice is a
|
|
16
|
+
// verbatim range of the original template literal; the assembled output is
|
|
17
|
+
// byte-identical to the pre-split single-file version (guarded by
|
|
18
|
+
// tests/packages/cuttlefish/runtime-header-byte-identity.test.ts).
|
|
19
|
+
// ---------------------------------------------------------------------------
|
|
20
|
+
import { emitTypesDefines } from "./runtime-header/types-defines.js";
|
|
21
|
+
import { emitStructs } from "./runtime-header/structs.js";
|
|
22
|
+
import { emitColorMonoRefresh } from "./runtime-header/color-mono-refresh.js";
|
|
23
|
+
import { emitStateBindingsNav } from "./runtime-header/state-bindings-nav.js";
|
|
24
|
+
import { emitForwardDecls } from "./runtime-header/forward-decls.js";
|
|
25
|
+
import { emitCanvasHelpers } from "./runtime-header/canvas-helpers.js";
|
|
26
|
+
import { emitDisplayShim } from "./runtime-header/display-shim.js";
|
|
27
|
+
import { emitScrollPhysics } from "./runtime-header/scroll-physics.js";
|
|
28
|
+
import { emitImageDrawing } from "./runtime-header/image-drawing.js";
|
|
29
|
+
import { emitCanvasScrollbar } from "./runtime-header/canvas-scrollbar.js";
|
|
30
|
+
import { emitDirtyScrollMutators } from "./runtime-header/dirty-scroll-mutators.js";
|
|
31
|
+
import { emitPaintOrderCoords } from "./runtime-header/paint-order-coords.js";
|
|
32
|
+
import { emitPaintRectsRepair } from "./runtime-header/paint-rects-repair.js";
|
|
33
|
+
import { emitInitPressInput } from "./runtime-header/init-press-input.js";
|
|
34
|
+
import { emitTouchKeyboardFwd } from "./runtime-header/touch-keyboard-fwd.js";
|
|
35
|
+
import { emitBlendBodies } from "./runtime-header/blend-bodies.js";
|
|
36
|
+
import { emitTextRendering } from "./runtime-header/text-rendering.js";
|
|
37
|
+
import { emitNodeDecoration } from "./runtime-header/node-decoration.js";
|
|
38
|
+
import { emitTickMotionHelpers } from "./runtime-header/tick-motion-helpers.js";
|
|
39
|
+
import { emitTick } from "./runtime-header/tick.js";
|
|
40
|
+
import { emitAntialiasing } from "./runtime-header/antialiasing.js";
|
|
41
|
+
import { emitKeyboard } from "./runtime-header/keyboard.js";
|
|
42
|
+
import { emitGuardClose } from "./runtime-header/guard-close.js";
|
|
43
|
+
export function emitRuntimeHeader() {
|
|
44
|
+
return [
|
|
45
|
+
emitTypesDefines(),
|
|
46
|
+
emitStructs(),
|
|
47
|
+
emitColorMonoRefresh(),
|
|
48
|
+
emitStateBindingsNav(),
|
|
49
|
+
emitForwardDecls(),
|
|
50
|
+
emitCanvasHelpers(),
|
|
51
|
+
emitDisplayShim(),
|
|
52
|
+
emitScrollPhysics(),
|
|
53
|
+
emitImageDrawing(),
|
|
54
|
+
emitCanvasScrollbar(),
|
|
55
|
+
emitDirtyScrollMutators(),
|
|
56
|
+
emitPaintOrderCoords(),
|
|
57
|
+
emitPaintRectsRepair(),
|
|
58
|
+
emitInitPressInput(),
|
|
59
|
+
emitTouchKeyboardFwd(),
|
|
60
|
+
emitBlendBodies(),
|
|
61
|
+
emitTextRendering(),
|
|
62
|
+
emitNodeDecoration(),
|
|
63
|
+
emitTickMotionHelpers(),
|
|
64
|
+
emitTick(),
|
|
65
|
+
emitAntialiasing(),
|
|
66
|
+
emitKeyboard(),
|
|
67
|
+
emitGuardClose(),
|
|
68
|
+
].join("");
|
|
69
|
+
}
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import type { Diagnostic } from "@typecad/cuttlefish/api/shared";
|
|
2
|
+
import type { UINodeModel } from "./model.js";
|
|
3
|
+
import { DEFAULT_SCROLL_CANVAS_BUDGET_BYTES } from "@typecad/cuttlefish/api/shared";
|
|
4
|
+
export { DEFAULT_SCROLL_CANVAS_BUDGET_BYTES };
|
|
5
|
+
export interface ScrollMemoryIssue {
|
|
6
|
+
nodeIndex: number;
|
|
7
|
+
id?: string;
|
|
8
|
+
width: number;
|
|
9
|
+
height: number;
|
|
10
|
+
canvasBytes: number;
|
|
11
|
+
budgetBytes: number;
|
|
12
|
+
}
|
|
13
|
+
/** Collect overflow scroll nodes whose viewport canvas exceeds the budget. */
|
|
14
|
+
export declare function collectScrollMemoryIssues(nodes: UINodeModel[], budgetBytes?: number): ScrollMemoryIssue[];
|
|
15
|
+
/** Emit transpile warnings for scroll viewports that cannot fit a Mode B canvas. */
|
|
16
|
+
export declare function analyzeScrollMemory(nodes: UINodeModel[], budgetBytes?: number): Diagnostic[];
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
// Compile-time scroll viewport memory analysis. Overflow scroll containers
|
|
2
|
+
// need a viewport-sized RGB565 canvas (~w×h×2 bytes) for smooth Mode B
|
|
3
|
+
// scrolling; when that exceeds the device budget, runtime falls back to
|
|
4
|
+
// Mode C strip scroll or freezes until memory is available.
|
|
5
|
+
import { DEFAULT_SCROLL_CANVAS_BUDGET_BYTES } from "@typecad/cuttlefish/api/shared";
|
|
6
|
+
export { DEFAULT_SCROLL_CANVAS_BUDGET_BYTES };
|
|
7
|
+
/** Collect overflow scroll nodes whose viewport canvas exceeds the budget. */
|
|
8
|
+
export function collectScrollMemoryIssues(nodes, budgetBytes = DEFAULT_SCROLL_CANVAS_BUDGET_BYTES) {
|
|
9
|
+
const issues = [];
|
|
10
|
+
for (const node of nodes) {
|
|
11
|
+
if (!node.scrollable)
|
|
12
|
+
continue;
|
|
13
|
+
if (node.contentHeight <= node.box.h)
|
|
14
|
+
continue;
|
|
15
|
+
const canvasBytes = node.box.w * node.box.h * 2;
|
|
16
|
+
if (canvasBytes <= budgetBytes)
|
|
17
|
+
continue;
|
|
18
|
+
issues.push({
|
|
19
|
+
nodeIndex: node.index,
|
|
20
|
+
id: node.id,
|
|
21
|
+
width: node.box.w,
|
|
22
|
+
height: node.box.h,
|
|
23
|
+
canvasBytes,
|
|
24
|
+
budgetBytes,
|
|
25
|
+
});
|
|
26
|
+
}
|
|
27
|
+
return issues;
|
|
28
|
+
}
|
|
29
|
+
function formatKb(bytes) {
|
|
30
|
+
return `${Math.round(bytes / 1024)} KB`;
|
|
31
|
+
}
|
|
32
|
+
function minimizationHint(issue) {
|
|
33
|
+
const maxArea = Math.floor(issue.budgetBytes / 2);
|
|
34
|
+
const maxHAtWidth = issue.width > 0 ? Math.floor(maxArea / issue.width) : 0;
|
|
35
|
+
const maxWAtHeight = issue.height > 0 ? Math.floor(maxArea / issue.height) : 0;
|
|
36
|
+
return [
|
|
37
|
+
`Reduce the scroll viewport in CSS (e.g. height ≤ ${maxHAtWidth}px at ${issue.width}px width, or width ≤ ${maxWAtHeight}px at ${issue.height}px height).`,
|
|
38
|
+
"Trim fonts, images, or inactive screens to free heap for canvas allocation.",
|
|
39
|
+
"Use PSRAM or a board with more SRAM if you need a larger scroll viewport.",
|
|
40
|
+
"Adjust display.scroll.scrollCanvasBudgetBytes in cuttlefish.config.ts only if your target has more headroom (runtime allocation may still fail).",
|
|
41
|
+
].join("\n");
|
|
42
|
+
}
|
|
43
|
+
/** Emit transpile warnings for scroll viewports that cannot fit a Mode B canvas. */
|
|
44
|
+
export function analyzeScrollMemory(nodes, budgetBytes = DEFAULT_SCROLL_CANVAS_BUDGET_BYTES) {
|
|
45
|
+
return collectScrollMemoryIssues(nodes, budgetBytes).map((issue) => {
|
|
46
|
+
const label = issue.id ? `#${issue.id}` : `node ${issue.nodeIndex}`;
|
|
47
|
+
return {
|
|
48
|
+
severity: "warning",
|
|
49
|
+
code: "scroll-canvas-memory",
|
|
50
|
+
message: `Scroll viewport ${label} (${issue.width}×${issue.height}px) needs ${issue.canvasBytes} bytes ` +
|
|
51
|
+
`(${formatKb(issue.canvasBytes)}) for accurate smooth scrolling, exceeding the ` +
|
|
52
|
+
`${issue.budgetBytes}-byte budget (~${formatKb(issue.budgetBytes)}). ` +
|
|
53
|
+
"Rendering will fall back to Mode C strip scroll or freeze updates until memory is available — " +
|
|
54
|
+
"scrolling may tear, stutter, or skip frames.",
|
|
55
|
+
hint: minimizationHint(issue),
|
|
56
|
+
};
|
|
57
|
+
});
|
|
58
|
+
}
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
// ---------------------------------------------------------------------------
|
|
2
|
+
// Engine selection — picks Yoga (flexbox) or BlockLayout based on whether any
|
|
3
|
+
// node in the styled tree declares display: flex.
|
|
4
|
+
// ---------------------------------------------------------------------------
|
|
5
|
+
import { isDisplayNone } from "./layout-engine.js";
|
|
6
|
+
import { BlockLayoutEngine } from "./block-layout.js";
|
|
7
|
+
import { YogaLayoutEngine } from "./yoga-layout.js";
|
|
8
|
+
/** Check if any node in the tree uses display: flex. */
|
|
9
|
+
function usesFlex(node) {
|
|
10
|
+
if (isDisplayNone(node))
|
|
11
|
+
return false;
|
|
12
|
+
if (node.style.display === "flex")
|
|
13
|
+
return true;
|
|
14
|
+
return node.children.some(usesFlex);
|
|
15
|
+
}
|
|
16
|
+
/** Select the layout engine: Yoga for flex, BlockLayoutEngine as fallback. */
|
|
17
|
+
export function selectEngine(root) {
|
|
18
|
+
return usesFlex(root) ? new YogaLayoutEngine() : new BlockLayoutEngine();
|
|
19
|
+
}
|