@typecad/ui 1.0.0-alpha.12 → 1.0.0-alpha.13
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/assets/fonts/dejavu/DejaVuSans-Bold.ttf +0 -0
- package/assets/fonts/dejavu/DejaVuSans-ExtraLight.ttf +0 -0
- package/assets/fonts/dejavu/DejaVuSans-Oblique.ttf +0 -0
- package/assets/fonts/dejavu/DejaVuSans.ttf +0 -0
- package/assets/fonts/dejavu/DejaVuSansMono-Bold.ttf +0 -0
- package/assets/fonts/dejavu/DejaVuSansMono.ttf +0 -0
- package/assets/fonts/dejavu/LICENSE +187 -0
- package/assets/fonts/dejavu/README.md +49 -0
- package/dist/engine-index.js +2 -0
- package/dist/preview/build-program.d.ts +7 -1
- package/dist/preview/build-program.js +94 -15
- package/dist/preview/host-gfx.d.ts +3 -0
- package/dist/preview/host-gfx.js +30 -3
- package/dist/preview/host-ui-runtime.d.ts +115 -0
- package/dist/preview/host-ui-runtime.js +928 -46
- package/dist/ui-engine/block-layout.js +25 -11
- package/dist/ui-engine/color.d.ts +6 -0
- package/dist/ui-engine/color.js +80 -2
- package/dist/ui-engine/compat-report.d.ts +8 -0
- package/dist/ui-engine/compat-report.js +124 -0
- package/dist/ui-engine/css-imports.d.ts +4 -0
- package/dist/ui-engine/css-imports.js +70 -0
- package/dist/ui-engine/css-parser.js +58 -14
- package/dist/ui-engine/default-font.d.ts +25 -0
- package/dist/ui-engine/default-font.js +80 -0
- package/dist/ui-engine/font-assets.d.ts +2 -2
- package/dist/ui-engine/font-assets.js +22 -5
- package/dist/ui-engine/html-parser.d.ts +4 -0
- package/dist/ui-engine/html-parser.js +241 -26
- package/dist/ui-engine/image-assets.d.ts +8 -1
- package/dist/ui-engine/image-assets.js +35 -2
- package/dist/ui-engine/image-decode.d.ts +22 -0
- package/dist/ui-engine/image-decode.js +194 -0
- package/dist/ui-engine/inline-parser.js +1 -1
- package/dist/ui-engine/layout-engine.d.ts +19 -0
- package/dist/ui-engine/layout-engine.js +59 -3
- package/dist/ui-engine/model.d.ts +18 -0
- package/dist/ui-engine/model.js +75 -7
- package/dist/ui-engine/runtime-header/blend-bodies.js +29 -9
- package/dist/ui-engine/runtime-header/canvas-helpers.js +3 -0
- package/dist/ui-engine/runtime-header/canvas-scrollbar.js +115 -111
- package/dist/ui-engine/runtime-header/cuttlefish-gfx.js +21 -0
- package/dist/ui-engine/runtime-header/dirty-scroll-mutators.js +182 -179
- package/dist/ui-engine/runtime-header/forward-decls.js +215 -167
- package/dist/ui-engine/runtime-header/init-press-input.js +138 -135
- package/dist/ui-engine/runtime-header/keyboard.js +682 -380
- package/dist/ui-engine/runtime-header/node-draw-body.js +1656 -1386
- package/dist/ui-engine/runtime-header/paint-order-coords.js +259 -214
- package/dist/ui-engine/runtime-header/paint-rects-repair.js +736 -568
- package/dist/ui-engine/runtime-header/scroll-physics.js +25 -1
- package/dist/ui-engine/runtime-header/state-bindings-nav.js +9 -0
- package/dist/ui-engine/runtime-header/structs.js +228 -216
- package/dist/ui-engine/runtime-header/text-rendering.js +888 -770
- package/dist/ui-engine/runtime-header/tick/bindings-phase.js +118 -116
- package/dist/ui-engine/runtime-header/tick/dirty-draw-phase.js +897 -633
- package/dist/ui-engine/runtime-header/tick/scroll-canvas-phase.js +24 -25
- package/dist/ui-engine/runtime-header/touch-keyboard-fwd.js +78 -23
- package/dist/ui-engine/runtime-header/types-defines.js +110 -93
- package/dist/ui-engine/shadcn-kit.d.ts +1 -0
- package/dist/ui-engine/shadcn-kit.js +502 -0
- package/dist/ui-engine/style-resolver.d.ts +4 -0
- package/dist/ui-engine/style-resolver.js +58 -6
- package/dist/ui-engine/transpile-ui.js +12 -5
- package/dist/ui-engine/ua-stylesheet.d.ts +22 -1
- package/dist/ui-engine/ua-stylesheet.js +119 -32
- package/dist/ui-engine/ui-lowering.js +37 -3
- package/dist/ui-engine/ui-registry.js +32 -4
- package/dist/ui-engine/yoga-layout.js +57 -52
- package/package.json +9 -3
- package/src/engine-index.ts +53 -51
- package/src/preview/build-program.ts +810 -734
- package/src/preview/host-gfx.ts +30 -3
- package/src/preview/host-ui-runtime.ts +4133 -3289
- package/src/ui-engine/block-layout.ts +24 -11
- package/src/ui-engine/color.ts +77 -2
- package/src/ui-engine/compat-report.ts +139 -0
- package/src/ui-engine/css-imports.ts +69 -0
- package/src/ui-engine/css-parser.ts +64 -15
- package/src/ui-engine/default-font.ts +95 -0
- package/src/ui-engine/font-assets.ts +545 -525
- package/src/ui-engine/html-parser.ts +615 -397
- package/src/ui-engine/image-assets.ts +37 -2
- package/src/ui-engine/image-decode.ts +244 -0
- package/src/ui-engine/inline-parser.ts +1 -1
- package/src/ui-engine/layout-engine.ts +61 -3
- package/src/ui-engine/model.ts +1317 -1230
- package/src/ui-engine/runtime-header/blend-bodies.ts +29 -9
- package/src/ui-engine/runtime-header/canvas-helpers.ts +3 -0
- package/src/ui-engine/runtime-header/canvas-scrollbar.ts +121 -117
- package/src/ui-engine/runtime-header/cuttlefish-gfx.ts +21 -0
- package/src/ui-engine/runtime-header/dirty-scroll-mutators.ts +188 -185
- package/src/ui-engine/runtime-header/forward-decls.ts +227 -179
- package/src/ui-engine/runtime-header/init-press-input.ts +144 -141
- package/src/ui-engine/runtime-header/keyboard.ts +689 -386
- package/src/ui-engine/runtime-header/node-draw-body.ts +1681 -1411
- package/src/ui-engine/runtime-header/paint-order-coords.ts +265 -220
- package/src/ui-engine/runtime-header/paint-rects-repair.ts +742 -574
- package/src/ui-engine/runtime-header/scroll-physics.ts +25 -1
- package/src/ui-engine/runtime-header/state-bindings-nav.ts +9 -0
- package/src/ui-engine/runtime-header/structs.ts +234 -222
- package/src/ui-engine/runtime-header/text-rendering.ts +894 -776
- package/src/ui-engine/runtime-header/tick/bindings-phase.ts +124 -122
- package/src/ui-engine/runtime-header/tick/dirty-draw-phase.ts +902 -638
- package/src/ui-engine/runtime-header/tick/scroll-canvas-phase.ts +30 -31
- package/src/ui-engine/runtime-header/touch-keyboard-fwd.ts +78 -23
- package/src/ui-engine/runtime-header/types-defines.ts +116 -99
- package/src/ui-engine/shadcn-kit.ts +503 -0
- package/src/ui-engine/style-resolver.ts +471 -416
- package/src/ui-engine/transpile-ui.ts +12 -5
- package/src/ui-engine/ua-stylesheet.ts +137 -31
- package/src/ui-engine/ui-lowering.ts +486 -452
- package/src/ui-engine/ui-registry.ts +556 -524
- package/src/ui-engine/yoga-layout.ts +307 -309
|
@@ -1,11 +1,70 @@
|
|
|
1
1
|
// ---------------------------------------------------------------------------
|
|
2
2
|
// UA (User-Agent) default stylesheet.
|
|
3
3
|
// Applied before user CSS so elements have sensible built-in behavior without
|
|
4
|
-
// requiring explicit CSS.
|
|
5
|
-
//
|
|
4
|
+
// requiring explicit CSS. Author CSS overrides it (cascade: the UA layer loses
|
|
5
|
+
// to any author rule; see style-resolver.ts).
|
|
6
|
+
//
|
|
7
|
+
// The sheet is display-anchored, like a browser's per-device-class defaults:
|
|
8
|
+
// the root type size (what a browser fixes at 16px on desktop) scales with the
|
|
9
|
+
// target display — 16px on large color panels, ~14px on 320×240 TFTs, ~12px on
|
|
10
|
+
// tiny panels — and every UA size derives from that root, so heading ratios
|
|
11
|
+
// and touch-target minimums stay web-familiar at every pixel budget.
|
|
12
|
+
// Monochrome displays additionally snap sizes to the stock font's buckets
|
|
13
|
+
// (only ~4 distinct rendered sizes exist there).
|
|
6
14
|
// ---------------------------------------------------------------------------
|
|
7
15
|
import { parseCss } from "./css-parser.js";
|
|
8
|
-
|
|
16
|
+
import { getDisplayProfile } from "@typecad/cuttlefish/stores/display-profile-store";
|
|
17
|
+
import { DEFAULT_FONT_FAMILY, DEFAULT_MONO_FAMILY, defaultFontApplies } from "./default-font.js";
|
|
18
|
+
/** Derive the UA type scale from the display class. Height is the portrait
|
|
19
|
+
* dimension the profile reports; an unbound profile gets the color/large
|
|
20
|
+
* default (root 16 — the classic browser default). */
|
|
21
|
+
export function uaScaleFor(height, mono) {
|
|
22
|
+
if (mono) {
|
|
23
|
+
// Stock font buckets: ≤12 → size 1, 13–20 → 2, 21–28 → 3. Snap UA sizes
|
|
24
|
+
// to 12/20/28 so every declared size renders at a DISTINCT bucket.
|
|
25
|
+
const tall = (height ?? 128) >= 96;
|
|
26
|
+
return {
|
|
27
|
+
root: 12,
|
|
28
|
+
mono: true,
|
|
29
|
+
h: tall ? [28, 20, 20, 12, 12, 12] : [20, 20, 12, 12, 12, 12],
|
|
30
|
+
blockMargin: 6,
|
|
31
|
+
controlMinHeight: 32,
|
|
32
|
+
buttonPadV: 6,
|
|
33
|
+
buttonPadH: 12,
|
|
34
|
+
};
|
|
35
|
+
}
|
|
36
|
+
const root = height === undefined ? 16 : height >= 300 ? 16 : height >= 150 ? 14 : 12;
|
|
37
|
+
const r = (m) => Math.round(root * m);
|
|
38
|
+
return {
|
|
39
|
+
root,
|
|
40
|
+
mono: false,
|
|
41
|
+
h: [r(1.5), r(1.25), r(1.125), r(1), r(0.875), r(0.75)],
|
|
42
|
+
blockMargin: r(0.5),
|
|
43
|
+
controlMinHeight: Math.max(32, Math.min(48, r(3))),
|
|
44
|
+
buttonPadV: r(0.625),
|
|
45
|
+
buttonPadH: r(1),
|
|
46
|
+
};
|
|
47
|
+
}
|
|
48
|
+
/** Read the active display profile (null when unbound — the default scale). */
|
|
49
|
+
function profileFor() {
|
|
50
|
+
try {
|
|
51
|
+
const p = getDisplayProfile();
|
|
52
|
+
return {
|
|
53
|
+
height: p.height,
|
|
54
|
+
mono: p.colorFormat === "mono",
|
|
55
|
+
colorFormat: p.colorFormat,
|
|
56
|
+
};
|
|
57
|
+
}
|
|
58
|
+
catch {
|
|
59
|
+
return { height: undefined, mono: false, colorFormat: undefined };
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
/** Build the UA stylesheet text for a scale. Sizes interpolate as concrete px
|
|
63
|
+
* so the CSS pipeline stays unit-simple. */
|
|
64
|
+
function uaCssFor(s, useDefaultFont) {
|
|
65
|
+
const gap = Math.round(s.root * 0.625);
|
|
66
|
+
const headerGap = Math.round(s.root * 0.5);
|
|
67
|
+
return `
|
|
9
68
|
/* Browser-like light defaults: dark text on a near-white background.
|
|
10
69
|
A dark theme overrides these via user CSS (e.g. screen { color: #f0f0f0;
|
|
11
70
|
background: #1a1a1a }). Without these, screen fg defaults to white
|
|
@@ -14,7 +73,7 @@ screen {
|
|
|
14
73
|
display: flex;
|
|
15
74
|
flex-direction: column;
|
|
16
75
|
color: #1a1a1a;
|
|
17
|
-
background: #ffffff
|
|
76
|
+
background: #ffffff;${useDefaultFont ? `\n /* Bundled default font: AA + continuous sizes out of the box. */\n font-family: "${DEFAULT_FONT_FAMILY}";` : ""}
|
|
18
77
|
}
|
|
19
78
|
body {
|
|
20
79
|
display: flex;
|
|
@@ -27,40 +86,41 @@ view {
|
|
|
27
86
|
flex-direction: column;
|
|
28
87
|
}
|
|
29
88
|
/* Scrollable content area — the standard pattern for multi-screen apps.
|
|
30
|
-
align-items:center + gap give readable card-style layouts out of the box.
|
|
31
|
-
.
|
|
89
|
+
align-items:center + gap give readable card-style layouts out of the box.
|
|
90
|
+
.ui-scroll-body / .ui-screen-header are the framework-prefixed spellings. */
|
|
91
|
+
.scrollBody, .ui-scroll-body {
|
|
32
92
|
overflow: scroll;
|
|
33
93
|
flex: 1;
|
|
34
94
|
align-items: center;
|
|
35
|
-
gap:
|
|
95
|
+
gap: ${gap}px;
|
|
36
96
|
}
|
|
37
97
|
/* Header bar (back link + title). flex-direction:row is the expected layout;
|
|
38
98
|
without it the back link and title stack vertically. */
|
|
39
|
-
.screenHeader {
|
|
99
|
+
.screenHeader, .ui-screen-header {
|
|
40
100
|
flex-direction: row;
|
|
41
101
|
align-items: center;
|
|
42
|
-
gap:
|
|
102
|
+
gap: ${headerGap}px;
|
|
43
103
|
}
|
|
44
104
|
button {
|
|
45
|
-
padding:
|
|
105
|
+
padding: ${s.buttonPadV}px ${s.buttonPadH}px;
|
|
46
106
|
border: 1px solid;
|
|
47
|
-
min-height:
|
|
107
|
+
min-height: ${s.controlMinHeight}px;
|
|
48
108
|
text-align: center;
|
|
49
109
|
}
|
|
50
110
|
input {
|
|
51
|
-
padding:
|
|
111
|
+
padding: ${Math.round(s.root * 0.5)}px;
|
|
52
112
|
border: 1px solid;
|
|
53
113
|
}
|
|
54
114
|
select {
|
|
55
|
-
padding:
|
|
115
|
+
padding: ${s.buttonPadV}px ${s.buttonPadH}px;
|
|
56
116
|
border: 1px solid;
|
|
57
|
-
min-height:
|
|
117
|
+
min-height: ${s.controlMinHeight}px;
|
|
58
118
|
}
|
|
59
119
|
check, radio {
|
|
60
|
-
min-height:
|
|
120
|
+
min-height: ${s.controlMinHeight}px;
|
|
61
121
|
}
|
|
62
122
|
range {
|
|
63
|
-
height:
|
|
123
|
+
height: ${Math.round(s.root * 2.5)}px;
|
|
64
124
|
}
|
|
65
125
|
list {
|
|
66
126
|
overflow: scroll;
|
|
@@ -72,17 +132,35 @@ a {
|
|
|
72
132
|
label {
|
|
73
133
|
text-align: left;
|
|
74
134
|
}
|
|
75
|
-
/*
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
135
|
+
/* Headings and paragraphs carry browser-like margins so a bare heading stack
|
|
136
|
+
doesn't render glued together. Sizes derive from the display-anchored root
|
|
137
|
+
(see uaScaleFor). */
|
|
138
|
+
h1 { font-size: ${s.h[0]}px; font-weight: bold; margin: ${s.blockMargin}px 0; }
|
|
139
|
+
h2 { font-size: ${s.h[1]}px; font-weight: bold; margin: ${s.blockMargin}px 0; }
|
|
140
|
+
h3 { font-size: ${s.h[2]}px; font-weight: bold; margin: ${s.blockMargin}px 0; }
|
|
141
|
+
h4 { font-size: ${s.h[3]}px; font-weight: bold; margin: ${s.blockMargin}px 0; }
|
|
142
|
+
h5 { font-size: ${s.h[4]}px; margin: ${s.blockMargin}px 0; }
|
|
143
|
+
h6 { font-size: ${s.h[5]}px; margin: ${s.blockMargin}px 0; }
|
|
144
|
+
p { font-size: ${s.root}px; margin: ${s.blockMargin}px 0; }
|
|
145
|
+
small { font-size: ${Math.round(s.root * 0.8)}px; }
|
|
146
|
+
hr { height: 1px; background: #808080; margin: ${s.blockMargin}px 0; }
|
|
147
|
+
/* Definition lists: bold term, indented description — the settings-screen
|
|
148
|
+
idiom web authors reach for. */
|
|
149
|
+
dt { font-weight: bold; }
|
|
150
|
+
dd { margin-left: ${Math.round(s.root * 0.75)}px; }
|
|
151
|
+
/* Tables: the equal-width flex approximation — tr is a row, td/th are
|
|
152
|
+
stretched cells (flex:1 basis 0), th bold + centered like browsers.
|
|
153
|
+
No auto column sizing; set widths/flex-grow on cells for custom columns. */
|
|
154
|
+
table { gap: 1px; }
|
|
155
|
+
tr { flex-direction: row; align-items: stretch; gap: 1px; }
|
|
156
|
+
td { flex: 1; padding: ${Math.round(s.root * 0.3)}px; text-align: left; }
|
|
157
|
+
th { flex: 1; padding: ${Math.round(s.root * 0.3)}px; text-align: center; font-weight: bold; }
|
|
158
|
+
caption { text-align: center; margin: ${Math.round(s.root * 0.25)}px 0; }
|
|
159
|
+
/* Grouped form chrome. */
|
|
160
|
+
fieldset { border: 1px solid; padding: ${s.blockMargin}px; }
|
|
161
|
+
/* Preformatted text keeps its whitespace (engine white-space modes); the
|
|
162
|
+
monospace family rides on the bundled DejaVu Sans Mono when available. */
|
|
163
|
+
pre { white-space: pre; }${useDefaultFont ? `\npre, code, kbd, samp { font-family: "${DEFAULT_MONO_FAMILY}"; }` : ""}
|
|
86
164
|
/* Keyboard structural defaults (the OSK is runtime-generated, not
|
|
87
165
|
user-authored HTML). Border keeps keys visually separated. Colors
|
|
88
166
|
are design choices left to user CSS. */
|
|
@@ -90,10 +168,19 @@ p { font-size: 16px; }
|
|
|
90
168
|
border: 1px solid;
|
|
91
169
|
}
|
|
92
170
|
`;
|
|
93
|
-
|
|
94
|
-
|
|
171
|
+
}
|
|
172
|
+
const cache = new Map();
|
|
173
|
+
/** Get the parsed UA default rules for the active display profile. Lazily
|
|
174
|
+
* computed, cached per scale (profile changes pick up a new scale key). */
|
|
95
175
|
export function getUARules() {
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
176
|
+
const profile = profileFor();
|
|
177
|
+
const scale = uaScaleFor(profile.height, profile.mono);
|
|
178
|
+
const useDefaultFont = defaultFontApplies(profile.colorFormat);
|
|
179
|
+
const key = `${scale.root}|${scale.mono}|${useDefaultFont}|${scale.h.join(",")}`;
|
|
180
|
+
let rules = cache.get(key);
|
|
181
|
+
if (!rules) {
|
|
182
|
+
rules = parseCss(uaCssFor(scale, useDefaultFont));
|
|
183
|
+
cache.set(key, rules);
|
|
184
|
+
}
|
|
185
|
+
return rules;
|
|
99
186
|
}
|
|
@@ -13,6 +13,7 @@
|
|
|
13
13
|
// ---------------------------------------------------------------------------
|
|
14
14
|
import { lowerUIToModel } from "./model.js";
|
|
15
15
|
import { resolveColor } from "./color.js";
|
|
16
|
+
import { defaultFontAttribution } from "./default-font.js";
|
|
16
17
|
import { DEFAULT_ALPHA_KEYBOARD, DEFAULT_NUMBER_KEYBOARD } from "./default-keyboards.js";
|
|
17
18
|
import { getListBindings } from "@typecad/cuttlefish/ir/transformers/ui-reactive";
|
|
18
19
|
export function lowerUIToCpp(root, boxes, colorFormat, storage, keyboards = [], rules = [], display, fontAssets = [], allScreens = [], imageAssetIds = new Map(), keyframeSets = []) {
|
|
@@ -51,6 +52,14 @@ export function lowerUIToCpp(root, boxes, colorFormat, storage, keyboards = [],
|
|
|
51
52
|
addIfNeeded(spec.type === "number" ? DEFAULT_NUMBER_KEYBOARD : DEFAULT_ALPHA_KEYBOARD);
|
|
52
53
|
}
|
|
53
54
|
}
|
|
55
|
+
// The runtime header's keyboard page-swap (the 123/ABC toggle key) calls
|
|
56
|
+
// BOTH default loaders regardless of which input opened the keyboard. Emit
|
|
57
|
+
// both whenever any input exists, or a text-only project fails to link
|
|
58
|
+
// (undefined __ui_kb_load_default_number on non-Arduino linkers).
|
|
59
|
+
if (inputSpecs.length > 0) {
|
|
60
|
+
addIfNeeded(DEFAULT_ALPHA_KEYBOARD);
|
|
61
|
+
addIfNeeded(DEFAULT_NUMBER_KEYBOARD);
|
|
62
|
+
}
|
|
54
63
|
const keyboardLoaders = neededKeyboards
|
|
55
64
|
.map(kb => emitKeyboardLoader(loaderNameForId(kb.id), kb, rules, colorFormat))
|
|
56
65
|
.join("\n\n");
|
|
@@ -204,7 +213,12 @@ function emitFontTables(model) {
|
|
|
204
213
|
}
|
|
205
214
|
const lines = [];
|
|
206
215
|
for (const asset of assets) {
|
|
207
|
-
lines.push(`// Font ${asset.id}: ${asset.family} ${asset.px}px ${asset.fontWeight} ${asset.fontStyle} ${asset.subset}`);
|
|
216
|
+
lines.push(`// Font ${asset.id}: ${asset.family} ${asset.px}px ${asset.fontWeight} ${asset.fontStyle} ${asset.subset} (source: ${asset.sourcePath.split(/[\\/]/).pop()})`);
|
|
217
|
+
// Bundled DejaVu faces carry their license attribution into the binary
|
|
218
|
+
// (the Bitstream Vera License requires the notice accompany distributions).
|
|
219
|
+
const attribution = defaultFontAttribution(asset.sourcePath);
|
|
220
|
+
if (attribution)
|
|
221
|
+
lines.push(`// ${attribution}`);
|
|
208
222
|
// Alpha data goes in PROGMEM (constants in flash, not RAM) - accessed via pgm_read_byte on AVR
|
|
209
223
|
lines.push(`static const uint8_t __ui_font_${asset.id}_alpha[] PROGMEM = {`);
|
|
210
224
|
lines.push(byteArray(asset.alpha));
|
|
@@ -231,6 +245,19 @@ function emitNodeTable(model) {
|
|
|
231
245
|
const listBindingByNode = new Map();
|
|
232
246
|
for (const lb of getListBindings())
|
|
233
247
|
listBindingByNode.set(lb.nodeIndex, lb);
|
|
248
|
+
// <select> option tables: one fn per select feeding the node's optionTextFn
|
|
249
|
+
// (the modal option list reads options through it). Emitted before the node
|
|
250
|
+
// table so the initializers can reference the names.
|
|
251
|
+
const optionFnByNode = new Map();
|
|
252
|
+
const optionFns = [];
|
|
253
|
+
for (const n of model.nodes) {
|
|
254
|
+
if (n.kind !== "select" || !n.options || n.options.length === 0)
|
|
255
|
+
continue;
|
|
256
|
+
const fnName = `__ui_select_opts_${n.index}`;
|
|
257
|
+
optionFnByNode.set(n.index, fnName);
|
|
258
|
+
const cases = n.options.map((o, i) => ` if (idx == ${i}) { snprintf(buf, static_cast<size_t>(size), "%s", ${JSON.stringify(o.text)}); return; }`).join("\n");
|
|
259
|
+
optionFns.push(`static void ${fnName}(uint8_t idx, char* buf, uint8_t size) {\n${cases}\n buf[0] = 0;\n}`);
|
|
260
|
+
}
|
|
234
261
|
// Accumulate rich-text run / segment / line parallel arrays across all
|
|
235
262
|
// run-bearing nodes, assigning each a contiguous slice (runStart/richSegStart/
|
|
236
263
|
// richLineStart + counts). Emitted as global tables after the node table.
|
|
@@ -285,9 +312,12 @@ function emitNodeTable(model) {
|
|
|
285
312
|
const richSegStart = rr ? rr.richSegStart : 0;
|
|
286
313
|
const richSegCount = rr ? rr.richSegCount : 0;
|
|
287
314
|
const richLineStart = rr ? rr.richLineStart : 0;
|
|
288
|
-
return ` { .box=${box}, .bg=${hex(n.bg)}, .fg=${hex(n.fg)}, .kind=${cppKind(n.kind)}, .text=${inputText}, .textBuffer={0}, .hasTextBinding=0, .font=${font}, .hasBg=${n.hasBg ? 1 : 0}, .textAlign=${n.textAlign}, .textSize=${n.textSize}, .lineHeight=${n.lineHeight}, .letterSpacing=${n.letterSpacing}, .fontAntialias=${n.fontAntialias ? 1 : 0}, .fontFace=${n.fontFace}, .borderColor=${hex(n.borderColor)}, .borderStyle=${n.borderStyle}, .borderWidth=${n.borderWidth}, .borderTopWidth=${n.borderTopWidth ?? n.borderWidth}, .borderRightWidth=${n.borderRightWidth ?? n.borderWidth}, .borderBottomWidth=${n.borderBottomWidth ?? n.borderWidth}, .borderLeftWidth=${n.borderLeftWidth ?? n.borderWidth}, .hasPerSideBorder=${((n.borderTopWidth ?? n.borderWidth) !== n.borderWidth || (n.borderRightWidth ?? n.borderWidth) !== n.borderWidth || (n.borderBottomWidth ?? n.borderWidth) !== n.borderWidth || (n.borderLeftWidth ?? n.borderWidth) !== n.borderWidth) ? 1 : 0}, .borderRadius=${n.borderRadius}, .paddingTop=${n.paddingTop ?? 0}, .paddingRight=${n.paddingRight ?? 0}, .paddingBottom=${n.paddingBottom ?? 0}, .paddingLeft=${n.paddingLeft ?? 0}, .gradientEnabled=${n.gradientEnabled}, .gradientColor1=${hex(n.gradientColor1)}, .gradientColor2=${hex(n.gradientColor2)}, .outlineColor=${hex(n.outlineColor)}, .outlineStyle=${n.outlineStyle}, .outlineWidth=${n.outlineWidth}, .zIndex=${n.zIndex}, .transformOffsetX=${n.transformOffsetX}, .transformOffsetY=${n.transformOffsetY}, .rotateDeg=${n.rotateDeg}, .pressedOffsetX=${n.pressedOffsetX}, .pressedOffsetY=${n.pressedOffsetY}, .shadowCount=${n.shadowCount}, .shadowOffsetX=${shArr(n.shadowOffsetX)}, .shadowOffsetY=${shArr(n.shadowOffsetY)}, .shadowBlur=${shArr(n.shadowBlur)}, .shadowColor={${n.shadowColor.slice(0, 4).map(hex).join(",")}}, .shadowAlpha=${shArr(n.shadowAlpha)}, .shadowInset=${shArr(n.shadowInset.map(v => v ? 1 : 0))}, .textShadowCount=${n.textShadowCount}, .textShadowOffsetX=${n.textShadowOffsetX}, .textShadowOffsetY=${n.textShadowOffsetY}, .textShadowBlur=${n.textShadowBlur}, .textShadowColor=${hex(n.textShadowColor)}, .textShadowAlpha=${n.textShadowAlpha}, .underline=${n.underline}, .textOverflow=${n.textOverflow ? 1 : 0}, .nowrap=${n.nowrap ? 1 : 0}, .whiteSpaceMode=${n.whiteSpaceMode}, .visible=${n.visible ? 1 : 0}, .opacity=${n.opacity}, .clearColor=${hex(n.clearColor)}, .lastTextWidth=${lastTextWidth}, .lastTextHeight=0, .layoutCacheKey=0, .layoutMetricsW=0, .layoutMetricsH=0, .scrollable=${n.scrollable ? 1 : 0}, .virtualized=${virtualized}, .scrollY=0, .contentHeight=${n.contentHeight}, .overscrollPx=0, .settling=0, .lastPaintedScrollY=0, .listCount=0, .listCountFn=${listCountFn}, .listItemFn=${listItemFn}, .listTapFn=${listTapFn}, .parent=${parent}, .subtreeEnd=${n.subtreeEnd}, .screenId=${n.screenId}, .imgDataId=${n.imgDataId ?? 255}, .objectFit=${n.objectFit ?? 1}, .listItemHeight=${n.listItemHeight ?? 0}, .rangeMin=${n.rangeMin}, .rangeMax=${n.rangeMax}, .maxlen=${n.maxlen}, .canvasW=${n.canvasW ?? 0}, .canvasH=${n.canvasH ?? 0}, .runCount=${runCount}, .richLineCount=${richLineCount}, .runStart=${runStart}, .richSegStart=${richSegStart}, .richSegCount=${richSegCount}, .richLineStart=${richLineStart}, .dirty=0, .value=${n.value} },`;
|
|
315
|
+
return ` { .box=${box}, .bg=${hex(n.bg)}, .fg=${hex(n.fg)}, .kind=${cppKind(n.kind)}, .text=${inputText}, .textBuffer={0}, .hasTextBinding=0, .font=${font}, .hasBg=${n.hasBg ? 1 : 0}, .textAlign=${n.textAlign}, .textSize=${n.textSize}, .lineHeight=${n.lineHeight}, .letterSpacing=${n.letterSpacing}, .fontAntialias=${n.fontAntialias ? 1 : 0}, .fontFace=${n.fontFace}, .borderColor=${hex(n.borderColor)}, .borderStyle=${n.borderStyle}, .borderWidth=${n.borderWidth}, .borderTopWidth=${n.borderTopWidth ?? n.borderWidth}, .borderRightWidth=${n.borderRightWidth ?? n.borderWidth}, .borderBottomWidth=${n.borderBottomWidth ?? n.borderWidth}, .borderLeftWidth=${n.borderLeftWidth ?? n.borderWidth}, .hasPerSideBorder=${((n.borderTopWidth ?? n.borderWidth) !== n.borderWidth || (n.borderRightWidth ?? n.borderWidth) !== n.borderWidth || (n.borderBottomWidth ?? n.borderWidth) !== n.borderWidth || (n.borderLeftWidth ?? n.borderWidth) !== n.borderWidth) ? 1 : 0}, .borderRadius=${Math.min(255, n.borderRadius)}, .paddingTop=${n.paddingTop ?? 0}, .paddingRight=${n.paddingRight ?? 0}, .paddingBottom=${n.paddingBottom ?? 0}, .paddingLeft=${n.paddingLeft ?? 0}, .gradientEnabled=${n.gradientEnabled}, .gradientColor1=${hex(n.gradientColor1)}, .gradientColor2=${hex(n.gradientColor2)}, .outlineColor=${hex(n.outlineColor)}, .outlineStyle=${n.outlineStyle}, .outlineWidth=${n.outlineWidth}, .zIndex=${n.zIndex}, .transformOffsetX=${n.transformOffsetX}, .transformOffsetY=${n.transformOffsetY}, .rotateDeg=${n.rotateDeg}, .pressedOffsetX=${n.pressedOffsetX}, .pressedOffsetY=${n.pressedOffsetY}, .shadowCount=${n.shadowCount}, .shadowOffsetX=${shArr(n.shadowOffsetX)}, .shadowOffsetY=${shArr(n.shadowOffsetY)}, .shadowBlur=${shArr(n.shadowBlur)}, .shadowColor={${n.shadowColor.slice(0, 4).map(hex).join(",")}}, .shadowAlpha=${shArr(n.shadowAlpha)}, .shadowInset=${shArr(n.shadowInset.map(v => v ? 1 : 0))}, .textShadowCount=${n.textShadowCount}, .textShadowOffsetX=${n.textShadowOffsetX}, .textShadowOffsetY=${n.textShadowOffsetY}, .textShadowBlur=${n.textShadowBlur}, .textShadowColor=${hex(n.textShadowColor)}, .textShadowAlpha=${n.textShadowAlpha}, .underline=${n.underline}, .textOverflow=${n.textOverflow ? 1 : 0}, .nowrap=${n.nowrap ? 1 : 0}, .whiteSpaceMode=${n.whiteSpaceMode}, .visible=${n.visible ? 1 : 0}, .opacity=${n.opacity}, .clearColor=${hex(n.clearColor)}, .lastTextWidth=${lastTextWidth}, .lastTextHeight=0, .layoutCacheKey=0, .layoutMetricsW=0, .layoutMetricsH=0, .scrollable=${n.scrollable ? 1 : 0}, .virtualized=${virtualized}, .scrollY=0, .contentHeight=${n.contentHeight}, .overscrollPx=0, .settling=0, .lastPaintedScrollY=0, .listCount=0, .listCountFn=${listCountFn}, .listItemFn=${listItemFn}, .listTapFn=${listTapFn}, .parent=${parent}, .subtreeEnd=${n.subtreeEnd}, .screenId=${n.screenId}, .imgDataId=${n.imgDataId ?? 255}, .objectFit=${n.objectFit ?? 1}, .listItemHeight=${n.listItemHeight ?? 0}, .rangeMin=${n.rangeMin}, .rangeMax=${n.rangeMax}, .maxlen=${n.maxlen}, .canvasW=${n.canvasW ?? 0}, .canvasH=${n.canvasH ?? 0}, .runCount=${runCount}, .richLineCount=${richLineCount}, .runStart=${runStart}, .richSegStart=${richSegStart}, .richSegCount=${richSegCount}, .richLineStart=${richLineStart}, .dirty=0, .value=${n.value}, .disabled=${n.disabled ? 1 : 0}, .optionCount=${n.kind === "select" && n.options ? n.options.length : 0}, .optionTextFn=${optionFnByNode.get(n.index) ?? "nullptr"}, .drawerSide=${n.drawerSide ?? -1}, .toastDuration=${n.toastDuration ?? 0}, .checkedBg=${n.checkedBg !== undefined && n.checkedBg >= 0 ? hex(n.checkedBg) : "0x0"}, .checkedFg=${n.checkedFg !== undefined && n.checkedFg >= 0 ? hex(n.checkedFg) : "0x0"}, .hasCheckedBg=${n.checkedBg !== undefined && n.checkedBg >= 0 ? 1 : 0}, .hasCheckedFg=${n.checkedFg !== undefined && n.checkedFg >= 0 ? 1 : 0}, .flowAxis=${n.flowAxis ?? 0}, .flowGap=${n.flowGap ?? 0}, .flowFlags=${n.flowFlags ?? 0} },`;
|
|
289
316
|
});
|
|
290
317
|
return [
|
|
318
|
+
// <select> option tables (must precede __ui_nodes[]: initializers
|
|
319
|
+
// reference the function names).
|
|
320
|
+
...optionFns,
|
|
291
321
|
// Mutable (not const) so ui_tick can update bg/dirty during transitions.
|
|
292
322
|
// AVR would want PROGMEM + a shadow copy; ESP32-class has RAM to spare.
|
|
293
323
|
`UINode __ui_nodes[] = {`,
|
|
@@ -310,7 +340,11 @@ function emitNodeTable(model) {
|
|
|
310
340
|
].join("\n");
|
|
311
341
|
}
|
|
312
342
|
function emitTransitionTable(model) {
|
|
313
|
-
|
|
343
|
+
// Skip no-op transitions (pressed target equals base) — they'd never change
|
|
344
|
+
// the node's appearance and only bloat the per-tick scan.
|
|
345
|
+
const entries = model.transitions
|
|
346
|
+
.filter((t) => t.pressedTarget !== t.baseTarget)
|
|
347
|
+
.map((t) => {
|
|
314
348
|
const prop = t.prop === "background" ? "PROP_BG" : "PROP_FG";
|
|
315
349
|
return ` { .node=${t.node}, .prop=${prop}, .durationMs=${t.durationMs}, .pressedTarget=${hex(t.pressedTarget)}, .baseTarget=${hex(t.baseTarget)} },`;
|
|
316
350
|
});
|
|
@@ -26,7 +26,11 @@ import { measureWithFonts } from "./layout-engine.js";
|
|
|
26
26
|
import { lowerUIToCpp } from "./ui-lowering.js";
|
|
27
27
|
import { getDisplayProfile } from "@typecad/cuttlefish/stores/display-profile-store";
|
|
28
28
|
import { buildUIFontAssets } from "./font-assets.js";
|
|
29
|
-
import {
|
|
29
|
+
import { injectDefaultFontFaces } from "./default-font.js";
|
|
30
|
+
import { cssCompatDiagnostics } from "./compat-report.js";
|
|
31
|
+
import { expandCssImports } from "./css-imports.js";
|
|
32
|
+
import { SHADCN_KIT_CSS } from "./shadcn-kit.js";
|
|
33
|
+
import { emitImageTables, loadImageAssets, applyDecodedImageSizes } from "./image-assets.js";
|
|
30
34
|
import { splitUiFile } from "./ui-file-splitter.js";
|
|
31
35
|
const modules = new Map();
|
|
32
36
|
const lowered = new Map();
|
|
@@ -70,11 +74,23 @@ export function loadUIModuleFromText(htmlPath, htmlText, cssText, cssPathForFont
|
|
|
70
74
|
const tree = parsed.tree;
|
|
71
75
|
const allScreens = parsed.screens;
|
|
72
76
|
const keyboards = parsed.keyboards;
|
|
73
|
-
// Merge <style> blocks from the HTML with the external CSS
|
|
77
|
+
// Merge <style> blocks from the HTML with the external CSS, then inline any
|
|
78
|
+
// local @import statements (shared stylesheets like the shadcn preset).
|
|
74
79
|
const styleBlocks = extractStyleBlocks(htmlText);
|
|
75
|
-
const
|
|
80
|
+
const cssBaseDir = path.dirname(cssPathForFonts);
|
|
81
|
+
// Built-in shadcn kit first, user CSS after: user token blocks and recipe
|
|
82
|
+
// overrides win by cascade order. The kit has no @imports of its own.
|
|
83
|
+
const fullCss = SHADCN_KIT_CSS + "\n" + expandCssImports(cssText + "\n" + styleBlocks, cssBaseDir);
|
|
76
84
|
const rules = parseCss(fullCss, moduleDiagnostics);
|
|
77
|
-
|
|
85
|
+
// Color displays get the bundled DejaVu faces appended (unless the author
|
|
86
|
+
// registered their own face under the default family) so AA text works out
|
|
87
|
+
// of the box. The UA sheet points at the same family.
|
|
88
|
+
let profileColorFormat;
|
|
89
|
+
try {
|
|
90
|
+
profileColorFormat = getDisplayProfile().colorFormat;
|
|
91
|
+
}
|
|
92
|
+
catch { /* unbound */ }
|
|
93
|
+
const fontFaces = injectDefaultFontFaces(parseFontFaces(fullCss), profileColorFormat);
|
|
78
94
|
const styled = resolveStyles(tree, rules, moduleDiagnostics);
|
|
79
95
|
const allStyledScreens = allScreens.map(s => resolveStyles(s, rules, moduleDiagnostics));
|
|
80
96
|
const fontRoot = { tag: "screen", classes: [], style: {}, children: allStyledScreens };
|
|
@@ -134,6 +150,11 @@ export function lowerOnMount(htmlPath, opts) {
|
|
|
134
150
|
// Layout all screens (each gets its own Yoga layout pass; boxes concatenated).
|
|
135
151
|
let allBoxes = [];
|
|
136
152
|
let allStyled = [];
|
|
153
|
+
// Converted images (<img src="*.png"> etc.) contribute their natural size
|
|
154
|
+
// to layout when the author gave no explicit width/height — must land
|
|
155
|
+
// BEFORE arrange. Served from the warm-up cache primed by the transpile
|
|
156
|
+
// graph collector / preview builder before this module loaded.
|
|
157
|
+
applyDecodedImageSizes(mod.allStyledScreens.length > 0 ? mod.allStyledScreens : [mod.styled], path.dirname(abs));
|
|
137
158
|
for (const screen of mod.allStyledScreens.length > 0 ? mod.allStyledScreens : [mod.styled]) {
|
|
138
159
|
const engine = selectEngine(screen);
|
|
139
160
|
const screenBoxes = engine.arrange(screen, viewport, measureWithFonts(mod.fontAssets));
|
|
@@ -205,6 +226,13 @@ export function lowerOnMount(htmlPath, opts) {
|
|
|
205
226
|
result.diagnostics.push(d);
|
|
206
227
|
mod.mountDiagnostics.push(d);
|
|
207
228
|
}
|
|
229
|
+
// CSS compatibility report: display-anchored scale summary, ignored alpha,
|
|
230
|
+
// stock-font quantization, viewport-share sanity. Ordinary diagnostics —
|
|
231
|
+
// printed with the rest, upgradeable to errors via --strict-css.
|
|
232
|
+
for (const d of cssCompatDiagnostics(allStyled.length > 0 ? allStyled : [mod.styled], mod.fontAssets, opts.viewport, opts.colorFormat, htmlBase)) {
|
|
233
|
+
result.diagnostics.push(d);
|
|
234
|
+
mod.mountDiagnostics.push(d);
|
|
235
|
+
}
|
|
208
236
|
// Emit image tables.
|
|
209
237
|
result.imageTables = emitImageTables(imageAssets.assets, opts.colorFormat);
|
|
210
238
|
lowered.set(abs, result);
|
|
@@ -10,11 +10,13 @@
|
|
|
10
10
|
// matching the StyledNode tree, so lowerUIToCpp's flatten() can index
|
|
11
11
|
// boxes[] in lockstep with the styled nodes.
|
|
12
12
|
// ---------------------------------------------------------------------------
|
|
13
|
-
import { isDisplayNone, parseAspectRatio } from "./layout-engine.js";
|
|
13
|
+
import { isDisplayNone, parseAspectRatio, cssDimValue, cssSizeValue, dimSet, expandBoxShorthand } from "./layout-engine.js";
|
|
14
14
|
import Yoga from "yoga-layout";
|
|
15
15
|
/** Parse the first numeric value from a CSS string ("8px", "8px 16px" → 8). */
|
|
16
16
|
/** Parse a single CSS length to device pixels.
|
|
17
|
-
* rem/em × 16 (root font size), px/bare as-is, decimals supported.
|
|
17
|
+
* rem/em × 16 (root font size), px/bare as-is, decimals supported.
|
|
18
|
+
* Percentages and "auto" fold to numbers here — use cssDimValue() for the
|
|
19
|
+
* properties (sizes, margins, paddings, insets) where they are meaningful. */
|
|
18
20
|
function cssLength(val) {
|
|
19
21
|
const v = val.trim();
|
|
20
22
|
const remM = /^(-?[\d.]+)rem$/.exec(v);
|
|
@@ -31,22 +33,6 @@ function cssNum(val) {
|
|
|
31
33
|
return 0;
|
|
32
34
|
return cssLength(val);
|
|
33
35
|
}
|
|
34
|
-
/** Parse horizontal padding from shorthand ("8px 16px" → 16 for left/right). */
|
|
35
|
-
function cssPadH(val) {
|
|
36
|
-
if (!val)
|
|
37
|
-
return 0;
|
|
38
|
-
const parts = val.trim().split(/\s+/);
|
|
39
|
-
if (parts.length <= 1)
|
|
40
|
-
return cssLength(parts[0] ?? "0");
|
|
41
|
-
return cssLength(parts[1] ?? parts[0]);
|
|
42
|
-
}
|
|
43
|
-
/** Parse vertical padding from shorthand ("8px 16px" → 8 for top/bottom). */
|
|
44
|
-
function cssPadV(val) {
|
|
45
|
-
if (!val)
|
|
46
|
-
return 0;
|
|
47
|
-
const parts = val.trim().split(/\s+/);
|
|
48
|
-
return cssLength(parts[0] ?? "0");
|
|
49
|
-
}
|
|
50
36
|
/** Parse border width from "2px solid #808080" → 2. */
|
|
51
37
|
function cssBorderWidth(val) {
|
|
52
38
|
if (!val)
|
|
@@ -92,28 +78,43 @@ export class YogaLayoutEngine {
|
|
|
92
78
|
yn.setFlexDirection(Yoga.FLEX_DIRECTION_COLUMN_REVERSE);
|
|
93
79
|
else
|
|
94
80
|
yn.setFlexDirection(Yoga.FLEX_DIRECTION_COLUMN);
|
|
95
|
-
// Padding
|
|
96
|
-
|
|
97
|
-
const
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
yn.setPadding(Yoga.
|
|
101
|
-
|
|
102
|
-
yn.setPadding(Yoga.EDGE_RIGHT,
|
|
81
|
+
// Padding: full 1-4 value shorthand (TRBL rotation) with % support.
|
|
82
|
+
// "auto" is not valid padding — fold it to the default 0 via asSize.
|
|
83
|
+
const asSize = (d) => (d === "auto" ? 0 : d);
|
|
84
|
+
const [padT, padR, padB, padL] = expandBoxShorthand(s.padding);
|
|
85
|
+
if (dimSet(padT))
|
|
86
|
+
yn.setPadding(Yoga.EDGE_TOP, asSize(padT));
|
|
87
|
+
if (dimSet(padR))
|
|
88
|
+
yn.setPadding(Yoga.EDGE_RIGHT, asSize(padR));
|
|
89
|
+
if (dimSet(padB))
|
|
90
|
+
yn.setPadding(Yoga.EDGE_BOTTOM, asSize(padB));
|
|
91
|
+
if (dimSet(padL))
|
|
92
|
+
yn.setPadding(Yoga.EDGE_LEFT, asSize(padL));
|
|
93
|
+
// Scroll containers draw an overlay scrollbar at the right edge (4px
|
|
94
|
+
// strip: box.x + box.w - 4). Reserve the strip plus a 4px breathing gap
|
|
95
|
+
// as right padding so stretched children (align-items: stretch selects,
|
|
96
|
+
// cards) never lay out or paint against/under the scrollbar — browser
|
|
97
|
+
// behavior for classic scrollbars, which take layout space.
|
|
98
|
+
if (s.overflow === "scroll") {
|
|
99
|
+
const basePad = typeof padR === "number" ? padR : 0;
|
|
100
|
+
yn.setPadding(Yoga.EDGE_RIGHT, basePad + 8);
|
|
103
101
|
}
|
|
104
|
-
// Margin: per-side
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
const
|
|
108
|
-
const
|
|
109
|
-
const
|
|
110
|
-
const
|
|
111
|
-
|
|
102
|
+
// Margin: per-side longhands win over the `margin` shorthand when set.
|
|
103
|
+
// Full 1-4 value shorthand expansion; percentages resolve against the
|
|
104
|
+
// parent, and "auto" margins absorb free space (margin: 0 auto centers).
|
|
105
|
+
const [mgT, mgR, mgB, mgL] = expandBoxShorthand(s.margin);
|
|
106
|
+
const mTop = s.marginTop !== undefined ? cssDimValue(s.marginTop) : mgT;
|
|
107
|
+
const mBottom = s.marginBottom !== undefined ? cssDimValue(s.marginBottom) : mgB;
|
|
108
|
+
const mLeft = s.marginLeft !== undefined ? cssDimValue(s.marginLeft) : mgL;
|
|
109
|
+
const mRight = s.marginRight !== undefined ? cssDimValue(s.marginRight) : mgR;
|
|
110
|
+
if (dimSet(mTop))
|
|
112
111
|
yn.setMargin(Yoga.EDGE_TOP, mTop);
|
|
112
|
+
if (dimSet(mBottom))
|
|
113
113
|
yn.setMargin(Yoga.EDGE_BOTTOM, mBottom);
|
|
114
|
+
if (dimSet(mLeft))
|
|
114
115
|
yn.setMargin(Yoga.EDGE_LEFT, mLeft);
|
|
116
|
+
if (dimSet(mRight))
|
|
115
117
|
yn.setMargin(Yoga.EDGE_RIGHT, mRight);
|
|
116
|
-
}
|
|
117
118
|
// Gap: row-gap / column-gap are applied per-axis. Uniform `gap`
|
|
118
119
|
// (both equal) uses GUTTER_ALL for efficiency; mismatched values
|
|
119
120
|
// set GUTTER_ROW and GUTTER_COLUMN separately.
|
|
@@ -199,24 +200,23 @@ export class YogaLayoutEngine {
|
|
|
199
200
|
if (s.flexShrink)
|
|
200
201
|
yn.setFlexShrink(cssNum(s.flexShrink));
|
|
201
202
|
if (s.flexBasis) {
|
|
202
|
-
const basis = cssNum(s.flexBasis);
|
|
203
203
|
if (s.flexBasis === "auto")
|
|
204
204
|
yn.setFlexBasisAuto();
|
|
205
205
|
else
|
|
206
|
-
yn.setFlexBasis(
|
|
206
|
+
yn.setFlexBasis(cssSizeValue(s.flexBasis));
|
|
207
207
|
}
|
|
208
208
|
// Order (Yoga may not expose setOrder in its types, but the runtime has it)
|
|
209
209
|
if (s.order)
|
|
210
210
|
yn.setOrder?.(cssNum(s.order));
|
|
211
|
-
// Min/max dimensions
|
|
211
|
+
// Min/max dimensions (px or % of the parent)
|
|
212
212
|
if (s.minWidth)
|
|
213
|
-
yn.setMinWidth(
|
|
213
|
+
yn.setMinWidth(cssSizeValue(s.minWidth));
|
|
214
214
|
if (s.maxWidth)
|
|
215
|
-
yn.setMaxWidth(
|
|
215
|
+
yn.setMaxWidth(cssSizeValue(s.maxWidth));
|
|
216
216
|
if (s.minHeight)
|
|
217
|
-
yn.setMinHeight(
|
|
217
|
+
yn.setMinHeight(cssSizeValue(s.minHeight));
|
|
218
218
|
if (s.maxHeight)
|
|
219
|
-
yn.setMaxHeight(
|
|
219
|
+
yn.setMaxHeight(cssSizeValue(s.maxHeight));
|
|
220
220
|
// Box sizing
|
|
221
221
|
if (s.boxSizing === "border-box")
|
|
222
222
|
yn.setBoxSizing?.(Yoga.BOX_SIZING_BORDER_BOX);
|
|
@@ -233,18 +233,18 @@ export class YogaLayoutEngine {
|
|
|
233
233
|
else
|
|
234
234
|
yn.setPositionType(Yoga.POSITION_TYPE_STATIC);
|
|
235
235
|
if (s.top !== undefined)
|
|
236
|
-
yn.setPosition(Yoga.EDGE_TOP,
|
|
236
|
+
yn.setPosition(Yoga.EDGE_TOP, cssSizeValue(s.top));
|
|
237
237
|
if (s.right !== undefined)
|
|
238
|
-
yn.setPosition(Yoga.EDGE_RIGHT,
|
|
238
|
+
yn.setPosition(Yoga.EDGE_RIGHT, cssSizeValue(s.right));
|
|
239
239
|
if (s.bottom !== undefined)
|
|
240
|
-
yn.setPosition(Yoga.EDGE_BOTTOM,
|
|
240
|
+
yn.setPosition(Yoga.EDGE_BOTTOM, cssSizeValue(s.bottom));
|
|
241
241
|
if (s.left !== undefined)
|
|
242
|
-
yn.setPosition(Yoga.EDGE_LEFT,
|
|
243
|
-
// Width/height (explicit)
|
|
242
|
+
yn.setPosition(Yoga.EDGE_LEFT, cssSizeValue(s.left));
|
|
243
|
+
// Width/height (explicit; px or % of the parent)
|
|
244
244
|
if (s.width)
|
|
245
|
-
yn.setWidth(
|
|
245
|
+
yn.setWidth(cssSizeValue(s.width));
|
|
246
246
|
if (s.height)
|
|
247
|
-
yn.setHeight(
|
|
247
|
+
yn.setHeight(cssSizeValue(s.height));
|
|
248
248
|
const aspectRatio = parseAspectRatio(s.aspectRatio);
|
|
249
249
|
if (aspectRatio !== undefined)
|
|
250
250
|
yn.setAspectRatio(aspectRatio);
|
|
@@ -271,7 +271,11 @@ export class YogaLayoutEngine {
|
|
|
271
271
|
// Leaf nodes with text: let Yoga pass available width into measurement so
|
|
272
272
|
// wrapped text can expand height under constraints.
|
|
273
273
|
if (node.children.length === 0) {
|
|
274
|
-
|
|
274
|
+
// select is text-like too: its label is the current option, and the
|
|
275
|
+
// generic else-branch sizes the width without the UA control padding —
|
|
276
|
+
// a wider option ("Gamma" vs "Alpha" in a proportional font) then
|
|
277
|
+
// overflows the box and wraps mid-word.
|
|
278
|
+
const textLike = node.tag === "text" || node.tag === "button" || node.tag === "check" || node.tag === "radio" || node.tag === "select";
|
|
275
279
|
if (textLike) {
|
|
276
280
|
yn.setMeasureFunc((width, widthMode) => {
|
|
277
281
|
const hasWidth = widthMode !== Yoga.MEASURE_MODE_UNDEFINED && Number.isFinite(width) && width > 0;
|
|
@@ -284,8 +288,9 @@ export class YogaLayoutEngine {
|
|
|
284
288
|
}
|
|
285
289
|
else {
|
|
286
290
|
const intrinsic = measureFn(node);
|
|
287
|
-
const
|
|
288
|
-
const
|
|
291
|
+
const [padTop, , , padLeft] = expandBoxShorthand(s.padding);
|
|
292
|
+
const childPadV = typeof padTop === "number" ? padTop : 0;
|
|
293
|
+
const childPadH = typeof padLeft === "number" ? padLeft : 0;
|
|
289
294
|
const childBorder = cssBorderWidth(s.border);
|
|
290
295
|
if (!s.width && intrinsic.w > 0) {
|
|
291
296
|
yn.setWidth(Math.ceil(intrinsic.w + childPadH * 2 + childBorder * 2));
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@typecad/ui",
|
|
3
|
-
"version": "1.0.0-alpha.
|
|
3
|
+
"version": "1.0.0-alpha.13",
|
|
4
4
|
"description": "TypeCAD UI authoring library — HTML/CSS-driven graphics for microcontrollers",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "./dist/index.js",
|
|
@@ -25,6 +25,9 @@
|
|
|
25
25
|
"types": "./dist/ui-engine/*.d.ts",
|
|
26
26
|
"default": "./dist/ui-engine/*.js"
|
|
27
27
|
},
|
|
28
|
+
"./themes/*": {
|
|
29
|
+
"default": "./themes/*"
|
|
30
|
+
},
|
|
28
31
|
"./preview/*": {
|
|
29
32
|
"types": "./dist/preview/*.d.ts",
|
|
30
33
|
"default": "./dist/preview/*.js"
|
|
@@ -37,14 +40,17 @@
|
|
|
37
40
|
"sideEffects": false,
|
|
38
41
|
"files": [
|
|
39
42
|
"dist",
|
|
40
|
-
"src"
|
|
43
|
+
"src",
|
|
44
|
+
"assets"
|
|
41
45
|
],
|
|
42
46
|
"dependencies": {
|
|
43
|
-
"@typecad/cuttlefish": "1.0.0-alpha.
|
|
47
|
+
"@typecad/cuttlefish": "1.0.0-alpha.13",
|
|
44
48
|
"chalk": "^4.1.2",
|
|
45
49
|
"css-tree": "^3.2.1",
|
|
50
|
+
"decode-ico": "^0.4.1",
|
|
46
51
|
"linkedom": "^0.18.12",
|
|
47
52
|
"opentype.js": "^2.0.0",
|
|
53
|
+
"sharp": "^0.35.3",
|
|
48
54
|
"typescript": "^5.7.3",
|
|
49
55
|
"yoga-layout": "^3.2.1"
|
|
50
56
|
},
|