@typecad/ui 1.0.0-alpha.6 → 1.0.0-alpha.8

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 (86) hide show
  1. package/README.md +6 -0
  2. package/dist/engine-index.js +2 -2
  3. package/dist/preview/host-ui-runtime.d.ts +1 -0
  4. package/dist/preview/host-ui-runtime.js +56 -11
  5. package/dist/ui-engine/font-assets.d.ts +8 -0
  6. package/dist/ui-engine/font-assets.js +13 -0
  7. package/dist/ui-engine/layout-engine.js +19 -6
  8. package/dist/ui-engine/model.d.ts +1 -1
  9. package/dist/ui-engine/model.js +10 -4
  10. package/dist/ui-engine/runtime-header/antialiasing.js +52 -32
  11. package/dist/ui-engine/runtime-header/blend-bodies.js +4 -4
  12. package/dist/ui-engine/runtime-header/canvas-helpers.js +42 -69
  13. package/dist/ui-engine/runtime-header/canvas-scrollbar.js +22 -10
  14. package/dist/ui-engine/runtime-header/color-mono-refresh.js +10 -10
  15. package/dist/ui-engine/runtime-header/cuttlefish-gfx.d.ts +1 -0
  16. package/dist/ui-engine/runtime-header/cuttlefish-gfx.js +563 -0
  17. package/dist/ui-engine/runtime-header/dirty-scroll-mutators.js +26 -10
  18. package/dist/ui-engine/runtime-header/display-shim.js +8 -8
  19. package/dist/ui-engine/runtime-header/forward-decls.js +52 -10
  20. package/dist/ui-engine/runtime-header/image-drawing.js +154 -84
  21. package/dist/ui-engine/runtime-header/init-press-input.js +20 -5
  22. package/dist/ui-engine/runtime-header/keyboard.js +62 -18
  23. package/dist/ui-engine/runtime-header/node-decoration.js +8 -9
  24. package/dist/ui-engine/runtime-header/node-draw-body.d.ts +1 -0
  25. package/dist/ui-engine/runtime-header/node-draw-body.js +1402 -0
  26. package/dist/ui-engine/runtime-header/paint-order-coords.js +48 -18
  27. package/dist/ui-engine/runtime-header/paint-rects-repair.js +38 -17
  28. package/dist/ui-engine/runtime-header/scroll-physics.js +15 -18
  29. package/dist/ui-engine/runtime-header/state-bindings-nav.js +23 -13
  30. package/dist/ui-engine/runtime-header/structs.js +12 -10
  31. package/dist/ui-engine/runtime-header/text-rendering.js +132 -71
  32. package/dist/ui-engine/runtime-header/tick/bindings-phase.js +29 -10
  33. package/dist/ui-engine/runtime-header/tick/dirty-draw-phase.js +336 -749
  34. package/dist/ui-engine/runtime-header/tick/flush-phase.js +7 -3
  35. package/dist/ui-engine/runtime-header/tick/scroll-canvas-phase.js +19 -3
  36. package/dist/ui-engine/runtime-header/tick/transitions-phase.js +31 -24
  37. package/dist/ui-engine/runtime-header/touch-keyboard-fwd.js +46 -41
  38. package/dist/ui-engine/runtime-header/types-defines.js +23 -3
  39. package/dist/ui-engine/runtime-header.d.ts +10 -1
  40. package/dist/ui-engine/runtime-header.js +7 -1
  41. package/dist/ui-engine/transpile-ui.js +2 -2
  42. package/dist/ui-engine/ua-stylesheet.js +46 -3
  43. package/dist/ui-engine/ui-lowering.d.ts +1 -3
  44. package/dist/ui-engine/ui-lowering.js +18 -24
  45. package/dist/ui-engine/ui-registry.js +1 -6
  46. package/package.json +2 -2
  47. package/src/engine-index.ts +2 -2
  48. package/src/preview/host-ui-runtime.ts +56 -12
  49. package/src/ui-engine/font-assets.ts +13 -0
  50. package/src/ui-engine/layout-engine.ts +19 -6
  51. package/src/ui-engine/model.ts +10 -5
  52. package/src/ui-engine/runtime-header/antialiasing.ts +52 -32
  53. package/src/ui-engine/runtime-header/blend-bodies.ts +4 -4
  54. package/src/ui-engine/runtime-header/canvas-helpers.ts +42 -69
  55. package/src/ui-engine/runtime-header/canvas-scrollbar.ts +22 -10
  56. package/src/ui-engine/runtime-header/color-mono-refresh.ts +10 -10
  57. package/src/ui-engine/runtime-header/cuttlefish-gfx.ts +566 -0
  58. package/src/ui-engine/runtime-header/dirty-scroll-mutators.ts +26 -10
  59. package/src/ui-engine/runtime-header/display-shim.ts +8 -8
  60. package/src/ui-engine/runtime-header/forward-decls.ts +52 -10
  61. package/src/ui-engine/runtime-header/image-drawing.ts +154 -84
  62. package/src/ui-engine/runtime-header/init-press-input.ts +20 -5
  63. package/src/ui-engine/runtime-header/keyboard.ts +62 -18
  64. package/src/ui-engine/runtime-header/node-decoration.ts +8 -9
  65. package/src/ui-engine/runtime-header/node-draw-body.ts +1402 -0
  66. package/src/ui-engine/runtime-header/paint-order-coords.ts +48 -18
  67. package/src/ui-engine/runtime-header/paint-rects-repair.ts +38 -17
  68. package/src/ui-engine/runtime-header/scroll-physics.ts +15 -18
  69. package/src/ui-engine/runtime-header/state-bindings-nav.ts +23 -13
  70. package/src/ui-engine/runtime-header/structs.ts +12 -10
  71. package/src/ui-engine/runtime-header/text-rendering.ts +132 -71
  72. package/src/ui-engine/runtime-header/tick/bindings-phase.ts +29 -10
  73. package/src/ui-engine/runtime-header/tick/dirty-draw-phase.ts +336 -749
  74. package/src/ui-engine/runtime-header/tick/flush-phase.ts +7 -3
  75. package/src/ui-engine/runtime-header/tick/scroll-canvas-phase.ts +19 -3
  76. package/src/ui-engine/runtime-header/tick/transitions-phase.ts +31 -24
  77. package/src/ui-engine/runtime-header/touch-keyboard-fwd.ts +46 -41
  78. package/src/ui-engine/runtime-header/types-defines.ts +23 -3
  79. package/src/ui-engine/runtime-header.ts +17 -1
  80. package/src/ui-engine/transpile-ui.ts +2 -2
  81. package/src/ui-engine/ua-stylesheet.ts +46 -3
  82. package/src/ui-engine/ui-lowering.ts +19 -26
  83. package/src/ui-engine/ui-registry.ts +1 -7
  84. package/dist/ui-engine/scroll-memory-diagnostics.d.ts +0 -16
  85. package/dist/ui-engine/scroll-memory-diagnostics.js +0 -58
  86. package/src/ui-engine/scroll-memory-diagnostics.ts +0 -79
