@typecad/ui 1.0.0-alpha.12 → 1.0.0-alpha.14

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 (124) hide show
  1. package/assets/fonts/dejavu/DejaVuSans-Bold.ttf +0 -0
  2. package/assets/fonts/dejavu/DejaVuSans-ExtraLight.ttf +0 -0
  3. package/assets/fonts/dejavu/DejaVuSans-Oblique.ttf +0 -0
  4. package/assets/fonts/dejavu/DejaVuSans.ttf +0 -0
  5. package/assets/fonts/dejavu/DejaVuSansMono-Bold.ttf +0 -0
  6. package/assets/fonts/dejavu/DejaVuSansMono.ttf +0 -0
  7. package/assets/fonts/dejavu/LICENSE +187 -0
  8. package/assets/fonts/dejavu/README.md +49 -0
  9. package/dist/cli.js +22 -19
  10. package/dist/engine-index.js +2 -0
  11. package/dist/preview/build-program.d.ts +7 -1
  12. package/dist/preview/build-program.js +94 -15
  13. package/dist/preview/host-gfx.d.ts +3 -0
  14. package/dist/preview/host-gfx.js +30 -3
  15. package/dist/preview/host-ui-runtime.d.ts +115 -0
  16. package/dist/preview/host-ui-runtime.js +927 -46
  17. package/dist/ui-engine/block-layout.js +25 -11
  18. package/dist/ui-engine/color.d.ts +6 -0
  19. package/dist/ui-engine/color.js +80 -2
  20. package/dist/ui-engine/compat-report.d.ts +8 -0
  21. package/dist/ui-engine/compat-report.js +124 -0
  22. package/dist/ui-engine/css-imports.d.ts +4 -0
  23. package/dist/ui-engine/css-imports.js +70 -0
  24. package/dist/ui-engine/css-parser.js +58 -14
  25. package/dist/ui-engine/default-font.d.ts +25 -0
  26. package/dist/ui-engine/default-font.js +80 -0
  27. package/dist/ui-engine/font-assets.d.ts +2 -2
  28. package/dist/ui-engine/font-assets.js +22 -5
  29. package/dist/ui-engine/html-parser.d.ts +4 -0
  30. package/dist/ui-engine/html-parser.js +241 -26
  31. package/dist/ui-engine/image-assets.d.ts +8 -1
  32. package/dist/ui-engine/image-assets.js +35 -2
  33. package/dist/ui-engine/image-decode.d.ts +22 -0
  34. package/dist/ui-engine/image-decode.js +194 -0
  35. package/dist/ui-engine/inline-parser.js +1 -1
  36. package/dist/ui-engine/layout-engine.d.ts +19 -0
  37. package/dist/ui-engine/layout-engine.js +59 -3
  38. package/dist/ui-engine/model.d.ts +18 -0
  39. package/dist/ui-engine/model.js +82 -8
  40. package/dist/ui-engine/runtime-header/blend-bodies.js +29 -9
  41. package/dist/ui-engine/runtime-header/canvas-helpers.js +3 -0
  42. package/dist/ui-engine/runtime-header/canvas-scrollbar.js +115 -111
  43. package/dist/ui-engine/runtime-header/cuttlefish-gfx.js +21 -0
  44. package/dist/ui-engine/runtime-header/dirty-scroll-mutators.js +182 -179
  45. package/dist/ui-engine/runtime-header/forward-decls.js +215 -167
  46. package/dist/ui-engine/runtime-header/init-press-input.js +138 -135
  47. package/dist/ui-engine/runtime-header/keyboard.js +682 -380
  48. package/dist/ui-engine/runtime-header/node-draw-body.js +1658 -1386
  49. package/dist/ui-engine/runtime-header/paint-order-coords.js +259 -214
  50. package/dist/ui-engine/runtime-header/paint-rects-repair.js +736 -568
  51. package/dist/ui-engine/runtime-header/scroll-physics.js +25 -1
  52. package/dist/ui-engine/runtime-header/state-bindings-nav.js +9 -0
  53. package/dist/ui-engine/runtime-header/structs.js +228 -216
  54. package/dist/ui-engine/runtime-header/text-rendering.js +888 -770
  55. package/dist/ui-engine/runtime-header/tick/bindings-phase.js +118 -116
  56. package/dist/ui-engine/runtime-header/tick/dirty-draw-phase.js +897 -633
  57. package/dist/ui-engine/runtime-header/tick/scroll-canvas-phase.js +24 -25
  58. package/dist/ui-engine/runtime-header/touch-keyboard-fwd.js +78 -23
  59. package/dist/ui-engine/runtime-header/types-defines.js +110 -93
  60. package/dist/ui-engine/shadcn-kit.d.ts +1 -0
  61. package/dist/ui-engine/shadcn-kit.js +506 -0
  62. package/dist/ui-engine/style-resolver.d.ts +4 -0
  63. package/dist/ui-engine/style-resolver.js +58 -6
  64. package/dist/ui-engine/transpile-ui.js +12 -5
  65. package/dist/ui-engine/ua-stylesheet.d.ts +22 -1
  66. package/dist/ui-engine/ua-stylesheet.js +119 -32
  67. package/dist/ui-engine/ui-lowering.js +37 -3
  68. package/dist/ui-engine/ui-registry.js +32 -4
  69. package/dist/ui-engine/yoga-layout.js +57 -52
  70. package/dist/wizard/index.d.ts +2 -0
  71. package/dist/wizard/index.js +1 -0
  72. package/dist/wizard/integration-wizard.d.ts +3 -0
  73. package/dist/wizard/integration-wizard.js +67 -7
  74. package/dist/wizard/package-writer.d.ts +20 -0
  75. package/dist/wizard/package-writer.js +79 -0
  76. package/package.json +11 -5
  77. package/src/cli.ts +90 -87
  78. package/src/engine-index.ts +53 -51
  79. package/src/preview/build-program.ts +810 -734
  80. package/src/preview/host-gfx.ts +30 -3
  81. package/src/preview/host-ui-runtime.ts +4132 -3289
  82. package/src/ui-engine/block-layout.ts +24 -11
  83. package/src/ui-engine/color.ts +77 -2
  84. package/src/ui-engine/compat-report.ts +139 -0
  85. package/src/ui-engine/css-imports.ts +69 -0
  86. package/src/ui-engine/css-parser.ts +64 -15
  87. package/src/ui-engine/default-font.ts +95 -0
  88. package/src/ui-engine/font-assets.ts +545 -525
  89. package/src/ui-engine/html-parser.ts +615 -397
  90. package/src/ui-engine/image-assets.ts +37 -2
  91. package/src/ui-engine/image-decode.ts +244 -0
  92. package/src/ui-engine/inline-parser.ts +1 -1
  93. package/src/ui-engine/layout-engine.ts +61 -3
  94. package/src/ui-engine/model.ts +1322 -1230
  95. package/src/ui-engine/runtime-header/blend-bodies.ts +29 -9
  96. package/src/ui-engine/runtime-header/canvas-helpers.ts +3 -0
  97. package/src/ui-engine/runtime-header/canvas-scrollbar.ts +121 -117
  98. package/src/ui-engine/runtime-header/cuttlefish-gfx.ts +21 -0
  99. package/src/ui-engine/runtime-header/dirty-scroll-mutators.ts +188 -185
  100. package/src/ui-engine/runtime-header/forward-decls.ts +227 -179
  101. package/src/ui-engine/runtime-header/init-press-input.ts +144 -141
  102. package/src/ui-engine/runtime-header/keyboard.ts +689 -386
  103. package/src/ui-engine/runtime-header/node-draw-body.ts +1683 -1411
  104. package/src/ui-engine/runtime-header/paint-order-coords.ts +265 -220
  105. package/src/ui-engine/runtime-header/paint-rects-repair.ts +742 -574
  106. package/src/ui-engine/runtime-header/scroll-physics.ts +25 -1
  107. package/src/ui-engine/runtime-header/state-bindings-nav.ts +9 -0
  108. package/src/ui-engine/runtime-header/structs.ts +234 -222
  109. package/src/ui-engine/runtime-header/text-rendering.ts +894 -776
  110. package/src/ui-engine/runtime-header/tick/bindings-phase.ts +124 -122
  111. package/src/ui-engine/runtime-header/tick/dirty-draw-phase.ts +902 -638
  112. package/src/ui-engine/runtime-header/tick/scroll-canvas-phase.ts +30 -31
  113. package/src/ui-engine/runtime-header/touch-keyboard-fwd.ts +78 -23
  114. package/src/ui-engine/runtime-header/types-defines.ts +116 -99
  115. package/src/ui-engine/shadcn-kit.ts +507 -0
  116. package/src/ui-engine/style-resolver.ts +471 -416
  117. package/src/ui-engine/transpile-ui.ts +12 -5
  118. package/src/ui-engine/ua-stylesheet.ts +137 -31
  119. package/src/ui-engine/ui-lowering.ts +486 -452
  120. package/src/ui-engine/ui-registry.ts +556 -524
  121. package/src/ui-engine/yoga-layout.ts +307 -309
  122. package/src/wizard/index.ts +46 -38
  123. package/src/wizard/integration-wizard.ts +689 -619
  124. package/src/wizard/package-writer.ts +98 -0
