@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,116 +2,120 @@
2
2
  // Canvas push/draw helpers, buffered scroll canvas push, scrollbar direct.
3
3
  // See docs/superpowers/specs/2026-07-12-split-runtime-header-design.md.
4
4
  export function emitCanvasScrollbar() {
5
- return `
6
- static inline void ui_push_canvas_rect(CuttlefishCanvas16* canvas, int16_t x, int16_t y, int16_t w, int16_t h) {
7
- if (!canvas || !display_canvasBuffer(canvas)) return;
8
- UI_COLOR_T* pixels = display_canvasBuffer(canvas);
9
- int16_t stride = display_canvasWidth(canvas);
10
- // The canvas is viewport-sized: buffer row 0 = the first row of the viewport.
11
- // The display destination is (x, y) but the source buffer starts at (0, 0).
12
- // When a full-screen framebuffer is the active draw target, composite into it
13
- // via __ui_gfx (so the single bulk push at frame end captures everything).
14
- // Otherwise write straight to the display in one SPI transaction.
15
- if (__ui_fb) {
16
- // The framebuffer is the composition target. Track this publish so a
17
- // geometry repair or scroll-canvas repair that does not leave a node dirty
18
- // still reaches the panel during the frame-end flush.
19
- ui_fb_add_rect(x, y, w, h);
20
- if (w == stride) {
21
- ui_display_draw_rgb_bitmap(x, y, pixels, w, h);
22
- return;
23
- }
24
- for (int16_t row = 0; row < h; row++) {
25
- ui_display_draw_rgb_bitmap(x, y + row, pixels + static_cast<int32_t>(row) * stride, w, 1);
26
- }
27
- return;
28
- }
29
- display_startWrite();
30
- display_setAddrWindow(x, y, w, h);
31
- if (w == stride) {
32
- // Full-width: contiguous in buffer, single write.
33
- display_writePixels(pixels, static_cast<uint32_t>(w) * h);
34
- display_endWrite();
35
- return;
36
- }
37
- for (int16_t row = 0; row < h; row++) {
38
- display_writePixels(pixels + static_cast<int32_t>(row) * stride, w);
39
- }
40
- display_endWrite();
41
- }
42
-
43
- static inline void ui_draw_canvas_rect(CuttlefishCanvas16* canvas, int16_t x, int16_t y, int16_t w, int16_t h) {
44
- if (!canvas || !display_canvasBuffer(canvas)) return;
45
- UI_COLOR_T* pixels = display_canvasBuffer(canvas);
46
- int16_t stride = display_canvasWidth(canvas);
47
- if (w == stride && h == display_canvasHeight(canvas)) {
48
- ui_display_draw_rgb_bitmap(x, y, pixels, w, h);
49
- return;
50
- }
51
- for (int16_t row = 0; row < h; row++) {
52
- ui_display_draw_rgb_bitmap(x, y + row, pixels + static_cast<int32_t>(row) * stride, w, 1);
53
- }
54
- }
55
-
56
- static inline void ui_push_buffered_scroll_canvas(CuttlefishCanvas16* bufferedScrollCanvas,
57
- CuttlefishCanvas16* bufferedScrollRepaintCanvas,
58
- int16_t bufferedScrollNode,
59
- int16_t bufferedScrollVX,
60
- int16_t bufferedScrollVY,
61
- int16_t bufferedScrollRepaintY,
62
- int16_t bufferedScrollRepaintH,
63
- CuttlefishDisplayTarget* drawTarget) {
64
- if (bufferedScrollNode < 0 || !bufferedScrollCanvas || !display_canvasBuffer(bufferedScrollCanvas)) return;
65
- // Draw scrollbar into the canvas (canvas-local coords: 0,0 = viewport top-left).
66
- ui_display_set_target(bufferedScrollCanvas);
67
- int16_t si = bufferedScrollNode;
68
- int16_t vw = __ui_nodes[si].box.w;
69
- int16_t vh = __ui_nodes[si].box.h;
70
- if (bufferedScrollRepaintCanvas && bufferedScrollRepaintH > 0) {
71
- ui_draw_canvas_rect(bufferedScrollRepaintCanvas, 0, bufferedScrollRepaintY, vw, bufferedScrollRepaintH);
72
- }
73
- int16_t tx = vw - 4;
74
- uint16_t thumbH = static_cast<uint32_t>(vh) * vh / __ui_nodes[si].contentHeight;
75
- if (thumbH < 8) thumbH = 8;
76
- // Clamp to vh so (vh - thumbH) below can't underflow on a degenerate sub-8px
77
- // viewport (which would cast a negative value to a huge uint32_t and place the
78
- // thumb off-screen). Matches the guard in ui_render_scroll_bands.
79
- if (thumbH > static_cast<uint16_t>(vh)) thumbH = static_cast<uint16_t>(vh);
80
- int16_t maxScroll = __ui_nodes[si].contentHeight - vh;
81
- uint16_t thumbY = static_cast<uint32_t>(vh - thumbH) * __ui_nodes[si].scrollY / (maxScroll > 0 ? maxScroll : 1);
82
- UI_COLOR_T dimFg = (UI_COLOR_T)((__ui_nodes[si].fg >> 1) & UI_DIM_MASK);
83
- ui_display_fill_rect(tx, 0, 3, vh, dimFg);
84
- ui_display_fill_rect(tx, thumbY, 3, thumbH, __ui_nodes[si].fg);
85
- // Record the scrollY this canvas now reflects, so the next scroll frame can
86
- // compute its shift delta (Mode B) from scrollY - lastPaintedScrollY.
87
- __ui_nodes[si].lastPaintedScrollY = __ui_nodes[si].scrollY;
88
- // Push the canvas to the draw target at the viewport position (the
89
- // framebuffer when active, else the display directly).
90
- ui_display_set_target(drawTarget);
91
- ui_push_canvas_rect(bufferedScrollCanvas,
92
- bufferedScrollVX, bufferedScrollVY,
93
- vw, vh);
94
- }
95
-
96
- // Draw a scroll container's scrollbar directly on the display (Mode C).
97
- static inline void ui_draw_scrollbar_direct(int16_t si, int16_t vox, int16_t voy) {
98
- if (si < 0 || si >= static_cast<int16_t>(__ui_node_count)) return;
99
- int16_t vw = __ui_nodes[si].box.w;
100
- int16_t vh = __ui_nodes[si].box.h;
101
- if (__ui_nodes[si].contentHeight <= vh) return;
102
- int16_t tx = vox + vw - 4;
103
- uint16_t thumbH = static_cast<uint32_t>(vh) * vh / __ui_nodes[si].contentHeight;
104
- if (thumbH < 8) thumbH = 8;
105
- // Clamp to vh so (vh - thumbH) below can't underflow on a degenerate sub-8px
106
- // viewport (which would cast a negative value to a huge uint32_t and place the
107
- // thumb off-screen). Matches the guard in ui_render_scroll_bands.
108
- if (thumbH > static_cast<uint16_t>(vh)) thumbH = static_cast<uint16_t>(vh);
109
- int16_t maxScroll = __ui_nodes[si].contentHeight - vh;
110
- uint16_t thumbY = static_cast<uint32_t>(vh - thumbH) * __ui_nodes[si].scrollY / (maxScroll > 0 ? maxScroll : 1);
111
- UI_COLOR_T dimFg = (UI_COLOR_T)((__ui_nodes[si].fg >> 1) & UI_DIM_MASK);
112
- ui_display_fill_rect(tx, voy, 3, vh, dimFg);
113
- ui_display_fill_rect(tx, static_cast<int16_t>(voy + thumbY), 3, thumbH, __ui_nodes[si].fg);
114
- }
115
-
5
+ return `
6
+ static inline void ui_push_canvas_rect(CuttlefishCanvas16* canvas, int16_t x, int16_t y, int16_t w, int16_t h) {
7
+ if (!canvas || !display_canvasBuffer(canvas)) return;
8
+ UI_COLOR_T* pixels = display_canvasBuffer(canvas);
9
+ int16_t stride = display_canvasWidth(canvas);
10
+ // The canvas is viewport-sized: buffer row 0 = the first row of the viewport.
11
+ // The display destination is (x, y) but the source buffer starts at (0, 0).
12
+ // When a full-screen framebuffer is the active draw target, composite into it
13
+ // via __ui_gfx (so the single bulk push at frame end captures everything).
14
+ // Otherwise write straight to the display in one SPI transaction.
15
+ if (__ui_fb) {
16
+ // The framebuffer is the composition target. Track this publish so a
17
+ // geometry repair or scroll-canvas repair that does not leave a node dirty
18
+ // still reaches the panel during the frame-end flush.
19
+ ui_fb_add_rect(x, y, w, h);
20
+ if (w == stride) {
21
+ ui_display_draw_rgb_bitmap(x, y, pixels, w, h);
22
+ return;
23
+ }
24
+ for (int16_t row = 0; row < h; row++) {
25
+ ui_display_draw_rgb_bitmap(x, y + row, pixels + static_cast<int32_t>(row) * stride, w, 1);
26
+ }
27
+ return;
28
+ }
29
+ display_startWrite();
30
+ display_setAddrWindow(x, y, w, h);
31
+ if (w == stride) {
32
+ // Full-width: contiguous in buffer, single write.
33
+ display_writePixels(pixels, static_cast<uint32_t>(w) * h);
34
+ display_endWrite();
35
+ return;
36
+ }
37
+ for (int16_t row = 0; row < h; row++) {
38
+ display_writePixels(pixels + static_cast<int32_t>(row) * stride, w);
39
+ }
40
+ display_endWrite();
41
+ }
42
+
43
+ static inline void ui_draw_canvas_rect(CuttlefishCanvas16* canvas, int16_t x, int16_t y, int16_t w, int16_t h) {
44
+ if (!canvas || !display_canvasBuffer(canvas)) return;
45
+ UI_COLOR_T* pixels = display_canvasBuffer(canvas);
46
+ int16_t stride = display_canvasWidth(canvas);
47
+ if (w == stride && h == display_canvasHeight(canvas)) {
48
+ ui_display_draw_rgb_bitmap(x, y, pixels, w, h);
49
+ return;
50
+ }
51
+ for (int16_t row = 0; row < h; row++) {
52
+ ui_display_draw_rgb_bitmap(x, y + row, pixels + static_cast<int32_t>(row) * stride, w, 1);
53
+ }
54
+ }
55
+
56
+ static inline void ui_push_buffered_scroll_canvas(CuttlefishCanvas16* bufferedScrollCanvas,
57
+ CuttlefishCanvas16* bufferedScrollRepaintCanvas,
58
+ int16_t bufferedScrollNode,
59
+ int16_t bufferedScrollVX,
60
+ int16_t bufferedScrollVY,
61
+ int16_t bufferedScrollRepaintY,
62
+ int16_t bufferedScrollRepaintH,
63
+ CuttlefishDisplayTarget* drawTarget) {
64
+ if (bufferedScrollNode < 0 || !bufferedScrollCanvas || !display_canvasBuffer(bufferedScrollCanvas)) return;
65
+ // Draw scrollbar into the canvas (canvas-local coords: 0,0 = viewport top-left).
66
+ ui_display_set_target(bufferedScrollCanvas);
67
+ int16_t si = bufferedScrollNode;
68
+ int16_t vw = __ui_nodes[si].box.w;
69
+ int16_t vh = __ui_nodes[si].box.h;
70
+ if (bufferedScrollRepaintCanvas && bufferedScrollRepaintH > 0) {
71
+ ui_draw_canvas_rect(bufferedScrollRepaintCanvas, 0, bufferedScrollRepaintY, vw, bufferedScrollRepaintH);
72
+ }
73
+ int16_t tx = vw - 4;
74
+ uint16_t thumbH = static_cast<uint32_t>(vh) * vh / __ui_nodes[si].contentHeight;
75
+ if (thumbH < 8) thumbH = 8;
76
+ // Clamp to vh so (vh - thumbH) below can't underflow on a degenerate sub-8px
77
+ // viewport (which would cast a negative value to a huge uint32_t and place the
78
+ // thumb off-screen). Matches the guard in ui_render_scroll_bands.
79
+ if (thumbH > static_cast<uint16_t>(vh)) thumbH = static_cast<uint16_t>(vh);
80
+ int16_t maxScroll = __ui_nodes[si].contentHeight - vh;
81
+ uint16_t thumbY = static_cast<uint32_t>(vh - thumbH) * __ui_nodes[si].scrollY / (maxScroll > 0 ? maxScroll : 1);
82
+ UI_COLOR_T dimFg = (UI_COLOR_T)((__ui_nodes[si].fg >> 1) & UI_DIM_MASK);
83
+ // Track covers the FULL reserved gutter (4px) so child decorations that
84
+ // poke past the content area (e.g. outset shadows, offset up to +6px)
85
+ // can't leave 1px ticks in the uncovered edge column. Thumb stays 3px.
86
+ ui_display_fill_rect(tx, 0, 4, vh, dimFg);
87
+ ui_display_fill_rect(tx, thumbY, 3, thumbH, __ui_nodes[si].fg);
88
+ // Record the scrollY this canvas now reflects, so the next scroll frame can
89
+ // compute its shift delta (Mode B) from scrollY - lastPaintedScrollY.
90
+ __ui_nodes[si].lastPaintedScrollY = __ui_nodes[si].scrollY;
91
+ // Push the canvas to the draw target at the viewport position (the
92
+ // framebuffer when active, else the display directly).
93
+ ui_display_set_target(drawTarget);
94
+ ui_push_canvas_rect(bufferedScrollCanvas,
95
+ bufferedScrollVX, bufferedScrollVY,
96
+ vw, vh);
97
+ }
98
+
99
+ // Draw a scroll container's scrollbar directly on the display (Mode C).
100
+ static inline void ui_draw_scrollbar_direct(int16_t si, int16_t vox, int16_t voy) {
101
+ if (si < 0 || si >= static_cast<int16_t>(__ui_node_count)) return;
102
+ int16_t vw = __ui_nodes[si].box.w;
103
+ int16_t vh = __ui_nodes[si].box.h;
104
+ if (__ui_nodes[si].contentHeight <= vh) return;
105
+ int16_t tx = vox + vw - 4;
106
+ uint16_t thumbH = static_cast<uint32_t>(vh) * vh / __ui_nodes[si].contentHeight;
107
+ if (thumbH < 8) thumbH = 8;
108
+ // Clamp to vh so (vh - thumbH) below can't underflow on a degenerate sub-8px
109
+ // viewport (which would cast a negative value to a huge uint32_t and place the
110
+ // thumb off-screen). Matches the guard in ui_render_scroll_bands.
111
+ if (thumbH > static_cast<uint16_t>(vh)) thumbH = static_cast<uint16_t>(vh);
112
+ int16_t maxScroll = __ui_nodes[si].contentHeight - vh;
113
+ uint16_t thumbY = static_cast<uint32_t>(vh - thumbH) * __ui_nodes[si].scrollY / (maxScroll > 0 ? maxScroll : 1);
114
+ UI_COLOR_T dimFg = (UI_COLOR_T)((__ui_nodes[si].fg >> 1) & UI_DIM_MASK);
115
+ // Full 4px gutter track (see the Mode B variant above).
116
+ ui_display_fill_rect(tx, voy, 4, vh, dimFg);
117
+ ui_display_fill_rect(tx, static_cast<int16_t>(voy + thumbY), 3, thumbH, __ui_nodes[si].fg);
118
+ }
119
+
116
120
  // Per-node dirty marker (called by press handlers and binding evaluation).`;
117
121
  }