@@ -1,20 +1,31 @@
1
1
  // Slice of the C++ runtime header (original source lines 622-752).
2
2
  // navigate, canvas/scroll/text/paint/clip/AA/image forward declarations, persistent canvas globals.
3
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.
4
10
  export function emitForwardDecls() {
5
11
  return `
6
- static uint8_t __ui_fade_opacity = 100; // fade-in animation (0=transparent, 100=full)
7
- static uint16_t __ui_fade_elapsed = 0;
8
- static uint16_t __ui_fade_duration = 200; // ms
9
-
10
12
  // Early forward declaration: ui_navigate (below) calls ui_release_canvas_state
11
13
  // and ui_set_pressed (defined later) during screen changes. Needed on native
12
14
  // (single TU, no Arduino auto-prototyper).
13
15
  static inline void ui_release_canvas_state();
14
16
  static inline void ui_set_pressed(uint16_t nodeIdx, uint8_t pressed);
15
17
  static inline void ui_refresh_active_screen_bg_node();
18
+ static inline CuttlefishCanvas16* ui_get_framebuffer();
19
+ // Set when the next frame must be composed from the active screen background;
20
+ // declared before ui_navigate because navigation can request that composition.
21
+ static uint8_t __ui_fb_needs_compose = 1;
16
22
 
17
- // Navigate to a screen by index. Marks the new screen's nodes dirty + starts fade.
23
+ // Navigate to a screen by index. Marks the new screen's nodes dirty, releases
24
+ // persistent canvas state, and requests a complete off-screen composition when
25
+ // the full-frame path is available. The physical panel is never cleared here on
26
+ // that path: navigation must not expose an intermediate frame while the next
27
+ // frame is built. Without a full-frame buffer, the fallback remains best-effort;
28
+ // no-TE TFT hardware cannot provide a strict tear-free guarantee.
18
29
  static inline void ui_navigate(uint8_t screenIdx) {
19
30
  if (screenIdx >= __ui_screen_count || screenIdx == __ui_active_screen) return;
20
31
  __ui_active_screen = screenIdx;
@@ -26,7 +37,7 @@ static inline void ui_navigate(uint8_t screenIdx) {
26
37
  // and the button would stay stuck in its :pressed color.
27
38
  if (__ui_touch_node >= 0 && __ui_touch_node < __ui_node_count &&
28
39
  __ui_nodes[__ui_touch_node].kind == NODE_BUTTON && __ui_nodes[__ui_touch_node].value != 0) {
29
- ui_set_pressed((uint16_t)__ui_touch_node, 0);
40
+ ui_set_pressed(static_cast<uint16_t>(__ui_touch_node), 0);
30
41
  }
31
42
  __ui_scroll_node = -1;
32
43
  __ui_touch_node = -1;
@@ -37,17 +48,34 @@ static inline void ui_navigate(uint8_t screenIdx) {
37
48
  // resident and fragments the heap, so the new screen's buffer can't get a
38
49
  // contiguous block (the "works first, then blanks until reset" symptom).
39
50
  ui_release_canvas_state();
40
- // Clear the entire display so old screen content doesn't show. On deferred-
41
- // refresh panels (e-ink) a full clear flashes, so skip it — the all-nodes-
42
- // dirty marking below drives a full repaint via partial refresh instead.
51
+ // A framebuffer-capable target keeps the old frame visible until the new one
52
+ // is complete. Targets without enough memory retain the legacy clear fallback
53
+ // so stale pixels from the old screen cannot remain; this constrained path is
54
+ // best-effort because the panel has no TE/vblank synchronization.
55
+ if (ui_get_framebuffer()) {
56
+ __ui_fb_needs_compose = 1;
57
+ } else {
43
58
  #ifndef UI_REFRESH_DEFERRED
44
- display_fillScreen(0x0000);
59
+ UI_COLOR_T navBg = (UI_COLOR_T)0;
60
+ if (__ui_active_screen_bg_node < __ui_node_count) {
61
+ navBg = __ui_nodes[__ui_active_screen_bg_node].hasBg
62
+ ? __ui_nodes[__ui_active_screen_bg_node].bg
63
+ : __ui_nodes[__ui_active_screen_bg_node].clearColor;
64
+ }
65
+ display_fillScreen(navBg);
45
66
  #endif
67
+ }
46
68
  // Mark all nodes dirty so the new screen fully redraws.
47
69
  for (uint16_t i = 0; i < __ui_node_count; i++) {
48
70
  __ui_nodes[i].dirty = 1;
49
71
  __ui_nodes[i].lastTextHeight = 0;
50
72
  __ui_nodes[i].layoutCacheKey = 0;
73
+ if (__ui_nodes[i].scrollable) {
74
+ // The next screen needs a complete scroll-canvas seed; subsequent local
75
+ // child updates can use retained-canvas repairs.
76
+ __ui_nodes[i].lastPaintedScrollY = static_cast<int16_t>(
77
+ __ui_nodes[i].scrollY - (__ui_nodes[i].box.h > 0 ? __ui_nodes[i].box.h : 1));
78
+ }
51
79
  if (__ui_nodes[i].kind == NODE_PROGRESS || __ui_nodes[i].kind == NODE_RANGE) __ui_nodes[i].lastTextWidth = -1;
52
80
  else __ui_nodes[i].lastTextWidth = 0;
53
81
  }
@@ -80,6 +108,7 @@ static inline uint8_t ui_subtree_current_paint_rect(uint16_t nodeIdx, UIRect* ou
80
108
  static inline void ui_mark_overlapping_higher_layers_dirty(uint16_t nodeIdx);
81
109
  static inline void ui_mark_overlapping_higher_layers_dirty_for_rect(uint16_t nodeIdx, const UIRect* r);
82
110
  static inline void ui_mark_scroll_view_dirty(uint16_t scrollNode);
111
+ static inline UI_COLOR_T ui_parent_clear_color(uint16_t nodeIdx);
83
112
  static inline void ui_release_canvas_state();
84
113
  static inline void ui_set_visible(uint16_t nodeIdx, uint8_t visible);
85
114
  static inline void ui_invalidate_scroll_canvas_for_node(uint16_t nodeIdx);
@@ -93,15 +122,22 @@ static inline void ui_draw_node_decoration_clipped(uint16_t nodeIdx, int16_t dra
93
122
  // single native translation unit (Arduino's auto-prototyper hides this;
94
123
  // native emits one TU so explicit forwards are needed).
95
124
  static inline int8_t ui_rich_link_hit(uint16_t nodeIdx, int16_t px, int16_t py);
125
+ #ifdef UI_AA
96
126
  static inline CuttlefishCanvas16* ui_aa_begin(int16_t w, int16_t h, UI_COLOR_T bg);
97
127
  static inline void ui_aa_end(CuttlefishCanvas16* c);
98
128
  static inline void ui_aa_push(CuttlefishCanvas16* c, int16_t dx, int16_t dy);
99
129
  static inline void ui_aa_line(CuttlefishCanvas16* c, float x0, float y0, float x1, float y1, UI_COLOR_T color);
100
130
  static inline void ui_aa_circle(CuttlefishCanvas16* c, int16_t cx, int16_t cy, float r, UI_COLOR_T color);
101
131
  static inline void ui_aa_fill_circle(CuttlefishCanvas16* c, int16_t cx, int16_t cy, float r, UI_COLOR_T color);
132
+ #endif
102
133
  static inline uint8_t ui_repair_current_node_paint_with_parent(uint16_t nodeIdx, UIRect* r);
103
134
  static inline void ui_clear_node_paint_rect(uint16_t nodeIdx, const UIRect* paintRect);
104
135
  static inline uint8_t ui_try_repair_geometry_fill(uint16_t nodeIdx, const UIRect* oldRect);
136
+ // Band renderers (defined in node-draw-body; forward-declared so the NODE_LIST
137
+ // case can fall back to ui_render_list_bands before its definition site).
138
+ static inline uint8_t ui_render_node_bands(uint16_t nodeIdx, int16_t prX, int16_t prY, int16_t prW, int16_t prH);
139
+ static inline uint8_t ui_render_list_bands(uint16_t i);
140
+ static inline uint8_t ui_render_list_direct(uint16_t i);
105
141
  static inline void ui_draw_node_border(uint16_t i, int16_t drawX, int16_t drawY, UI_COLOR_T color);
106
142
  static inline void ui_draw_node_outline(uint16_t i, int16_t drawX, int16_t drawY);
107
143
  static inline void ui_draw_gradient_fill(uint16_t i, int16_t drawY);
@@ -115,6 +151,10 @@ static inline void ui_draw_scaled_image(const UIImage* img, int16_t x, int16_t y
115
151
  int16_t drawW, int16_t drawH);
116
152
 
117
153
  static CuttlefishDisplayTarget* __ui_gfx = display_defaultTarget();
154
+ // Full-frame composition is required for the first frame and after navigation.
155
+ // Once composed, subsequent dirty updates can safely modify the retained
156
+ // framebuffer without rebuilding unchanged nodes. The flags are declared
157
+ // before ui_navigate above because navigation sets the compose request.
118
158
  // Persistent canvas slots, all freed on screen change (ui_navigate) so each
119
159
  // screen starts with a clean heap. Without this, the first screen's canvas
120
160
  // buffer stays resident and fragments the heap, so a later screen's buffer
@@ -126,6 +166,8 @@ static CuttlefishCanvas16* __ui_list_canvas = nullptr; // virtualized <lis
126
166
  static int16_t __ui_list_canvas_node = -1; // node currently represented by __ui_list_canvas
127
167
  static CuttlefishCanvas16* __ui_node_canvas = nullptr; // <canvas> element offscreen
128
168
  static CuttlefishCanvas16* __ui_repair_canvas = nullptr; // buffered-paint / exposed-strip
169
+ static CuttlefishCanvas16* __ui_band_canvas = nullptr; // band renderer (no-PSRAM scroll)
170
+ static CuttlefishCanvas16* __ui_kb_canvas = nullptr; // on-screen keyboard overlay
129
171
  static int16_t __ui_canvas_fallback_w = 0; // dimensions for direct <canvas> fallback
130
172
  static int16_t __ui_canvas_fallback_h = 0;
131
173
  // Draw offset: normally zero. The <canvas> allocation fallback sets it so
@@ -3,12 +3,54 @@
3
3
  // See docs/superpowers/specs/2026-07-12-split-runtime-header-design.md.
4
4
  export function emitImageDrawing() {
5
5
  return `
6
- // ── Full-screen framebuffer (opt-in PSRAM perf experiment) ───────────────────
7
- // Disabled by default: pushing a full 320×240 frame for a tiny dirty button can
8
- // look like a global brightness flash on SPI TFTs. Enable only when a target
9
- // has enough PSRAM and a full-frame repaint is preferable to partial updates.
6
+ // ── Full-screen framebuffer (opt-in PSRAM composition path) ──────────────────
7
+ // Composes a complete frame off-screen so navigation and dirty-node updates do
8
+ // not expose intermediate clears/primitive writes. The panel still receives a
9
+ // sequential SPI transfer; without a TE/vblank signal this is a strong tearing
10
+ // reduction, not a strict hardware-level tear-free guarantee.
10
11
  static CuttlefishCanvas16* __ui_fb = nullptr;
11
12
  static uint8_t __ui_fb_tried = 0; // 0 = not yet attempted, 1 = alloc attempted
13
+ // Dirty bounds for the retained framebuffer. A local touch update should not
14
+ // retransmit the entire panel: the full-frame burst is long enough to look like
15
+ // a brightness dip on SPI TFTs even though the framebuffer itself is coherent.
16
+ static uint8_t __ui_fb_dirty = 0;
17
+ static int16_t __ui_fb_dirty_x0 = 0;
18
+ static int16_t __ui_fb_dirty_y0 = 0;
19
+ static int16_t __ui_fb_dirty_x1 = 0;
20
+ static int16_t __ui_fb_dirty_y1 = 0;
21
+
22
+ static inline void ui_fb_begin_frame() {
23
+ __ui_fb_dirty = 0;
24
+ __ui_fb_dirty_x0 = display_width();
25
+ __ui_fb_dirty_y0 = display_height();
26
+ __ui_fb_dirty_x1 = 0;
27
+ __ui_fb_dirty_y1 = 0;
28
+ }
29
+
30
+ static inline void ui_fb_add_rect(int16_t x, int16_t y, int16_t w, int16_t h) {
31
+ if (w <= 0 || h <= 0) return;
32
+ int16_t x0 = x < 0 ? 0 : x;
33
+ int16_t y0 = y < 0 ? 0 : y;
34
+ int16_t x1 = x + w;
35
+ int16_t y1 = y + h;
36
+ int16_t dw = display_width();
37
+ int16_t dh = display_height();
38
+ if (x1 > dw) x1 = dw;
39
+ if (y1 > dh) y1 = dh;
40
+ if (x0 >= x1 || y0 >= y1) return;
41
+ if (!__ui_fb_dirty) {
42
+ __ui_fb_dirty_x0 = x0;
43
+ __ui_fb_dirty_y0 = y0;
44
+ __ui_fb_dirty_x1 = x1;
45
+ __ui_fb_dirty_y1 = y1;
46
+ __ui_fb_dirty = 1;
47
+ return;
48
+ }
49
+ if (x0 < __ui_fb_dirty_x0) __ui_fb_dirty_x0 = x0;
50
+ if (y0 < __ui_fb_dirty_y0) __ui_fb_dirty_y0 = y0;
51
+ if (x1 > __ui_fb_dirty_x1) __ui_fb_dirty_x1 = x1;
52
+ if (y1 > __ui_fb_dirty_y1) __ui_fb_dirty_y1 = y1;
53
+ }
12
54
 
13
55
  // Get the framebuffer, allocating once (in PSRAM when available). Returns null
14
56
  // when PSRAM is absent or the allocation failed — callers fall back to direct.
@@ -31,14 +73,42 @@ static inline CuttlefishCanvas16* ui_get_framebuffer() {
31
73
  return __ui_fb;
32
74
  }
33
75
 
34
- // Push the entire framebuffer to the display in one bulk SPI transaction.
76
+ // Push only the changed framebuffer bounds. Rows are sent separately when the
77
+ // dirty union is narrower than the framebuffer because the source canvas has a
78
+ // wider stride than the panel window. Each row is complete before it reaches
79
+ // the panel, and the display adapter can synchronize each window to scanline.
35
80
  static inline void ui_push_framebuffer() {
36
- if (!__ui_fb || !display_canvasBuffer(__ui_fb)) return;
37
- int16_t w = display_canvasWidth(__ui_fb);
38
- int16_t h = display_canvasHeight(__ui_fb);
81
+ if (!__ui_fb || !display_canvasBuffer(__ui_fb) || !__ui_fb_dirty) return;
82
+ int16_t fw = display_canvasWidth(__ui_fb);
83
+ int16_t fh = display_canvasHeight(__ui_fb);
84
+ int16_t x = __ui_fb_dirty_x0;
85
+ int16_t y = __ui_fb_dirty_y0;
86
+ int16_t w = static_cast<int16_t>(__ui_fb_dirty_x1 - x);
87
+ int16_t h = static_cast<int16_t>(__ui_fb_dirty_y1 - y);
88
+ if (x < 0 || y < 0 || w <= 0 || h <= 0 || x >= fw || y >= fh) return;
89
+ if (x + w > fw) w = static_cast<int16_t>(fw - x);
90
+ if (y + h > fh) h = static_cast<int16_t>(fh - y);
91
+ // Use the panel's native bulk-pixel path, not drawRGBBitmap(). The native
92
+ // CuttlefishGFX implementation of drawRGBBitmap is intentionally generic and
93
+ // emits one writePixel transaction per pixel; using it for a retained-frame
94
+ // flush turns a small local update into hundreds of visible SPI flashes.
95
+ UI_COLOR_T* pixels = display_canvasBuffer(__ui_fb);
39
96
  display_startWrite();
40
- display_setAddrWindow(0, 0, w, h);
41
- display_writePixels(display_canvasBuffer(__ui_fb), (uint32_t)w * h);
97
+ if (x == 0 && w == fw) {
98
+ // Full-width rows are contiguous and can use one bulk write.
99
+ display_setAddrWindow(x, y, w, h);
100
+ display_writePixels(pixels + static_cast<int32_t>(y) * fw,
101
+ static_cast<uint32_t>(w) * h);
102
+ } else {
103
+ // Narrow dirty bounds require one row at a time because the source stride
104
+ // is fw while each destination window is only w pixels wide.
105
+ for (int16_t row = 0; row < h; row++) {
106
+ display_setAddrWindow(x, static_cast<int16_t>(y + row), w, 1);
107
+ display_writePixels(
108
+ pixels + static_cast<int32_t>(y + row) * fw + x,
109
+ static_cast<uint32_t>(w));
110
+ }
111
+ }
42
112
  display_endWrite();
43
113
  }
44
114
 
@@ -82,8 +152,8 @@ static inline void ui_draw_image_with_fit(const UIImage* img, int16_t x, int16_t
82
152
  offX = 0;
83
153
  offY = 0;
84
154
  } else if (fitMode == 2 || fitMode == 3 || fitMode == 4) {
85
- int32_t scaleX = ((int32_t)targetW * 1000) / srcW;
86
- int32_t scaleY = ((int32_t)targetH * 1000) / srcH;
155
+ int32_t scaleX = (static_cast<int32_t>(targetW) * 1000) / srcW;
156
+ int32_t scaleY = (static_cast<int32_t>(targetH) * 1000) / srcH;
87
157
  if (scaleX < 1) scaleX = 1;
88
158
  if (scaleY < 1) scaleY = 1;
89
159
  int32_t scale = scaleX;
@@ -95,8 +165,8 @@ static inline void ui_draw_image_with_fit(const UIImage* img, int16_t x, int16_t
95
165
  if (scaleY < scaleX) scale = scaleY;
96
166
  if (scale > 1000) scale = 1000;
97
167
  }
98
- drawW = (int16_t)((int32_t)srcW * scale / 1000);
99
- drawH = (int16_t)((int32_t)srcH * scale / 1000);
168
+ drawW = static_cast<int16_t>(static_cast<int32_t>(srcW) * scale / 1000);
169
+ drawH = static_cast<int16_t>(static_cast<int32_t>(srcH) * scale / 1000);
100
170
  if (drawW < 1) drawW = 1;
101
171
  if (drawH < 1) drawH = 1;
102
172
  if (fitMode == 3) {
@@ -115,40 +185,40 @@ static inline void ui_draw_image_with_fit(const UIImage* img, int16_t x, int16_t
115
185
  if (!ui_clip_rect_to_display_target(&clipX, &clipY, &clipW, &clipH)) return;
116
186
  int16_t txStart = 0, txEnd = targetW, tyStart = 0, tyEnd = targetH;
117
187
  if (q == 0) {
118
- txStart = ui_clamp_i16((int16_t)(clipX - x), 0, targetW);
119
- txEnd = ui_clamp_i16((int16_t)(clipX + clipW - x), 0, targetW);
120
- tyStart = ui_clamp_i16((int16_t)(clipY - y), 0, targetH);
121
- tyEnd = ui_clamp_i16((int16_t)(clipY + clipH - y), 0, targetH);
188
+ txStart = ui_clamp_i16(static_cast<int16_t>(clipX - x), 0, targetW);
189
+ txEnd = ui_clamp_i16(static_cast<int16_t>(clipX + clipW - x), 0, targetW);
190
+ tyStart = ui_clamp_i16(static_cast<int16_t>(clipY - y), 0, targetH);
191
+ tyEnd = ui_clamp_i16(static_cast<int16_t>(clipY + clipH - y), 0, targetH);
122
192
  } else if (q == 1) {
123
- txStart = ui_clamp_i16((int16_t)(clipY - y), 0, targetW);
124
- txEnd = ui_clamp_i16((int16_t)(clipY + clipH - y), 0, targetW);
125
- tyStart = ui_clamp_i16((int16_t)(x + targetH - (clipX + clipW)), 0, targetH);
126
- tyEnd = ui_clamp_i16((int16_t)(x + targetH - clipX), 0, targetH);
193
+ txStart = ui_clamp_i16(static_cast<int16_t>(clipY - y), 0, targetW);
194
+ txEnd = ui_clamp_i16(static_cast<int16_t>(clipY + clipH - y), 0, targetW);
195
+ tyStart = ui_clamp_i16(static_cast<int16_t>(x + targetH - (clipX + clipW)), 0, targetH);
196
+ tyEnd = ui_clamp_i16(static_cast<int16_t>(x + targetH - clipX), 0, targetH);
127
197
  } else if (q == 2) {
128
- txStart = ui_clamp_i16((int16_t)(x + targetW - (clipX + clipW)), 0, targetW);
129
- txEnd = ui_clamp_i16((int16_t)(x + targetW - clipX), 0, targetW);
130
- tyStart = ui_clamp_i16((int16_t)(y + targetH - (clipY + clipH)), 0, targetH);
131
- tyEnd = ui_clamp_i16((int16_t)(y + targetH - clipY), 0, targetH);
198
+ txStart = ui_clamp_i16(static_cast<int16_t>(x + targetW - (clipX + clipW)), 0, targetW);
199
+ txEnd = ui_clamp_i16(static_cast<int16_t>(x + targetW - clipX), 0, targetW);
200
+ tyStart = ui_clamp_i16(static_cast<int16_t>(y + targetH - (clipY + clipH)), 0, targetH);
201
+ tyEnd = ui_clamp_i16(static_cast<int16_t>(y + targetH - clipY), 0, targetH);
132
202
  } else {
133
- txStart = ui_clamp_i16((int16_t)(y + targetW - (clipY + clipH)), 0, targetW);
134
- txEnd = ui_clamp_i16((int16_t)(y + targetW - clipY), 0, targetW);
135
- tyStart = ui_clamp_i16((int16_t)(clipX - x), 0, targetH);
136
- tyEnd = ui_clamp_i16((int16_t)(clipX + clipW - x), 0, targetH);
203
+ txStart = ui_clamp_i16(static_cast<int16_t>(y + targetW - (clipY + clipH)), 0, targetW);
204
+ txEnd = ui_clamp_i16(static_cast<int16_t>(y + targetW - clipY), 0, targetW);
205
+ tyStart = ui_clamp_i16(static_cast<int16_t>(clipX - x), 0, targetH);
206
+ tyEnd = ui_clamp_i16(static_cast<int16_t>(clipX + clipW - x), 0, targetH);
137
207
  }
138
208
  if (txStart >= txEnd || tyStart >= tyEnd) return;
139
209
  for (int16_t ty = tyStart; ty < tyEnd; ty++) {
140
210
  int16_t localY = ty - offY;
141
211
  if (localY < 0 || localY >= drawH) continue;
142
- int16_t srcY = ((int32_t)localY * srcH) / drawH;
212
+ int16_t srcY = (static_cast<int32_t>(localY) * srcH) / drawH;
143
213
  if (srcY < 0) srcY = 0;
144
214
  if (srcY >= srcH) srcY = srcH - 1;
145
215
  for (int16_t tx = txStart; tx < txEnd; tx++) {
146
216
  int16_t localX = tx - offX;
147
217
  if (localX < 0 || localX >= drawW) continue;
148
- int16_t srcX = ((int32_t)localX * srcW) / drawW;
218
+ int16_t srcX = (static_cast<int32_t>(localX) * srcW) / drawW;
149
219
  if (srcX < 0) srcX = 0;
150
220
  if (srcX >= srcW) srcX = srcW - 1;
151
- UI_COLOR_T color = img->data[(int32_t)srcY * srcW + srcX];
221
+ UI_COLOR_T color = img->data[static_cast<int32_t>(srcY) * srcW + srcX];
152
222
  int16_t dx = tx;
153
223
  int16_t dy = ty;
154
224
  if (q == 1) {
@@ -177,43 +247,43 @@ static inline void ui_draw_scaled_image(const UIImage* img, int16_t x, int16_t y
177
247
  if (!ui_clip_rect_to_display_target(&clipX, &clipY, &clipW, &clipH)) return;
178
248
  int16_t dxStart = 0, dxEnd = drawW, dyStart = 0, dyEnd = drawH;
179
249
  if (q == 0) {
180
- dxStart = ui_clamp_i16((int16_t)(clipX - x), 0, drawW);
181
- dxEnd = ui_clamp_i16((int16_t)(clipX + clipW - x), 0, drawW);
182
- dyStart = ui_clamp_i16((int16_t)(clipY - y), 0, drawH);
183
- dyEnd = ui_clamp_i16((int16_t)(clipY + clipH - y), 0, drawH);
250
+ dxStart = ui_clamp_i16(static_cast<int16_t>(clipX - x), 0, drawW);
251
+ dxEnd = ui_clamp_i16(static_cast<int16_t>(clipX + clipW - x), 0, drawW);
252
+ dyStart = ui_clamp_i16(static_cast<int16_t>(clipY - y), 0, drawH);
253
+ dyEnd = ui_clamp_i16(static_cast<int16_t>(clipY + clipH - y), 0, drawH);
184
254
  } else if (q == 1) {
185
- dxStart = ui_clamp_i16((int16_t)(clipY - y), 0, drawW);
186
- dxEnd = ui_clamp_i16((int16_t)(clipY + clipH - y), 0, drawW);
187
- dyStart = ui_clamp_i16((int16_t)(x + drawH - (clipX + clipW)), 0, drawH);
188
- dyEnd = ui_clamp_i16((int16_t)(x + drawH - clipX), 0, drawH);
255
+ dxStart = ui_clamp_i16(static_cast<int16_t>(clipY - y), 0, drawW);
256
+ dxEnd = ui_clamp_i16(static_cast<int16_t>(clipY + clipH - y), 0, drawW);
257
+ dyStart = ui_clamp_i16(static_cast<int16_t>(x + drawH - (clipX + clipW)), 0, drawH);
258
+ dyEnd = ui_clamp_i16(static_cast<int16_t>(x + drawH - clipX), 0, drawH);
189
259
  } else if (q == 2) {
190
- dxStart = ui_clamp_i16((int16_t)(x + drawW - (clipX + clipW)), 0, drawW);
191
- dxEnd = ui_clamp_i16((int16_t)(x + drawW - clipX), 0, drawW);
192
- dyStart = ui_clamp_i16((int16_t)(y + drawH - (clipY + clipH)), 0, drawH);
193
- dyEnd = ui_clamp_i16((int16_t)(y + drawH - clipY), 0, drawH);
260
+ dxStart = ui_clamp_i16(static_cast<int16_t>(x + drawW - (clipX + clipW)), 0, drawW);
261
+ dxEnd = ui_clamp_i16(static_cast<int16_t>(x + drawW - clipX), 0, drawW);
262
+ dyStart = ui_clamp_i16(static_cast<int16_t>(y + drawH - (clipY + clipH)), 0, drawH);
263
+ dyEnd = ui_clamp_i16(static_cast<int16_t>(y + drawH - clipY), 0, drawH);
194
264
  } else {
195
- dxStart = ui_clamp_i16((int16_t)(y + drawW - (clipY + clipH)), 0, drawW);
196
- dxEnd = ui_clamp_i16((int16_t)(y + drawW - clipY), 0, drawW);
197
- dyStart = ui_clamp_i16((int16_t)(clipX - x), 0, drawH);
198
- dyEnd = ui_clamp_i16((int16_t)(clipX + clipW - x), 0, drawH);
265
+ dxStart = ui_clamp_i16(static_cast<int16_t>(y + drawW - (clipY + clipH)), 0, drawW);
266
+ dxEnd = ui_clamp_i16(static_cast<int16_t>(y + drawW - clipY), 0, drawW);
267
+ dyStart = ui_clamp_i16(static_cast<int16_t>(clipX - x), 0, drawH);
268
+ dyEnd = ui_clamp_i16(static_cast<int16_t>(clipX + clipW - x), 0, drawH);
199
269
  }
200
270
  if (dxStart >= dxEnd || dyStart >= dyEnd) return;
201
271
  if (q == 0) {
202
272
  // Simple case: no rotation, draw with scaling
203
273
  if (drawW == img->w && drawH == img->h) {
204
- int16_t rows = (int16_t)(dyEnd - dyStart);
205
- int16_t cols = (int16_t)(dxEnd - dxStart);
274
+ int16_t rows = static_cast<int16_t>(dyEnd - dyStart);
275
+ int16_t cols = static_cast<int16_t>(dxEnd - dxStart);
206
276
  for (int16_t row = 0; row < rows; row++) {
207
- ui_display_draw_rgb_bitmap((int16_t)(x + dxStart), (int16_t)(y + dyStart + row),
208
- img->data + (int32_t)(dyStart + row) * img->w + dxStart, cols, 1);
277
+ ui_display_draw_rgb_bitmap(static_cast<int16_t>(x + dxStart), static_cast<int16_t>(y + dyStart + row),
278
+ img->data + static_cast<int32_t>(dyStart + row) * img->w + dxStart, cols, 1);
209
279
  }
210
280
  } else {
211
281
  // Scale using nearest-neighbor
212
282
  for (int16_t dy = dyStart; dy < dyEnd; dy++) {
213
- int16_t srcY = ((int32_t)dy * img->h) / drawH;
283
+ int16_t srcY = (static_cast<int32_t>(dy) * img->h) / drawH;
214
284
  for (int16_t dx = dxStart; dx < dxEnd; dx++) {
215
- int16_t srcX = ((int32_t)dx * img->w) / drawW;
216
- UI_COLOR_T color = img->data[(int32_t)srcY * img->w + srcX];
285
+ int16_t srcX = (static_cast<int32_t>(dx) * img->w) / drawW;
286
+ UI_COLOR_T color = img->data[static_cast<int32_t>(srcY) * img->w + srcX];
217
287
  ui_display_draw_pixel(x + dx, y + dy, color);
218
288
  }
219
289
  }
@@ -221,10 +291,10 @@ static inline void ui_draw_scaled_image(const UIImage* img, int16_t x, int16_t y
221
291
  return;
222
292
  }
223
293
  for (int16_t dy = dyStart; dy < dyEnd; dy++) {
224
- int16_t srcY = ((int32_t)dy * img->h) / drawH;
294
+ int16_t srcY = (static_cast<int32_t>(dy) * img->h) / drawH;
225
295
  for (int16_t dx = dxStart; dx < dxEnd; dx++) {
226
- int16_t srcX = ((int32_t)dx * img->w) / drawW;
227
- UI_COLOR_T color = img->data[(int32_t)srcY * img->w + srcX];
296
+ int16_t srcX = (static_cast<int32_t>(dx) * img->w) / drawW;
297
+ UI_COLOR_T color = img->data[static_cast<int32_t>(srcY) * img->w + srcX];
228
298
  int16_t rdx = 0, rdy = 0;
229
299
  if (q == 1) {
230
300
  rdx = drawH - 1 - dy;
@@ -249,48 +319,48 @@ static inline void ui_draw_image_rotated(const UIImage* img, int16_t x, int16_t
249
319
  int16_t clipH = (q == 1 || q == 3) ? img->w : img->h;
250
320
  if (!ui_clip_rect_to_display_target(&clipX, &clipY, &clipW, &clipH)) return;
251
321
  if (q == 0) {
252
- int16_t sx0 = ui_clamp_i16((int16_t)(clipX - x), 0, (int16_t)img->w);
253
- int16_t sy0 = ui_clamp_i16((int16_t)(clipY - y), 0, (int16_t)img->h);
254
- int16_t sw = ui_clamp_i16(clipW, 0, (int16_t)(img->w - sx0));
255
- int16_t sh = ui_clamp_i16(clipH, 0, (int16_t)(img->h - sy0));
322
+ int16_t sx0 = ui_clamp_i16(static_cast<int16_t>(clipX - x), 0, static_cast<int16_t>(img->w));
323
+ int16_t sy0 = ui_clamp_i16(static_cast<int16_t>(clipY - y), 0, static_cast<int16_t>(img->h));
324
+ int16_t sw = ui_clamp_i16(clipW, 0, static_cast<int16_t>(img->w - sx0));
325
+ int16_t sh = ui_clamp_i16(clipH, 0, static_cast<int16_t>(img->h - sy0));
256
326
  for (int16_t row = 0; row < sh; row++) {
257
- ui_display_draw_rgb_bitmap((int16_t)(x + sx0), (int16_t)(y + sy0 + row),
258
- img->data + (int32_t)(sy0 + row) * img->w + sx0, sw, 1);
327
+ ui_display_draw_rgb_bitmap(static_cast<int16_t>(x + sx0), static_cast<int16_t>(y + sy0 + row),
328
+ img->data + static_cast<int32_t>(sy0 + row) * img->w + sx0, sw, 1);
259
329
  }
260
330
  return;
261
331
  }
262
332
  uint16_t sxStart = 0, sxEnd = img->w, syStart = 0, syEnd = img->h;
263
333
  if (q == 1) {
264
- sxStart = (uint16_t)ui_clamp_i16((int16_t)(clipY - y), 0, (int16_t)img->w);
265
- sxEnd = (uint16_t)ui_clamp_i16((int16_t)(clipY + clipH - y), 0, (int16_t)img->w);
266
- syStart = (uint16_t)ui_clamp_i16((int16_t)(x + img->h - (clipX + clipW)), 0, (int16_t)img->h);
267
- syEnd = (uint16_t)ui_clamp_i16((int16_t)(x + img->h - clipX), 0, (int16_t)img->h);
334
+ sxStart = static_cast<uint16_t>(ui_clamp_i16(static_cast<int16_t>(clipY - y), 0, static_cast<int16_t>(img->w)));
335
+ sxEnd = static_cast<uint16_t>(ui_clamp_i16(static_cast<int16_t>(clipY + clipH - y), 0, static_cast<int16_t>(img->w)));
336
+ syStart = static_cast<uint16_t>(ui_clamp_i16(static_cast<int16_t>(x + img->h - (clipX + clipW)), 0, static_cast<int16_t>(img->h)));
337
+ syEnd = static_cast<uint16_t>(ui_clamp_i16(static_cast<int16_t>(x + img->h - clipX), 0, static_cast<int16_t>(img->h)));
268
338
  } else if (q == 2) {
269
- sxStart = (uint16_t)ui_clamp_i16((int16_t)(x + img->w - (clipX + clipW)), 0, (int16_t)img->w);
270
- sxEnd = (uint16_t)ui_clamp_i16((int16_t)(x + img->w - clipX), 0, (int16_t)img->w);
271
- syStart = (uint16_t)ui_clamp_i16((int16_t)(y + img->h - (clipY + clipH)), 0, (int16_t)img->h);
272
- syEnd = (uint16_t)ui_clamp_i16((int16_t)(y + img->h - clipY), 0, (int16_t)img->h);
339
+ sxStart = static_cast<uint16_t>(ui_clamp_i16(static_cast<int16_t>(x + img->w - (clipX + clipW)), 0, static_cast<int16_t>(img->w)));
340
+ sxEnd = static_cast<uint16_t>(ui_clamp_i16(static_cast<int16_t>(x + img->w - clipX), 0, static_cast<int16_t>(img->w)));
341
+ syStart = static_cast<uint16_t>(ui_clamp_i16(static_cast<int16_t>(y + img->h - (clipY + clipH)), 0, static_cast<int16_t>(img->h)));
342
+ syEnd = static_cast<uint16_t>(ui_clamp_i16(static_cast<int16_t>(y + img->h - clipY), 0, static_cast<int16_t>(img->h)));
273
343
  } else {
274
- sxStart = (uint16_t)ui_clamp_i16((int16_t)(y + img->w - (clipY + clipH)), 0, (int16_t)img->w);
275
- sxEnd = (uint16_t)ui_clamp_i16((int16_t)(y + img->w - clipY), 0, (int16_t)img->w);
276
- syStart = (uint16_t)ui_clamp_i16((int16_t)(clipX - x), 0, (int16_t)img->h);
277
- syEnd = (uint16_t)ui_clamp_i16((int16_t)(clipX + clipW - x), 0, (int16_t)img->h);
344
+ sxStart = static_cast<uint16_t>(ui_clamp_i16(static_cast<int16_t>(y + img->w - (clipY + clipH)), 0, static_cast<int16_t>(img->w)));
345
+ sxEnd = static_cast<uint16_t>(ui_clamp_i16(static_cast<int16_t>(y + img->w - clipY), 0, static_cast<int16_t>(img->w)));
346
+ syStart = static_cast<uint16_t>(ui_clamp_i16(static_cast<int16_t>(clipX - x), 0, static_cast<int16_t>(img->h)));
347
+ syEnd = static_cast<uint16_t>(ui_clamp_i16(static_cast<int16_t>(clipX + clipW - x), 0, static_cast<int16_t>(img->h)));
278
348
  }
279
349
  if (sxStart >= sxEnd || syStart >= syEnd) return;
280
350
  for (uint16_t sy = syStart; sy < syEnd; sy++) {
281
351
  for (uint16_t sx = sxStart; sx < sxEnd; sx++) {
282
- UI_COLOR_T color = img->data[(uint32_t)sy * img->w + sx];
352
+ UI_COLOR_T color = img->data[static_cast<uint32_t>(sy) * img->w + sx];
283
353
  int16_t dx = 0;
284
354
  int16_t dy = 0;
285
355
  if (q == 1) {
286
- dx = (int16_t)(img->h - 1 - sy);
287
- dy = (int16_t)sx;
356
+ dx = static_cast<int16_t>(img->h - 1 - sy);
357
+ dy = static_cast<int16_t>(sx);
288
358
  } else if (q == 2) {
289
- dx = (int16_t)(img->w - 1 - sx);
290
- dy = (int16_t)(img->h - 1 - sy);
359
+ dx = static_cast<int16_t>(img->w - 1 - sx);
360
+ dy = static_cast<int16_t>(img->h - 1 - sy);
291
361
  } else {
292
- dx = (int16_t)sy;
293
- dy = (int16_t)(img->w - 1 - sx);
362
+ dx = static_cast<int16_t>(sy);
363
+ dy = static_cast<int16_t>(img->w - 1 - sx);
294
364
  }
295
365
  ui_display_draw_pixel(x + dx, y + dy, color);
296
366
  }
@@ -13,15 +13,22 @@ static inline void ui_init(void) {
13
13
  // containers start locked until the scroll-container loop proves their
14
14
  // canvas fits.
15
15
  if (!__ui_scroll_canvas_ok && __ui_node_count > 0) {
16
- __ui_scroll_canvas_ok = (uint8_t*)calloc(__ui_node_count, sizeof(uint8_t));
16
+ __ui_scroll_canvas_ok = new (std::nothrow) uint8_t[__ui_node_count]();
17
17
  }
18
- if (!__ui_scroll_mem_warned && __ui_node_count > 0) {
19
- __ui_scroll_mem_warned = (uint8_t*)calloc(__ui_node_count, sizeof(uint8_t));
18
+ if (!__ui_scroll_render_locked && __ui_node_count > 0) {
19
+ __ui_scroll_render_locked = new (std::nothrow) uint8_t[__ui_node_count]();
20
20
  }
21
21
  for (uint16_t i = 0; i < __ui_node_count; i++) {
22
22
  __ui_nodes[i].dirty = 1;
23
23
  __ui_nodes[i].lastTextHeight = 0;
24
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
+ }
25
32
  if (__ui_nodes[i].kind == NODE_PROGRESS || __ui_nodes[i].kind == NODE_RANGE) {
26
33
  __ui_nodes[i].lastTextWidth = -1;
27
34
  }
@@ -32,6 +39,11 @@ static inline void ui_init(void) {
32
39
  __ui_nodes[n].hasTextBinding = 1;
33
40
  strncpy(__ui_nodes[n].textBuffer, __ui_nodes[n].text ? __ui_nodes[n].text : "", UI_TEXT_BUF);
34
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;
35
47
  }
36
48
  }
37
49
  // Seed virtualized-list runtime state. The fn pointers can't be baked into
@@ -52,7 +64,7 @@ static inline void ui_init(void) {
52
64
  uint16_t ih = __ui_nodes[i].listItemHeight > 0 ? __ui_nodes[i].listItemHeight : 24;
53
65
  uint16_t cnt = __ui_nodes[i].listCountFn();
54
66
  __ui_nodes[i].listCount = cnt;
55
- __ui_nodes[i].contentHeight = (int16_t)((uint32_t)cnt * ih);
67
+ __ui_nodes[i].contentHeight = static_cast<int16_t>(static_cast<uint32_t>(cnt) * ih);
56
68
  __ui_nodes[i].scrollY = 0;
57
69
  __ui_nodes[i].overscrollPx = 0;
58
70
  __ui_nodes[i].settling = 0;
@@ -61,6 +73,9 @@ static inline void ui_init(void) {
61
73
  ui_build_draw_order();
62
74
  ui_build_scroll_owner_table();
63
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
+ }
64
79
  }
65
80
 
66
81
  // Debounce: ignore press/release events within 50ms of the last edge.
@@ -99,7 +114,7 @@ static inline void ui_on_release(uint16_t nodeIdx) {
99
114
  }
100
115
 
101
116
  // Pin-watch callback type: void fn(void)
102
- typedef void (*PinWatchCallback)(void);
117
+ using PinWatchCallback = void (*)(void);
103
118
 
104
119
  struct UIPinWatch {
105
120
  uint8_t pin;