@typecad/ui 0.1.0-alpha.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/README.md +1443 -0
- package/dist/engine-index.d.ts +7 -0
- package/dist/engine-index.js +41 -0
- package/dist/index.d.ts +165 -0
- package/dist/index.js +42 -0
- package/dist/preview/build-program.d.ts +7 -0
- package/dist/preview/build-program.js +665 -0
- package/dist/preview/host-gfx.d.ts +90 -0
- package/dist/preview/host-gfx.js +552 -0
- package/dist/preview/host-ui-runtime.d.ts +241 -0
- package/dist/preview/host-ui-runtime.js +3236 -0
- package/dist/preview/types.d.ts +83 -0
- package/dist/preview/types.js +1 -0
- package/dist/types.d.ts +108 -0
- package/dist/types.js +10 -0
- package/dist/ui-engine/block-layout.d.ts +8 -0
- package/dist/ui-engine/block-layout.js +95 -0
- package/dist/ui-engine/color.d.ts +49 -0
- package/dist/ui-engine/color.js +273 -0
- package/dist/ui-engine/css-parser.d.ts +155 -0
- package/dist/ui-engine/css-parser.js +1058 -0
- package/dist/ui-engine/default-keyboards.d.ts +3 -0
- package/dist/ui-engine/default-keyboards.js +53 -0
- package/dist/ui-engine/easing.d.ts +12 -0
- package/dist/ui-engine/easing.js +76 -0
- package/dist/ui-engine/font-assets.d.ts +50 -0
- package/dist/ui-engine/font-assets.js +431 -0
- package/dist/ui-engine/html-parser.d.ts +116 -0
- package/dist/ui-engine/html-parser.js +284 -0
- package/dist/ui-engine/image-assets.d.ts +21 -0
- package/dist/ui-engine/image-assets.js +125 -0
- package/dist/ui-engine/inline-parser.d.ts +30 -0
- package/dist/ui-engine/inline-parser.js +66 -0
- package/dist/ui-engine/keyframes.d.ts +3 -0
- package/dist/ui-engine/keyframes.js +39 -0
- package/dist/ui-engine/layout-engine.d.ts +35 -0
- package/dist/ui-engine/layout-engine.js +203 -0
- package/dist/ui-engine/model.d.ts +239 -0
- package/dist/ui-engine/model.js +987 -0
- package/dist/ui-engine/rich-layout.d.ts +51 -0
- package/dist/ui-engine/rich-layout.js +220 -0
- package/dist/ui-engine/run-types.d.ts +13 -0
- package/dist/ui-engine/run-types.js +1 -0
- package/dist/ui-engine/runtime-header/antialiasing.d.ts +1 -0
- package/dist/ui-engine/runtime-header/antialiasing.js +141 -0
- package/dist/ui-engine/runtime-header/blend-bodies.d.ts +1 -0
- package/dist/ui-engine/runtime-header/blend-bodies.js +39 -0
- package/dist/ui-engine/runtime-header/canvas-helpers.d.ts +1 -0
- package/dist/ui-engine/runtime-header/canvas-helpers.js +155 -0
- package/dist/ui-engine/runtime-header/canvas-scrollbar.d.ts +1 -0
- package/dist/ui-engine/runtime-header/canvas-scrollbar.js +105 -0
- package/dist/ui-engine/runtime-header/color-mono-refresh.d.ts +1 -0
- package/dist/ui-engine/runtime-header/color-mono-refresh.js +154 -0
- package/dist/ui-engine/runtime-header/dirty-scroll-mutators.d.ts +1 -0
- package/dist/ui-engine/runtime-header/dirty-scroll-mutators.js +169 -0
- package/dist/ui-engine/runtime-header/display-shim.d.ts +1 -0
- package/dist/ui-engine/runtime-header/display-shim.js +104 -0
- package/dist/ui-engine/runtime-header/forward-decls.d.ts +1 -0
- package/dist/ui-engine/runtime-header/forward-decls.js +137 -0
- package/dist/ui-engine/runtime-header/guard-close.d.ts +1 -0
- package/dist/ui-engine/runtime-header/guard-close.js +8 -0
- package/dist/ui-engine/runtime-header/image-drawing.d.ts +1 -0
- package/dist/ui-engine/runtime-header/image-drawing.js +300 -0
- package/dist/ui-engine/runtime-header/init-press-input.d.ts +1 -0
- package/dist/ui-engine/runtime-header/init-press-input.js +126 -0
- package/dist/ui-engine/runtime-header/keyboard.d.ts +1 -0
- package/dist/ui-engine/runtime-header/keyboard.js +342 -0
- package/dist/ui-engine/runtime-header/node-decoration.d.ts +1 -0
- package/dist/ui-engine/runtime-header/node-decoration.js +212 -0
- package/dist/ui-engine/runtime-header/paint-order-coords.d.ts +1 -0
- package/dist/ui-engine/runtime-header/paint-order-coords.js +190 -0
- package/dist/ui-engine/runtime-header/paint-rects-repair.d.ts +1 -0
- package/dist/ui-engine/runtime-header/paint-rects-repair.js +553 -0
- package/dist/ui-engine/runtime-header/scroll-physics.d.ts +1 -0
- package/dist/ui-engine/runtime-header/scroll-physics.js +161 -0
- package/dist/ui-engine/runtime-header/state-bindings-nav.d.ts +1 -0
- package/dist/ui-engine/runtime-header/state-bindings-nav.js +176 -0
- package/dist/ui-engine/runtime-header/structs.d.ts +1 -0
- package/dist/ui-engine/runtime-header/structs.js +220 -0
- package/dist/ui-engine/runtime-header/text-rendering.d.ts +1 -0
- package/dist/ui-engine/runtime-header/text-rendering.js +715 -0
- package/dist/ui-engine/runtime-header/tick/bindings-phase.d.ts +1 -0
- package/dist/ui-engine/runtime-header/tick/bindings-phase.js +103 -0
- package/dist/ui-engine/runtime-header/tick/dirty-draw-phase.d.ts +1 -0
- package/dist/ui-engine/runtime-header/tick/dirty-draw-phase.js +1051 -0
- package/dist/ui-engine/runtime-header/tick/flush-phase.d.ts +1 -0
- package/dist/ui-engine/runtime-header/tick/flush-phase.js +17 -0
- package/dist/ui-engine/runtime-header/tick/scroll-canvas-phase.d.ts +1 -0
- package/dist/ui-engine/runtime-header/tick/scroll-canvas-phase.js +15 -0
- package/dist/ui-engine/runtime-header/tick/transitions-phase.d.ts +1 -0
- package/dist/ui-engine/runtime-header/tick/transitions-phase.js +213 -0
- package/dist/ui-engine/runtime-header/tick-motion-helpers.d.ts +1 -0
- package/dist/ui-engine/runtime-header/tick-motion-helpers.js +26 -0
- package/dist/ui-engine/runtime-header/tick.d.ts +1 -0
- package/dist/ui-engine/runtime-header/tick.js +17 -0
- package/dist/ui-engine/runtime-header/touch-keyboard-fwd.d.ts +1 -0
- package/dist/ui-engine/runtime-header/touch-keyboard-fwd.js +406 -0
- package/dist/ui-engine/runtime-header/types-defines.d.ts +1 -0
- package/dist/ui-engine/runtime-header/types-defines.js +79 -0
- package/dist/ui-engine/runtime-header.d.ts +1 -0
- package/dist/ui-engine/runtime-header.js +69 -0
- package/dist/ui-engine/scroll-memory-diagnostics.d.ts +16 -0
- package/dist/ui-engine/scroll-memory-diagnostics.js +58 -0
- package/dist/ui-engine/select-engine.d.ts +4 -0
- package/dist/ui-engine/select-engine.js +19 -0
- package/dist/ui-engine/style-resolver.d.ts +77 -0
- package/dist/ui-engine/style-resolver.js +341 -0
- package/dist/ui-engine/text-layout.d.ts +19 -0
- package/dist/ui-engine/text-layout.js +92 -0
- package/dist/ui-engine/transpile-ui.d.ts +11 -0
- package/dist/ui-engine/transpile-ui.js +33 -0
- package/dist/ui-engine/ua-stylesheet.d.ts +3 -0
- package/dist/ui-engine/ua-stylesheet.js +56 -0
- package/dist/ui-engine/ui-file-splitter.d.ts +14 -0
- package/dist/ui-engine/ui-file-splitter.js +47 -0
- package/dist/ui-engine/ui-lowering.d.ts +33 -0
- package/dist/ui-engine/ui-lowering.js +390 -0
- package/dist/ui-engine/ui-registry.d.ts +81 -0
- package/dist/ui-engine/ui-registry.js +429 -0
- package/dist/ui-engine/yoga-layout.d.ts +12 -0
- package/dist/ui-engine/yoga-layout.js +314 -0
- package/package.json +86 -0
- package/src/engine-index.ts +51 -0
- package/src/index.ts +232 -0
- package/src/preview/build-program.ts +734 -0
- package/src/preview/host-gfx.ts +580 -0
- package/src/preview/host-ui-runtime.ts +3245 -0
- package/src/preview/types.ts +96 -0
- package/src/types.ts +127 -0
- package/src/ui-engine/block-layout.ts +104 -0
- package/src/ui-engine/color.ts +263 -0
- package/src/ui-engine/css-parser.ts +1003 -0
- package/src/ui-engine/default-keyboards.ts +58 -0
- package/src/ui-engine/easing.ts +53 -0
- package/src/ui-engine/font-assets.ts +512 -0
- package/src/ui-engine/html-parser.ts +397 -0
- package/src/ui-engine/image-assets.ts +170 -0
- package/src/ui-engine/inline-parser.ts +78 -0
- package/src/ui-engine/keyframes.ts +47 -0
- package/src/ui-engine/layout-engine.ts +228 -0
- package/src/ui-engine/model.ts +1225 -0
- package/src/ui-engine/rich-layout.ts +257 -0
- package/src/ui-engine/run-types.ts +14 -0
- package/src/ui-engine/runtime-header/antialiasing.ts +141 -0
- package/src/ui-engine/runtime-header/blend-bodies.ts +39 -0
- package/src/ui-engine/runtime-header/canvas-helpers.ts +155 -0
- package/src/ui-engine/runtime-header/canvas-scrollbar.ts +105 -0
- package/src/ui-engine/runtime-header/color-mono-refresh.ts +154 -0
- package/src/ui-engine/runtime-header/dirty-scroll-mutators.ts +169 -0
- package/src/ui-engine/runtime-header/display-shim.ts +104 -0
- package/src/ui-engine/runtime-header/forward-decls.ts +137 -0
- package/src/ui-engine/runtime-header/guard-close.ts +8 -0
- package/src/ui-engine/runtime-header/image-drawing.ts +300 -0
- package/src/ui-engine/runtime-header/init-press-input.ts +126 -0
- package/src/ui-engine/runtime-header/keyboard.ts +342 -0
- package/src/ui-engine/runtime-header/node-decoration.ts +212 -0
- package/src/ui-engine/runtime-header/paint-order-coords.ts +190 -0
- package/src/ui-engine/runtime-header/paint-rects-repair.ts +553 -0
- package/src/ui-engine/runtime-header/scroll-physics.ts +161 -0
- package/src/ui-engine/runtime-header/state-bindings-nav.ts +176 -0
- package/src/ui-engine/runtime-header/structs.ts +220 -0
- package/src/ui-engine/runtime-header/text-rendering.ts +715 -0
- package/src/ui-engine/runtime-header/tick/bindings-phase.ts +103 -0
- package/src/ui-engine/runtime-header/tick/dirty-draw-phase.ts +1051 -0
- package/src/ui-engine/runtime-header/tick/flush-phase.ts +17 -0
- package/src/ui-engine/runtime-header/tick/scroll-canvas-phase.ts +15 -0
- package/src/ui-engine/runtime-header/tick/transitions-phase.ts +213 -0
- package/src/ui-engine/runtime-header/tick-motion-helpers.ts +26 -0
- package/src/ui-engine/runtime-header/tick.ts +18 -0
- package/src/ui-engine/runtime-header/touch-keyboard-fwd.ts +406 -0
- package/src/ui-engine/runtime-header/types-defines.ts +79 -0
- package/src/ui-engine/runtime-header.ts +71 -0
- package/src/ui-engine/scroll-memory-diagnostics.ts +79 -0
- package/src/ui-engine/select-engine.ts +21 -0
- package/src/ui-engine/style-resolver.ts +416 -0
- package/src/ui-engine/text-layout.ts +114 -0
- package/src/ui-engine/transpile-ui.ts +45 -0
- package/src/ui-engine/ua-stylesheet.ts +60 -0
- package/src/ui-engine/ui-file-splitter.ts +59 -0
- package/src/ui-engine/ui-lowering.ts +459 -0
- package/src/ui-engine/ui-registry.ts +530 -0
- package/src/ui-engine/yoga-layout.ts +309 -0
|
@@ -0,0 +1,1051 @@
|
|
|
1
|
+
// Slice of the C++ runtime header (original source lines 4234-5278).
|
|
2
|
+
// refresh begin, kb dirty flush, scroll Mode B/C, fb target, stacking draw loop.
|
|
3
|
+
// See docs/superpowers/specs/2026-07-12-split-runtime-header-design.md.
|
|
4
|
+
export function emitTickDirtyDrawPhase() {
|
|
5
|
+
return `
|
|
6
|
+
|
|
7
|
+
// ② Draw dirty nodes directly to the display object.
|
|
8
|
+
// Begin a deferred-refresh frame: resets the dirty-rect accumulator. On TFT
|
|
9
|
+
// (no backing store) this compiles to a no-op.
|
|
10
|
+
ui_refresh_begin_frame();
|
|
11
|
+
// Skip the node draw pass while the keyboard overlay is visible — its opaque
|
|
12
|
+
// background covers everything underneath, so redrawing app nodes wastes SPI
|
|
13
|
+
// bandwidth and causes flashing. Nodes redraw once when the keyboard closes
|
|
14
|
+
// (ui_kb_close marks the edited input dirty; ui_kb_open had marked all dirty
|
|
15
|
+
// on open so they're stale-but-covered while the keyboard is up).
|
|
16
|
+
//
|
|
17
|
+
// UI_HIDE_OSK (desktop SDL): the editing session still runs (buffer, target,
|
|
18
|
+
// commit-on-close) so real-keyboard typing works, but the 6×4 grid isn't drawn
|
|
19
|
+
// and the node pass ISN'T skipped — the app keeps rendering normally with the
|
|
20
|
+
// edited input's textBuffer showing the typed text. The grid is redundant when
|
|
21
|
+
// the host has a real keyboard.
|
|
22
|
+
#if !defined(UI_HIDE_OSK)
|
|
23
|
+
if (__ui_kb_visible) {
|
|
24
|
+
// Redraw only what changed:
|
|
25
|
+
// 1 = full redraw (open, shift toggle, page swap)
|
|
26
|
+
// 2 = text row + single key (char insert, delete, highlight change)
|
|
27
|
+
if (__ui_kb_dirty == 1) {
|
|
28
|
+
ui_kb_draw();
|
|
29
|
+
} else if (__ui_kb_dirty == 2) {
|
|
30
|
+
ui_kb_draw_text_row();
|
|
31
|
+
if (__ui_kb_repaint_key >= 0 && __ui_kb_keys[__ui_kb_repaint_key].special != 255) {
|
|
32
|
+
ui_kb_draw_key((uint8_t)__ui_kb_repaint_key);
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
__ui_kb_dirty = 0;
|
|
36
|
+
__ui_kb_repaint_key = -1;
|
|
37
|
+
return;
|
|
38
|
+
}
|
|
39
|
+
#endif
|
|
40
|
+
// Process each dirty scroll container (Mode B shift-and-repair). A scroll
|
|
41
|
+
// delta shifts existing canvas pixels by the delta and repaints only the
|
|
42
|
+
// newly-exposed strip; a full invalidation (or no canvas) redraws the subtree.
|
|
43
|
+
// One container per frame — the active scroll owner repaints via its canvas.
|
|
44
|
+
int16_t bufferedScrollNode = -1; // int16: node index can exceed 127
|
|
45
|
+
int16_t bufferedScrollVX = 0; // viewport origin X for coord translation
|
|
46
|
+
int16_t bufferedScrollVY = 0; // viewport origin Y
|
|
47
|
+
CuttlefishCanvas16* bufferedScrollCanvas = nullptr;
|
|
48
|
+
CuttlefishCanvas16* bufferedScrollRepaintCanvas = nullptr;
|
|
49
|
+
int16_t bufferedScrollRepaintY = 0;
|
|
50
|
+
int16_t bufferedScrollRepaintH = 0;
|
|
51
|
+
uint8_t bufferedScrollDirectStrip = 0;
|
|
52
|
+
for (uint16_t oi = 0; ; oi++) {
|
|
53
|
+
uint16_t s;
|
|
54
|
+
if (__ui_scroll_owners) {
|
|
55
|
+
if (oi >= __ui_scroll_owner_count) break;
|
|
56
|
+
s = __ui_scroll_owners[oi];
|
|
57
|
+
} else {
|
|
58
|
+
if (oi >= __ui_node_count) break;
|
|
59
|
+
s = oi;
|
|
60
|
+
if (!__ui_nodes[s].scrollable || __ui_nodes[s].virtualized) continue;
|
|
61
|
+
}
|
|
62
|
+
if (!ui_is_effectively_visible(s)) continue;
|
|
63
|
+
if (__ui_nodes[s].screenId != __ui_active_screen) continue;
|
|
64
|
+
if (__ui_nodes[s].contentHeight <= __ui_nodes[s].box.h) continue;
|
|
65
|
+
if (!__ui_nodes[s].dirty) continue;
|
|
66
|
+
|
|
67
|
+
int16_t vw = __ui_nodes[s].box.w;
|
|
68
|
+
int16_t vh = __ui_nodes[s].box.h;
|
|
69
|
+
int16_t vox = __ui_nodes[s].box.x;
|
|
70
|
+
int16_t voy = __ui_nodes[s].box.y;
|
|
71
|
+
UI_COLOR_T scrollBg = __ui_nodes[s].hasBg ? __ui_nodes[s].bg : __ui_nodes[s].clearColor;
|
|
72
|
+
bufferedScrollCanvas = ui_get_container_canvas(vw, vh);
|
|
73
|
+
// Track canvas-allocation success so ui_apply_scroll_delta can lock scrolling
|
|
74
|
+
// for containers whose canvas won't fit (frozen-but-not-torn contract).
|
|
75
|
+
if (__ui_scroll_canvas_ok) {
|
|
76
|
+
__ui_scroll_canvas_ok[s] = bufferedScrollCanvas ? 1 : 0;
|
|
77
|
+
}
|
|
78
|
+
if (bufferedScrollCanvas) {
|
|
79
|
+
bufferedScrollNode = (int16_t)s;
|
|
80
|
+
bufferedScrollVX = vox;
|
|
81
|
+
bufferedScrollVY = voy;
|
|
82
|
+
|
|
83
|
+
// Mode B: shift delta = how far scrollY moved since this canvas was last
|
|
84
|
+
// painted. Small non-zero delta within one viewport → shift + repair strip.
|
|
85
|
+
int16_t deltaY = __ui_nodes[s].scrollY - __ui_nodes[s].lastPaintedScrollY;
|
|
86
|
+
int16_t absDelta = deltaY < 0 ? -deltaY : deltaY;
|
|
87
|
+
uint8_t canShift = (deltaY != 0 && absDelta < vh);
|
|
88
|
+
|
|
89
|
+
if (canShift) {
|
|
90
|
+
int16_t exposedY = 0;
|
|
91
|
+
int16_t exposedH = 0;
|
|
92
|
+
ui_shift_container_canvas(bufferedScrollCanvas, deltaY, scrollBg, &exposedY, &exposedH);
|
|
93
|
+
bufferedScrollRepaintCanvas = ui_get_repair_canvas(vw, exposedH);
|
|
94
|
+
if (bufferedScrollRepaintCanvas) {
|
|
95
|
+
bufferedScrollRepaintY = exposedY;
|
|
96
|
+
bufferedScrollRepaintH = exposedH;
|
|
97
|
+
display_canvasFillScreen(bufferedScrollRepaintCanvas, scrollBg);
|
|
98
|
+
UIRect exposed = { vox, (int16_t)(voy + exposedY), vw, exposedH };
|
|
99
|
+
for (uint16_t c = s + 1; c < __ui_nodes[s].subtreeEnd; c++) {
|
|
100
|
+
if (!ui_is_effectively_visible(c) || __ui_nodes[c].screenId != __ui_active_screen) {
|
|
101
|
+
__ui_nodes[c].dirty = 0;
|
|
102
|
+
continue;
|
|
103
|
+
}
|
|
104
|
+
if (!__ui_nodes[c].dirty && exposedH > 0) {
|
|
105
|
+
UIRect cr;
|
|
106
|
+
ui_node_current_paint_rect(c, &cr);
|
|
107
|
+
if (cr.w > 0 && cr.h > 0 &&
|
|
108
|
+
ui_rects_intersect(cr.x, cr.y, cr.w, cr.h, exposed.x, exposed.y, exposed.w, exposed.h)) {
|
|
109
|
+
__ui_nodes[c].dirty = 1;
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
if (__ui_nodes[c].dirty) {
|
|
113
|
+
if (__ui_nodes[c].kind == NODE_PROGRESS) __ui_nodes[c].lastTextWidth = -1;
|
|
114
|
+
else if (__ui_nodes[c].kind == NODE_RANGE) __ui_nodes[c].lastTextWidth = -1;
|
|
115
|
+
__ui_nodes[c].lastTextHeight = 0;
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
} else {
|
|
119
|
+
canShift = 0;
|
|
120
|
+
}
|
|
121
|
+
}
|
|
122
|
+
if (!canShift) {
|
|
123
|
+
for (uint16_t c = s; c < __ui_nodes[s].subtreeEnd; c++) {
|
|
124
|
+
__ui_nodes[c].dirty = 1;
|
|
125
|
+
if (__ui_nodes[c].kind == NODE_PROGRESS) __ui_nodes[c].lastTextWidth = -1;
|
|
126
|
+
else if (__ui_nodes[c].kind == NODE_RANGE) __ui_nodes[c].lastTextWidth = -1;
|
|
127
|
+
__ui_nodes[c].lastTextHeight = 0;
|
|
128
|
+
}
|
|
129
|
+
display_canvasFillScreen(bufferedScrollCanvas, scrollBg);
|
|
130
|
+
}
|
|
131
|
+
// The scroll owner is represented by bufferedScrollCanvas this frame.
|
|
132
|
+
// Drawing it directly first clears the live display and makes scrolling
|
|
133
|
+
// visibly flash before the canvas is pushed.
|
|
134
|
+
__ui_nodes[s].dirty = 0;
|
|
135
|
+
} else {
|
|
136
|
+
// Canvas won't fit. Mode C strip-only for small in-viewport deltas; otherwise
|
|
137
|
+
// graceful skip (keep last frame, retry next tick). Never direct-draw a full
|
|
138
|
+
// scroll subtree to the display — that clears the live viewport and flashes.
|
|
139
|
+
int16_t deltaY = __ui_nodes[s].scrollY - __ui_nodes[s].lastPaintedScrollY;
|
|
140
|
+
int16_t absDelta = deltaY < 0 ? -deltaY : deltaY;
|
|
141
|
+
uint32_t scrollNeed = (uint32_t)(vw > 0 ? vw : 0) * (uint32_t)(vh > 0 ? vh : 0) * 2u;
|
|
142
|
+
if (absDelta > 0 && absDelta < vh) {
|
|
143
|
+
bufferedScrollNode = (int16_t)s;
|
|
144
|
+
bufferedScrollDirectStrip = 1;
|
|
145
|
+
ui_warn_scroll_memory((uint16_t)s, 2);
|
|
146
|
+
ui_scroll_direct_prepare(s, &bufferedScrollVX, &bufferedScrollVY);
|
|
147
|
+
break;
|
|
148
|
+
}
|
|
149
|
+
ui_warn_scroll_memory((uint16_t)s, scrollNeed > (uint32_t)UI_SCROLL_CANVAS_BUDGET_BYTES ? 1 : 0);
|
|
150
|
+
if (__ui_scroll_canvas_ok) __ui_scroll_canvas_ok[s] = 0;
|
|
151
|
+
continue;
|
|
152
|
+
}
|
|
153
|
+
// Only one scroll container per frame (the canvas is reused for subsequent
|
|
154
|
+
// ones in the next dirty frame). This matches the original design.
|
|
155
|
+
break;
|
|
156
|
+
}
|
|
157
|
+
// ── Framebuffer target selection ──────────────────────────────────────────
|
|
158
|
+
// When a full-screen framebuffer is available (ESP32 + PSRAM), redirect the
|
|
159
|
+
// entire dirty-node draw pass into it and push once at the end. Otherwise
|
|
160
|
+
// __ui_draw_target == display_defaultTarget() and draws go straight to the display
|
|
161
|
+
// (byte-identical to the pre-framebuffer path).
|
|
162
|
+
CuttlefishCanvas16* __ui_fb = ui_get_framebuffer();
|
|
163
|
+
CuttlefishDisplayTarget* __ui_draw_target = __ui_fb ? (CuttlefishDisplayTarget*)__ui_fb : display_defaultTarget();
|
|
164
|
+
if (__ui_fb) {
|
|
165
|
+
// Seed the framebuffer with the active screen's background so cleared/
|
|
166
|
+
// transparent regions resolve correctly, then draw dirty nodes on top.
|
|
167
|
+
uint16_t fbBg = 0x0000;
|
|
168
|
+
if (__ui_active_screen_bg_node < __ui_node_count) {
|
|
169
|
+
fbBg = __ui_nodes[__ui_active_screen_bg_node].hasBg
|
|
170
|
+
? __ui_nodes[__ui_active_screen_bg_node].bg
|
|
171
|
+
: __ui_nodes[__ui_active_screen_bg_node].clearColor;
|
|
172
|
+
}
|
|
173
|
+
display_canvasFillScreen(__ui_fb, fbBg);
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
// Draw dirty nodes in stacking order: lower z-index first, then source order.
|
|
177
|
+
// __ui_draw_order is built once in ui_init so each frame is O(N).
|
|
178
|
+
for (uint16_t __ui_draw_pass = 0; __ui_draw_pass < __ui_node_count; ) {
|
|
179
|
+
int16_t i;
|
|
180
|
+
if (__ui_draw_order) {
|
|
181
|
+
i = (int16_t)__ui_draw_order[__ui_draw_pass++];
|
|
182
|
+
if (!__ui_nodes[i].dirty) continue;
|
|
183
|
+
if (!ui_is_effectively_visible(i)) { __ui_nodes[i].dirty = 0; continue; }
|
|
184
|
+
if (__ui_nodes[i].screenId != __ui_active_screen) { __ui_nodes[i].dirty = 0; continue; }
|
|
185
|
+
} else {
|
|
186
|
+
// malloc failed at startup: preserve correct z-order via selection sort.
|
|
187
|
+
i = -1;
|
|
188
|
+
for (uint16_t candidate = 0; candidate < __ui_node_count; candidate++) {
|
|
189
|
+
if (!__ui_nodes[candidate].dirty) continue;
|
|
190
|
+
if (!ui_is_effectively_visible(candidate)) { __ui_nodes[candidate].dirty = 0; continue; }
|
|
191
|
+
if (__ui_nodes[candidate].screenId != __ui_active_screen) { __ui_nodes[candidate].dirty = 0; continue; }
|
|
192
|
+
if (i < 0 || ui_node_draws_before(candidate, (uint16_t)i)) i = (int16_t)candidate;
|
|
193
|
+
}
|
|
194
|
+
if (i < 0) break;
|
|
195
|
+
__ui_draw_pass++;
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
// Defer direct display draws for overflow scroll subtrees not composited this
|
|
199
|
+
// frame (Mode B canvas or Mode C strip). Drawing them directly clears the live
|
|
200
|
+
// viewport and produces sequential flashes (AGENTS.md).
|
|
201
|
+
{
|
|
202
|
+
int16_t scrollComp = ui_overflow_scroll_compositor((uint16_t)i);
|
|
203
|
+
if (scrollComp >= 0) {
|
|
204
|
+
uint8_t compositing = scrollComp == bufferedScrollNode &&
|
|
205
|
+
(bufferedScrollCanvas || bufferedScrollDirectStrip);
|
|
206
|
+
if (!compositing) {
|
|
207
|
+
if ((uint16_t)i == (uint16_t)scrollComp) break;
|
|
208
|
+
__ui_nodes[i].dirty = 0;
|
|
209
|
+
continue;
|
|
210
|
+
}
|
|
211
|
+
}
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
// Mode C strip: scroll owner is not drawn directly (would fill the viewport).
|
|
215
|
+
if (bufferedScrollDirectStrip && bufferedScrollNode >= 0 &&
|
|
216
|
+
(uint16_t)i == (uint16_t)bufferedScrollNode) {
|
|
217
|
+
__ui_nodes[i].dirty = 0;
|
|
218
|
+
continue;
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
if (bufferedScrollNode >= 0 && bufferedScrollCanvas &&
|
|
222
|
+
!(i > bufferedScrollNode && i < __ui_nodes[bufferedScrollNode].subtreeEnd) &&
|
|
223
|
+
ui_node_draws_before((uint16_t)bufferedScrollNode, (uint16_t)i) &&
|
|
224
|
+
!ui_scroll_subtree_has_dirty((uint16_t)bufferedScrollNode)) {
|
|
225
|
+
ui_push_buffered_scroll_canvas(bufferedScrollCanvas, bufferedScrollRepaintCanvas,
|
|
226
|
+
bufferedScrollNode, bufferedScrollVX, bufferedScrollVY,
|
|
227
|
+
bufferedScrollRepaintY, bufferedScrollRepaintH, __ui_draw_target);
|
|
228
|
+
bufferedScrollNode = -1;
|
|
229
|
+
bufferedScrollCanvas = nullptr;
|
|
230
|
+
bufferedScrollRepaintCanvas = nullptr;
|
|
231
|
+
}
|
|
232
|
+
|
|
233
|
+
// Redirect to the scroll canvas if this node is inside the buffered container.
|
|
234
|
+
uint8_t drawingBufferedScroll = bufferedScrollNode >= 0 && i > bufferedScrollNode && i < __ui_nodes[bufferedScrollNode].subtreeEnd;
|
|
235
|
+
int16_t origBoxX = __ui_nodes[i].box.x;
|
|
236
|
+
int16_t origBoxY = __ui_nodes[i].box.y;
|
|
237
|
+
if (drawingBufferedScroll) {
|
|
238
|
+
CuttlefishCanvas16* scrollDrawCanvas = bufferedScrollRepaintCanvas ? bufferedScrollRepaintCanvas : bufferedScrollCanvas;
|
|
239
|
+
ui_display_set_target(scrollDrawCanvas);
|
|
240
|
+
// Translate display coords → canvas-local coords (subtract viewport origin).
|
|
241
|
+
__ui_nodes[i].box.x = origBoxX - bufferedScrollVX;
|
|
242
|
+
__ui_nodes[i].box.y = origBoxY - bufferedScrollVY - (bufferedScrollRepaintCanvas ? bufferedScrollRepaintY : 0);
|
|
243
|
+
} else {
|
|
244
|
+
ui_display_set_target(__ui_draw_target);
|
|
245
|
+
}
|
|
246
|
+
int16_t baseDrawX = ui_base_draw_x_for_node(i);
|
|
247
|
+
int16_t baseDrawY = ui_base_draw_y_for_node(i);
|
|
248
|
+
int16_t drawX = ui_draw_x_for_node(i);
|
|
249
|
+
int16_t drawY = ui_draw_y_for_node(i);
|
|
250
|
+
// Source selection: text-bound nodes show their dynamic buffer; others show
|
|
251
|
+
// the immutable flash literal.
|
|
252
|
+
const char* displayText = __ui_nodes[i].hasTextBinding
|
|
253
|
+
? __ui_nodes[i].textBuffer
|
|
254
|
+
: __ui_nodes[i].text;
|
|
255
|
+
uint8_t ts = __ui_nodes[i].textSize ? __ui_nodes[i].textSize : 2;
|
|
256
|
+
uint16_t textMaxW = ui_node_text_max_width(i);
|
|
257
|
+
uint16_t tw = 0;
|
|
258
|
+
uint16_t th = 0;
|
|
259
|
+
ui_node_text_layout_metrics(i, textMaxW, &tw, &th);
|
|
260
|
+
uint16_t paintTextW = tw;
|
|
261
|
+
uint16_t paintTextH = th;
|
|
262
|
+
if (__ui_nodes[i].kind == NODE_TEXT) {
|
|
263
|
+
uint16_t hInset = (uint16_t)__ui_nodes[i].paddingLeft + (uint16_t)__ui_nodes[i].paddingRight + (uint16_t)__ui_nodes[i].borderWidth * 2;
|
|
264
|
+
uint16_t vInset = (uint16_t)__ui_nodes[i].paddingTop + (uint16_t)__ui_nodes[i].paddingBottom + (uint16_t)__ui_nodes[i].borderWidth * 2;
|
|
265
|
+
paintTextW = (uint16_t)(tw + hInset);
|
|
266
|
+
paintTextH = (uint16_t)(th + vInset);
|
|
267
|
+
}
|
|
268
|
+
if (__ui_nodes[i].kind == NODE_CHECK || __ui_nodes[i].kind == NODE_RADIO) {
|
|
269
|
+
paintTextW = tw + 22;
|
|
270
|
+
if (paintTextH < 16) paintTextH = 16;
|
|
271
|
+
}
|
|
272
|
+
|
|
273
|
+
UIRect paintRect;
|
|
274
|
+
ui_node_paint_rect(i, baseDrawX, baseDrawY, drawX, drawY, paintTextW, paintTextH, &paintRect);
|
|
275
|
+
int16_t cullX = paintRect.x;
|
|
276
|
+
int16_t cullY = paintRect.y;
|
|
277
|
+
int16_t cullW = paintRect.w;
|
|
278
|
+
int16_t cullH = paintRect.h;
|
|
279
|
+
if (drawingBufferedScroll) {
|
|
280
|
+
// Canvas-local clip: skip nodes fully outside the viewport (0..vw, 0..vh).
|
|
281
|
+
// Use the node's face rect (box.w/h), NOT the paint rect — the paint rect
|
|
282
|
+
// includes shadow/border extents, which legitimately overflow a scroll
|
|
283
|
+
// viewport. Clipping on the paint rect falsely rejects nodes whose shadow
|
|
284
|
+
// pokes past the container edge while the face is fully inside (showed up
|
|
285
|
+
// as the home-nav buttons never painting on ST7796S, where the nav
|
|
286
|
+
// container is exactly button-width).
|
|
287
|
+
int16_t faceX = drawX;
|
|
288
|
+
int16_t faceY = drawY;
|
|
289
|
+
int16_t faceW = __ui_nodes[i].box.w;
|
|
290
|
+
int16_t faceH = __ui_nodes[i].box.h;
|
|
291
|
+
CuttlefishCanvas16* scrollDrawCanvas = bufferedScrollRepaintCanvas ? bufferedScrollRepaintCanvas : bufferedScrollCanvas;
|
|
292
|
+
int16_t scrollDrawW = display_canvasWidth(scrollDrawCanvas);
|
|
293
|
+
int16_t scrollDrawH = display_canvasHeight(scrollDrawCanvas);
|
|
294
|
+
if (bufferedScrollRepaintCanvas) {
|
|
295
|
+
scrollDrawW = __ui_nodes[bufferedScrollNode].box.w;
|
|
296
|
+
scrollDrawH = bufferedScrollRepaintH;
|
|
297
|
+
}
|
|
298
|
+
if (faceY + faceH <= 0 || faceY >= scrollDrawH ||
|
|
299
|
+
faceX + faceW <= 0 || faceX >= scrollDrawW) {
|
|
300
|
+
__ui_nodes[i].box.x = origBoxX;
|
|
301
|
+
__ui_nodes[i].box.y = origBoxY;
|
|
302
|
+
__ui_nodes[i].dirty = 0;
|
|
303
|
+
continue;
|
|
304
|
+
}
|
|
305
|
+
} else if (ui_is_rect_clipped_by_scroll(i, drawX, drawY, __ui_nodes[i].box.w, __ui_nodes[i].box.h)) {
|
|
306
|
+
__ui_nodes[i].box.x = origBoxX;
|
|
307
|
+
__ui_nodes[i].box.y = origBoxY;
|
|
308
|
+
__ui_nodes[i].dirty = 0;
|
|
309
|
+
continue;
|
|
310
|
+
}
|
|
311
|
+
|
|
312
|
+
uint8_t drawingPaintCanvas = 0;
|
|
313
|
+
CuttlefishCanvas16* paintCanvas = nullptr;
|
|
314
|
+
int16_t paintCanvasX = paintRect.x;
|
|
315
|
+
int16_t paintCanvasY = paintRect.y;
|
|
316
|
+
int16_t paintCanvasW = paintRect.w;
|
|
317
|
+
int16_t paintCanvasH = paintRect.h;
|
|
318
|
+
// RAM-composite pixel-heavy nodes before SPI push. Skip when already drawing
|
|
319
|
+
// into a scroll canvas or a full-screen framebuffer (both are RAM targets).
|
|
320
|
+
if (!drawingBufferedScroll && !__ui_fb && ui_should_buffer_paint(i, paintCanvasW, paintCanvasH)) {
|
|
321
|
+
paintCanvas = ui_get_repair_canvas(paintCanvasW, paintCanvasH);
|
|
322
|
+
if (paintCanvas) {
|
|
323
|
+
drawingPaintCanvas = 1;
|
|
324
|
+
ui_display_set_target(paintCanvas);
|
|
325
|
+
ui_seed_paint_canvas_for_node(i, paintCanvas, paintCanvasX, paintCanvasY);
|
|
326
|
+
baseDrawX -= paintCanvasX;
|
|
327
|
+
baseDrawY -= paintCanvasY;
|
|
328
|
+
drawX -= paintCanvasX;
|
|
329
|
+
drawY -= paintCanvasY;
|
|
330
|
+
if (__ui_nodes[i].kind == NODE_PROGRESS || __ui_nodes[i].kind == NODE_RANGE) {
|
|
331
|
+
__ui_nodes[i].lastTextWidth = -1;
|
|
332
|
+
}
|
|
333
|
+
}
|
|
334
|
+
}
|
|
335
|
+
if (!drawingPaintCanvas) {
|
|
336
|
+
ui_clear_press_offset_area(i, baseDrawX, baseDrawY, drawX, drawY, paintTextW, paintTextH);
|
|
337
|
+
}
|
|
338
|
+
uint8_t skipListOutsetShadow =
|
|
339
|
+
(__ui_nodes[i].kind == NODE_LIST && !drawingBufferedScroll && !__ui_fb);
|
|
340
|
+
if (!skipListOutsetShadow) {
|
|
341
|
+
__ui_nodes[i].box.x = baseDrawX;
|
|
342
|
+
ui_draw_shadow(i, baseDrawY, 0);
|
|
343
|
+
__ui_nodes[i].box.x = drawX;
|
|
344
|
+
} else {
|
|
345
|
+
__ui_nodes[i].box.x = drawX;
|
|
346
|
+
}
|
|
347
|
+
|
|
348
|
+
// Border color: use borderColor if set, otherwise fg.
|
|
349
|
+
UI_COLOR_T bColor = __ui_nodes[i].borderColor ? __ui_nodes[i].borderColor : __ui_nodes[i].fg;
|
|
350
|
+
// Background color blended toward clearColor by opacity (raw bg when 100%).
|
|
351
|
+
// NODE_FILL draws the fill at this color so opacity actually fades the
|
|
352
|
+
// element's background toward what's behind it.
|
|
353
|
+
UI_COLOR_T fillBg = __ui_nodes[i].bg;
|
|
354
|
+
// Apply opacity: blend fg/bg/border toward what's BEHIND the node when <100%.
|
|
355
|
+
// NOTE: blend toward the parent's clear color (ui_parent_clear_color), not
|
|
356
|
+
// the node's own clearColor — a filled node's clearColor IS its own bg, so
|
|
357
|
+
// blending bg toward it is a no-op (red toward red = red). The parent clear
|
|
358
|
+
// is the actual backdrop showing through the translucent element.
|
|
359
|
+
if (__ui_nodes[i].opacity < 100) {
|
|
360
|
+
UI_COLOR_T backdrop = ui_parent_clear_color(i);
|
|
361
|
+
bColor = ui_blend(bColor, backdrop, __ui_nodes[i].opacity);
|
|
362
|
+
fillBg = ui_blend(__ui_nodes[i].bg, backdrop, __ui_nodes[i].opacity);
|
|
363
|
+
}
|
|
364
|
+
switch (__ui_nodes[i].kind) {
|
|
365
|
+
case NODE_FILL:
|
|
366
|
+
if (__ui_nodes[i].gradientEnabled > 0) {
|
|
367
|
+
ui_draw_gradient_fill(i, drawY);
|
|
368
|
+
} else if (__ui_nodes[i].borderRadius > 0 && __ui_nodes[i].hasBg) {
|
|
369
|
+
int16_t fillW = ui_rotated_face_w(i, __ui_nodes[i].box.w, __ui_nodes[i].box.h);
|
|
370
|
+
int16_t fillH = ui_rotated_face_h(i, __ui_nodes[i].box.w, __ui_nodes[i].box.h);
|
|
371
|
+
ui_display_fill_round_rect(__ui_nodes[i].box.x, drawY, fillW, fillH, __ui_nodes[i].borderRadius, fillBg);
|
|
372
|
+
} else if (__ui_nodes[i].hasBg) {
|
|
373
|
+
int16_t fillW = ui_rotated_face_w(i, __ui_nodes[i].box.w, __ui_nodes[i].box.h);
|
|
374
|
+
int16_t fillH = ui_rotated_face_h(i, __ui_nodes[i].box.w, __ui_nodes[i].box.h);
|
|
375
|
+
ui_display_fill_rect(__ui_nodes[i].box.x, drawY, fillW, fillH, fillBg);
|
|
376
|
+
}
|
|
377
|
+
ui_draw_shadow(i, drawY, 1);
|
|
378
|
+
if (__ui_nodes[i].borderStyle != 0) {
|
|
379
|
+
UI_COLOR_T bColor = __ui_nodes[i].borderColor ? __ui_nodes[i].borderColor : __ui_nodes[i].fg;
|
|
380
|
+
int16_t borderW = ui_rotated_face_w(i, __ui_nodes[i].box.w, __ui_nodes[i].box.h);
|
|
381
|
+
int16_t borderH = ui_rotated_face_h(i, __ui_nodes[i].box.w, __ui_nodes[i].box.h);
|
|
382
|
+
ui_draw_rect_outline(__ui_nodes[i].box.x, drawY, borderW, borderH,
|
|
383
|
+
__ui_nodes[i].borderRadius, __ui_nodes[i].borderStyle, __ui_nodes[i].borderWidth, bColor);
|
|
384
|
+
}
|
|
385
|
+
break;
|
|
386
|
+
case NODE_TEXT:
|
|
387
|
+
{
|
|
388
|
+
int16_t insetL = (int16_t)__ui_nodes[i].borderWidth + (int16_t)__ui_nodes[i].paddingLeft;
|
|
389
|
+
int16_t insetR = (int16_t)__ui_nodes[i].borderWidth + (int16_t)__ui_nodes[i].paddingRight;
|
|
390
|
+
int16_t insetT = (int16_t)__ui_nodes[i].borderWidth + (int16_t)__ui_nodes[i].paddingTop;
|
|
391
|
+
int16_t textX = __ui_nodes[i].box.x + insetL;
|
|
392
|
+
int16_t textY = drawY + insetT;
|
|
393
|
+
int16_t textW = (int16_t)__ui_nodes[i].box.w - insetL - insetR;
|
|
394
|
+
if (textW < 1) textW = 1;
|
|
395
|
+
uint8_t textBoxPainted = 0;
|
|
396
|
+
if (__ui_nodes[i].gradientEnabled > 0) {
|
|
397
|
+
ui_draw_gradient_fill(i, drawY);
|
|
398
|
+
textBoxPainted = 1;
|
|
399
|
+
} else if (__ui_nodes[i].borderRadius > 0 && __ui_nodes[i].hasBg) {
|
|
400
|
+
ui_display_fill_round_rect(__ui_nodes[i].box.x, drawY,
|
|
401
|
+
__ui_nodes[i].box.w, __ui_nodes[i].box.h, __ui_nodes[i].borderRadius, fillBg);
|
|
402
|
+
textBoxPainted = 1;
|
|
403
|
+
} else if (__ui_nodes[i].hasBg) {
|
|
404
|
+
ui_display_fill_rect(__ui_nodes[i].box.x, drawY,
|
|
405
|
+
__ui_nodes[i].box.w, __ui_nodes[i].box.h, fillBg);
|
|
406
|
+
textBoxPainted = 1;
|
|
407
|
+
}
|
|
408
|
+
{
|
|
409
|
+
uint16_t clearW = __ui_nodes[i].box.w;
|
|
410
|
+
int16_t paintedTextW = (int16_t)__ui_nodes[i].lastTextWidth + insetL + insetR;
|
|
411
|
+
if (__ui_nodes[i].lastTextWidth > 0 && paintedTextW > (int16_t)clearW) {
|
|
412
|
+
clearW = (uint16_t)paintedTextW;
|
|
413
|
+
}
|
|
414
|
+
uint16_t paddedTw = (uint16_t)((int16_t)tw + insetL + insetR);
|
|
415
|
+
if (paddedTw > clearW) clearW = paddedTw;
|
|
416
|
+
// overflow:hidden/scroll: never clear past the node's own box. A nowrap
|
|
417
|
+
// line wider than its box would otherwise erase the parent's border.
|
|
418
|
+
if (__ui_nodes[i].scrollable) clearW = __ui_nodes[i].box.w;
|
|
419
|
+
uint16_t clearH = __ui_nodes[i].box.h;
|
|
420
|
+
int16_t paintedTextH = (int16_t)__ui_nodes[i].lastTextHeight + insetT + (int16_t)__ui_nodes[i].paddingBottom + (int16_t)__ui_nodes[i].borderWidth;
|
|
421
|
+
if (__ui_nodes[i].lastTextHeight > 0 && paintedTextH > (int16_t)clearH) {
|
|
422
|
+
clearH = (uint16_t)paintedTextH;
|
|
423
|
+
}
|
|
424
|
+
uint16_t paddedTh = (uint16_t)((int16_t)th + insetT + (int16_t)__ui_nodes[i].paddingBottom + (int16_t)__ui_nodes[i].borderWidth);
|
|
425
|
+
if (paddedTh > clearH) clearH = paddedTh;
|
|
426
|
+
// Dynamic transparent text still needs a clear, otherwise old glyph
|
|
427
|
+
// pixels accumulate when only this text node is dirty.
|
|
428
|
+
UI_COLOR_T clearCol = __ui_nodes[i].hasBg ? __ui_nodes[i].bg : __ui_nodes[i].clearColor;
|
|
429
|
+
// Blend the clear toward the backdrop by opacity so a translucent text
|
|
430
|
+
// node (inherited from an opacity:<1 parent) doesn't repaint a solid
|
|
431
|
+
// block of its parent's fill around the glyphs.
|
|
432
|
+
if (__ui_nodes[i].opacity < 100) {
|
|
433
|
+
clearCol = ui_blend(clearCol, ui_parent_clear_color(i), __ui_nodes[i].opacity);
|
|
434
|
+
}
|
|
435
|
+
if (!textBoxPainted) {
|
|
436
|
+
ui_display_fill_rect(__ui_nodes[i].box.x, drawY, clearW, clearH, clearCol);
|
|
437
|
+
}
|
|
438
|
+
__ui_nodes[i].lastTextWidth = tw;
|
|
439
|
+
__ui_nodes[i].lastTextHeight = th;
|
|
440
|
+
}
|
|
441
|
+
ui_draw_shadow(i, drawY, 1);
|
|
442
|
+
if (__ui_nodes[i].borderStyle != 0) {
|
|
443
|
+
ui_draw_node_border(i, __ui_nodes[i].box.x, drawY, bColor);
|
|
444
|
+
}
|
|
445
|
+
// Rich-text (inline runs): draw from precomputed geometry instead of the
|
|
446
|
+
// single-string wrapped path. Geometry is baked at transpile time; the
|
|
447
|
+
// runtime does not re-wrap.
|
|
448
|
+
if (__ui_nodes[i].runCount > 0) {
|
|
449
|
+
UI_COLOR_T richTextBg;
|
|
450
|
+
if (__ui_nodes[i].opacity < 100) {
|
|
451
|
+
uint16_t p = __ui_nodes[i].parent;
|
|
452
|
+
UI_COLOR_T source = (p != UI_NO_PARENT && __ui_nodes[p].hasBg) ? __ui_nodes[p].bg
|
|
453
|
+
: (__ui_nodes[i].hasBg ? __ui_nodes[i].bg : __ui_nodes[i].clearColor);
|
|
454
|
+
richTextBg = ui_blend(source, __ui_nodes[i].clearColor, __ui_nodes[i].opacity);
|
|
455
|
+
} else {
|
|
456
|
+
richTextBg = __ui_nodes[i].hasBg ? __ui_nodes[i].bg : ui_parent_clear_color(i);
|
|
457
|
+
}
|
|
458
|
+
if (__ui_nodes[i].textShadowCount > 0) {
|
|
459
|
+
UI_COLOR_T tsClear = __ui_nodes[i].hasBg ? __ui_nodes[i].bg : __ui_nodes[i].clearColor;
|
|
460
|
+
uint32_t tsCol = ui_blend(__ui_nodes[i].textShadowColor, tsClear, __ui_nodes[i].textShadowAlpha);
|
|
461
|
+
// Shadow pass: draw the rich block in the shadow color at the offset.
|
|
462
|
+
// (Per-segment shadow color is approximated by drawing the whole
|
|
463
|
+
// block once in tsCol.)
|
|
464
|
+
ui_draw_rich_text(i,
|
|
465
|
+
textX + __ui_nodes[i].textShadowOffsetX,
|
|
466
|
+
textY + __ui_nodes[i].textShadowOffsetY,
|
|
467
|
+
tsClear, __ui_nodes[i].fontAntialias, 1, tsCol, (uint16_t)textW);
|
|
468
|
+
}
|
|
469
|
+
ui_draw_rich_text(i, textX, textY, richTextBg, __ui_nodes[i].fontAntialias, 0, 0, (uint16_t)textW);
|
|
470
|
+
break;
|
|
471
|
+
}
|
|
472
|
+
{
|
|
473
|
+
// Text shadow: draw the text in the shadow color at the offset first.
|
|
474
|
+
UI_COLOR_T tsClear = __ui_nodes[i].hasBg ? __ui_nodes[i].bg : __ui_nodes[i].clearColor;
|
|
475
|
+
if (__ui_nodes[i].textShadowCount > 0) {
|
|
476
|
+
uint32_t tsCol = ui_blend(__ui_nodes[i].textShadowColor, tsClear, __ui_nodes[i].textShadowAlpha);
|
|
477
|
+
ui_draw_wrapped_text(displayText,
|
|
478
|
+
textX + __ui_nodes[i].textShadowOffsetX,
|
|
479
|
+
textY + __ui_nodes[i].textShadowOffsetY,
|
|
480
|
+
(uint16_t)textW, tsCol, tsCol, ts, __ui_nodes[i].fontAntialias,
|
|
481
|
+
__ui_nodes[i].fontFace, __ui_nodes[i].letterSpacing, __ui_nodes[i].lineHeight,
|
|
482
|
+
__ui_nodes[i].whiteSpaceMode, __ui_nodes[i].textAlign, 0, __ui_nodes[i].textOverflow);
|
|
483
|
+
}
|
|
484
|
+
// Use the parent's clear color as the text background when the node
|
|
485
|
+
// has no own background. This makes Adafruit_GFX's opaque glyph-cell
|
|
486
|
+
// fill blend with the parent (instead of drawing solid fg blocks that
|
|
487
|
+
// overlap adjacent lines/elements). For AA text, fg != bg so the
|
|
488
|
+
// edge-detection path still runs correctly.
|
|
489
|
+
// Glyph-cell background. For a translucent text node sitting on a
|
|
490
|
+
// filled translucent parent, the glyph cells must match the parent's
|
|
491
|
+
// blended fill: blend565(parent.bg, backdrop, opacity). The node's own
|
|
492
|
+
// clearColor carries the backdrop (set by flatten), and opacity has
|
|
493
|
+
// already inherited from the parent. Use the parent's raw bg as the
|
|
494
|
+
// source so the glyph cells reproduce the parent's translucent fill.
|
|
495
|
+
UI_COLOR_T textBg;
|
|
496
|
+
if (__ui_nodes[i].opacity < 100) {
|
|
497
|
+
uint16_t p = __ui_nodes[i].parent;
|
|
498
|
+
UI_COLOR_T source = (p != UI_NO_PARENT && __ui_nodes[p].hasBg) ? __ui_nodes[p].bg
|
|
499
|
+
: (__ui_nodes[i].hasBg ? __ui_nodes[i].bg : __ui_nodes[i].clearColor);
|
|
500
|
+
textBg = ui_blend(source, __ui_nodes[i].clearColor, __ui_nodes[i].opacity);
|
|
501
|
+
} else {
|
|
502
|
+
textBg = __ui_nodes[i].hasBg ? __ui_nodes[i].bg : ui_parent_clear_color(i);
|
|
503
|
+
}
|
|
504
|
+
ui_draw_wrapped_text(displayText, textX, textY, (uint16_t)textW,
|
|
505
|
+
__ui_nodes[i].fg, textBg, ts, __ui_nodes[i].fontAntialias,
|
|
506
|
+
__ui_nodes[i].fontFace, __ui_nodes[i].letterSpacing, __ui_nodes[i].lineHeight,
|
|
507
|
+
__ui_nodes[i].whiteSpaceMode, __ui_nodes[i].textAlign, __ui_nodes[i].underline, __ui_nodes[i].textOverflow);
|
|
508
|
+
}
|
|
509
|
+
}
|
|
510
|
+
break;
|
|
511
|
+
case NODE_BUTTON:
|
|
512
|
+
if (__ui_nodes[i].borderRadius > 0 && __ui_nodes[i].hasBg)
|
|
513
|
+
ui_display_fill_round_rect(__ui_nodes[i].box.x, drawY, __ui_nodes[i].box.w, __ui_nodes[i].box.h, __ui_nodes[i].borderRadius, __ui_nodes[i].bg);
|
|
514
|
+
else if (__ui_nodes[i].hasBg)
|
|
515
|
+
ui_display_fill_rect(__ui_nodes[i].box.x, drawY, __ui_nodes[i].box.w, __ui_nodes[i].box.h, __ui_nodes[i].bg);
|
|
516
|
+
ui_draw_shadow(i, drawY, 1);
|
|
517
|
+
if (__ui_nodes[i].borderStyle != 0) {
|
|
518
|
+
ui_draw_node_border(i, __ui_nodes[i].box.x, drawY, bColor);
|
|
519
|
+
}
|
|
520
|
+
{
|
|
521
|
+
int16_t insetL = (int16_t)__ui_nodes[i].borderWidth + (int16_t)__ui_nodes[i].paddingLeft;
|
|
522
|
+
int16_t insetR = (int16_t)__ui_nodes[i].borderWidth + (int16_t)__ui_nodes[i].paddingRight;
|
|
523
|
+
int16_t insetT = (int16_t)__ui_nodes[i].borderWidth + (int16_t)__ui_nodes[i].paddingTop;
|
|
524
|
+
int16_t insetB = (int16_t)__ui_nodes[i].borderWidth + (int16_t)__ui_nodes[i].paddingBottom;
|
|
525
|
+
int16_t textX = __ui_nodes[i].box.x + insetL;
|
|
526
|
+
int16_t textY = drawY + insetT;
|
|
527
|
+
int16_t textW = (int16_t)__ui_nodes[i].box.w - insetL - insetR;
|
|
528
|
+
int16_t textH = (int16_t)__ui_nodes[i].box.h - insetT - insetB;
|
|
529
|
+
if (textW < 1) textW = 1;
|
|
530
|
+
if (textH < 1) textH = (int16_t)th;
|
|
531
|
+
ui_draw_wrapped_text(displayText,
|
|
532
|
+
textX,
|
|
533
|
+
textY + (textH - (int16_t)th) / 2,
|
|
534
|
+
(uint16_t)textW,
|
|
535
|
+
__ui_nodes[i].fg,
|
|
536
|
+
__ui_nodes[i].hasBg ? __ui_nodes[i].bg : __ui_nodes[i].clearColor,
|
|
537
|
+
ts, __ui_nodes[i].fontAntialias, __ui_nodes[i].fontFace, __ui_nodes[i].letterSpacing,
|
|
538
|
+
__ui_nodes[i].lineHeight, __ui_nodes[i].whiteSpaceMode, __ui_nodes[i].textAlign, __ui_nodes[i].underline, __ui_nodes[i].textOverflow);
|
|
539
|
+
}
|
|
540
|
+
break;
|
|
541
|
+
case NODE_CHECK:
|
|
542
|
+
{
|
|
543
|
+
uint16_t clearW = __ui_nodes[i].box.w;
|
|
544
|
+
if (__ui_nodes[i].lastTextWidth > 0 && __ui_nodes[i].lastTextWidth > (int16_t)clearW) {
|
|
545
|
+
clearW = (uint16_t)__ui_nodes[i].lastTextWidth;
|
|
546
|
+
}
|
|
547
|
+
if (paintTextW > clearW) clearW = paintTextW;
|
|
548
|
+
uint16_t clearH = __ui_nodes[i].box.h;
|
|
549
|
+
if (__ui_nodes[i].lastTextHeight > 0 && __ui_nodes[i].lastTextHeight > (int16_t)clearH) {
|
|
550
|
+
clearH = (uint16_t)__ui_nodes[i].lastTextHeight;
|
|
551
|
+
}
|
|
552
|
+
if (paintTextH > clearH) clearH = paintTextH;
|
|
553
|
+
ui_display_fill_rect(__ui_nodes[i].box.x, drawY, clearW, clearH,
|
|
554
|
+
__ui_nodes[i].hasBg ? __ui_nodes[i].bg : __ui_nodes[i].clearColor);
|
|
555
|
+
__ui_nodes[i].lastTextWidth = paintTextW;
|
|
556
|
+
__ui_nodes[i].lastTextHeight = paintTextH;
|
|
557
|
+
}
|
|
558
|
+
{
|
|
559
|
+
int16_t cbX = __ui_nodes[i].box.x;
|
|
560
|
+
int16_t cbY = drawY;
|
|
561
|
+
if (__ui_nodes[i].value) {
|
|
562
|
+
ui_display_fill_rect(cbX, cbY, 16, 16, __ui_nodes[i].fg);
|
|
563
|
+
UI_COLOR_T inv = __ui_nodes[i].hasBg ? __ui_nodes[i].bg : __ui_nodes[i].clearColor;
|
|
564
|
+
#ifdef UI_AA
|
|
565
|
+
{
|
|
566
|
+
// Draw the checkmark to a 16×16 AA canvas for smooth diagonals.
|
|
567
|
+
CuttlefishCanvas16* c = ui_aa_begin(16, 16, __ui_nodes[i].fg);
|
|
568
|
+
// First stroke: down-left (3,8 → 7,12)
|
|
569
|
+
ui_aa_line(c, 4.0f, 8.0f, 7.0f, 12.0f, inv);
|
|
570
|
+
ui_aa_line(c, 5.0f, 8.0f, 8.0f, 12.0f, inv);
|
|
571
|
+
// Second stroke: up-right (7,11 → 13,4)
|
|
572
|
+
ui_aa_line(c, 7.0f, 11.0f, 13.0f, 4.0f, inv);
|
|
573
|
+
ui_aa_line(c, 8.0f, 11.0f, 14.0f, 4.0f, inv);
|
|
574
|
+
ui_aa_push(c, cbX, cbY);
|
|
575
|
+
}
|
|
576
|
+
#else
|
|
577
|
+
ui_display_draw_line(cbX + 3, cbY + 8, cbX + 7, cbY + 12, inv);
|
|
578
|
+
ui_display_draw_line(cbX + 4, cbY + 8, cbX + 8, cbY + 12, inv);
|
|
579
|
+
ui_display_draw_line(cbX + 3, cbY + 9, cbX + 7, cbY + 13, inv);
|
|
580
|
+
ui_display_draw_line(cbX + 7, cbY + 12, cbX + 13, cbY + 4, inv);
|
|
581
|
+
ui_display_draw_line(cbX + 8, cbY + 12, cbX + 14, cbY + 4, inv);
|
|
582
|
+
ui_display_draw_line(cbX + 7, cbY + 13, cbX + 13, cbY + 5, inv);
|
|
583
|
+
#endif
|
|
584
|
+
} else {
|
|
585
|
+
ui_display_draw_rect(cbX, cbY, 16, 16, __ui_nodes[i].fg);
|
|
586
|
+
}
|
|
587
|
+
}
|
|
588
|
+
ui_draw_wrapped_text(displayText, __ui_nodes[i].box.x + 22, drawY, textMaxW,
|
|
589
|
+
__ui_nodes[i].fg, __ui_nodes[i].hasBg ? __ui_nodes[i].bg : __ui_nodes[i].clearColor,
|
|
590
|
+
ts, __ui_nodes[i].fontAntialias, __ui_nodes[i].fontFace, __ui_nodes[i].letterSpacing,
|
|
591
|
+
__ui_nodes[i].lineHeight, __ui_nodes[i].whiteSpaceMode, 0, __ui_nodes[i].underline, __ui_nodes[i].textOverflow);
|
|
592
|
+
break;
|
|
593
|
+
case NODE_RADIO:
|
|
594
|
+
{
|
|
595
|
+
uint16_t clearW = __ui_nodes[i].box.w;
|
|
596
|
+
if (__ui_nodes[i].lastTextWidth > 0 && __ui_nodes[i].lastTextWidth > (int16_t)clearW) {
|
|
597
|
+
clearW = (uint16_t)__ui_nodes[i].lastTextWidth;
|
|
598
|
+
}
|
|
599
|
+
if (paintTextW > clearW) clearW = paintTextW;
|
|
600
|
+
uint16_t clearH = __ui_nodes[i].box.h;
|
|
601
|
+
if (__ui_nodes[i].lastTextHeight > 0 && __ui_nodes[i].lastTextHeight > (int16_t)clearH) {
|
|
602
|
+
clearH = (uint16_t)__ui_nodes[i].lastTextHeight;
|
|
603
|
+
}
|
|
604
|
+
if (paintTextH > clearH) clearH = paintTextH;
|
|
605
|
+
ui_display_fill_rect(__ui_nodes[i].box.x, drawY, clearW, clearH,
|
|
606
|
+
__ui_nodes[i].hasBg ? __ui_nodes[i].bg : __ui_nodes[i].clearColor);
|
|
607
|
+
__ui_nodes[i].lastTextWidth = paintTextW;
|
|
608
|
+
__ui_nodes[i].lastTextHeight = paintTextH;
|
|
609
|
+
int16_t cbX = __ui_nodes[i].box.x;
|
|
610
|
+
int16_t cbY = drawY;
|
|
611
|
+
#ifdef UI_AA
|
|
612
|
+
{
|
|
613
|
+
// Render the radio circle to a 16×16 AA canvas, then push.
|
|
614
|
+
UI_COLOR_T radioBg = __ui_nodes[i].hasBg ? __ui_nodes[i].bg : __ui_nodes[i].clearColor;
|
|
615
|
+
CuttlefishCanvas16* c = ui_aa_begin(16, 16, radioBg);
|
|
616
|
+
if (__ui_nodes[i].value) {
|
|
617
|
+
ui_aa_fill_circle(c, 8, 8, 7.0f, __ui_nodes[i].fg);
|
|
618
|
+
ui_aa_fill_circle(c, 8, 8, 3.0f, radioBg);
|
|
619
|
+
} else {
|
|
620
|
+
ui_aa_circle(c, 8, 8, 7.0f, __ui_nodes[i].fg);
|
|
621
|
+
}
|
|
622
|
+
ui_aa_push(c, cbX, cbY);
|
|
623
|
+
}
|
|
624
|
+
#else
|
|
625
|
+
if (__ui_nodes[i].value) {
|
|
626
|
+
ui_display_fill_circle(cbX + 8, cbY + 8, 7, __ui_nodes[i].fg);
|
|
627
|
+
ui_display_fill_circle(cbX + 8, cbY + 8, 3, __ui_nodes[i].hasBg ? __ui_nodes[i].bg : __ui_nodes[i].clearColor);
|
|
628
|
+
} else {
|
|
629
|
+
ui_display_draw_circle(cbX + 8, cbY + 8, 7, __ui_nodes[i].fg);
|
|
630
|
+
}
|
|
631
|
+
#endif
|
|
632
|
+
}
|
|
633
|
+
ui_draw_wrapped_text(displayText, __ui_nodes[i].box.x + 22, drawY, textMaxW,
|
|
634
|
+
__ui_nodes[i].fg, __ui_nodes[i].hasBg ? __ui_nodes[i].bg : __ui_nodes[i].clearColor,
|
|
635
|
+
ts, __ui_nodes[i].fontAntialias, __ui_nodes[i].fontFace, __ui_nodes[i].letterSpacing,
|
|
636
|
+
__ui_nodes[i].lineHeight, __ui_nodes[i].whiteSpaceMode, 0, __ui_nodes[i].underline, __ui_nodes[i].textOverflow);
|
|
637
|
+
break;
|
|
638
|
+
case NODE_PROGRESS:
|
|
639
|
+
// Progress bar: outline track + filled portion based on .value (0-100).
|
|
640
|
+
// Incremental redraw — only draws/clears the delta to avoid flashing.
|
|
641
|
+
{
|
|
642
|
+
int16_t bx = __ui_nodes[i].box.x;
|
|
643
|
+
int16_t by = drawY;
|
|
644
|
+
int16_t bw = __ui_nodes[i].box.w;
|
|
645
|
+
int16_t bh = __ui_nodes[i].box.h;
|
|
646
|
+
UI_COLOR_T bgCol = __ui_nodes[i].hasBg ? __ui_nodes[i].bg : __ui_nodes[i].clearColor;
|
|
647
|
+
UI_COLOR_T fgCol = __ui_nodes[i].fg;
|
|
648
|
+
|
|
649
|
+
// On first draw (lastTextWidth < 0), draw everything.
|
|
650
|
+
// Otherwise incremental: only update the changed portion.
|
|
651
|
+
uint8_t pct = constrain(__ui_nodes[i].value, 0, 100);
|
|
652
|
+
int16_t fillW = ((int32_t)(bw - 2) * pct) / 100;
|
|
653
|
+
int16_t prevW = __ui_nodes[i].lastTextWidth; // reused as previous fill width
|
|
654
|
+
|
|
655
|
+
if (prevW < 0) {
|
|
656
|
+
// Full redraw: outline + background + fill
|
|
657
|
+
ui_display_draw_rect(bx, by, bw, bh, fgCol);
|
|
658
|
+
ui_display_fill_rect(bx + 1, by + 1, bw - 2, bh - 2, bgCol);
|
|
659
|
+
if (fillW > 0) {
|
|
660
|
+
ui_display_fill_rect(bx + 1, by + 1, fillW, bh - 2, fgCol);
|
|
661
|
+
}
|
|
662
|
+
} else if (fillW > prevW) {
|
|
663
|
+
// Value increased: draw new fill segment on top (no clear needed)
|
|
664
|
+
ui_display_fill_rect(bx + 1 + prevW, by + 1, fillW - prevW, bh - 2, fgCol);
|
|
665
|
+
} else if (fillW < prevW) {
|
|
666
|
+
// Value decreased: clear the removed portion
|
|
667
|
+
ui_display_fill_rect(bx + 1 + fillW, by + 1, prevW - fillW, bh - 2, bgCol);
|
|
668
|
+
}
|
|
669
|
+
// Remember current fill width for next incremental update
|
|
670
|
+
__ui_nodes[i].lastTextWidth = fillW;
|
|
671
|
+
}
|
|
672
|
+
break;
|
|
673
|
+
case NODE_RANGE:
|
|
674
|
+
// Range slider: horizontal track + draggable thumb.
|
|
675
|
+
// Incremental redraw (like NODE_PROGRESS): lastTextWidth holds the
|
|
676
|
+
// previous fill width. We erase the delta region between old and new
|
|
677
|
+
// thumb positions with the background, then redraw the track portion
|
|
678
|
+
// and the new thumb — so dragging backward doesn't leave ghost thumbs.
|
|
679
|
+
{
|
|
680
|
+
int16_t bx = __ui_nodes[i].box.x;
|
|
681
|
+
int16_t by = drawY;
|
|
682
|
+
int16_t bw = __ui_nodes[i].box.w;
|
|
683
|
+
int16_t bh = __ui_nodes[i].box.h;
|
|
684
|
+
UI_COLOR_T fgCol = __ui_nodes[i].fg;
|
|
685
|
+
UI_COLOR_T bgCol = __ui_nodes[i].hasBg ? __ui_nodes[i].bg : __ui_nodes[i].clearColor;
|
|
686
|
+
UI_COLOR_T dimFg = (UI_COLOR_T)((fgCol >> 1) & UI_DIM_MASK);
|
|
687
|
+
|
|
688
|
+
int16_t trackY = by + bh / 2;
|
|
689
|
+
int16_t rMin = __ui_nodes[i].rangeMin;
|
|
690
|
+
int16_t rMax = __ui_nodes[i].rangeMax;
|
|
691
|
+
int16_t range = rMax - rMin;
|
|
692
|
+
if (range <= 0) range = 100;
|
|
693
|
+
int16_t pct = constrain(__ui_nodes[i].value, rMin, rMax) - rMin;
|
|
694
|
+
int16_t fillW = ((int32_t)(bw - 8) * pct) / range;
|
|
695
|
+
// lastTextWidth carries the previous fill width, or -1 if this node
|
|
696
|
+
// has never been drawn (fillW=0 at value=min is a valid thumb pos).
|
|
697
|
+
int16_t prevFillW = __ui_nodes[i].lastTextWidth;
|
|
698
|
+
int16_t newThumbX = bx + 4 + fillW - 3;
|
|
699
|
+
|
|
700
|
+
if (prevFillW < 0) {
|
|
701
|
+
// First draw: redraw the whole track + fill from scratch.
|
|
702
|
+
ui_display_draw_fast_hline(bx, trackY, bw, dimFg);
|
|
703
|
+
ui_display_draw_fast_hline(bx + 4, trackY, fillW, fgCol);
|
|
704
|
+
} else {
|
|
705
|
+
// Incremental: wipe the strip between the old and new thumb
|
|
706
|
+
// positions (whichever extends further on each side), then restore
|
|
707
|
+
// the track line. This is symmetric — old thumbs disappear whether
|
|
708
|
+
// the drag moves forward or backward.
|
|
709
|
+
int16_t prevThumbX = bx + 4 + prevFillW - 3;
|
|
710
|
+
int16_t left = prevThumbX < newThumbX ? prevThumbX : newThumbX;
|
|
711
|
+
int16_t right = prevThumbX + 6 > newThumbX + 6 ? prevThumbX + 6 : newThumbX + 6;
|
|
712
|
+
if (left < bx) left = bx;
|
|
713
|
+
if (right > bx + bw) right = bx + bw;
|
|
714
|
+
// Erase the thumb band (10px tall) to background.
|
|
715
|
+
ui_display_fill_rect(left, trackY - 5, right - left, 10, bgCol);
|
|
716
|
+
// Restore the track line over the wiped strip: bright up to the
|
|
717
|
+
// current fill end, dim beyond it.
|
|
718
|
+
int16_t fillEnd = bx + 4 + fillW;
|
|
719
|
+
if (right <= fillEnd) {
|
|
720
|
+
ui_display_draw_fast_hline(left, trackY, right - left, fgCol);
|
|
721
|
+
} else if (left >= fillEnd) {
|
|
722
|
+
ui_display_draw_fast_hline(left, trackY, right - left, dimFg);
|
|
723
|
+
} else {
|
|
724
|
+
ui_display_draw_fast_hline(left, trackY, fillEnd - left, fgCol);
|
|
725
|
+
ui_display_draw_fast_hline(fillEnd, trackY, right - fillEnd, dimFg);
|
|
726
|
+
}
|
|
727
|
+
}
|
|
728
|
+
|
|
729
|
+
// Thumb: small filled rectangle at the current position.
|
|
730
|
+
if (newThumbX < bx + 1) newThumbX = bx + 1;
|
|
731
|
+
if (newThumbX > bx + bw - 7) newThumbX = bx + bw - 7;
|
|
732
|
+
ui_display_fill_rect(newThumbX, trackY - 5, 6, 10, fgCol);
|
|
733
|
+
|
|
734
|
+
// Remember current fill width for the next incremental update.
|
|
735
|
+
__ui_nodes[i].lastTextWidth = fillW;
|
|
736
|
+
}
|
|
737
|
+
break;
|
|
738
|
+
case NODE_INPUT:
|
|
739
|
+
// Input field: bordered rect + current text (or placeholder), clipped to box width.
|
|
740
|
+
{
|
|
741
|
+
int16_t bx = __ui_nodes[i].box.x;
|
|
742
|
+
int16_t by = drawY;
|
|
743
|
+
int16_t bw = __ui_nodes[i].box.w;
|
|
744
|
+
int16_t bh = __ui_nodes[i].box.h;
|
|
745
|
+
UI_COLOR_T fgCol = __ui_nodes[i].fg;
|
|
746
|
+
UI_COLOR_T bgCol = __ui_nodes[i].hasBg ? __ui_nodes[i].bg : __ui_nodes[i].clearColor;
|
|
747
|
+
if (__ui_nodes[i].borderRadius > 0) {
|
|
748
|
+
ui_display_fill_round_rect(bx, by, bw, bh, __ui_nodes[i].borderRadius, bgCol);
|
|
749
|
+
} else {
|
|
750
|
+
ui_display_fill_rect(bx, by, bw, bh, bgCol);
|
|
751
|
+
}
|
|
752
|
+
UI_COLOR_T inputBorder = __ui_nodes[i].borderColor ? __ui_nodes[i].borderColor : fgCol;
|
|
753
|
+
uint8_t inputBorderStyle = __ui_nodes[i].borderStyle ? __ui_nodes[i].borderStyle : 1;
|
|
754
|
+
uint8_t inputBorderWidth = __ui_nodes[i].borderWidth ? __ui_nodes[i].borderWidth : 1;
|
|
755
|
+
ui_draw_rect_outline(bx, by, bw, bh, __ui_nodes[i].borderRadius, inputBorderStyle, inputBorderWidth, inputBorder);
|
|
756
|
+
// Show typed text (textBuffer) in fg color, or placeholder (.text)
|
|
757
|
+
// dimmed gray when the buffer is empty.
|
|
758
|
+
UI_COLOR_T textCol = fgCol;
|
|
759
|
+
const char* disp = (__ui_nodes[i].textBuffer[0] != 0)
|
|
760
|
+
? __ui_nodes[i].textBuffer
|
|
761
|
+
: (__ui_nodes[i].text ? __ui_nodes[i].text : "");
|
|
762
|
+
#if defined(UI_HIDE_OSK)
|
|
763
|
+
// Desktop target: when this input is the active edit target, suppress
|
|
764
|
+
// the placeholder. The editing buffer is loaded from the (likely empty)
|
|
765
|
+
// textBuffer on focus, so without this the placeholder ("enter name")
|
|
766
|
+
// would render with the caret at its end. Hide it so the caret shows
|
|
767
|
+
// on a clean field at position 0 until the user types.
|
|
768
|
+
if (__ui_kb_visible && (int16_t)__ui_kb_target == (int16_t)i && __ui_nodes[i].textBuffer[0] == 0) {
|
|
769
|
+
disp = "";
|
|
770
|
+
}
|
|
771
|
+
#endif
|
|
772
|
+
if (__ui_nodes[i].textBuffer[0] == 0) {
|
|
773
|
+
#if UI_COLOR_DEPTH == 888
|
|
774
|
+
textCol = 0x848484;
|
|
775
|
+
#else
|
|
776
|
+
textCol = 0x8410;
|
|
777
|
+
#endif
|
|
778
|
+
}
|
|
779
|
+
// Note: the actual text draw is via ui_draw_text (which uses __ui_gfx).
|
|
780
|
+
// The setCursor/setTextColor/setTextSize below are legacy — ui_draw_text
|
|
781
|
+
// handles its own cursor/colors. Keep them on __ui_gfx for consistency
|
|
782
|
+
// (in case __ui_gfx is the scroll canvas, not __tc_display).
|
|
783
|
+
ui_display_set_cursor(bx + 4, by + (bh - ts * 8) / 2);
|
|
784
|
+
ui_display_set_text_color(textCol, bgCol);
|
|
785
|
+
ui_display_set_text_size(ts);
|
|
786
|
+
// Clip: at textSize ts, each char is ts*6px advance. Only print chars
|
|
787
|
+
// that fit within the box (bw - 8px margin), so text never overflows
|
|
788
|
+
// the border or wraps to the next line.
|
|
789
|
+
int16_t maxChars = (bw - 8) / (ts * 6);
|
|
790
|
+
if (maxChars < 0) maxChars = 0;
|
|
791
|
+
int16_t len = (int16_t)strlen(disp);
|
|
792
|
+
if (len > maxChars) len = maxChars;
|
|
793
|
+
if (len > UI_TEXT_BUF) len = UI_TEXT_BUF;
|
|
794
|
+
char clipped[UI_TEXT_BUF + 1];
|
|
795
|
+
for (int16_t c = 0; c < len; c++) {
|
|
796
|
+
clipped[c] = disp[c];
|
|
797
|
+
}
|
|
798
|
+
clipped[len] = 0;
|
|
799
|
+
ui_draw_text(clipped, bx + 4, by + (bh - ui_text_height(ts, __ui_nodes[i].fontFace)) / 2,
|
|
800
|
+
textCol, bgCol, ts, __ui_nodes[i].fontAntialias, __ui_nodes[i].fontFace, __ui_nodes[i].letterSpacing);
|
|
801
|
+
#if defined(UI_HIDE_OSK)
|
|
802
|
+
// Desktop target: with no OSK grid there's no focus indicator. Draw a
|
|
803
|
+
// blinking caret at the end of the typed text on the active edit target
|
|
804
|
+
// so the user sees which field they're editing. Blink ~3×/sec via the
|
|
805
|
+
// top bits of __ui_kb_blink (mask 0x20 toggles every 32 ticks ≈ 530ms).
|
|
806
|
+
if (__ui_kb_visible && (int16_t)__ui_kb_target == (int16_t)i && (__ui_kb_blink & 0x20)) {
|
|
807
|
+
uint16_t caretW = ui_text_width(clipped, ts, __ui_nodes[i].fontFace, __ui_nodes[i].letterSpacing);
|
|
808
|
+
int16_t caretX = bx + 4 + (int16_t)caretW;
|
|
809
|
+
int16_t caretYTop = by + (bh - ts * 8) / 2;
|
|
810
|
+
// fgCol (not textCol): the placeholder-dimming path sets textCol to
|
|
811
|
+
// gray, which would make the caret nearly invisible on a focused
|
|
812
|
+
// empty field. The caret should always be the input's foreground.
|
|
813
|
+
ui_display_fill_rect(caretX, caretYTop, (int16_t)(ts > 1 ? 2 : 1), (int16_t)(ts * 8), fgCol);
|
|
814
|
+
}
|
|
815
|
+
#endif
|
|
816
|
+
}
|
|
817
|
+
break;
|
|
818
|
+
case NODE_IMG:
|
|
819
|
+
{
|
|
820
|
+
UI_COLOR_T imgBg = __ui_nodes[i].hasBg ? fillBg : __ui_nodes[i].clearColor;
|
|
821
|
+
int16_t fillW = ui_rotated_face_w(i, __ui_nodes[i].box.w, __ui_nodes[i].box.h);
|
|
822
|
+
int16_t fillH = ui_rotated_face_h(i, __ui_nodes[i].box.w, __ui_nodes[i].box.h);
|
|
823
|
+
ui_display_fill_rect(__ui_nodes[i].box.x, drawY, fillW, fillH, imgBg);
|
|
824
|
+
}
|
|
825
|
+
if (__ui_nodes[i].imgDataId < __ui_image_count) {
|
|
826
|
+
const UIImage* img = &__ui_images[__ui_nodes[i].imgDataId];
|
|
827
|
+
int16_t targetW = __ui_nodes[i].box.w;
|
|
828
|
+
int16_t targetH = __ui_nodes[i].box.h;
|
|
829
|
+
ui_draw_image_with_fit(img, __ui_nodes[i].box.x, drawY, __ui_nodes[i].rotateDeg, __ui_nodes[i].objectFit, targetW, targetH);
|
|
830
|
+
}
|
|
831
|
+
if (__ui_nodes[i].borderStyle != 0) {
|
|
832
|
+
int16_t borderW = ui_rotated_face_w(i, __ui_nodes[i].box.w, __ui_nodes[i].box.h);
|
|
833
|
+
int16_t borderH = ui_rotated_face_h(i, __ui_nodes[i].box.w, __ui_nodes[i].box.h);
|
|
834
|
+
ui_draw_rect_outline(__ui_nodes[i].box.x, drawY, borderW, borderH,
|
|
835
|
+
__ui_nodes[i].borderRadius, __ui_nodes[i].borderStyle, __ui_nodes[i].borderWidth, bColor);
|
|
836
|
+
}
|
|
837
|
+
break;
|
|
838
|
+
case NODE_CANVAS: {
|
|
839
|
+
// Find this node's draw callback.
|
|
840
|
+
void (*__ui_canvas_fn)(CuttlefishCanvas16*) = nullptr;
|
|
841
|
+
for (uint16_t b = 0; b < __ui_canvas_binding_count; b++) {
|
|
842
|
+
if (__ui_canvas_bindings[b].node == i) { __ui_canvas_fn = __ui_canvas_bindings[b].fn; break; }
|
|
843
|
+
}
|
|
844
|
+
if (__ui_canvas_fn) {
|
|
845
|
+
int16_t __ui_cw = __ui_nodes[i].canvasW;
|
|
846
|
+
int16_t __ui_ch = __ui_nodes[i].canvasH;
|
|
847
|
+
if (__ui_cw > 0 && __ui_ch > 0) {
|
|
848
|
+
uint8_t __ui_canvas_drawn = 0;
|
|
849
|
+
if (ui_display_is_default_target()) {
|
|
850
|
+
// Cache a canvas sized to the buffer for direct hardware draws so
|
|
851
|
+
// the callback's many primitives push as one bitmap. When the
|
|
852
|
+
// active target is already a memory canvas (scroll/repair/full
|
|
853
|
+
// framebuffer), skip the nested allocation and draw directly below.
|
|
854
|
+
// A canvas can construct but fail its internal pixel-buffer malloc
|
|
855
|
+
// (non-null canvas, null buffer). Treat that as "no canvas" so a
|
|
856
|
+
// transient malloc failure self-heals instead of blanking the node.
|
|
857
|
+
if (!__ui_node_canvas || !display_canvasBuffer(__ui_node_canvas) ||
|
|
858
|
+
display_canvasWidth(__ui_node_canvas) != __ui_cw || display_canvasHeight(__ui_node_canvas) != __ui_ch) {
|
|
859
|
+
display_deleteCanvas(__ui_node_canvas);
|
|
860
|
+
__ui_node_canvas = display_createCanvas(__ui_cw, __ui_ch);
|
|
861
|
+
}
|
|
862
|
+
CuttlefishCanvas16* __ui_lc = __ui_node_canvas;
|
|
863
|
+
if (__ui_lc && display_canvasBuffer(__ui_lc)) {
|
|
864
|
+
display_canvasFillScreen(__ui_lc, __ui_nodes[i].clearColor);
|
|
865
|
+
CuttlefishDisplayTarget* __ui_prev_target = ui_display_get_target();
|
|
866
|
+
ui_display_set_target((CuttlefishDisplayTarget*)__ui_lc);
|
|
867
|
+
__ui_canvas_fn(__ui_lc);
|
|
868
|
+
ui_display_set_target(__ui_prev_target);
|
|
869
|
+
ui_draw_canvas_rect(__ui_lc, __ui_nodes[i].box.x, drawY, __ui_cw, __ui_ch);
|
|
870
|
+
__ui_canvas_drawn = 1;
|
|
871
|
+
}
|
|
872
|
+
}
|
|
873
|
+
if (!__ui_canvas_drawn) {
|
|
874
|
+
UI_COLOR_T __ui_canvas_bg = __ui_nodes[i].hasBg ? fillBg : __ui_nodes[i].clearColor;
|
|
875
|
+
ui_display_fill_rect(__ui_nodes[i].box.x, drawY, __ui_nodes[i].box.w, __ui_nodes[i].box.h, __ui_canvas_bg);
|
|
876
|
+
int16_t __ui_prev_off_x = __ui_draw_off_x;
|
|
877
|
+
int16_t __ui_prev_off_y = __ui_draw_off_y;
|
|
878
|
+
int16_t __ui_prev_canvas_w = __ui_canvas_fallback_w;
|
|
879
|
+
int16_t __ui_prev_canvas_h = __ui_canvas_fallback_h;
|
|
880
|
+
__ui_canvas_fallback_w = __ui_cw;
|
|
881
|
+
__ui_canvas_fallback_h = __ui_ch;
|
|
882
|
+
__ui_draw_off_x = (int16_t)(__ui_prev_off_x + __ui_nodes[i].box.x);
|
|
883
|
+
__ui_draw_off_y = (int16_t)(__ui_prev_off_y + drawY);
|
|
884
|
+
__ui_canvas_fn(nullptr);
|
|
885
|
+
__ui_draw_off_x = __ui_prev_off_x;
|
|
886
|
+
__ui_draw_off_y = __ui_prev_off_y;
|
|
887
|
+
__ui_canvas_fallback_w = __ui_prev_canvas_w;
|
|
888
|
+
__ui_canvas_fallback_h = __ui_prev_canvas_h;
|
|
889
|
+
}
|
|
890
|
+
}
|
|
891
|
+
}
|
|
892
|
+
break;
|
|
893
|
+
}
|
|
894
|
+
case NODE_LIST: {
|
|
895
|
+
// Virtualized list: state lives on the node now (listCountFn/listItemFn/
|
|
896
|
+
// listCount/scrollY/contentHeight/listItemHeight), not in a side table.
|
|
897
|
+
if (!__ui_nodes[i].listItemFn) break;
|
|
898
|
+
int16_t bx = __ui_nodes[i].box.x;
|
|
899
|
+
int16_t by = drawY;
|
|
900
|
+
int16_t bw = __ui_nodes[i].box.w;
|
|
901
|
+
int16_t bh = __ui_nodes[i].box.h;
|
|
902
|
+
uint16_t ih = __ui_nodes[i].listItemHeight > 0 ? __ui_nodes[i].listItemHeight : 24;
|
|
903
|
+
uint16_t itemCount = __ui_nodes[i].listCount;
|
|
904
|
+
int16_t listScrollY = __ui_nodes[i].scrollY;
|
|
905
|
+
int16_t listContentH = __ui_nodes[i].contentHeight;
|
|
906
|
+
UI_COLOR_T clearCol = __ui_nodes[i].clearColor;
|
|
907
|
+
uint8_t listFullRepaint = 0;
|
|
908
|
+
// Render to a viewport-sized canvas so edge glyphs are naturally clipped.
|
|
909
|
+
// Treat a null buffer (failed internal malloc) as "no canvas" and retry —
|
|
910
|
+
// see __ui_node_canvas for the zombie-caching rationale.
|
|
911
|
+
// __ui_list_canvas is file-scoped so ui_release_canvas_state() can free it.
|
|
912
|
+
if (!__ui_list_canvas || !display_canvasBuffer(__ui_list_canvas) ||
|
|
913
|
+
display_canvasWidth(__ui_list_canvas) != bw || display_canvasHeight(__ui_list_canvas) != bh) {
|
|
914
|
+
display_deleteCanvas(__ui_list_canvas);
|
|
915
|
+
__ui_list_canvas_node = -1;
|
|
916
|
+
__ui_list_canvas = display_createCanvas(bw, bh);
|
|
917
|
+
listFullRepaint = 1;
|
|
918
|
+
}
|
|
919
|
+
CuttlefishCanvas16* lc = __ui_list_canvas;
|
|
920
|
+
if (!lc || !display_canvasBuffer(lc)) {
|
|
921
|
+
break;
|
|
922
|
+
}
|
|
923
|
+
if (__ui_list_canvas_node != (int16_t)i) listFullRepaint = 1;
|
|
924
|
+
int16_t repaintY = 0;
|
|
925
|
+
int16_t repaintH = bh;
|
|
926
|
+
int16_t deltaY = listScrollY - __ui_nodes[i].lastPaintedScrollY;
|
|
927
|
+
int16_t absDelta = deltaY < 0 ? -deltaY : deltaY;
|
|
928
|
+
uint8_t canShiftList = (!listFullRepaint && deltaY != 0 && absDelta < bh);
|
|
929
|
+
if (canShiftList) {
|
|
930
|
+
ui_shift_container_canvas(lc, deltaY, clearCol, &repaintY, &repaintH);
|
|
931
|
+
} else {
|
|
932
|
+
display_canvasFillScreen(lc, clearCol);
|
|
933
|
+
repaintY = 0;
|
|
934
|
+
repaintH = bh;
|
|
935
|
+
}
|
|
936
|
+
// GFXcanvas text has no clipping. For shift-and-repair frames, draw row
|
|
937
|
+
// text into a strip-sized repair canvas first, then blit only that strip
|
|
938
|
+
// into the shifted list canvas. This prevents a 1-5px repair from
|
|
939
|
+
// repainting full glyphs across pixels that were already shifted.
|
|
940
|
+
CuttlefishCanvas16* listTextCanvas = lc;
|
|
941
|
+
int16_t listTextOffsetY = 0;
|
|
942
|
+
uint8_t drawingListRepair = 0;
|
|
943
|
+
if (canShiftList && repaintH > 0 && repaintH < bh) {
|
|
944
|
+
CuttlefishCanvas16* rc = ui_get_repair_canvas(bw, repaintH);
|
|
945
|
+
if (rc) {
|
|
946
|
+
display_canvasFillScreen(rc, clearCol);
|
|
947
|
+
listTextCanvas = rc;
|
|
948
|
+
listTextOffsetY = repaintY;
|
|
949
|
+
drawingListRepair = 1;
|
|
950
|
+
} else {
|
|
951
|
+
display_canvasFillScreen(lc, clearCol);
|
|
952
|
+
repaintY = 0;
|
|
953
|
+
repaintH = bh;
|
|
954
|
+
canShiftList = 0;
|
|
955
|
+
}
|
|
956
|
+
}
|
|
957
|
+
// Compute visible range for the repainted strip. Previously-rendered
|
|
958
|
+
// pixels are shifted in-place; only the exposed band needs new rows.
|
|
959
|
+
uint16_t first = (listScrollY + repaintY) / ih;
|
|
960
|
+
uint16_t last = (listScrollY + repaintY + repaintH - 1) / ih + 1;
|
|
961
|
+
if (itemCount > 0 && last >= itemCount) last = itemCount - 1;
|
|
962
|
+
// Draw each visible item (canvas-local coords: 0,0 = viewport top).
|
|
963
|
+
char listBuf[UI_TEXT_BUF + 1];
|
|
964
|
+
display_targetSetTextWrap((CuttlefishDisplayTarget*)listTextCanvas, false);
|
|
965
|
+
if (itemCount > 0 && repaintH > 0) {
|
|
966
|
+
for (uint16_t idx = first; idx <= last; idx++) {
|
|
967
|
+
int16_t itemY = (int16_t)(idx * ih) - listScrollY - listTextOffsetY;
|
|
968
|
+
__ui_nodes[i].listItemFn(idx, listBuf, UI_TEXT_BUF + 1);
|
|
969
|
+
listBuf[UI_TEXT_BUF] = 0;
|
|
970
|
+
display_targetSetCursor((CuttlefishDisplayTarget*)listTextCanvas, 4, itemY + (ih - 16) / 2);
|
|
971
|
+
display_targetSetTextColor((CuttlefishDisplayTarget*)listTextCanvas, __ui_nodes[i].fg);
|
|
972
|
+
display_targetSetTextSize((CuttlefishDisplayTarget*)listTextCanvas, 2);
|
|
973
|
+
display_targetPrint((CuttlefishDisplayTarget*)listTextCanvas, listBuf);
|
|
974
|
+
}
|
|
975
|
+
}
|
|
976
|
+
if (drawingListRepair) {
|
|
977
|
+
CuttlefishDisplayTarget* prevTarget = ui_display_get_target();
|
|
978
|
+
ui_display_set_target((CuttlefishDisplayTarget*)lc);
|
|
979
|
+
ui_draw_canvas_rect(listTextCanvas, 0, repaintY, bw, repaintH);
|
|
980
|
+
ui_display_set_target(prevTarget);
|
|
981
|
+
}
|
|
982
|
+
// Scrollbar (canvas-local coords).
|
|
983
|
+
if (listContentH > bh) {
|
|
984
|
+
int16_t tx = bw - 4;
|
|
985
|
+
uint16_t thumbH = (uint32_t)bh * bh / listContentH;
|
|
986
|
+
if (thumbH < 8) thumbH = 8;
|
|
987
|
+
int16_t maxScroll = listContentH - bh;
|
|
988
|
+
uint16_t thumbY = maxScroll > 0 ? (uint32_t)(bh - thumbH) * listScrollY / maxScroll : 0;
|
|
989
|
+
UI_COLOR_T dimFg = (UI_COLOR_T)((__ui_nodes[i].fg >> 1) & UI_DIM_MASK);
|
|
990
|
+
display_canvasFillRect(lc, tx, 0, 3, bh, dimFg);
|
|
991
|
+
display_canvasFillRect(lc, tx, thumbY, 3, thumbH, __ui_nodes[i].fg);
|
|
992
|
+
}
|
|
993
|
+
// Outset shadows are static decoration. Redrawing the hard shadow
|
|
994
|
+
// directly to the panel before every small scroll-frame creates a
|
|
995
|
+
// visible shadow-then-content intermediate state on SPI TFTs. Keep it
|
|
996
|
+
// for full list repaints, but skip it for shift-and-repair scrolls.
|
|
997
|
+
if (!drawingBufferedScroll && !__ui_fb && !canShiftList) {
|
|
998
|
+
ui_draw_shadow(i, by, 0);
|
|
999
|
+
}
|
|
1000
|
+
// Standalone lists push directly. Lists inside a buffered scroll
|
|
1001
|
+
// container must composite into that scroll canvas; their box has
|
|
1002
|
+
// already been translated to canvas-local coordinates.
|
|
1003
|
+
if (drawingBufferedScroll) {
|
|
1004
|
+
ui_draw_canvas_rect(lc, bx, by, bw, bh);
|
|
1005
|
+
} else {
|
|
1006
|
+
ui_push_canvas_rect(lc, bx, by, bw, bh);
|
|
1007
|
+
}
|
|
1008
|
+
// Draw static decoration after the scrollable pixels are composited.
|
|
1009
|
+
// Keeping border rows out of __ui_list_canvas prevents the cached
|
|
1010
|
+
// shift step from dragging top/bottom border pixels through the list.
|
|
1011
|
+
ui_draw_shadow(i, by, 1);
|
|
1012
|
+
if (__ui_nodes[i].borderStyle != 0) {
|
|
1013
|
+
ui_draw_node_border(i, bx, by, bColor);
|
|
1014
|
+
}
|
|
1015
|
+
ui_draw_node_outline(i, bx, by);
|
|
1016
|
+
__ui_list_canvas_node = (int16_t)i;
|
|
1017
|
+
__ui_nodes[i].lastPaintedScrollY = listScrollY;
|
|
1018
|
+
__ui_nodes[i].dirty = 0;
|
|
1019
|
+
ui_display_set_target(__ui_draw_target);
|
|
1020
|
+
__ui_nodes[i].box.x = origBoxX;
|
|
1021
|
+
__ui_nodes[i].box.y = origBoxY;
|
|
1022
|
+
continue; // list handled canvas push, decoration, and coordinate restore
|
|
1023
|
+
}
|
|
1024
|
+
}
|
|
1025
|
+
if (__ui_nodes[i].kind == NODE_FILL && ui_rotation_quadrant(__ui_nodes[i].rotateDeg) != 0 &&
|
|
1026
|
+
__ui_nodes[i].outlineStyle != 0 && __ui_nodes[i].outlineWidth > 0) {
|
|
1027
|
+
uint8_t w = __ui_nodes[i].outlineWidth;
|
|
1028
|
+
int16_t outlineW = ui_rotated_face_w(i, __ui_nodes[i].box.w, __ui_nodes[i].box.h);
|
|
1029
|
+
int16_t outlineH = ui_rotated_face_h(i, __ui_nodes[i].box.w, __ui_nodes[i].box.h);
|
|
1030
|
+
ui_draw_rect_outline(__ui_nodes[i].box.x - w, drawY - w,
|
|
1031
|
+
outlineW + 2 * w, outlineH + 2 * w,
|
|
1032
|
+
__ui_nodes[i].borderRadius + w, __ui_nodes[i].outlineStyle, w, __ui_nodes[i].outlineColor);
|
|
1033
|
+
} else {
|
|
1034
|
+
ui_draw_node_outline(i, __ui_nodes[i].box.x, drawY);
|
|
1035
|
+
}
|
|
1036
|
+
if (drawingPaintCanvas) {
|
|
1037
|
+
ui_display_set_target(__ui_draw_target);
|
|
1038
|
+
ui_push_canvas_rect(paintCanvas, paintCanvasX, paintCanvasY, paintCanvasW, paintCanvasH);
|
|
1039
|
+
}
|
|
1040
|
+
if (!drawingBufferedScroll) {
|
|
1041
|
+
ui_invalidate_scroll_canvas_for_node(i);
|
|
1042
|
+
}
|
|
1043
|
+
// Restore original box coords (translated for canvas-local drawing above).
|
|
1044
|
+
__ui_nodes[i].box.x = origBoxX;
|
|
1045
|
+
__ui_nodes[i].box.y = origBoxY;
|
|
1046
|
+
__ui_nodes[i].dirty = 0;
|
|
1047
|
+
// Report this node's bounding box to the deferred-refresh accumulator. Phase
|
|
1048
|
+
// 4 uses the box as the dirty rect (a safe over-estimate); Phase 5 tightens
|
|
1049
|
+
// to the actual paint rect. No-op on TFT (compiles to nothing).
|
|
1050
|
+
ui_refresh_add_rect(__ui_nodes[i].box.x, __ui_nodes[i].box.y, __ui_nodes[i].box.w, __ui_nodes[i].box.h);`;
|
|
1051
|
+
}
|