@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,83 @@
|
|
|
1
|
+
import type { UIProgram } from "../ui-engine/model.js";
|
|
2
|
+
import type { CSSRule } from "../ui-engine/css-parser.js";
|
|
3
|
+
import type { KeyboardTemplate } from "../ui-engine/html-parser.js";
|
|
4
|
+
export interface PreviewBindingSpec {
|
|
5
|
+
nodeId: string;
|
|
6
|
+
nodeIndex: number;
|
|
7
|
+
property: string;
|
|
8
|
+
expression: string;
|
|
9
|
+
}
|
|
10
|
+
export interface PreviewCallbackSpec {
|
|
11
|
+
nodeId: string;
|
|
12
|
+
nodeIndex: number;
|
|
13
|
+
kind: "click" | "hold" | "release" | "change" | "rangechange";
|
|
14
|
+
body: string;
|
|
15
|
+
/** For ui.bindInput callbacks: the parameter name that receives the input's
|
|
16
|
+
* committed text (the runtime renames the arrow's first param to `text`).
|
|
17
|
+
* When set, the dispatch path binds the committed string to this name as a
|
|
18
|
+
* local before running the body. */
|
|
19
|
+
param?: string;
|
|
20
|
+
}
|
|
21
|
+
export interface PreviewInitialAssignment {
|
|
22
|
+
nodeId: string;
|
|
23
|
+
nodeIndex: number;
|
|
24
|
+
expression: string;
|
|
25
|
+
}
|
|
26
|
+
export interface PreviewListBindingSpec {
|
|
27
|
+
nodeId: string;
|
|
28
|
+
nodeIndex: number;
|
|
29
|
+
countExpression: string;
|
|
30
|
+
itemExpression: string;
|
|
31
|
+
itemParam?: string;
|
|
32
|
+
tapBody?: string;
|
|
33
|
+
tapParam?: string;
|
|
34
|
+
}
|
|
35
|
+
export interface PreviewIntervalSpec {
|
|
36
|
+
body: string;
|
|
37
|
+
delayMs: number;
|
|
38
|
+
}
|
|
39
|
+
export interface PreviewPinControlSpec {
|
|
40
|
+
label: string;
|
|
41
|
+
kind: "watch" | "toggle" | "change" | "press" | "release";
|
|
42
|
+
pin: string;
|
|
43
|
+
nodeId?: string;
|
|
44
|
+
nodeIndex?: number;
|
|
45
|
+
optionCount?: number;
|
|
46
|
+
body?: string;
|
|
47
|
+
}
|
|
48
|
+
export interface PreviewCanvasBindingSpec {
|
|
49
|
+
nodeId: string;
|
|
50
|
+
nodeIndex: number;
|
|
51
|
+
/** The drawBody source text: `ctx.X(...)` calls the host runtime re-lowers. */
|
|
52
|
+
drawBody: string;
|
|
53
|
+
}
|
|
54
|
+
export interface PreviewModuleVarSpec {
|
|
55
|
+
name: string;
|
|
56
|
+
/** Initializer source text (without the `=`), e.g. "180" for `let x = 180;`. */
|
|
57
|
+
initializer?: string;
|
|
58
|
+
}
|
|
59
|
+
export interface PreviewDiagnostic {
|
|
60
|
+
severity: "info" | "warning" | "error";
|
|
61
|
+
message: string;
|
|
62
|
+
}
|
|
63
|
+
export interface PreviewSnapshot {
|
|
64
|
+
projectRoot: string;
|
|
65
|
+
entryFile: string;
|
|
66
|
+
htmlFile: string;
|
|
67
|
+
profileName?: string;
|
|
68
|
+
program: UIProgram;
|
|
69
|
+
keyboardTemplates: KeyboardTemplate[];
|
|
70
|
+
cssRules: CSSRule[];
|
|
71
|
+
uiTreeNames: string[];
|
|
72
|
+
font: number[];
|
|
73
|
+
bindings: PreviewBindingSpec[];
|
|
74
|
+
listBindings: PreviewListBindingSpec[];
|
|
75
|
+
callbacks: PreviewCallbackSpec[];
|
|
76
|
+
initialAssignments: PreviewInitialAssignment[];
|
|
77
|
+
intervals: PreviewIntervalSpec[];
|
|
78
|
+
pinControls: PreviewPinControlSpec[];
|
|
79
|
+
canvasBindings: PreviewCanvasBindingSpec[];
|
|
80
|
+
/** Top-level `let`/`const`/`var` declarations, hoisted like the device does. */
|
|
81
|
+
moduleVars: PreviewModuleVarSpec[];
|
|
82
|
+
diagnostics: PreviewDiagnostic[];
|
|
83
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
package/dist/types.d.ts
ADDED
|
@@ -0,0 +1,108 @@
|
|
|
1
|
+
/** Base interface: all elements have a readable/writable .value. */
|
|
2
|
+
export interface UIElement {
|
|
3
|
+
/** The element's state. check: 0/1, button: 0/1, select: 0..N, text: number.
|
|
4
|
+
* Reading returns the current state; writing updates the display. */
|
|
5
|
+
value: number;
|
|
6
|
+
/** Fires on short tap (touch down + up within 600ms). */
|
|
7
|
+
onClick(callback?: () => void): void;
|
|
8
|
+
/** Fires on long press (touch held ≥600ms). */
|
|
9
|
+
onHold(callback?: () => void): void;
|
|
10
|
+
/** Fires when finger lifts off the element. */
|
|
11
|
+
onRelease(callback?: () => void): void;
|
|
12
|
+
}
|
|
13
|
+
/** GPIO edge handlers for :pressed transitions. */
|
|
14
|
+
export interface PressBinding {
|
|
15
|
+
onPress(pin: number | string): void;
|
|
16
|
+
onRelease(pin: number | string): void;
|
|
17
|
+
}
|
|
18
|
+
export type TextElement = UIElement & PressBinding & {
|
|
19
|
+
readonly __kind: "text";
|
|
20
|
+
onChange(pin: number, optionCount: number, onChange?: () => void): void;
|
|
21
|
+
};
|
|
22
|
+
export type ButtonElement = UIElement & PressBinding & {
|
|
23
|
+
readonly __kind: "button";
|
|
24
|
+
};
|
|
25
|
+
export type ViewElement = UIElement & {
|
|
26
|
+
readonly __kind: "view";
|
|
27
|
+
onToggle(pin: number, onChange?: () => void): void;
|
|
28
|
+
onChange(pin: number, optionCount: number, onChange?: () => void): void;
|
|
29
|
+
};
|
|
30
|
+
/** A checkbox element. .value is 0 (unchecked) or 1 (checked). */
|
|
31
|
+
export interface CheckElement extends UIElement {
|
|
32
|
+
readonly __kind: "check";
|
|
33
|
+
/** Watch a GPIO pin for falling edges. Flips .value automatically.
|
|
34
|
+
* Optional callback runs after the flip. */
|
|
35
|
+
onToggle(pin: number, onChange?: () => void): void;
|
|
36
|
+
}
|
|
37
|
+
/** A selector element. .value cycles 0..N-1 on tap. Text auto-shows the option. */
|
|
38
|
+
export interface SelectElement extends UIElement {
|
|
39
|
+
readonly __kind: "select";
|
|
40
|
+
}
|
|
41
|
+
/** A radio element. .value is 0 (unselected) or 1 (selected).
|
|
42
|
+
* Radios with the same name attribute are mutually exclusive. */
|
|
43
|
+
export interface RadioElement extends UIElement {
|
|
44
|
+
readonly __kind: "radio";
|
|
45
|
+
}
|
|
46
|
+
/** A progress bar. .value is 0-100 (percentage filled). Read-only display. */
|
|
47
|
+
export interface ProgressElement extends UIElement {
|
|
48
|
+
readonly __kind: "progress";
|
|
49
|
+
}
|
|
50
|
+
/** A slider/range. .value is between min and max (default 0-100).
|
|
51
|
+
* Drag the thumb or write .value to change it. */
|
|
52
|
+
export interface RangeElement extends UIElement {
|
|
53
|
+
readonly __kind: "range";
|
|
54
|
+
/** Fires whenever .value changes during a drag. Read screen.elem.value
|
|
55
|
+
* inside the callback to get the new value. */
|
|
56
|
+
onChange(callback?: () => void): void;
|
|
57
|
+
}
|
|
58
|
+
/** A text input. .text is the string value; tap opens the on-screen keyboard. */
|
|
59
|
+
export interface InputElement extends UIElement {
|
|
60
|
+
readonly __kind: "input";
|
|
61
|
+
/** The current text value. Reading returns the string; writing updates the display. */
|
|
62
|
+
text: string;
|
|
63
|
+
/** Fires when the text changes (after the keyboard commits). */
|
|
64
|
+
onChange(callback?: () => void): void;
|
|
65
|
+
}
|
|
66
|
+
/** Drawing context handed to a ui.drawCanvas callback. Each method maps to a
|
|
67
|
+
* display graphics primitive; coordinates are canvas-relative ((0,0) = element
|
|
68
|
+
* top-left) and drawing is auto-clipped to the buffer. Color args are CSS color
|
|
69
|
+
* strings resolved to RGB565 at transpile time. */
|
|
70
|
+
export interface CanvasCtx {
|
|
71
|
+
/** Read-only canvas buffer dimensions (px). */
|
|
72
|
+
readonly width: number;
|
|
73
|
+
readonly height: number;
|
|
74
|
+
drawPixel(x: number, y: number, color: string): void;
|
|
75
|
+
fillRect(x: number, y: number, w: number, h: number, color: string): void;
|
|
76
|
+
rect(x: number, y: number, w: number, h: number, color: string): void;
|
|
77
|
+
fillRoundRect(x: number, y: number, w: number, h: number, r: number, color: string): void;
|
|
78
|
+
roundRect(x: number, y: number, w: number, h: number, r: number, color: string): void;
|
|
79
|
+
line(x0: number, y0: number, x1: number, y1: number, color: string): void;
|
|
80
|
+
hline(x: number, y: number, w: number, color: string): void;
|
|
81
|
+
vline(x: number, y: number, h: number, color: string): void;
|
|
82
|
+
fillCircle(x: number, y: number, r: number, color: string): void;
|
|
83
|
+
circle(x: number, y: number, r: number, color: string): void;
|
|
84
|
+
rgbBitmap(x: number, y: number, data: number[], w: number, h: number): void;
|
|
85
|
+
text(x: number, y: number, str: string, color?: string): void;
|
|
86
|
+
fillScreen(color: string): void;
|
|
87
|
+
}
|
|
88
|
+
/** A user-drawn canvas. Contents are drawn by a ui.drawCanvas callback each
|
|
89
|
+
* frame using the display shim primitives (fillRect, line, circle, text, ...).
|
|
90
|
+
* Coordinates are canvas-relative ((0,0) = top-left); drawing is auto-clipped. */
|
|
91
|
+
export interface CanvasElement extends UIElement {
|
|
92
|
+
readonly __kind: "canvas";
|
|
93
|
+
}
|
|
94
|
+
/**
|
|
95
|
+
* Loose index signature used as the *fallback* type before the transpiler
|
|
96
|
+
* emits the precise per-file `.d.ts`. The cuttlefish transpiler generates a
|
|
97
|
+
* concrete `ScreenTree` for each `.ui.html` file with named fields matching
|
|
98
|
+
* the `id` attributes in the source (e.g. `screen.counter`, `screen.btn`),
|
|
99
|
+
* which is what editor autocomplete and type-checking actually use.
|
|
100
|
+
*
|
|
101
|
+
* Do NOT tighten this to a literal union or remove the index signature —
|
|
102
|
+
* concrete generated interfaces are structurally assignable to this loose
|
|
103
|
+
* shape, and `ui.mount(tree: unknown)` accepts it. The index signature only
|
|
104
|
+
* needs to *permit* the call; the transpiler validates the real structure.
|
|
105
|
+
*/
|
|
106
|
+
export interface ScreenTree {
|
|
107
|
+
[id: string]: TextElement | ButtonElement | ViewElement | CheckElement | SelectElement | RadioElement | ProgressElement | RangeElement | InputElement | CanvasElement;
|
|
108
|
+
}
|
package/dist/types.js
ADDED
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
// ---------------------------------------------------------------------------
|
|
2
|
+
// Element types — the shape of typed `.ui.html` imports.
|
|
3
|
+
//
|
|
4
|
+
// Every interactive element has a `.value` property that is both readable
|
|
5
|
+
// and writable. Writing updates the display; reading returns the current
|
|
6
|
+
// state. Pin input (onToggle/onPress/onChange) auto-updates `.value`.
|
|
7
|
+
//
|
|
8
|
+
// The press/release methods are for the :pressed transition state.
|
|
9
|
+
// ---------------------------------------------------------------------------
|
|
10
|
+
export {};
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
import { Box, IntrinsicSize, LayoutEngine } from "./layout-engine.js";
|
|
2
|
+
import { StyledNode } from "./style-resolver.js";
|
|
3
|
+
export declare class BlockLayoutEngine implements LayoutEngine {
|
|
4
|
+
readonly id: "block";
|
|
5
|
+
arrange(root: StyledNode, viewport: Box, measureFn: (n: StyledNode, availableWidth?: number) => IntrinsicSize): Box[];
|
|
6
|
+
private layoutNode;
|
|
7
|
+
private layoutHiddenSubtree;
|
|
8
|
+
}
|
|
@@ -0,0 +1,95 @@
|
|
|
1
|
+
// ---------------------------------------------------------------------------
|
|
2
|
+
// BlockLayoutEngine — v1 fallback layout. Stacks children vertically inside the
|
|
3
|
+
// parent's content box (parent box minus padding). Each child's width fills
|
|
4
|
+
// the content box; height comes from measure() for text/button leaves, or a
|
|
5
|
+
// synthesized height for containers.
|
|
6
|
+
//
|
|
7
|
+
// This is the fallback when no `display: flex` is present. YogaLayoutEngine
|
|
8
|
+
// handles flexbox layouts.
|
|
9
|
+
// ---------------------------------------------------------------------------
|
|
10
|
+
import { isDisplayNone, parseAspectRatio } from "./layout-engine.js";
|
|
11
|
+
/** Parse a CSS value string ("8px", "8") to a number. */
|
|
12
|
+
function cssNum(val) {
|
|
13
|
+
if (val === undefined)
|
|
14
|
+
return 0;
|
|
15
|
+
if (typeof val === "number")
|
|
16
|
+
return val;
|
|
17
|
+
const m = val.match(/(\d+)/);
|
|
18
|
+
return m ? parseInt(m[1]) : 0;
|
|
19
|
+
}
|
|
20
|
+
export class BlockLayoutEngine {
|
|
21
|
+
id = "block";
|
|
22
|
+
arrange(root, viewport, measureFn) {
|
|
23
|
+
const boxes = [];
|
|
24
|
+
this.layoutNode(root, viewport, boxes, measureFn);
|
|
25
|
+
return boxes;
|
|
26
|
+
}
|
|
27
|
+
layoutNode(node, box, out, measureFn) {
|
|
28
|
+
if (isDisplayNone(node)) {
|
|
29
|
+
this.layoutHiddenSubtree(node, out);
|
|
30
|
+
return;
|
|
31
|
+
}
|
|
32
|
+
out.push(box);
|
|
33
|
+
if (node.children.length === 0)
|
|
34
|
+
return;
|
|
35
|
+
const pad = cssNum(node.style.padding);
|
|
36
|
+
const content = {
|
|
37
|
+
x: box.x + pad,
|
|
38
|
+
y: box.y + pad,
|
|
39
|
+
w: box.w - pad * 2,
|
|
40
|
+
h: box.h - pad * 2,
|
|
41
|
+
};
|
|
42
|
+
let cursorY = content.y;
|
|
43
|
+
for (const child of node.children) {
|
|
44
|
+
if (isDisplayNone(child)) {
|
|
45
|
+
this.layoutHiddenSubtree(child, out);
|
|
46
|
+
continue;
|
|
47
|
+
}
|
|
48
|
+
// Buttons size to their content (text + padding), not the full
|
|
49
|
+
// container width. Other elements fill the content width (block flow).
|
|
50
|
+
const childPad = cssNum(child.style.padding);
|
|
51
|
+
const isButton = child.tag === "button";
|
|
52
|
+
const measureWidth = isButton ? undefined : Math.max(0, content.w - childPad * 2);
|
|
53
|
+
const intrinsic = measureFn(child, measureWidth);
|
|
54
|
+
const childW = isButton && intrinsic.w > 0
|
|
55
|
+
? intrinsic.w + childPad * 2
|
|
56
|
+
: content.w;
|
|
57
|
+
const childH = intrinsic.h > 0 ? intrinsic.h + (isButton ? childPad * 2 : 0) : 16;
|
|
58
|
+
const explicitW = cssNum(child.style.width);
|
|
59
|
+
const explicitH = cssNum(child.style.height);
|
|
60
|
+
const aspectRatio = parseAspectRatio(child.style.aspectRatio);
|
|
61
|
+
let resolvedW = childW;
|
|
62
|
+
let resolvedH = childH;
|
|
63
|
+
if (aspectRatio !== undefined) {
|
|
64
|
+
if (explicitW > 0 && explicitH <= 0) {
|
|
65
|
+
resolvedW = explicitW;
|
|
66
|
+
resolvedH = Math.round(resolvedW / aspectRatio);
|
|
67
|
+
}
|
|
68
|
+
else if (explicitH > 0 && explicitW <= 0) {
|
|
69
|
+
resolvedH = explicitH;
|
|
70
|
+
resolvedW = Math.round(resolvedH * aspectRatio);
|
|
71
|
+
}
|
|
72
|
+
else if (explicitW > 0 && explicitH > 0) {
|
|
73
|
+
resolvedW = explicitW;
|
|
74
|
+
resolvedH = explicitH;
|
|
75
|
+
}
|
|
76
|
+
else if (!isButton) {
|
|
77
|
+
resolvedH = Math.round(resolvedW / aspectRatio);
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
const childBox = {
|
|
81
|
+
x: content.x,
|
|
82
|
+
y: cursorY,
|
|
83
|
+
w: resolvedW,
|
|
84
|
+
h: resolvedH,
|
|
85
|
+
};
|
|
86
|
+
this.layoutNode(child, childBox, out, measureFn);
|
|
87
|
+
cursorY += childBox.h;
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
layoutHiddenSubtree(node, out) {
|
|
91
|
+
out.push({ x: 0, y: 0, w: 0, h: 0 });
|
|
92
|
+
for (const child of node.children)
|
|
93
|
+
this.layoutHiddenSubtree(child, out);
|
|
94
|
+
}
|
|
95
|
+
}
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
export interface RGB {
|
|
2
|
+
r: number;
|
|
3
|
+
g: number;
|
|
4
|
+
b: number;
|
|
5
|
+
}
|
|
6
|
+
/** Parse any CSS color string to {r,g,b}. Alpha is ignored (no blending). */
|
|
7
|
+
export declare function parseColor(input: string): RGB;
|
|
8
|
+
/** @deprecated Use parseColor instead — parseHexColor only handles #rrggbb. */
|
|
9
|
+
export declare function parseHexColor(hex: string): RGB;
|
|
10
|
+
export declare function toRGB565(r: number, g: number, b: number): number;
|
|
11
|
+
export declare function toMono(r: number, g: number, b: number): 0 | 1;
|
|
12
|
+
/** Pack 8-bit channels into a uint32 RGB888 value (R<<16 | G<<8 | B). */
|
|
13
|
+
export declare function pack888(r: number, g: number, b: number): number;
|
|
14
|
+
/** Unpack a uint32 RGB888 value into 8-bit channels. */
|
|
15
|
+
export declare function unpack888(c: number): {
|
|
16
|
+
r: number;
|
|
17
|
+
g: number;
|
|
18
|
+
b: number;
|
|
19
|
+
};
|
|
20
|
+
/** Quantize RGB888 → RGB565 (uint16). Channel math identical to toRGB565. */
|
|
21
|
+
export declare function rgb888To565(c: number): number;
|
|
22
|
+
/** Quantize RGB888 → RGB666 (uint18 packed in lower 18 bits). */
|
|
23
|
+
export declare function rgb888To666(c: number): number;
|
|
24
|
+
/** Quantize RGB888 → 1-bit mono via luminance threshold (matches toMono). */
|
|
25
|
+
export declare function rgb888ToMono(c: number): 0 | 1;
|
|
26
|
+
/**
|
|
27
|
+
* Snap RGB888 to the nearest ink in a palette. Palette entries are RGB888 values.
|
|
28
|
+
* Used by e-ink palette shims (Phase 4); included so the quantizer set is complete.
|
|
29
|
+
*/
|
|
30
|
+
export declare function rgb888ToNearest(c: number, palette888: number[]): number;
|
|
31
|
+
/**
|
|
32
|
+
* Resolve any CSS color string to packed RGB888 (uint32, R<<16 | G<<8 | B).
|
|
33
|
+
* Canonical color resolution for the display-agnostic core. Alpha is ignored
|
|
34
|
+
* (no blending), matching parseColor semantics.
|
|
35
|
+
*/
|
|
36
|
+
export declare function resolveColor888(input: string): number;
|
|
37
|
+
/**
|
|
38
|
+
* Resolve a CSS color string and quantize to a target format. Backwards-
|
|
39
|
+
* compatible wrapper over resolveColor888 + quantizer. Existing call sites
|
|
40
|
+
* keep their behavior (565/mono output unchanged).
|
|
41
|
+
*/
|
|
42
|
+
export declare function resolveColor(input: string, format: "rgb565" | "rgb666" | "rgb888" | "mono"): number;
|
|
43
|
+
/**
|
|
44
|
+
* Resolve a CSS color to the INTERNAL representation value stored in node
|
|
45
|
+
* fields. For rgb666 targets this is RGB888 (666 quantization happens at the
|
|
46
|
+
* push boundary so blends keep full precision); for rgb565 it is RGB565
|
|
47
|
+
* (byte-identical with pre-Phase-3 behavior); for mono it is 0/1. The value
|
|
48
|
+
* depth and the runtime blend math switch together (see UI_COLOR_DEPTH). */
|
|
49
|
+
export declare function resolveColorInternal(input: string, format: "rgb565" | "rgb666" | "rgb888" | "mono"): number;
|
|
@@ -0,0 +1,273 @@
|
|
|
1
|
+
// ---------------------------------------------------------------------------
|
|
2
|
+
// Color resolution — CSS color formats → target color format (RGB565 / mono)
|
|
3
|
+
//
|
|
4
|
+
// Supports: #rrggbb, #rgb, #rrggbbaa, rgb(r,g,b), rgba(r,g,b,a),
|
|
5
|
+
// named colors (red, white, transparent, ...), transparent
|
|
6
|
+
//
|
|
7
|
+
// Resolved ONCE at transpile time against the framework's colorFormat(), so the
|
|
8
|
+
// device never performs color conversion. Alpha is ignored (no blending on
|
|
9
|
+
// bare metal) — rgba/transparent resolve to the RGB value as-is.
|
|
10
|
+
// ---------------------------------------------------------------------------
|
|
11
|
+
// CSS named colors (the 147 from the CSS Color Module Level 4 spec, excluding
|
|
12
|
+
// CSS4 additions). Each maps to {r,g,b}.
|
|
13
|
+
const NAMED_COLORS = {
|
|
14
|
+
aliceblue: { r: 240, g: 248, b: 255 }, antiquewhite: { r: 250, g: 235, b: 215 },
|
|
15
|
+
aqua: { r: 0, g: 255, b: 255 }, aquamarine: { r: 127, g: 255, b: 212 },
|
|
16
|
+
azure: { r: 240, g: 255, b: 255 }, beige: { r: 245, g: 245, b: 220 },
|
|
17
|
+
bisque: { r: 255, g: 228, b: 196 }, black: { r: 0, g: 0, b: 0 },
|
|
18
|
+
blanchedalmond: { r: 255, g: 235, b: 205 }, blue: { r: 0, g: 0, b: 255 },
|
|
19
|
+
blueviolet: { r: 138, g: 43, b: 226 }, brown: { r: 165, g: 42, b: 42 },
|
|
20
|
+
burlywood: { r: 222, g: 184, b: 135 }, cadetblue: { r: 95, g: 158, b: 160 },
|
|
21
|
+
chartreuse: { r: 127, g: 255, b: 0 }, chocolate: { r: 210, g: 105, b: 30 },
|
|
22
|
+
coral: { r: 255, g: 127, b: 80 }, cornflowerblue: { r: 100, g: 149, b: 237 },
|
|
23
|
+
cornsilk: { r: 255, g: 248, b: 220 }, crimson: { r: 220, g: 20, b: 60 },
|
|
24
|
+
cyan: { r: 0, g: 255, b: 255 }, darkblue: { r: 0, g: 0, b: 139 },
|
|
25
|
+
darkcyan: { r: 0, g: 139, b: 139 }, darkgoldenrod: { r: 184, g: 134, b: 11 },
|
|
26
|
+
darkgray: { r: 169, g: 169, b: 169 }, darkgrey: { r: 169, g: 169, b: 169 },
|
|
27
|
+
darkgreen: { r: 0, g: 100, b: 0 }, darkkhaki: { r: 189, g: 183, b: 107 },
|
|
28
|
+
darkmagenta: { r: 139, g: 0, b: 139 }, darkolivegreen: { r: 85, g: 107, b: 47 },
|
|
29
|
+
darkorange: { r: 255, g: 140, b: 0 }, darkorchid: { r: 153, g: 50, b: 204 },
|
|
30
|
+
darkred: { r: 139, g: 0, b: 0 }, darksalmon: { r: 233, g: 150, b: 122 },
|
|
31
|
+
darkseagreen: { r: 143, g: 188, b: 143 }, darkslateblue: { r: 72, g: 61, b: 139 },
|
|
32
|
+
darkslategray: { r: 47, g: 79, b: 79 }, darkslategrey: { r: 47, g: 79, b: 79 },
|
|
33
|
+
darkturquoise: { r: 0, g: 206, b: 209 }, darkviolet: { r: 148, g: 0, b: 211 },
|
|
34
|
+
deeppink: { r: 255, g: 20, b: 147 }, deepskyblue: { r: 0, g: 191, b: 255 },
|
|
35
|
+
dimgray: { r: 105, g: 105, b: 105 }, dimgrey: { r: 105, g: 105, b: 105 },
|
|
36
|
+
dodgerblue: { r: 30, g: 144, b: 255 }, firebrick: { r: 178, g: 34, b: 34 },
|
|
37
|
+
floralwhite: { r: 255, g: 250, b: 240 }, forestgreen: { r: 34, g: 139, b: 34 },
|
|
38
|
+
fuchsia: { r: 255, g: 0, b: 255 }, gainsboro: { r: 220, g: 220, b: 220 },
|
|
39
|
+
ghostwhite: { r: 248, g: 248, b: 255 }, gold: { r: 255, g: 215, b: 0 },
|
|
40
|
+
goldenrod: { r: 218, g: 165, b: 32 }, gray: { r: 128, g: 128, b: 128 },
|
|
41
|
+
grey: { r: 128, g: 128, b: 128 }, green: { r: 0, g: 128, b: 0 },
|
|
42
|
+
greenyellow: { r: 173, g: 255, b: 47 }, honeydew: { r: 240, g: 255, b: 240 },
|
|
43
|
+
hotpink: { r: 255, g: 105, b: 180 }, indianred: { r: 205, g: 92, b: 92 },
|
|
44
|
+
indigo: { r: 75, g: 0, b: 130 }, ivory: { r: 255, g: 255, b: 240 },
|
|
45
|
+
khaki: { r: 240, g: 230, b: 140 }, lavender: { r: 230, g: 230, b: 250 },
|
|
46
|
+
lavenderblush: { r: 255, g: 240, b: 245 }, lawngreen: { r: 124, g: 252, b: 0 },
|
|
47
|
+
lemonchiffon: { r: 255, g: 250, b: 205 }, lightblue: { r: 173, g: 216, b: 230 },
|
|
48
|
+
lightcoral: { r: 240, g: 128, b: 128 }, lightcyan: { r: 224, g: 255, b: 255 },
|
|
49
|
+
lightgoldenrodyellow: { r: 250, g: 250, b: 210 }, lightgray: { r: 211, g: 211, b: 211 },
|
|
50
|
+
lightgrey: { r: 211, g: 211, b: 211 }, lightgreen: { r: 144, g: 238, b: 144 },
|
|
51
|
+
lightpink: { r: 255, g: 182, b: 193 }, lightsalmon: { r: 255, g: 160, b: 122 },
|
|
52
|
+
lightseagreen: { r: 32, g: 178, b: 170 }, lightskyblue: { r: 135, g: 206, b: 250 },
|
|
53
|
+
lightslategray: { r: 119, g: 136, b: 153 }, lightslategrey: { r: 119, g: 136, b: 153 },
|
|
54
|
+
lightsteelblue: { r: 176, g: 196, b: 222 }, lightyellow: { r: 255, g: 255, b: 224 },
|
|
55
|
+
lime: { r: 0, g: 255, b: 0 }, limegreen: { r: 50, g: 205, b: 50 },
|
|
56
|
+
linen: { r: 250, g: 240, b: 230 }, magenta: { r: 255, g: 0, b: 255 },
|
|
57
|
+
maroon: { r: 128, g: 0, b: 0 }, mediumaquamarine: { r: 102, g: 205, b: 170 },
|
|
58
|
+
mediumblue: { r: 0, g: 0, b: 205 }, mediumorchid: { r: 186, g: 85, b: 211 },
|
|
59
|
+
mediumpurple: { r: 147, g: 112, b: 219 }, mediumseagreen: { r: 60, g: 179, b: 113 },
|
|
60
|
+
mediumslateblue: { r: 123, g: 104, b: 238 }, mediumspringgreen: { r: 0, g: 250, b: 154 },
|
|
61
|
+
mediumturquoise: { r: 72, g: 209, b: 204 }, mediumvioletred: { r: 199, g: 21, b: 133 },
|
|
62
|
+
midnightblue: { r: 25, g: 25, b: 112 }, mintcream: { r: 245, g: 255, b: 250 },
|
|
63
|
+
mistyrose: { r: 255, g: 228, b: 225 }, moccasin: { r: 255, g: 228, b: 181 },
|
|
64
|
+
navajowhite: { r: 255, g: 222, b: 173 }, navy: { r: 0, g: 0, b: 128 },
|
|
65
|
+
oldlace: { r: 253, g: 245, b: 230 }, olive: { r: 128, g: 128, b: 0 },
|
|
66
|
+
olivedrab: { r: 107, g: 142, b: 35 }, orange: { r: 255, g: 165, b: 0 },
|
|
67
|
+
orangered: { r: 255, g: 69, b: 0 }, orchid: { r: 218, g: 112, b: 214 },
|
|
68
|
+
palegoldenrod: { r: 238, g: 232, b: 170 }, palegreen: { r: 152, g: 251, b: 152 },
|
|
69
|
+
paleturquoise: { r: 175, g: 238, b: 238 }, palevioletred: { r: 219, g: 112, b: 147 },
|
|
70
|
+
papayawhip: { r: 255, g: 239, b: 213 }, peachpuff: { r: 255, g: 218, b: 185 },
|
|
71
|
+
peru: { r: 205, g: 133, b: 63 }, pink: { r: 255, g: 192, b: 203 },
|
|
72
|
+
plum: { r: 221, g: 160, b: 221 }, powderblue: { r: 176, g: 224, b: 230 },
|
|
73
|
+
purple: { r: 128, g: 0, b: 128 }, rebeccapurple: { r: 102, g: 51, b: 153 },
|
|
74
|
+
red: { r: 255, g: 0, b: 0 }, rosybrown: { r: 188, g: 143, b: 143 },
|
|
75
|
+
royalblue: { r: 65, g: 105, b: 225 }, saddlebrown: { r: 139, g: 69, b: 19 },
|
|
76
|
+
salmon: { r: 250, g: 128, b: 114 }, sandybrown: { r: 244, g: 164, b: 96 },
|
|
77
|
+
seagreen: { r: 46, g: 139, b: 87 }, seashell: { r: 255, g: 245, b: 238 },
|
|
78
|
+
sienna: { r: 160, g: 82, b: 45 }, silver: { r: 192, g: 192, b: 192 },
|
|
79
|
+
skyblue: { r: 135, g: 206, b: 235 }, slateblue: { r: 106, g: 90, b: 205 },
|
|
80
|
+
slategray: { r: 112, g: 128, b: 144 }, slategrey: { r: 112, g: 128, b: 144 },
|
|
81
|
+
snow: { r: 255, g: 250, b: 250 }, springgreen: { r: 0, g: 255, b: 127 },
|
|
82
|
+
steelblue: { r: 70, g: 130, b: 180 }, tan: { r: 210, g: 180, b: 140 },
|
|
83
|
+
teal: { r: 0, g: 128, b: 128 }, thistle: { r: 216, g: 191, b: 216 },
|
|
84
|
+
tomato: { r: 255, g: 99, b: 71 }, turquoise: { r: 64, g: 224, b: 208 },
|
|
85
|
+
violet: { r: 238, g: 130, b: 238 }, wheat: { r: 245, g: 222, b: 179 },
|
|
86
|
+
white: { r: 255, g: 255, b: 255 }, whitesmoke: { r: 245, g: 245, b: 245 },
|
|
87
|
+
yellow: { r: 255, g: 255, b: 0 }, yellowgreen: { r: 154, g: 205, b: 50 },
|
|
88
|
+
transparent: { r: 0, g: 0, b: 0 },
|
|
89
|
+
};
|
|
90
|
+
/** Parse any CSS color string to {r,g,b}. Alpha is ignored (no blending). */
|
|
91
|
+
export function parseColor(input) {
|
|
92
|
+
const s = input.trim().toLowerCase();
|
|
93
|
+
// rgb(r, g, b) or rgba(r, g, b, a)
|
|
94
|
+
const rgbM = /^rgba?\(\s*(\d+)\s*,\s*(\d+)\s*,\s*(\d+)(?:\s*,\s*[\d.]+)?\s*\)$/.exec(s);
|
|
95
|
+
if (rgbM)
|
|
96
|
+
return { r: +rgbM[1], g: +rgbM[2], b: +rgbM[3] };
|
|
97
|
+
// rgb(r g b) space-separated (CSS4)
|
|
98
|
+
const rgbSpaceM = /^rgba?\(\s*(\d+)\s+(\d+)\s+(\d+)(?:\s\/\s[\d.]+)?\s*\)$/.exec(s);
|
|
99
|
+
if (rgbSpaceM)
|
|
100
|
+
return { r: +rgbSpaceM[1], g: +rgbSpaceM[2], b: +rgbSpaceM[3] };
|
|
101
|
+
// hsl(h, s%, l%) or hsla(h, s%, l%, a) — comma syntax. Angles accept deg/turn/none.
|
|
102
|
+
const hslCommaM = /^hsla?\(\s*([\d.]+)(?:deg|turn|rad)?\s*,\s*([\d.]+)%\s*,\s*([\d.]+)%(?:\s*,\s*[\d.]+)?\s*\)$/.exec(s);
|
|
103
|
+
if (hslCommaM)
|
|
104
|
+
return hslToRgb(+hslCommaM[1], +hslCommaM[2], +hslCommaM[3]);
|
|
105
|
+
// hsl(h s% l%) or hsla(h s% l% / a) — space syntax (CSS4), with optional slash alpha.
|
|
106
|
+
const hslSpaceM = /^hsla?\(\s*([\d.]+)(?:deg|turn|rad)?\s+([\d.]+)%\s+([\d.]+)%(?:\s*\/\s*[\d.%]+)?\s*\)$/.exec(s);
|
|
107
|
+
if (hslSpaceM) {
|
|
108
|
+
// 'turn' suffix multiplies by 360; bare number or 'deg' is degrees.
|
|
109
|
+
let h = +hslSpaceM[1];
|
|
110
|
+
if (/turn\b/.test(s))
|
|
111
|
+
h = h * 360;
|
|
112
|
+
return hslToRgb(h, +hslSpaceM[2], +hslSpaceM[3]);
|
|
113
|
+
}
|
|
114
|
+
// #rrggbb (6-digit hex)
|
|
115
|
+
const hex6M = /^#([0-9a-f]{6})$/.exec(s);
|
|
116
|
+
if (hex6M) {
|
|
117
|
+
const v = parseInt(hex6M[1], 16);
|
|
118
|
+
return { r: (v >> 16) & 0xff, g: (v >> 8) & 0xff, b: v & 0xff };
|
|
119
|
+
}
|
|
120
|
+
// #rrggbbaa (8-digit hex — alpha ignored)
|
|
121
|
+
const hex8M = /^#([0-9a-f]{8})$/.exec(s);
|
|
122
|
+
if (hex8M) {
|
|
123
|
+
const v = parseInt(hex8M[1].slice(0, 6), 16);
|
|
124
|
+
return { r: (v >> 16) & 0xff, g: (v >> 8) & 0xff, b: v & 0xff };
|
|
125
|
+
}
|
|
126
|
+
// #rgb (3-digit short hex)
|
|
127
|
+
const hex3M = /^#([0-9a-f]{3})$/.exec(s);
|
|
128
|
+
if (hex3M) {
|
|
129
|
+
const h = hex3M[1];
|
|
130
|
+
return {
|
|
131
|
+
r: parseInt(h[0] + h[0], 16),
|
|
132
|
+
g: parseInt(h[1] + h[1], 16),
|
|
133
|
+
b: parseInt(h[2] + h[2], 16),
|
|
134
|
+
};
|
|
135
|
+
}
|
|
136
|
+
// Named colors
|
|
137
|
+
if (NAMED_COLORS[s])
|
|
138
|
+
return NAMED_COLORS[s];
|
|
139
|
+
throw new Error(`Unsupported color format "${input}" — use #hex, rgb(), hsl(), or a named color`);
|
|
140
|
+
}
|
|
141
|
+
/** Convert HSL (h: 0-360, s/l: 0-100) to RGB. Standard CSS algorithm. */
|
|
142
|
+
function hslToRgb(h, s, l) {
|
|
143
|
+
h = ((h % 360) + 360) % 360;
|
|
144
|
+
s = Math.max(0, Math.min(100, s)) / 100;
|
|
145
|
+
l = Math.max(0, Math.min(100, l)) / 100;
|
|
146
|
+
const c = (1 - Math.abs(2 * l - 1)) * s;
|
|
147
|
+
const x = c * (1 - Math.abs(((h / 60) % 2) - 1));
|
|
148
|
+
const m = l - c / 2;
|
|
149
|
+
let r = 0, g = 0, b = 0;
|
|
150
|
+
if (h < 60) {
|
|
151
|
+
r = c;
|
|
152
|
+
g = x;
|
|
153
|
+
b = 0;
|
|
154
|
+
}
|
|
155
|
+
else if (h < 120) {
|
|
156
|
+
r = x;
|
|
157
|
+
g = c;
|
|
158
|
+
b = 0;
|
|
159
|
+
}
|
|
160
|
+
else if (h < 180) {
|
|
161
|
+
r = 0;
|
|
162
|
+
g = c;
|
|
163
|
+
b = x;
|
|
164
|
+
}
|
|
165
|
+
else if (h < 240) {
|
|
166
|
+
r = 0;
|
|
167
|
+
g = x;
|
|
168
|
+
b = c;
|
|
169
|
+
}
|
|
170
|
+
else if (h < 300) {
|
|
171
|
+
r = x;
|
|
172
|
+
g = 0;
|
|
173
|
+
b = c;
|
|
174
|
+
}
|
|
175
|
+
else {
|
|
176
|
+
r = c;
|
|
177
|
+
g = 0;
|
|
178
|
+
b = x;
|
|
179
|
+
}
|
|
180
|
+
return {
|
|
181
|
+
r: Math.round((r + m) * 255),
|
|
182
|
+
g: Math.round((g + m) * 255),
|
|
183
|
+
b: Math.round((b + m) * 255),
|
|
184
|
+
};
|
|
185
|
+
}
|
|
186
|
+
/** @deprecated Use parseColor instead — parseHexColor only handles #rrggbb. */
|
|
187
|
+
export function parseHexColor(hex) {
|
|
188
|
+
return parseColor(hex);
|
|
189
|
+
}
|
|
190
|
+
export function toRGB565(r, g, b) {
|
|
191
|
+
return ((r & 0xf8) << 8) | ((g & 0xfc) << 3) | (b >> 3);
|
|
192
|
+
}
|
|
193
|
+
export function toMono(r, g, b) {
|
|
194
|
+
const lum = (0.299 * r + 0.587 * g + 0.114 * b) / 255;
|
|
195
|
+
return lum >= 0.27 ? 1 : 0;
|
|
196
|
+
}
|
|
197
|
+
/** Pack 8-bit channels into a uint32 RGB888 value (R<<16 | G<<8 | B). */
|
|
198
|
+
export function pack888(r, g, b) {
|
|
199
|
+
return ((r & 0xff) << 16) | ((g & 0xff) << 8) | (b & 0xff);
|
|
200
|
+
}
|
|
201
|
+
/** Unpack a uint32 RGB888 value into 8-bit channels. */
|
|
202
|
+
export function unpack888(c) {
|
|
203
|
+
return { r: (c >> 16) & 0xff, g: (c >> 8) & 0xff, b: c & 0xff };
|
|
204
|
+
}
|
|
205
|
+
/** Quantize RGB888 → RGB565 (uint16). Channel math identical to toRGB565. */
|
|
206
|
+
export function rgb888To565(c) {
|
|
207
|
+
const r = (c >> 16) & 0xff, g = (c >> 8) & 0xff, b = c & 0xff;
|
|
208
|
+
return ((r & 0xf8) << 8) | ((g & 0xfc) << 3) | (b >> 3);
|
|
209
|
+
}
|
|
210
|
+
/** Quantize RGB888 → RGB666 (uint18 packed in lower 18 bits). */
|
|
211
|
+
export function rgb888To666(c) {
|
|
212
|
+
const r = (c >> 16) & 0xff, g = (c >> 8) & 0xff, b = c & 0xff;
|
|
213
|
+
return ((r & 0xfc) << 10) | ((g & 0xfc) << 4) | (b >> 2);
|
|
214
|
+
}
|
|
215
|
+
/** Quantize RGB888 → 1-bit mono via luminance threshold (matches toMono). */
|
|
216
|
+
export function rgb888ToMono(c) {
|
|
217
|
+
const r = (c >> 16) & 0xff, g = (c >> 8) & 0xff, b = c & 0xff;
|
|
218
|
+
return toMono(r, g, b);
|
|
219
|
+
}
|
|
220
|
+
/**
|
|
221
|
+
* Snap RGB888 to the nearest ink in a palette. Palette entries are RGB888 values.
|
|
222
|
+
* Used by e-ink palette shims (Phase 4); included so the quantizer set is complete.
|
|
223
|
+
*/
|
|
224
|
+
export function rgb888ToNearest(c, palette888) {
|
|
225
|
+
const r1 = (c >> 16) & 0xff, g1 = (c >> 8) & 0xff, b1 = c & 0xff;
|
|
226
|
+
let best = palette888[0];
|
|
227
|
+
let bestD = Infinity;
|
|
228
|
+
for (const ink of palette888) {
|
|
229
|
+
const dr = (ink >> 16) & 0xff, dg = (ink >> 8) & 0xff, db = ink & 0xff;
|
|
230
|
+
const dr2 = r1 - dr, dg2 = g1 - dg, db2 = b1 - db;
|
|
231
|
+
const d = dr2 * dr2 + dg2 * dg2 + db2 * db2;
|
|
232
|
+
if (d < bestD) {
|
|
233
|
+
bestD = d;
|
|
234
|
+
best = ink;
|
|
235
|
+
}
|
|
236
|
+
}
|
|
237
|
+
return best;
|
|
238
|
+
}
|
|
239
|
+
/**
|
|
240
|
+
* Resolve any CSS color string to packed RGB888 (uint32, R<<16 | G<<8 | B).
|
|
241
|
+
* Canonical color resolution for the display-agnostic core. Alpha is ignored
|
|
242
|
+
* (no blending), matching parseColor semantics.
|
|
243
|
+
*/
|
|
244
|
+
export function resolveColor888(input) {
|
|
245
|
+
const { r, g, b } = parseColor(input);
|
|
246
|
+
return pack888(r, g, b);
|
|
247
|
+
}
|
|
248
|
+
/**
|
|
249
|
+
* Resolve a CSS color string and quantize to a target format. Backwards-
|
|
250
|
+
* compatible wrapper over resolveColor888 + quantizer. Existing call sites
|
|
251
|
+
* keep their behavior (565/mono output unchanged).
|
|
252
|
+
*/
|
|
253
|
+
export function resolveColor(input, format) {
|
|
254
|
+
const c = resolveColor888(input);
|
|
255
|
+
if (format === "rgb565")
|
|
256
|
+
return rgb888To565(c);
|
|
257
|
+
if (format === "rgb666")
|
|
258
|
+
return rgb888To666(c);
|
|
259
|
+
if (format === "rgb888")
|
|
260
|
+
return c;
|
|
261
|
+
return rgb888ToMono(c);
|
|
262
|
+
}
|
|
263
|
+
/**
|
|
264
|
+
* Resolve a CSS color to the INTERNAL representation value stored in node
|
|
265
|
+
* fields. For rgb666 targets this is RGB888 (666 quantization happens at the
|
|
266
|
+
* push boundary so blends keep full precision); for rgb565 it is RGB565
|
|
267
|
+
* (byte-identical with pre-Phase-3 behavior); for mono it is 0/1. The value
|
|
268
|
+
* depth and the runtime blend math switch together (see UI_COLOR_DEPTH). */
|
|
269
|
+
export function resolveColorInternal(input, format) {
|
|
270
|
+
if (format === "rgb666" || format === "rgb888")
|
|
271
|
+
return resolveColor888(input);
|
|
272
|
+
return resolveColor(input, format);
|
|
273
|
+
}
|