@@ -2,140 +2,143 @@
2
2
  // ui_init, press/release entry points, pin-watch polling.
3
3
  // See docs/superpowers/specs/2026-07-12-split-runtime-header-design.md.
4
4
  export function emitInitPressInput() {
5
- return `
6
- // Initial draw: mark all nodes dirty so the first ui_tick renders everything.
7
- // Called once in setup() before the loop begins.
8
- // Also seed each text-bound node's buffer from its flash literal so the first
9
- // strcmp in ui_tick has a valid baseline (no spurious redraw on frame 1).
10
- static inline void ui_init(void) {
11
- // Allocate the per-node scroll-canvas-OK flag array (once; __ui_node_count
12
- // is a compile-time constant known by this point). calloc zeroes it — all
13
- // containers start locked until the scroll-container loop proves their
14
- // canvas fits.
15
- if (!__ui_scroll_canvas_ok && __ui_node_count > 0) {
16
- __ui_scroll_canvas_ok = new (std::nothrow) uint8_t[__ui_node_count]();
17
- }
18
- if (!__ui_scroll_render_locked && __ui_node_count > 0) {
19
- __ui_scroll_render_locked = new (std::nothrow) uint8_t[__ui_node_count]();
20
- }
21
- for (uint16_t i = 0; i < __ui_node_count; i++) {
22
- __ui_nodes[i].dirty = 1;
23
- __ui_nodes[i].lastTextHeight = 0;
24
- __ui_nodes[i].layoutCacheKey = 0;
25
- if (__ui_nodes[i].scrollable) {
26
- // Force the first scroll-canvas composition to seed every pixel. After
27
- // that, a dirty child can be repaired in place without repainting the
28
- // entire viewport.
29
- __ui_nodes[i].lastPaintedScrollY = static_cast<int16_t>(
30
- __ui_nodes[i].scrollY - (__ui_nodes[i].box.h > 0 ? __ui_nodes[i].box.h : 1));
31
- }
32
- if (__ui_nodes[i].kind == NODE_PROGRESS || __ui_nodes[i].kind == NODE_RANGE) {
33
- __ui_nodes[i].lastTextWidth = -1;
34
- }
35
- }
36
- for (uint16_t i = 0; i < __ui_binding_count; i++) {
37
- if (__ui_bindings[i].prop == PROP_TEXT && __ui_bindings[i].textFn) {
38
- uint16_t n = __ui_bindings[i].node;
39
- __ui_nodes[n].hasTextBinding = 1;
40
- strncpy(__ui_nodes[n].textBuffer, __ui_nodes[n].text ? __ui_nodes[n].text : "", UI_TEXT_BUF);
41
- __ui_nodes[n].textBuffer[UI_TEXT_BUF] = '\\0';
42
- } else if (__ui_bindings[i].fn) {
43
- // Prime numeric binding caches once at setup; the first tick can then skip
44
- // redundant assignments and dirty propagation when values are unchanged.
45
- __ui_bindings[i].lastValue = __ui_bindings[i].fn();
46
- __ui_bindings[i].initialized = 1;
47
- }
48
- }
49
- // Seed virtualized-list runtime state. The fn pointers can't be baked into
50
- // the static node initializer: ui.bindList is resolved AFTER ui.mount lowers
51
- // the HTML, so the lowering can't see the binding yet. Instead the lowering
52
- // emits the UIListBinding table (the binding's fn bodies) and ui_init copies
53
- // the pointers onto each <list> node here, then computes the initial count
54
- // and contentHeight so the first paint and the scroll clamp bound are correct.
55
- for (uint16_t b = 0; b < __ui_list_binding_count; b++) {
56
- uint16_t n = __ui_list_bindings[b].node;
57
- if (n >= __ui_node_count || !__ui_nodes[n].virtualized) continue;
58
- __ui_nodes[n].listCountFn = __ui_list_bindings[b].countFn;
59
- __ui_nodes[n].listItemFn = __ui_list_bindings[b].itemFn;
60
- __ui_nodes[n].listTapFn = __ui_list_bindings[b].tapFn;
61
- }
62
- for (uint16_t i = 0; i < __ui_node_count; i++) {
63
- if (!__ui_nodes[i].virtualized || !__ui_nodes[i].listCountFn) continue;
64
- uint16_t ih = __ui_nodes[i].listItemHeight > 0 ? __ui_nodes[i].listItemHeight : 24;
65
- uint16_t cnt = __ui_nodes[i].listCountFn();
66
- __ui_nodes[i].listCount = cnt;
67
- __ui_nodes[i].contentHeight = static_cast<int16_t>(static_cast<uint32_t>(cnt) * ih);
68
- __ui_nodes[i].scrollY = 0;
69
- __ui_nodes[i].overscrollPx = 0;
70
- __ui_nodes[i].settling = 0;
71
- __ui_nodes[i].lastPaintedScrollY = -(__ui_nodes[i].box.h > 0 ? __ui_nodes[i].box.h : 1);
72
- }
73
- ui_build_draw_order();
74
- ui_build_scroll_owner_table();
75
- ui_refresh_active_screen_bg_node();
76
- if (!__ui_scroll_candidates && __ui_node_count > 0) {
77
- __ui_scroll_candidates = new (std::nothrow) UIScrollPaintCandidate[__ui_node_count];
78
- }
79
- }
80
-
81
- // Debounce: ignore press/release events within 50ms of the last edge.
82
- // Mechanical switches bounce (multiple edges in ~5-20ms); without this, the
83
- // transition gets armed/interrupted dozens of times per physical press.
84
- static volatile uint32_t __ui_last_edge_time = 0;
85
- #define UI_DEBOUNCE_MS 50
86
-
87
- static inline void ui_set_pressed(uint16_t nodeIdx, uint8_t pressed) {
88
- __ui_nodes[nodeIdx].value = pressed ? 1 : 0;
89
- ui_mark_dirty(nodeIdx);
90
- for (uint16_t i = 0; i < __ui_trans_count; i++) {
91
- if (__ui_trans[i].node == nodeIdx) {
92
- __ui_trans[i].prevValue = __ui_trans[i].prop == PROP_FG ? __ui_nodes[nodeIdx].fg : __ui_nodes[nodeIdx].bg;
93
- __ui_trans[i].targetValue = pressed ? __ui_trans[i].pressedTarget : __ui_trans[i].baseTarget;
94
- __ui_trans[i].elapsed = 0;
95
- __ui_trans[i].active = 1;
96
- }
97
- }
98
- }
99
-
100
- // Press / release entry points that node.onPress(pin) lowers to.
101
- // On press, arm transitions toward the :pressed target color; on release,
102
- // arm them back toward the base color (interrupt-and-re-lerp from current).
103
- static inline void ui_on_press(uint16_t nodeIdx) {
104
- uint32_t now = millis();
105
- if (now - __ui_last_edge_time < UI_DEBOUNCE_MS) return;
106
- __ui_last_edge_time = now;
107
- ui_set_pressed(nodeIdx, 1);
108
- }
109
- static inline void ui_on_release(uint16_t nodeIdx) {
110
- uint32_t now = millis();
111
- if (now - __ui_last_edge_time < UI_DEBOUNCE_MS) return;
112
- __ui_last_edge_time = now;
113
- ui_set_pressed(nodeIdx, 0);
114
- }
115
-
116
- // Pin-watch callback type: void fn(void)
117
- using PinWatchCallback = void (*)(void);
118
-
119
- struct UIPinWatch {
120
- uint8_t pin;
121
- uint8_t lastState; // for edge detection
122
- PinWatchCallback cb; // fires on falling edge
123
- };
124
-
125
- // Populated by the emit layer from ui.watchPin() calls.
126
- extern UIPinWatch __ui_pin_watches[];
127
- extern const uint16_t __ui_pin_watch_count;
128
-
129
- // Poll all configured pin-watchers. Called at the start of ui_tick each frame.
130
- // Detects falling edges with natural debounce from the ~16ms frame rate.
131
- static inline void ui_poll_inputs() {
132
- for (uint16_t i = 0; i < __ui_pin_watch_count; i++) {
133
- uint8_t val = digitalRead(__ui_pin_watches[i].pin);
134
- if (val == LOW && __ui_pin_watches[i].lastState == HIGH) {
135
- if (__ui_pin_watches[i].cb) __ui_pin_watches[i].cb();
136
- }
137
- __ui_pin_watches[i].lastState = val;
138
- }
139
- }
5
+ return `
6
+ // Initial draw: mark all nodes dirty so the first ui_tick renders everything.
7
+ // Called once in setup() before the loop begins.
8
+ // Also seed each text-bound node's buffer from its flash literal so the first
9
+ // strcmp in ui_tick has a valid baseline (no spurious redraw on frame 1).
10
+ static inline void ui_init(void) {
11
+ // Discover <drawer> panels and seed them closed (full-travel offsets, so a
12
+ // closed drawer never paints at its rest position).
13
+ ui_drawer_discover();
14
+ // Allocate the per-node scroll-canvas-OK flag array (once; __ui_node_count
15
+ // is a compile-time constant known by this point). calloc zeroes it — all
16
+ // containers start locked until the scroll-container loop proves their
17
+ // canvas fits.
18
+ if (!__ui_scroll_canvas_ok && __ui_node_count > 0) {
19
+ __ui_scroll_canvas_ok = new (std::nothrow) uint8_t[__ui_node_count]();
20
+ }
21
+ if (!__ui_scroll_render_locked && __ui_node_count > 0) {
22
+ __ui_scroll_render_locked = new (std::nothrow) uint8_t[__ui_node_count]();
23
+ }
24
+ for (uint16_t i = 0; i < __ui_node_count; i++) {
25
+ __ui_nodes[i].dirty = 1;
26
+ __ui_nodes[i].lastTextHeight = 0;
27
+ __ui_nodes[i].layoutCacheKey = 0;
28
+ if (__ui_nodes[i].scrollable) {
29
+ // Force the first scroll-canvas composition to seed every pixel. After
30
+ // that, a dirty child can be repaired in place without repainting the
31
+ // entire viewport.
32
+ __ui_nodes[i].lastPaintedScrollY = static_cast<int16_t>(
33
+ __ui_nodes[i].scrollY - (__ui_nodes[i].box.h > 0 ? __ui_nodes[i].box.h : 1));
34
+ }
35
+ if (__ui_nodes[i].kind == NODE_PROGRESS || __ui_nodes[i].kind == NODE_RANGE) {
36
+ __ui_nodes[i].lastTextWidth = -1;
37
+ }
38
+ }
39
+ for (uint16_t i = 0; i < __ui_binding_count; i++) {
40
+ if (__ui_bindings[i].prop == PROP_TEXT && __ui_bindings[i].textFn) {
41
+ uint16_t n = __ui_bindings[i].node;
42
+ __ui_nodes[n].hasTextBinding = 1;
43
+ strncpy(__ui_nodes[n].textBuffer, __ui_nodes[n].text ? __ui_nodes[n].text : "", UI_TEXT_BUF);
44
+ __ui_nodes[n].textBuffer[UI_TEXT_BUF] = '\\0';
45
+ } else if (__ui_bindings[i].fn) {
46
+ // Prime numeric binding caches once at setup; the first tick can then skip
47
+ // redundant assignments and dirty propagation when values are unchanged.
48
+ __ui_bindings[i].lastValue = __ui_bindings[i].fn();
49
+ __ui_bindings[i].initialized = 1;
50
+ }
51
+ }
52
+ // Seed virtualized-list runtime state. The fn pointers can't be baked into
53
+ // the static node initializer: ui.bindList is resolved AFTER ui.mount lowers
54
+ // the HTML, so the lowering can't see the binding yet. Instead the lowering
55
+ // emits the UIListBinding table (the binding's fn bodies) and ui_init copies
56
+ // the pointers onto each <list> node here, then computes the initial count
57
+ // and contentHeight so the first paint and the scroll clamp bound are correct.
58
+ for (uint16_t b = 0; b < __ui_list_binding_count; b++) {
59
+ uint16_t n = __ui_list_bindings[b].node;
60
+ if (n >= __ui_node_count || !__ui_nodes[n].virtualized) continue;
61
+ __ui_nodes[n].listCountFn = __ui_list_bindings[b].countFn;
62
+ __ui_nodes[n].listItemFn = __ui_list_bindings[b].itemFn;
63
+ __ui_nodes[n].listTapFn = __ui_list_bindings[b].tapFn;
64
+ }
65
+ for (uint16_t i = 0; i < __ui_node_count; i++) {
66
+ if (!__ui_nodes[i].virtualized || !__ui_nodes[i].listCountFn) continue;
67
+ uint16_t ih = __ui_nodes[i].listItemHeight > 0 ? __ui_nodes[i].listItemHeight : 24;
68
+ uint16_t cnt = __ui_nodes[i].listCountFn();
69
+ __ui_nodes[i].listCount = cnt;
70
+ __ui_nodes[i].contentHeight = static_cast<int16_t>(static_cast<uint32_t>(cnt) * ih);
71
+ __ui_nodes[i].scrollY = 0;
72
+ __ui_nodes[i].overscrollPx = 0;
73
+ __ui_nodes[i].settling = 0;
74
+ __ui_nodes[i].lastPaintedScrollY = -(__ui_nodes[i].box.h > 0 ? __ui_nodes[i].box.h : 1);
75
+ }
76
+ ui_build_draw_order();
77
+ ui_build_scroll_owner_table();
78
+ ui_refresh_active_screen_bg_node();
79
+ if (!__ui_scroll_candidates && __ui_node_count > 0) {
80
+ __ui_scroll_candidates = new (std::nothrow) UIScrollPaintCandidate[__ui_node_count];
81
+ }
82
+ }
83
+
84
+ // Debounce: ignore press/release events within 50ms of the last edge.
85
+ // Mechanical switches bounce (multiple edges in ~5-20ms); without this, the
86
+ // transition gets armed/interrupted dozens of times per physical press.
87
+ static volatile uint32_t __ui_last_edge_time = 0;
88
+ #define UI_DEBOUNCE_MS 50
89
+
90
+ static inline void ui_set_pressed(uint16_t nodeIdx, uint8_t pressed) {
91
+ __ui_nodes[nodeIdx].value = pressed ? 1 : 0;
92
+ ui_mark_dirty(nodeIdx);
93
+ for (uint16_t i = 0; i < __ui_trans_count; i++) {
94
+ if (__ui_trans[i].node == nodeIdx) {
95
+ __ui_trans[i].prevValue = __ui_trans[i].prop == PROP_FG ? __ui_nodes[nodeIdx].fg : __ui_nodes[nodeIdx].bg;
96
+ __ui_trans[i].targetValue = pressed ? __ui_trans[i].pressedTarget : __ui_trans[i].baseTarget;
97
+ __ui_trans[i].elapsed = 0;
98
+ __ui_trans[i].active = 1;
99
+ }
100
+ }
101
+ }
102
+
103
+ // Press / release entry points that node.onPress(pin) lowers to.
104
+ // On press, arm transitions toward the :pressed target color; on release,
105
+ // arm them back toward the base color (interrupt-and-re-lerp from current).
106
+ static inline void ui_on_press(uint16_t nodeIdx) {
107
+ uint32_t now = millis();
108
+ if (now - __ui_last_edge_time < UI_DEBOUNCE_MS) return;
109
+ __ui_last_edge_time = now;
110
+ ui_set_pressed(nodeIdx, 1);
111
+ }
112
+ static inline void ui_on_release(uint16_t nodeIdx) {
113
+ uint32_t now = millis();
114
+ if (now - __ui_last_edge_time < UI_DEBOUNCE_MS) return;
115
+ __ui_last_edge_time = now;
116
+ ui_set_pressed(nodeIdx, 0);
117
+ }
118
+
119
+ // Pin-watch callback type: void fn(void)
120
+ using PinWatchCallback = void (*)(void);
121
+
122
+ struct UIPinWatch {
123
+ uint8_t pin;
124
+ uint8_t lastState; // for edge detection
125
+ PinWatchCallback cb; // fires on falling edge
126
+ };
127
+
128
+ // Populated by the emit layer from ui.watchPin() calls.
129
+ extern UIPinWatch __ui_pin_watches[];
130
+ extern const uint16_t __ui_pin_watch_count;
131
+
132
+ // Poll all configured pin-watchers. Called at the start of ui_tick each frame.
133
+ // Detects falling edges with natural debounce from the ~16ms frame rate.
134
+ static inline void ui_poll_inputs() {
135
+ for (uint16_t i = 0; i < __ui_pin_watch_count; i++) {
136
+ uint8_t val = digitalRead(__ui_pin_watches[i].pin);
137
+ if (val == LOW && __ui_pin_watches[i].lastState == HIGH) {
138
+ if (__ui_pin_watches[i].cb) __ui_pin_watches[i].cb();
139
+ }
140
+ __ui_pin_watches[i].lastState = val;
141
+ }
142
+ }
140
143
  `;
141
144
  }