@@ -344,6 +344,17 @@ void CuttlefishGFX::fillCircleHelper(int16_t x0, int16_t y0, int16_t r, uint8_t
344
344
  }
345
345
 
346
346
  void CuttlefishGFX::drawRoundRect(int16_t x, int16_t y, int16_t w, int16_t h, int16_t r, uint16_t color) {
347
+ if (w <= 0 || h <= 0) return;
348
+ if (r <= 0) {
349
+ drawRect(x, y, w, h, color);
350
+ return;
351
+ }
352
+ // CSS border-radius semantics: radii larger than half the shorter side
353
+ // collapse to a pill/box (the preview host-gfx clamps identically). Kit
354
+ // styles lower border-radius:9999px to r=255; without this clamp the
355
+ // corner arcs overgrow the box instead of forming a true pill.
356
+ const int16_t halfMin = static_cast<int16_t>((w < h ? w : h) / 2);
357
+ if (r > halfMin) r = halfMin;
347
358
  drawFastHLine(x + r, y, w - 2 * r, color); // Top
348
359
  drawFastHLine(x + r, y + h - 1, w - 2 * r, color); // Bottom
349
360
  drawFastVLine(x, y + r, h - 2 * r, color); // Left
@@ -355,7 +366,17 @@ void CuttlefishGFX::drawRoundRect(int16_t x, int16_t y, int16_t w, int16_t h, in
355
366
  }
356
367
 
357
368
  void CuttlefishGFX::fillRoundRect(int16_t x, int16_t y, int16_t w, int16_t h, int16_t r, uint16_t color) {
369
+ if (w <= 0 || h <= 0) return;
370
+ if (r <= 0) {
371
+ fillRect(x, y, w, h, color);
372
+ return;
373
+ }
374
+ const int16_t halfMin = static_cast<int16_t>((w < h ? w : h) / 2);
375
+ if (r > halfMin) r = halfMin;
358
376
  fillRect(x + r, y, w - 2 * r, h, color);
377
+ // h - 2*r - 1 stays unclamped: for a perfect pill (h == 2r) it is -1 and the
378
+ // helper's +1+delta lands the arc lines exactly on the box (the preview
379
+ // host-gfx passes the same unclamped value).
359
380
  fillCircleHelper(x + w - r - 1, y + r, r, 1, h - 2 * r - 1, color);
360
381
  fillCircleHelper(x + r, y + r, r, 2, h - 2 * r - 1, color);
361
382
  }