@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.
- package/README.md +6 -0
- package/dist/engine-index.js +2 -2
- package/dist/preview/host-ui-runtime.d.ts +1 -0
- package/dist/preview/host-ui-runtime.js +56 -11
- package/dist/ui-engine/font-assets.d.ts +8 -0
- package/dist/ui-engine/font-assets.js +13 -0
- package/dist/ui-engine/layout-engine.js +19 -6
- package/dist/ui-engine/model.d.ts +1 -1
- package/dist/ui-engine/model.js +10 -4
- package/dist/ui-engine/runtime-header/antialiasing.js +52 -32
- package/dist/ui-engine/runtime-header/blend-bodies.js +4 -4
- package/dist/ui-engine/runtime-header/canvas-helpers.js +42 -69
- package/dist/ui-engine/runtime-header/canvas-scrollbar.js +22 -10
- package/dist/ui-engine/runtime-header/color-mono-refresh.js +10 -10
- package/dist/ui-engine/runtime-header/cuttlefish-gfx.d.ts +1 -0
- package/dist/ui-engine/runtime-header/cuttlefish-gfx.js +563 -0
- package/dist/ui-engine/runtime-header/dirty-scroll-mutators.js +26 -10
- package/dist/ui-engine/runtime-header/display-shim.js +8 -8
- package/dist/ui-engine/runtime-header/forward-decls.js +52 -10
- package/dist/ui-engine/runtime-header/image-drawing.js +154 -84
- package/dist/ui-engine/runtime-header/init-press-input.js +20 -5
- package/dist/ui-engine/runtime-header/keyboard.js +62 -18
- package/dist/ui-engine/runtime-header/node-decoration.js +8 -9
- package/dist/ui-engine/runtime-header/node-draw-body.d.ts +1 -0
- package/dist/ui-engine/runtime-header/node-draw-body.js +1402 -0
- package/dist/ui-engine/runtime-header/paint-order-coords.js +48 -18
- package/dist/ui-engine/runtime-header/paint-rects-repair.js +38 -17
- package/dist/ui-engine/runtime-header/scroll-physics.js +15 -18
- package/dist/ui-engine/runtime-header/state-bindings-nav.js +23 -13
- package/dist/ui-engine/runtime-header/structs.js +12 -10
- package/dist/ui-engine/runtime-header/text-rendering.js +132 -71
- package/dist/ui-engine/runtime-header/tick/bindings-phase.js +29 -10
- package/dist/ui-engine/runtime-header/tick/dirty-draw-phase.js +336 -749
- package/dist/ui-engine/runtime-header/tick/flush-phase.js +7 -3
- package/dist/ui-engine/runtime-header/tick/scroll-canvas-phase.js +19 -3
- package/dist/ui-engine/runtime-header/tick/transitions-phase.js +31 -24
- package/dist/ui-engine/runtime-header/touch-keyboard-fwd.js +46 -41
- package/dist/ui-engine/runtime-header/types-defines.js +23 -3
- package/dist/ui-engine/runtime-header.d.ts +10 -1
- package/dist/ui-engine/runtime-header.js +7 -1
- package/dist/ui-engine/transpile-ui.js +2 -2
- package/dist/ui-engine/ua-stylesheet.js +46 -3
- package/dist/ui-engine/ui-lowering.d.ts +1 -3
- package/dist/ui-engine/ui-lowering.js +18 -24
- package/dist/ui-engine/ui-registry.js +1 -6
- package/package.json +2 -2
- package/src/engine-index.ts +2 -2
- package/src/preview/host-ui-runtime.ts +56 -12
- package/src/ui-engine/font-assets.ts +13 -0
- package/src/ui-engine/layout-engine.ts +19 -6
- package/src/ui-engine/model.ts +10 -5
- package/src/ui-engine/runtime-header/antialiasing.ts +52 -32
- package/src/ui-engine/runtime-header/blend-bodies.ts +4 -4
- package/src/ui-engine/runtime-header/canvas-helpers.ts +42 -69
- package/src/ui-engine/runtime-header/canvas-scrollbar.ts +22 -10
- package/src/ui-engine/runtime-header/color-mono-refresh.ts +10 -10
- package/src/ui-engine/runtime-header/cuttlefish-gfx.ts +566 -0
- package/src/ui-engine/runtime-header/dirty-scroll-mutators.ts +26 -10
- package/src/ui-engine/runtime-header/display-shim.ts +8 -8
- package/src/ui-engine/runtime-header/forward-decls.ts +52 -10
- package/src/ui-engine/runtime-header/image-drawing.ts +154 -84
- package/src/ui-engine/runtime-header/init-press-input.ts +20 -5
- package/src/ui-engine/runtime-header/keyboard.ts +62 -18
- package/src/ui-engine/runtime-header/node-decoration.ts +8 -9
- package/src/ui-engine/runtime-header/node-draw-body.ts +1402 -0
- package/src/ui-engine/runtime-header/paint-order-coords.ts +48 -18
- package/src/ui-engine/runtime-header/paint-rects-repair.ts +38 -17
- package/src/ui-engine/runtime-header/scroll-physics.ts +15 -18
- package/src/ui-engine/runtime-header/state-bindings-nav.ts +23 -13
- package/src/ui-engine/runtime-header/structs.ts +12 -10
- package/src/ui-engine/runtime-header/text-rendering.ts +132 -71
- package/src/ui-engine/runtime-header/tick/bindings-phase.ts +29 -10
- package/src/ui-engine/runtime-header/tick/dirty-draw-phase.ts +336 -749
- package/src/ui-engine/runtime-header/tick/flush-phase.ts +7 -3
- package/src/ui-engine/runtime-header/tick/scroll-canvas-phase.ts +19 -3
- package/src/ui-engine/runtime-header/tick/transitions-phase.ts +31 -24
- package/src/ui-engine/runtime-header/touch-keyboard-fwd.ts +46 -41
- package/src/ui-engine/runtime-header/types-defines.ts +23 -3
- package/src/ui-engine/runtime-header.ts +17 -1
- package/src/ui-engine/transpile-ui.ts +2 -2
- package/src/ui-engine/ua-stylesheet.ts +46 -3
- package/src/ui-engine/ui-lowering.ts +19 -26
- package/src/ui-engine/ui-registry.ts +1 -7
- package/dist/ui-engine/scroll-memory-diagnostics.d.ts +0 -16
- package/dist/ui-engine/scroll-memory-diagnostics.js +0 -58
- package/src/ui-engine/scroll-memory-diagnostics.ts +0 -79
|
@@ -40,12 +40,12 @@ static inline uint16_t ui_next_utf8_codepoint(const unsigned char** p) {
|
|
|
40
40
|
return b0;
|
|
41
41
|
}
|
|
42
42
|
if ((b0 & 0xE0) == 0xC0 && (s[0] & 0xC0) == 0x80) {
|
|
43
|
-
uint16_t cp = (
|
|
43
|
+
uint16_t cp = (static_cast<uint16_t>(b0 & 0x1F) << 6) | static_cast<uint16_t>(s[0] & 0x3F);
|
|
44
44
|
*p = s + 1;
|
|
45
45
|
return cp;
|
|
46
46
|
}
|
|
47
47
|
if ((b0 & 0xF0) == 0xE0 && (s[0] & 0xC0) == 0x80 && (s[1] & 0xC0) == 0x80) {
|
|
48
|
-
uint16_t cp = (
|
|
48
|
+
uint16_t cp = (static_cast<uint16_t>(b0 & 0x0F) << 12) | (static_cast<uint16_t>(s[0] & 0x3F) << 6) | static_cast<uint16_t>(s[1] & 0x3F);
|
|
49
49
|
*p = s + 2;
|
|
50
50
|
return cp;
|
|
51
51
|
}
|
|
@@ -121,12 +121,12 @@ static inline uint16_t ui_next_utf8_codepoint_bounded(const unsigned char** p, c
|
|
|
121
121
|
return b0;
|
|
122
122
|
}
|
|
123
123
|
if ((b0 & 0xE0) == 0xC0 && s < end && (s[0] & 0xC0) == 0x80) {
|
|
124
|
-
uint16_t cp = (
|
|
124
|
+
uint16_t cp = (static_cast<uint16_t>(b0 & 0x1F) << 6) | static_cast<uint16_t>(s[0] & 0x3F);
|
|
125
125
|
*p = s + 1;
|
|
126
126
|
return cp;
|
|
127
127
|
}
|
|
128
128
|
if ((b0 & 0xF0) == 0xE0 && s + 1 < end && (s[0] & 0xC0) == 0x80 && (s[1] & 0xC0) == 0x80) {
|
|
129
|
-
uint16_t cp = (
|
|
129
|
+
uint16_t cp = (static_cast<uint16_t>(b0 & 0x0F) << 12) | (static_cast<uint16_t>(s[0] & 0x3F) << 6) | static_cast<uint16_t>(s[1] & 0x3F);
|
|
130
130
|
*p = s + 2;
|
|
131
131
|
return cp;
|
|
132
132
|
}
|
|
@@ -144,8 +144,8 @@ static inline uint16_t ui_text_codepoint_advance(uint16_t codepoint, uint8_t ts,
|
|
|
144
144
|
const UIFontGlyph* glyph = ui_font_glyph(face, codepoint);
|
|
145
145
|
return glyph ? glyph->advance : (face->lineHeight / 2);
|
|
146
146
|
}
|
|
147
|
-
int16_t adv =
|
|
148
|
-
return adv > 0 ? (
|
|
147
|
+
int16_t adv = static_cast<int16_t>(ts ? ts : 2) * 6 + letterSpacing;
|
|
148
|
+
return adv > 0 ? static_cast<uint16_t>(adv) : 1;
|
|
149
149
|
}
|
|
150
150
|
|
|
151
151
|
static inline uint16_t ui_text_span_width(const char* start, const char* end, uint8_t ts, uint8_t fontFace, int8_t letterSpacing) {
|
|
@@ -285,12 +285,12 @@ static inline uint32_t ui_text_layout_cache_key(uint16_t nodeIdx, uint16_t textM
|
|
|
285
285
|
uint8_t ts = __ui_nodes[nodeIdx].textSize ? __ui_nodes[nodeIdx].textSize : 2;
|
|
286
286
|
key = key * 31u + ts;
|
|
287
287
|
key = key * 31u + __ui_nodes[nodeIdx].fontFace;
|
|
288
|
-
key = key * 31u +
|
|
288
|
+
key = key * 31u + static_cast<uint8_t>(__ui_nodes[nodeIdx].letterSpacing + 128);
|
|
289
289
|
key = key * 31u + __ui_nodes[nodeIdx].lineHeight;
|
|
290
290
|
if (__ui_nodes[nodeIdx].hasTextBinding) {
|
|
291
291
|
const char* t = __ui_nodes[nodeIdx].textBuffer;
|
|
292
292
|
while (t && *t) {
|
|
293
|
-
key = key * 31u + (
|
|
293
|
+
key = key * 31u + static_cast<uint8_t>(*t);
|
|
294
294
|
t++;
|
|
295
295
|
}
|
|
296
296
|
}
|
|
@@ -300,12 +300,12 @@ static inline uint32_t ui_text_layout_cache_key(uint16_t nodeIdx, uint16_t textM
|
|
|
300
300
|
static inline uint16_t ui_node_text_max_width(uint16_t nodeIdx) {
|
|
301
301
|
if (nodeIdx >= __ui_node_count) return 0;
|
|
302
302
|
uint16_t textMaxW = __ui_nodes[nodeIdx].box.w;
|
|
303
|
-
if (__ui_nodes[nodeIdx].kind == NODE_TEXT || __ui_nodes[nodeIdx].kind == NODE_BUTTON) {
|
|
304
|
-
uint16_t hInset = (
|
|
305
|
-
(
|
|
306
|
-
textMaxW = __ui_nodes[nodeIdx].box.w > hInset ?
|
|
303
|
+
if (__ui_nodes[nodeIdx].kind == NODE_TEXT || __ui_nodes[nodeIdx].kind == NODE_BUTTON || __ui_nodes[nodeIdx].kind == NODE_SELECT) {
|
|
304
|
+
uint16_t hInset = static_cast<uint16_t>(__ui_nodes[nodeIdx].paddingLeft) + static_cast<uint16_t>(__ui_nodes[nodeIdx].paddingRight) +
|
|
305
|
+
static_cast<uint16_t>(__ui_nodes[nodeIdx].borderWidth) * 2;
|
|
306
|
+
textMaxW = __ui_nodes[nodeIdx].box.w > hInset ? static_cast<uint16_t>(__ui_nodes[nodeIdx].box.w - hInset) : 0;
|
|
307
307
|
} else if (__ui_nodes[nodeIdx].kind == NODE_CHECK || __ui_nodes[nodeIdx].kind == NODE_RADIO) {
|
|
308
|
-
textMaxW = __ui_nodes[nodeIdx].box.w > 22 ?
|
|
308
|
+
textMaxW = __ui_nodes[nodeIdx].box.w > 22 ? static_cast<uint16_t>(__ui_nodes[nodeIdx].box.w - 22) : 0;
|
|
309
309
|
}
|
|
310
310
|
return textMaxW;
|
|
311
311
|
}
|
|
@@ -363,18 +363,18 @@ static inline uint8_t ui_draw_asset_text(const char* text, int16_t x, int16_t y,
|
|
|
363
363
|
}
|
|
364
364
|
int16_t glyphX = cursor + glyph->xOffset;
|
|
365
365
|
int16_t glyphY = baseline + glyph->yOffset;
|
|
366
|
-
if (glyphX + (
|
|
367
|
-
glyphY + (
|
|
366
|
+
if (glyphX + static_cast<int16_t>(glyph->width) <= targetLeft || glyphX >= targetRight ||
|
|
367
|
+
glyphY + static_cast<int16_t>(glyph->height) <= targetTop || glyphY >= targetBottom) {
|
|
368
368
|
cursor += glyph->advance;
|
|
369
369
|
continue;
|
|
370
370
|
}
|
|
371
|
-
int16_t gxStart = glyphX < targetLeft ?
|
|
372
|
-
int16_t gyStart = glyphY < targetTop ?
|
|
373
|
-
int16_t gxEnd = glyphX + (
|
|
374
|
-
int16_t gyEnd = glyphY + (
|
|
371
|
+
int16_t gxStart = glyphX < targetLeft ? static_cast<int16_t>(targetLeft - glyphX) : 0;
|
|
372
|
+
int16_t gyStart = glyphY < targetTop ? static_cast<int16_t>(targetTop - glyphY) : 0;
|
|
373
|
+
int16_t gxEnd = glyphX + static_cast<int16_t>(glyph->width) > targetRight ? static_cast<int16_t>(targetRight - glyphX) : glyph->width;
|
|
374
|
+
int16_t gyEnd = glyphY + static_cast<int16_t>(glyph->height) > targetBottom ? static_cast<int16_t>(targetBottom - glyphY) : glyph->height;
|
|
375
375
|
for (int16_t gy = gyStart; gy < gyEnd; gy++) {
|
|
376
376
|
for (int16_t gx = gxStart; gx < gxEnd; gx++) {
|
|
377
|
-
uint16_t pixelIndex = (
|
|
377
|
+
uint16_t pixelIndex = static_cast<uint16_t>(gy) * glyph->width + static_cast<uint16_t>(gx);
|
|
378
378
|
uint8_t alpha = ui_font_alpha_at(face, glyph, pixelIndex);
|
|
379
379
|
if (alpha == 0) continue;
|
|
380
380
|
int16_t dx = glyphX + gx;
|
|
@@ -386,7 +386,7 @@ static inline uint8_t ui_draw_asset_text(const char* text, int16_t x, int16_t y,
|
|
|
386
386
|
// the bumpy look from flattening sub-pixel coverage to solid.
|
|
387
387
|
if (alpha >= 8) ui_display_draw_pixel(dx, dy, fg);
|
|
388
388
|
} else {
|
|
389
|
-
ui_display_draw_pixel(dx, dy, alpha >= 15 ? fg : ui_blend(fg, bg,
|
|
389
|
+
ui_display_draw_pixel(dx, dy, alpha >= 15 ? fg : ui_blend(fg, bg, static_cast<uint8_t>(static_cast<uint16_t>(alpha) * 100 / 15)));
|
|
390
390
|
}
|
|
391
391
|
} else if (alpha >= 8) {
|
|
392
392
|
ui_display_draw_pixel(dx, dy, fg);
|
|
@@ -437,10 +437,10 @@ static inline void ui_draw_bitmap_text(const char* text, int16_t x, int16_t y, U
|
|
|
437
437
|
|
|
438
438
|
static inline uint8_t ui_text_fg_neighbors(CuttlefishCanvas16* src, int16_t x, int16_t y, int16_t w, int16_t h, UI_COLOR_T fg, uint8_t radius = 1) {
|
|
439
439
|
uint8_t count = 0;
|
|
440
|
-
for (int8_t dy = -(
|
|
440
|
+
for (int8_t dy = -static_cast<int8_t>(radius); dy <= static_cast<int8_t>(radius); dy++) {
|
|
441
441
|
int16_t yy = y + dy;
|
|
442
442
|
if (yy < 0 || yy >= h) continue;
|
|
443
|
-
for (int8_t dx = -(
|
|
443
|
+
for (int8_t dx = -static_cast<int8_t>(radius); dx <= static_cast<int8_t>(radius); dx++) {
|
|
444
444
|
int16_t xx = x + dx;
|
|
445
445
|
if (xx < 0 || xx >= w) continue;
|
|
446
446
|
if (display_canvasGetPixel(src, xx, yy) == fg) count++;
|
|
@@ -482,15 +482,15 @@ static inline void ui_draw_aa_text(const char* text, int16_t x, int16_t y, UI_CO
|
|
|
482
482
|
}
|
|
483
483
|
int16_t clipX = x;
|
|
484
484
|
int16_t clipY = y;
|
|
485
|
-
int16_t clipW = (
|
|
486
|
-
int16_t clipH = (
|
|
485
|
+
int16_t clipW = static_cast<int16_t>(w);
|
|
486
|
+
int16_t clipH = static_cast<int16_t>(h);
|
|
487
487
|
if (!ui_clip_rect_to_display_target(&clipX, &clipY, &clipW, &clipH)) return;
|
|
488
|
-
int16_t localX =
|
|
489
|
-
int16_t localY =
|
|
488
|
+
int16_t localX = static_cast<int16_t>(clipX - x);
|
|
489
|
+
int16_t localY = static_cast<int16_t>(clipY - y);
|
|
490
490
|
|
|
491
491
|
// Use pre-allocated static canvases (no dynamic allocation)
|
|
492
|
-
CuttlefishCanvas16* src = ui_text_canvas(&__ui_text_src_canvas, (
|
|
493
|
-
CuttlefishCanvas16* dst = ui_text_canvas(&__ui_text_dst_canvas, (
|
|
492
|
+
CuttlefishCanvas16* src = ui_text_canvas(&__ui_text_src_canvas, static_cast<int16_t>(w), static_cast<int16_t>(h));
|
|
493
|
+
CuttlefishCanvas16* dst = ui_text_canvas(&__ui_text_dst_canvas, static_cast<int16_t>(w), static_cast<int16_t>(h));
|
|
494
494
|
if (!src || !dst || !display_canvasBuffer(src) || !display_canvasBuffer(dst)) {
|
|
495
495
|
ui_draw_bitmap_text(text, x, y, fg, bg, ts, 0);
|
|
496
496
|
return;
|
|
@@ -507,10 +507,10 @@ static inline void ui_draw_aa_text(const char* text, int16_t x, int16_t y, UI_CO
|
|
|
507
507
|
for (int16_t yy = localY; yy < localY + clipH; yy++) {
|
|
508
508
|
for (int16_t xx = localX; xx < localX + clipW; xx++) {
|
|
509
509
|
UI_COLOR_T px = display_canvasGetPixel(src, xx, yy);
|
|
510
|
-
uint8_t neighbors = ui_text_fg_neighbors(src, xx, yy, (
|
|
510
|
+
uint8_t neighbors = ui_text_fg_neighbors(src, xx, yy, static_cast<int16_t>(w), h, fg);
|
|
511
511
|
uint8_t outerNeighbors = 0;
|
|
512
512
|
if (ts >= 3 && px != fg && neighbors == 0) {
|
|
513
|
-
outerNeighbors = ui_text_fg_neighbors(src, xx, yy, (
|
|
513
|
+
outerNeighbors = ui_text_fg_neighbors(src, xx, yy, static_cast<int16_t>(w), h, fg, 2);
|
|
514
514
|
}
|
|
515
515
|
uint8_t coverage = ui_text_aa_coverage(neighbors, outerNeighbors, px == fg ? 1 : 0, ts);
|
|
516
516
|
display_targetDrawPixel((CuttlefishDisplayTarget*)dst, xx, yy, coverage == 0 ? bg : ui_blend(fg, bg, coverage));
|
|
@@ -520,8 +520,8 @@ static inline void ui_draw_aa_text(const char* text, int16_t x, int16_t y, UI_CO
|
|
|
520
520
|
int16_t stride = display_canvasWidth(dst);
|
|
521
521
|
UI_COLOR_T* pixels = display_canvasBuffer(dst);
|
|
522
522
|
for (int16_t row = 0; row < clipH; row++) {
|
|
523
|
-
ui_display_draw_rgb_bitmap(clipX,
|
|
524
|
-
pixels +
|
|
523
|
+
ui_display_draw_rgb_bitmap(clipX, static_cast<int16_t>(clipY + row),
|
|
524
|
+
pixels + static_cast<int32_t>(localY + row) * stride + localX, clipW, 1);
|
|
525
525
|
}
|
|
526
526
|
}
|
|
527
527
|
|
|
@@ -548,23 +548,81 @@ static inline void ui_draw_text(const char* text, int16_t x, int16_t y, UI_COLOR
|
|
|
548
548
|
}
|
|
549
549
|
#endif
|
|
550
550
|
|
|
551
|
+
// Draw classic list rows without routing through CuttlefishGFX::print(). The
|
|
552
|
+
// Zephyr native target uses CuttlefishGFX for both the panel and RGB565 canvas;
|
|
553
|
+
// its virtual text path is reliable on the panel but can be lost when the
|
|
554
|
+
// canvas is later copied to the panel. Writing the same 5x7 glyphs through the
|
|
555
|
+
// normal pixel/fill shims keeps the row pixels in the canvas buffer and works
|
|
556
|
+
// for both cached-list and band-list rendering. Other adapters retain the
|
|
557
|
+
// existing ui_draw_text fallback because they provide their own canvas text
|
|
558
|
+
// implementation.
|
|
559
|
+
static inline void ui_draw_list_text(const char* text, int16_t x, int16_t y,
|
|
560
|
+
UI_COLOR_T fg, UI_COLOR_T bg, uint8_t ts,
|
|
561
|
+
uint8_t fontFace, int8_t letterSpacing) {
|
|
562
|
+
#if defined(CUTTLEFISH_GFX_DEFINED)
|
|
563
|
+
if (fontFace == 0) {
|
|
564
|
+
if (!text) text = "";
|
|
565
|
+
if (ts == 0) ts = 2;
|
|
566
|
+
int16_t cx = x;
|
|
567
|
+
for (const unsigned char* p = (const unsigned char*)text; *p; p++) {
|
|
568
|
+
uint8_t ch = *p;
|
|
569
|
+
// CuttlefishGFX's classic font is the full 256-glyph Adafruit table.
|
|
570
|
+
for (uint8_t col = 0; col < 5; col++) {
|
|
571
|
+
uint8_t bits = cuttlefish_glcdfont[static_cast<uint16_t>(ch) * 5u + col];
|
|
572
|
+
for (uint8_t row = 0; row < 7; row++) {
|
|
573
|
+
if (bits & static_cast<uint8_t>(1u << row)) {
|
|
574
|
+
ui_display_fill_rect(static_cast<int16_t>(cx + static_cast<int16_t>(col) * ts),
|
|
575
|
+
static_cast<int16_t>(y + static_cast<int16_t>(row) * ts), ts, ts, fg);
|
|
576
|
+
}
|
|
577
|
+
}
|
|
578
|
+
}
|
|
579
|
+
// Match the classic GFX advance: five columns plus one blank column.
|
|
580
|
+
cx = static_cast<int16_t>(cx + static_cast<int16_t>(ts) * 6 + letterSpacing);
|
|
581
|
+
}
|
|
582
|
+
return;
|
|
583
|
+
}
|
|
584
|
+
#endif
|
|
585
|
+
ui_draw_text(text, x, y, fg, bg, ts, 0, fontFace, letterSpacing);
|
|
586
|
+
}
|
|
587
|
+
|
|
588
|
+
// Direct-panel list fallback. CuttlefishGFX's bitmap glyph path above is
|
|
589
|
+
// intentionally used for RAM canvases, but a few native panel adapters only
|
|
590
|
+
// commit their text state correctly through print()/drawChar(). Use that
|
|
591
|
+
// established path when the list is painted straight to the panel; the caller
|
|
592
|
+
// keeps the whole list inside one write transaction, so this does not expose a
|
|
593
|
+
// partially composed row set.
|
|
594
|
+
static inline void ui_draw_list_text_direct(const char* text, int16_t x, int16_t y,
|
|
595
|
+
UI_COLOR_T fg, UI_COLOR_T bg, uint8_t ts,
|
|
596
|
+
uint8_t fontFace, int8_t letterSpacing) {
|
|
597
|
+
if (fontFace == 0) {
|
|
598
|
+
ui_draw_bitmap_text(text, x, y, fg, bg, ts, letterSpacing);
|
|
599
|
+
} else {
|
|
600
|
+
ui_draw_text(text, x, y, fg, bg, ts, 0, fontFace, letterSpacing);
|
|
601
|
+
}
|
|
602
|
+
}
|
|
603
|
+
|
|
551
604
|
/** Truncate a NUL-terminated buffer in place to fit within maxWidth (px) and
|
|
552
605
|
* append "...". Used by text-overflow: ellipsis. Adafruit_GFX has no ellipsis
|
|
553
606
|
* glyph, so three ASCII dots approximate it. */
|
|
554
607
|
static inline void ui_truncate_ellipsis(char* buf, uint8_t bufSize, uint16_t maxWidth,
|
|
555
608
|
uint8_t ts, uint8_t fontFace, int8_t letterSpacing) {
|
|
556
609
|
if (!buf || bufSize == 0) return;
|
|
557
|
-
uint8_t len = (
|
|
610
|
+
uint8_t len = static_cast<uint8_t>(strlen(buf));
|
|
558
611
|
// Reserve space for the three trailing dots.
|
|
559
|
-
uint16_t dotsW = (
|
|
560
|
-
int16_t budget = (
|
|
612
|
+
uint16_t dotsW = static_cast<uint16_t>(3) * ui_text_codepoint_advance(static_cast<uint16_t>('.'), ts, fontFace, letterSpacing);
|
|
613
|
+
int16_t budget = static_cast<int16_t>(maxWidth) - static_cast<int16_t>(dotsW);
|
|
561
614
|
if (budget <= 0) { if (bufSize > 3) { buf[0]='.'; buf[1]='.'; buf[2]='.'; buf[3]=0; } return; }
|
|
562
|
-
//
|
|
615
|
+
// Measure once, then remove one UTF-8 codepoint at a time from the end.
|
|
616
|
+
// The previous prefix-- loop rescanned the whole prefix on every iteration
|
|
617
|
+
// (O(n²)); this keeps truncation linear for long bound strings.
|
|
563
618
|
uint8_t prefix = len;
|
|
564
|
-
|
|
565
|
-
|
|
566
|
-
|
|
567
|
-
|
|
619
|
+
uint16_t prefixW = ui_text_span_width(buf, buf + prefix, ts, fontFace, letterSpacing);
|
|
620
|
+
while (prefix > 0 && static_cast<int16_t>(prefixW) > budget) {
|
|
621
|
+
uint8_t cut = static_cast<uint8_t>(prefix - 1);
|
|
622
|
+
while (cut > 0 && (static_cast<uint8_t>(buf[cut]) & 0xC0) == 0x80) cut--;
|
|
623
|
+
uint16_t removed = ui_text_span_width(buf + cut, buf + prefix, ts, fontFace, letterSpacing);
|
|
624
|
+
prefixW = prefixW > removed ? static_cast<uint16_t>(prefixW - removed) : 0;
|
|
625
|
+
prefix = cut;
|
|
568
626
|
}
|
|
569
627
|
if (prefix + 3 < bufSize) {
|
|
570
628
|
buf[prefix] = '.'; buf[prefix+1] = '.'; buf[prefix+2] = '.'; buf[prefix+3] = 0;
|
|
@@ -578,12 +636,15 @@ static inline void ui_truncate_ellipsis(char* buf, uint8_t bufSize, uint16_t max
|
|
|
578
636
|
static inline void ui_truncate_clip(char* buf, uint8_t bufSize, uint16_t maxWidth,
|
|
579
637
|
uint8_t ts, uint8_t fontFace, int8_t letterSpacing) {
|
|
580
638
|
if (!buf || bufSize == 0) return;
|
|
581
|
-
uint8_t len = (
|
|
639
|
+
uint8_t len = static_cast<uint8_t>(strlen(buf));
|
|
582
640
|
uint8_t prefix = len;
|
|
583
|
-
|
|
584
|
-
|
|
585
|
-
|
|
586
|
-
|
|
641
|
+
uint16_t prefixW = ui_text_span_width(buf, buf + prefix, ts, fontFace, letterSpacing);
|
|
642
|
+
while (prefix > 0 && static_cast<int16_t>(prefixW) > static_cast<int16_t>(maxWidth)) {
|
|
643
|
+
uint8_t cut = static_cast<uint8_t>(prefix - 1);
|
|
644
|
+
while (cut > 0 && (static_cast<uint8_t>(buf[cut]) & 0xC0) == 0x80) cut--;
|
|
645
|
+
uint16_t removed = ui_text_span_width(buf + cut, buf + prefix, ts, fontFace, letterSpacing);
|
|
646
|
+
prefixW = prefixW > removed ? static_cast<uint16_t>(prefixW - removed) : 0;
|
|
647
|
+
prefix = cut;
|
|
587
648
|
}
|
|
588
649
|
if (prefix < bufSize) buf[prefix] = 0;
|
|
589
650
|
}
|
|
@@ -600,15 +661,15 @@ static inline void ui_draw_wrapped_text(const char* text, int16_t x, int16_t y,
|
|
|
600
661
|
int16_t targetLeft, targetTop, targetRight, targetBottom;
|
|
601
662
|
ui_display_target_bounds(&targetLeft, &targetTop, &targetRight, &targetBottom);
|
|
602
663
|
while (ui_text_next_line(&cursor, maxWidth, whiteSpaceMode, ts, fontFace, letterSpacing, &line)) {
|
|
603
|
-
int16_t lineBottom =
|
|
664
|
+
int16_t lineBottom = static_cast<int16_t>(lineY + lh);
|
|
604
665
|
if (lineBottom <= targetTop || lineY >= targetBottom) {
|
|
605
666
|
lineY += lh;
|
|
606
667
|
continue;
|
|
607
668
|
}
|
|
608
669
|
int16_t lineX = x;
|
|
609
|
-
if (textAlign == 1) lineX = x + ((
|
|
610
|
-
else if (textAlign == 2) lineX = x + (
|
|
611
|
-
if (lineX + (
|
|
670
|
+
if (textAlign == 1) lineX = x + (static_cast<int16_t>(maxWidth) - static_cast<int16_t>(line.width)) / 2;
|
|
671
|
+
else if (textAlign == 2) lineX = x + static_cast<int16_t>(maxWidth) - static_cast<int16_t>(line.width);
|
|
672
|
+
if (lineX + static_cast<int16_t>(line.width) <= targetLeft || lineX >= targetRight) {
|
|
612
673
|
lineY += lh;
|
|
613
674
|
continue;
|
|
614
675
|
}
|
|
@@ -616,7 +677,7 @@ static inline void ui_draw_wrapped_text(const char* text, int16_t x, int16_t y,
|
|
|
616
677
|
// text-overflow — only applies when the line is wider than maxWidth.
|
|
617
678
|
// textOverflow==1 (ellipsis): trim the span and append "...".
|
|
618
679
|
// textOverflow==0 (clip): trim the span to the edge, no dots.
|
|
619
|
-
if ((
|
|
680
|
+
if (static_cast<int16_t>(line.width) > static_cast<int16_t>(maxWidth)) {
|
|
620
681
|
if (textOverflow) ui_truncate_ellipsis(lineBuf, UI_TEXT_LINE_BUF, maxWidth, ts, fontFace, letterSpacing);
|
|
621
682
|
else ui_truncate_clip(lineBuf, UI_TEXT_LINE_BUF, maxWidth, ts, fontFace, letterSpacing);
|
|
622
683
|
}
|
|
@@ -638,23 +699,23 @@ static inline void ui_draw_rich_text(uint16_t nodeIdx, int16_t x, int16_t y, UI_
|
|
|
638
699
|
uint8_t useFgOverride = 0, UI_COLOR_T fgOverride = 0, uint16_t maxWidth = 0) {
|
|
639
700
|
UINode* n = &__ui_nodes[nodeIdx];
|
|
640
701
|
uint16_t alignWidth = maxWidth ? maxWidth : n->box.w;
|
|
641
|
-
int16_t targetLeft =
|
|
642
|
-
int16_t targetTop =
|
|
643
|
-
int16_t targetRight =
|
|
644
|
-
int16_t targetBottom =
|
|
645
|
-
uint16_t richSegEnd =
|
|
702
|
+
int16_t targetLeft = static_cast<int16_t>(-__ui_draw_off_x);
|
|
703
|
+
int16_t targetTop = static_cast<int16_t>(-__ui_draw_off_y);
|
|
704
|
+
int16_t targetRight = static_cast<int16_t>(ui_display_target_width() - __ui_draw_off_x);
|
|
705
|
+
int16_t targetBottom = static_cast<int16_t>(ui_display_target_height() - __ui_draw_off_y);
|
|
706
|
+
uint16_t richSegEnd = static_cast<uint16_t>(n->richSegStart + n->richSegCount);
|
|
646
707
|
for (uint16_t si = n->richSegStart; si < richSegEnd; si++) {
|
|
647
708
|
UIRichSeg* seg = &__ui_rich_segs[si];
|
|
648
709
|
if (seg->line >= n->richLineCount) continue;
|
|
649
710
|
UIRichLine* line = &__ui_rich_lines[n->richLineStart + seg->line];
|
|
650
711
|
int16_t lineTop = y + line->y;
|
|
651
|
-
int16_t lineBottom =
|
|
712
|
+
int16_t lineBottom = static_cast<int16_t>(lineTop + static_cast<int16_t>(line->h));
|
|
652
713
|
if (lineBottom <= targetTop || lineTop >= targetBottom) continue;
|
|
653
714
|
int16_t lineX = x;
|
|
654
|
-
if (n->textAlign == 1) lineX = x + ((
|
|
655
|
-
else if (n->textAlign == 2) lineX = x + (
|
|
656
|
-
int16_t segX =
|
|
657
|
-
int16_t segRight =
|
|
715
|
+
if (n->textAlign == 1) lineX = x + (static_cast<int16_t>(alignWidth) - static_cast<int16_t>(line->w)) / 2;
|
|
716
|
+
else if (n->textAlign == 2) lineX = x + static_cast<int16_t>(alignWidth) - static_cast<int16_t>(line->w);
|
|
717
|
+
int16_t segX = static_cast<int16_t>(lineX + seg->x);
|
|
718
|
+
int16_t segRight = static_cast<int16_t>(segX + static_cast<int16_t>(seg->w));
|
|
658
719
|
if (segRight <= targetLeft || segX >= targetRight) continue;
|
|
659
720
|
UIRichRun* run = &__ui_runs[n->runStart + seg->runIndex];
|
|
660
721
|
// Baseline alignment: for asset fonts, the ascent is the font face's
|
|
@@ -664,9 +725,9 @@ static inline void ui_draw_rich_text(uint16_t nodeIdx, int16_t x, int16_t y, UI_
|
|
|
664
725
|
int16_t ascent;
|
|
665
726
|
if (run->fontFace) {
|
|
666
727
|
const UIFontFace* face = ui_font_face(run->fontFace);
|
|
667
|
-
ascent = face ? (
|
|
728
|
+
ascent = face ? static_cast<int16_t>(face->baseline) : (7 * static_cast<int16_t>(run->textSize));
|
|
668
729
|
} else {
|
|
669
|
-
ascent = 7 * (
|
|
730
|
+
ascent = 7 * static_cast<int16_t>(run->textSize);
|
|
670
731
|
}
|
|
671
732
|
int16_t segY = y + line->baseline - ascent;
|
|
672
733
|
UI_COLOR_T fg = useFgOverride ? fgOverride : run->fg;
|
|
@@ -682,13 +743,13 @@ static inline void ui_draw_rich_text(uint16_t nodeIdx, int16_t x, int16_t y, UI_
|
|
|
682
743
|
// but uses measured segment rects instead of fixed row heights.
|
|
683
744
|
static inline int8_t ui_rich_link_hit(uint16_t nodeIdx, int16_t px, int16_t py) {
|
|
684
745
|
UINode* n = &__ui_nodes[nodeIdx];
|
|
685
|
-
int16_t insetL = (
|
|
686
|
-
int16_t insetR = (
|
|
687
|
-
int16_t insetT = (
|
|
746
|
+
int16_t insetL = static_cast<int16_t>(n->borderWidth) + static_cast<int16_t>(n->paddingLeft);
|
|
747
|
+
int16_t insetR = static_cast<int16_t>(n->borderWidth) + static_cast<int16_t>(n->paddingRight);
|
|
748
|
+
int16_t insetT = static_cast<int16_t>(n->borderWidth) + static_cast<int16_t>(n->paddingTop);
|
|
688
749
|
int16_t localX = px - insetL;
|
|
689
750
|
int16_t localY = py - insetT;
|
|
690
|
-
uint16_t alignWidth = n->box.w >
|
|
691
|
-
?
|
|
751
|
+
uint16_t alignWidth = n->box.w > static_cast<uint16_t>(insetL + insetR)
|
|
752
|
+
? static_cast<uint16_t>(static_cast<int16_t>(n->box.w) - insetL - insetR)
|
|
692
753
|
: 0;
|
|
693
754
|
for (uint16_t si = n->richSegStart; si < n->richSegStart + n->richSegCount; si++) {
|
|
694
755
|
UIRichSeg* seg = &__ui_rich_segs[si];
|
|
@@ -698,11 +759,11 @@ static inline int8_t ui_rich_link_hit(uint16_t nodeIdx, int16_t px, int16_t py)
|
|
|
698
759
|
// Segment x is relative to its line's left edge (pre-alignment). For the
|
|
699
760
|
// hit-test, account for center/right alignment the same way draw does.
|
|
700
761
|
int16_t originX = 0;
|
|
701
|
-
if (n->textAlign == 1) originX = ((
|
|
702
|
-
else if (n->textAlign == 2) originX = (
|
|
762
|
+
if (n->textAlign == 1) originX = (static_cast<int16_t>(alignWidth) - static_cast<int16_t>(line->w)) / 2;
|
|
763
|
+
else if (n->textAlign == 2) originX = static_cast<int16_t>(alignWidth) - static_cast<int16_t>(line->w);
|
|
703
764
|
int16_t sx = originX + seg->x;
|
|
704
765
|
int16_t sy = line->y;
|
|
705
|
-
if (localX >= sx && localX < sx + (
|
|
766
|
+
if (localX >= sx && localX < sx + static_cast<int16_t>(seg->w) && localY >= sy && localY < sy + static_cast<int16_t>(line->h)) {
|
|
706
767
|
return run->linkTarget;
|
|
707
768
|
}
|
|
708
769
|
}
|
|
@@ -6,6 +6,9 @@ export function emitTickBindingsPhase() {
|
|
|
6
6
|
static inline void ui_tick(uint16_t deltaMs) {
|
|
7
7
|
// Touch poll — runs if touch is configured (defined by the emit layer)
|
|
8
8
|
ui_poll_touch();
|
|
9
|
+
// Allocate the retained framebuffer before transitions run. Geometry repair
|
|
10
|
+
// must clear old animation footprints into RAM, not directly onto the panel.
|
|
11
|
+
(void)ui_get_framebuffer();
|
|
9
12
|
// ⓪' Poll GPIO inputs
|
|
10
13
|
ui_poll_inputs();
|
|
11
14
|
#if defined(UI_HIDE_OSK)
|
|
@@ -15,21 +18,27 @@ static inline void ui_tick(uint16_t deltaMs) {
|
|
|
15
18
|
// keeps cycling even when no other state changes (otherwise the caret
|
|
16
19
|
// freezes once the typed-text dirty clears).
|
|
17
20
|
if (__ui_kb_visible && __ui_kb_target >= 0 && (__ui_kb_blink & 0x10)) {
|
|
18
|
-
ui_mark_dirty((
|
|
21
|
+
ui_mark_dirty(static_cast<uint16_t>(__ui_kb_target));
|
|
19
22
|
}
|
|
20
23
|
#endif
|
|
21
24
|
// ⓪ Evaluate bindings: call each binding's fn, compare to the node's
|
|
22
25
|
// current property value, mark dirty if changed.
|
|
23
26
|
for (uint16_t i = 0; i < __ui_binding_count; i++) {
|
|
24
27
|
if (__ui_bindings[i].prop == PROP_TEXT && __ui_bindings[i].textFn) {
|
|
25
|
-
// Text
|
|
28
|
+
// Text callbacks are necessarily evaluated to detect changes (the callback
|
|
29
|
+
// may format a signal or read external state), but avoid a stack-sized old
|
|
30
|
+
// copy: compare the generated value against the node buffer after writing.
|
|
31
|
+
// The callback contract is fill-style and must be deterministic for the same
|
|
32
|
+
// inputs; the temporary buffer keeps a change from destroying the old text.
|
|
26
33
|
uint16_t n = __ui_bindings[i].node;
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
__ui_bindings[i].textFn(
|
|
31
|
-
|
|
32
|
-
if (strcmp(
|
|
34
|
+
if (n >= __ui_node_count) continue;
|
|
35
|
+
char nextBuf[UI_TEXT_BUF + 1];
|
|
36
|
+
nextBuf[0] = '\\0';
|
|
37
|
+
__ui_bindings[i].textFn(nextBuf, UI_TEXT_BUF + 1);
|
|
38
|
+
nextBuf[UI_TEXT_BUF] = '\\0';
|
|
39
|
+
if (strcmp(nextBuf, __ui_nodes[n].textBuffer) != 0) {
|
|
40
|
+
strncpy(__ui_nodes[n].textBuffer, nextBuf, UI_TEXT_BUF);
|
|
41
|
+
__ui_nodes[n].textBuffer[UI_TEXT_BUF] = '\\0';
|
|
33
42
|
ui_invalidate_text_layout_cache(n);
|
|
34
43
|
ui_mark_dirty(n);
|
|
35
44
|
}
|
|
@@ -46,13 +55,18 @@ static inline void ui_tick(uint16_t deltaMs) {
|
|
|
46
55
|
if (__ui_bindings[i].prop == PROP_VALUE) {
|
|
47
56
|
// Numeric value binding: drive a progress/range node's value live.
|
|
48
57
|
uint16_t n = __ui_bindings[i].node;
|
|
49
|
-
int16_t v = (
|
|
58
|
+
int16_t v = static_cast<int16_t>(__ui_bindings[i].fn());
|
|
50
59
|
if (v != __ui_nodes[n].value) {
|
|
51
60
|
__ui_nodes[n].value = v;
|
|
52
61
|
ui_mark_dirty(n);
|
|
53
62
|
}
|
|
54
63
|
continue;
|
|
55
64
|
}
|
|
65
|
+
// Cache numeric binding results. The first sample establishes the baseline;
|
|
66
|
+
// subsequent identical values return immediately without touching node state.
|
|
67
|
+
if (__ui_bindings[i].initialized && newVal == __ui_bindings[i].lastValue) continue;
|
|
68
|
+
__ui_bindings[i].lastValue = newVal;
|
|
69
|
+
__ui_bindings[i].initialized = 1;
|
|
56
70
|
uint32_t* target = (__ui_bindings[i].prop == PROP_BG) ? &__ui_nodes[__ui_bindings[i].node].bg
|
|
57
71
|
: (__ui_bindings[i].prop == PROP_FG) ? &__ui_nodes[__ui_bindings[i].node].fg
|
|
58
72
|
: (__ui_bindings[i].prop == PROP_BORDER_COLOR) ? &__ui_nodes[__ui_bindings[i].node].borderColor
|
|
@@ -75,10 +89,14 @@ static inline void ui_tick(uint16_t deltaMs) {
|
|
|
75
89
|
for (uint16_t i = 0; i < __ui_node_count; i++) {
|
|
76
90
|
if (__ui_nodes[i].virtualized && __ui_nodes[i].listCountFn) {
|
|
77
91
|
uint16_t ih = __ui_nodes[i].listItemHeight > 0 ? __ui_nodes[i].listItemHeight : 24;
|
|
92
|
+
// Keep the callback result as the list's invalidation token. The callback
|
|
93
|
+
// is still evaluated because it is the public list-count source, but an
|
|
94
|
+
// unchanged token avoids layout/dirty work and preserves the retained
|
|
95
|
+
// viewport canvas.
|
|
78
96
|
uint16_t newCount = __ui_nodes[i].listCountFn();
|
|
79
97
|
if (newCount != __ui_nodes[i].listCount) {
|
|
80
98
|
__ui_nodes[i].listCount = newCount;
|
|
81
|
-
__ui_nodes[i].contentHeight =
|
|
99
|
+
__ui_nodes[i].contentHeight = static_cast<int16_t>(static_cast<uint32_t>(newCount) * ih);
|
|
82
100
|
__ui_nodes[i].lastPaintedScrollY = __ui_nodes[i].scrollY - (__ui_nodes[i].box.h > 0 ? __ui_nodes[i].box.h : 1);
|
|
83
101
|
ui_mark_dirty(i);
|
|
84
102
|
}
|
|
@@ -94,6 +112,7 @@ static inline void ui_tick(uint16_t deltaMs) {
|
|
|
94
112
|
for (uint16_t i = 0; i < __ui_input_binding_count; i++) {
|
|
95
113
|
if (!__ui_input_bindings[i].cb) continue;
|
|
96
114
|
uint16_t n = __ui_input_bindings[i].node;
|
|
115
|
+
if (n >= __ui_node_count) continue;
|
|
97
116
|
const char* cur = __ui_nodes[n].textBuffer;
|
|
98
117
|
if (strcmp(cur, __ui_input_bindings[i].lastSeen) != 0) {
|
|
99
118
|
strncpy(__ui_input_bindings[i].lastSeen, cur, UI_TEXT_BUF);
|