@motionscript/ui 0.1.0-alpha.0 → 0.1.0-alpha.3
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/CHANGELOG.md +18 -0
- package/dist/browser/chunks/chunk-SFBJ6J3M.js +2 -0
- package/dist/browser/chunks/chunk-SFBJ6J3M.js.map +7 -0
- package/dist/browser/index.js +1 -1
- package/dist/browser/index.js.map +3 -3
- package/dist/browser/kit.js +1 -1
- package/dist/browser/manifest.json +2 -2
- package/dist/button/button.d.ts +6 -4
- package/dist/button/button.d.ts.map +1 -1
- package/dist/button/button.js +10 -10
- package/dist/button/button.js.map +1 -1
- package/dist/cursor/cursor.d.ts +1 -1
- package/dist/cursor/cursor.d.ts.map +1 -1
- package/dist/cursor/cursor.js +1 -1
- package/dist/cursor/cursor.js.map +1 -1
- package/dist/kit/control-icons.d.ts +1 -1
- package/dist/kit/control-icons.d.ts.map +1 -1
- package/dist/kit/control-icons.js +1 -1
- package/dist/kit/control-icons.js.map +1 -1
- package/dist/kit/control-node.d.ts +1 -1
- package/dist/kit/control-node.d.ts.map +1 -1
- package/dist/kit/control-node.js +1 -1
- package/dist/kit/control-node.js.map +1 -1
- package/dist/kit/cursor-outline.d.ts +1 -1
- package/dist/kit/cursor-outline.d.ts.map +1 -1
- package/dist/kit/cursor-outline.js +1 -1
- package/dist/kit/cursor-outline.js.map +1 -1
- package/dist/nodes.d.ts +1 -1
- package/dist/switch/switch.d.ts +1 -1
- package/dist/switch/switch.d.ts.map +1 -1
- package/dist/switch/switch.js +2 -2
- package/dist/switch/switch.js.map +1 -1
- package/dist/window/window.d.ts +1 -1
- package/dist/window/window.d.ts.map +1 -1
- package/dist/window/window.js +1 -1
- package/dist/window/window.js.map +1 -1
- package/package.json +4 -3
- package/src/button/button.ts +15 -12
- package/src/cursor/cursor.ts +1 -1
- package/src/kit/control-icons.ts +1 -1
- package/src/kit/control-node.ts +1 -1
- package/src/kit/cursor-outline.ts +1 -1
- package/src/switch/switch.ts +2 -2
- package/src/window/window.ts +1 -1
- package/dist/browser/chunks/chunk-W7G2N4A4.js +0 -2
- package/dist/browser/chunks/chunk-W7G2N4A4.js.map +0 -7
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"version": 3,
|
|
3
3
|
"sources": ["../../src/switch/switch.ts", "../../src/button/button.ts", "../../src/window/window.ts", "../../src/window/window-style.ts", "../../src/cursor/cursor.ts", "../../src/nodes.ts"],
|
|
4
|
-
"sourcesContent": ["import {\n command,\n easeInOut,\n node,\n type Command,\n type CommandArgs,\n Graphics2D,\n property,\n type Node,\n type NodeConfig,\n type RenderContext2D,\n} from \"@motionscript/core\"\n\nimport {\n CONTROL_ICONS,\n controlIconPixels,\n DEFAULT_CONTROL_ICON,\n type ControlIcon,\n} from \"../kit/control-icons\"\nimport {\n CONTROL_STYLES,\n DEFAULT_CONTROL_STYLE,\n mixHex,\n switchLook,\n type ControlStyle,\n} from \"../kit/control-style\"\nimport { pathOps } from \"@motionscript/core\"\nimport { ControlNode, type ControlNodeProps } from \"../kit/control-node\"\n\n/** A switch's own props, over the box and the sound schedule every control has. */\nexport interface SwitchProps extends ControlNodeProps {\n /** Which design language to draw in — see {@link ControlStyle}. */\n style: ControlStyle\n /**\n * Where the knob is, `0` (off) to `1` (on).\n *\n * The **whole** of the node's animated state, and an ordinary tweenable number\n * rather than a boolean, which is the point: a switch's flip is a slide, and\n * \"half a boolean\" is not a frame anyone could draw. The document's Toggle\n * commands are lowered to tweens of this by the builder, each toward the\n * concrete state its chain resolves to (see `nodes/ui-kit/commands.ts`).\n */\n progress: number\n /** The colour the track turns when the switch is on. */\n accent: string\n /** The mark inside the knob when on. */\n iconOn: ControlIcon\n /** …and when off. */\n iconOff: ControlIcon\n /**\n * The track when off, the knob, and the mark's ink — honoured **only** in the\n * `custom` style, where they are what \"custom\" means. In the three named looks\n * these are ignored in favour of the platform's own, because a Material switch\n * with someone's grey in it is not a Material switch. See `model/control-style`.\n */\n track: string\n knob: string\n ink: string\n /** Corner radius as a fraction of half the track's height. `custom` only. */\n radius: number\n /** Seconds a {@link Switch.toggle} runs for when its row states no duration. */\n toggleDuration: number\n}\n\n/**\n * A **switch**, as a node.\n *\n * The control every product demo turns on at some point, and — like the cursor\n * one shelf over — a thing that was previously either a screen recording or four\n * rectangles animated by hand. Four rectangles is the interesting failure: it\n * works until you want the knob to arrive a beat later, at which point you are\n * keeping three tweens in sync by eye.\n *\n * ## One control, not thirty\n *\n * Its whole surface is a style, an accent, two marks and a resting state.\n * Everything else — the knob's diameter, its inset, whether it grows on the way\n * across, whether the track carries an outline — comes out of the style, because\n * those are facts about iOS and Material rather than decisions anybody making a\n * video wants to take. `model/control-style` has that argument at length, and\n * the `custom` style is the escape hatch for the case none of the three covers.\n *\n * ## Why it paints itself\n *\n * A `node` in the registry rather than a `shape`, on the cursor's reasoning with\n * one addition. A switch is *five* painted parts — track, outline, knob, and a\n * mark on each side of the fade — and a Fills section could only ever reach one\n * of them, so it would be a control whose effect an author would have to\n * discover by experiment. What it takes instead is the accent, which is the one\n * paint decision a switch actually has.\n *\n * ## Drawing, in four passes\n *\n * Track, knob, then the two marks. Four `draw` calls rather than one chained\n * `Graphics2D` because three of them carry a group opacity or a shadow of their\n * own, and `Graphics2D.opacity` composites the *whole* list — chaining them\n * would fade the track along with the mark it was meant to be cross-fading.\n */\n\n/** What {@link Switch.toggle} should leave the switch in. */\nexport type ToggleState = \"toggle\" | \"on\" | \"off\"\n\n@node({\n key: \"switch\",\n parentKey: \"node\",\n forkable: true,\n layout: {\n children: \"freeform\",\n defaultWidthMode: \"fixed\",\n defaultHeightMode: \"fixed\",\n acceptsChildren: false,\n },\n seed: {\n width: 204,\n height: 124,\n },\n})\nexport class Switch extends ControlNode<SwitchProps> {\n @property({ default: DEFAULT_CONTROL_STYLE, kind: \"enum\", options: CONTROL_STYLES })\n declare style: ControlStyle\n @property({ default: 0, min: 0, max: 1, step: 0.01 }) declare progress: number\n @property({ default: \"#34c759\", kind: \"color\" }) declare accent: string\n @property({ default: DEFAULT_CONTROL_ICON, kind: \"enum\", options: CONTROL_ICONS })\n declare iconOn: ControlIcon\n @property({ default: DEFAULT_CONTROL_ICON, kind: \"enum\", options: CONTROL_ICONS })\n declare iconOff: ControlIcon\n @property({ default: \"#e9e9eb\", kind: \"color\" }) declare track: string\n @property({ default: \"#ffffff\", kind: \"color\" }) declare knob: string\n @property({ default: \"#8e8e93\", kind: \"color\" }) declare ink: string\n @property({ default: 1, min: 0, max: 1, step: 0.01 }) declare radius: number\n @property({ default: 0.28, min: 0, step: 0.05, unit: \"s\" })\n declare toggleDuration: number\n\n constructor(props: NodeConfig<Switch, SwitchProps>) {\n super(props)\n }\n\n /** A switch is a control, not a container — its five painted parts are its own. */\n protected override acceptsChild(_child: Node): boolean {\n return false\n }\n\n /**\n * Throw the switch. `\"on\"`/`\"off\"` are absolute; `\"toggle\"` reads the knob's\n * current position and goes the other way — read when the **command is\n * built**, matching the timeline's own order, so two `toggle`s at the same\n * `at` agree rather than alternating.\n */\n @command({ args: [{ key: \"toggleState\", kind: \"enum\", default: \"toggle\", options: [\"toggle\", \"on\", \"off\"] }] })\n toggle(args: CommandArgs<{ toggleState?: ToggleState }> & { duration?: number }): Command<SwitchProps> {\n const toggleState = args.data?.toggleState ?? \"toggle\"\n const duration = args.duration && args.duration > 0 ? args.duration : this.toggleDuration\n const easing = args.easing ?? easeInOut()\n const to =\n toggleState === \"on\" ? 1 : toggleState === \"off\" ? 0 : this.progress > 0.5 ? 0 : 1\n return this.to({ data: { progress: to } as Partial<SwitchProps>, duration, easing })\n }\n\n /**\n * Every measurement the drawing needs, in pixels of the node's own box.\n *\n * Resolved in one place rather than at each use so the knob, its travel and\n * the mark inside it cannot disagree about where the knob is — three\n * near-identical expressions is how a mark ends up a pixel behind the thing it\n * is drawn on.\n */\n private metrics() {\n const bounds = this.layoutBounds\n const width = bounds?.width ?? 0\n const height = bounds?.height ?? 0\n const look = switchLook(this.style)\n const custom = this.style === \"custom\"\n const p = Math.min(1, Math.max(0, this.progress))\n\n const trackHeight = height * look.track\n // The margin is taken from the *largest* the knob ever gets, so a knob that\n // grows (Material's does) grows in place rather than dragging its own travel\n // with it — which would make the same switch stop at a different place\n // depending on how far through the flip it was.\n const widest = trackHeight * Math.max(look.knobOff, look.knobOn)\n const margin = (trackHeight - widest) / 2\n const knobSize =\n trackHeight * (look.knobOff + (look.knobOn - look.knobOff) * p)\n const travel = width / 2 - margin - widest / 2\n\n return {\n width,\n height,\n look,\n p,\n trackHeight,\n trackRadius: (trackHeight / 2) * (custom ? this.radius : look.radius),\n outline: trackHeight * look.outline,\n knobSize,\n knobX: -travel + travel * 2 * p,\n offTrack: custom ? this.track : look.offTrack,\n offKnob: custom ? this.knob : look.offKnob,\n offIcon: custom ? this.ink : look.offIcon,\n }\n }\n\n protected override renderSelf(ctx: RenderContext2D): void {\n const m = this.metrics()\n if (m.width <= 0 || m.trackHeight <= 0) return\n\n // The track, and its outline where the look has one. Both interpolate\n // toward the accent, which is what makes Material's outline disappear as it\n // turns on without needing a second rule for the weight.\n const track = new Graphics2D().rect({\n x: 0,\n y: 0,\n width: m.width,\n height: m.trackHeight,\n cornerRadius: m.trackRadius,\n })\n track.fill(mixHex(m.offTrack, this.accent, m.p))\n if (m.outline > 0) {\n track.stroke({\n weight: m.outline,\n fill: mixHex(m.look.offOutline, this.accent, m.p),\n align: \"inside\",\n })\n }\n ctx.draw(track)\n\n // The knob. Its shadow is the look's rather than a paint stack's, for the\n // same reason its diameter is: an iOS knob has a soft shadow and a Material\n // one does not, and that is part of what tells them apart.\n const knob = new Graphics2D().ellipse({\n x: m.knobX,\n y: 0,\n width: m.knobSize,\n height: m.knobSize,\n })\n if (m.look.shadow > 0) {\n knob.shadow({\n blur: m.trackHeight * m.look.shadow,\n offset: { x: 0, y: -m.trackHeight * m.look.shadow * 0.25 },\n fill: \"rgba(0, 0, 0, 0.22)\",\n })\n }\n knob.fill(mixHex(m.offKnob, m.look.onKnob, m.p))\n ctx.draw(knob)\n\n // The two marks, cross-fading. Drawn even at zero opacity — skipping them\n // would be a branch that saves nothing and puts a pop at each end of the\n // fade the moment a renderer rounds the alpha differently.\n this.drawIcon(ctx, this.iconOff, m.knobX, m.knobSize, m.offIcon, 1 - m.p)\n this.drawIcon(ctx, this.iconOn, m.knobX, m.knobSize, this.accent, m.p)\n }\n\n /**\n * One mark, centred in the knob.\n *\n * Its ink is the *other* side's colour by construction — the off mark takes\n * the look's own grey against a light knob, and the on mark takes the accent,\n * which the knob has turned white behind by the time it is visible. That is\n * why neither is a field: the pairing is what makes the mark legible, and an\n * author free to set it is an author free to make it invisible.\n *\n * Stroked and filled contours are two draws because they are two paints; a\n * glyph carries one or the other and never both, so the second is a no-op for\n * every mark in the catalogue today (see `model/control-icons`).\n */\n private drawIcon(\n ctx: RenderContext2D,\n icon: ControlIcon,\n x: number,\n knobSize: number,\n color: string,\n opacity: number\n ): void {\n if (icon === \"none\" || opacity <= 0 || knobSize <= 0) return\n // Just over half the knob, which is where every platform sets a glyph\n // inside a circular target: larger reads as a badge, smaller as a speck.\n const art = controlIconPixels(icon, knobSize * 0.56)\n const shift = (p: { x: number; y: number }) => ({ x: p.x + x, y: p.y })\n\n if (art.fills.length > 0) {\n ctx.draw(\n new Graphics2D()\n .path({ data: pathOps.anchorCommands(art.fills, shift) })\n .fill(color)\n .opacity(opacity)\n )\n }\n if (art.strokes.length > 0) {\n ctx.draw(\n new Graphics2D()\n .path({ data: pathOps.anchorCommands(art.strokes, shift) })\n .stroke({\n weight: art.weight,\n fill: color,\n cap: \"round\",\n join: \"round\",\n align: \"center\",\n })\n .opacity(opacity)\n )\n }\n }\n\n}\n", "import {\n command,\n driveCommand,\n easeInOut,\n node,\n type Command,\n type CommandArgs,\n Graphics2D,\n property,\n type Node,\n type NodeConfig,\n type RenderContext2D,\n} from \"@motionscript/core\"\n\nimport {\n CONTROL_ICONS,\n controlIconPixels,\n DEFAULT_CONTROL_ICON,\n type ControlIcon,\n} from \"../kit/control-icons\"\nimport {\n buttonLook,\n CONTROL_STYLES,\n DEFAULT_CONTROL_STYLE,\n mixHex,\n readableInk,\n type ControlStyle,\n} from \"../kit/control-style\"\nimport { pathOps } from \"@motionscript/core\"\nimport { pressDepth } from \"../kit/press\"\nimport { ControlNode, type ControlNodeProps } from \"../kit/control-node\"\n\n/** A button's own props, over the box and the sound schedule every control has. */\nexport interface ButtonProps extends ControlNodeProps {\n /** Which design language to draw in — see {@link ControlStyle}. */\n style: ControlStyle\n /** The words on it. */\n label: string\n /** The face they are set in. Empty falls back to the built-in. */\n fontFamily: string\n /** The button's fill, and the colour the label is chosen to read against. */\n accent: string\n /** A mark before the label. `none` draws none and takes no room. */\n icon: ControlIcon\n /**\n * How many presses deep the chain is — a **monotonic phase**, not a depth.\n *\n * The one prop the Press command animates, and it climbs by exactly one per\n * press rather than going down and coming back: a tween can only run one way,\n * and a press is two. {@link pressDepth} turns the fractional part into the\n * down-and-up envelope, which is zero at both whole numbers — so a finished\n * press leaves the button at rest by arithmetic rather than by a reset the\n * builder would have to remember to write. See `nodes/ui-kit/commands.ts`.\n */\n press: number\n /**\n * The label's ink and the corner radius — honoured **only** in the `custom`\n * style, for the reason {@link SwitchProps.track} states. Empty ink means\n * \"work it out\", which is what every named style does.\n */\n ink: string\n radius: number\n /** Seconds a press runs for when its row states no duration. */\n pressDuration: number\n}\n\n/**\n * A **button**, as a node.\n *\n * The Switch's sibling and the other half of \"a video about an interface\": the\n * thing the cursor moves to before the switch flips. Same bargain — one style\n * question rather than thirty controls, an accent, a mark — and one difference\n * that runs all the way through it: a button's whole state is *transient*. A\n * switch is on or off between commands; a button is at rest, and a Press is a\n * moment rather than a change.\n *\n * ## The label's colour is not a control\n *\n * It is computed from the fill ({@link readableInk}), because it is not a\n * decision — it is a consequence, and the only decisions available are the\n * correct one and a mistake. The `custom` style exposes it for the case where\n * the fill is an image or the author is matching a real product's off-spec\n * button; every other style works it out and is right.\n *\n * ## The label is placed, not measured\n *\n * A `Graphics2D.text` hands back no metrics, so the icon-plus-label pair is\n * centred against an **estimate** of the label's width — the same trade the\n * grapher's axis labels already make (see `labelWidthEm`), and acceptable for\n * the same reason: being a few pixels off centre on a control the author is\n * sizing by eye is a smaller error than the alternatives, which are measuring\n * text in a package with no DOM or composing the button out of real nodes and\n * giving up the press animation.\n */\n@node({\n key: \"button\",\n parentKey: \"node\",\n forkable: true,\n layout: {\n children: \"freeform\",\n defaultWidthMode: \"fixed\",\n defaultHeightMode: \"fixed\",\n acceptsChildren: false,\n },\n seed: {\n width: 282,\n height: 88,\n },\n})\nexport class Button extends ControlNode<ButtonProps> {\n @property({ default: DEFAULT_CONTROL_STYLE, kind: \"enum\", options: CONTROL_STYLES })\n declare style: ControlStyle\n @property({ default: \"Button\" }) declare label: string\n @property({ default: \"\" }) declare fontFamily: string\n @property({ default: \"#007aff\", kind: \"color\" }) declare accent: string\n @property({ default: DEFAULT_CONTROL_ICON, kind: \"enum\", options: CONTROL_ICONS })\n declare icon: ControlIcon\n @property({ default: 0, min: 0 }) declare press: number\n @property({ default: \"\", kind: \"color\" }) declare ink: string\n @property({ default: 0.48, min: 0, max: 1, step: 0.01 }) declare radius: number\n @property({ default: 0.24, min: 0, step: 0.05, unit: \"s\" })\n declare pressDuration: number\n\n constructor(props: NodeConfig<Button, ButtonProps>) {\n super(props)\n }\n\n /** A button is a control, not a container — its label and mark are its own. */\n protected override acceptsChild(_child: Node): boolean {\n return false\n }\n\n /**\n * Press and release the button — a pulse on `press`, not a state change, so\n * a document that only ever presses never leaves the button stuck down.\n *\n * Named `pulse` and published as `press` to avoid shadowing the `press` prop\n * this drives. Same split `Code.erase` makes to avoid shadowing `Node.remove`.\n */\n @command({ key: \"press\" })\n pulse(args: CommandArgs<Record<string, never>> & { duration?: number }): Command<ButtonProps> {\n const duration = args.duration && args.duration > 0 ? args.duration : this.pressDuration\n const easing = args.easing ?? easeInOut()\n const DOWN = 0.35\n return driveCommand(duration, (t) => {\n const phase = t < DOWN ? easing(t / DOWN) : 1 - easing((t - DOWN) / (1 - DOWN))\n this.set({ press: phase } as Partial<ButtonProps>)\n }) as Command<ButtonProps>\n }\n\n /** Every measurement the drawing needs, resolved once — see {@link Switch.metrics}. */\n private metrics() {\n const bounds = this.layoutBounds\n const width = bounds?.width ?? 0\n const height = bounds?.height ?? 0\n const look = buttonLook(this.style)\n const custom = this.style === \"custom\"\n const depth = pressDepth(this.press)\n\n // The pressed fill, which the ink is then chosen against rather than against\n // the resting one: a button dark enough at rest to want white text is darker\n // still while held, so deriving from the resting colour would be deriving\n // from a frame that isn't on screen.\n const fill = mixHex(this.accent, \"#000000\", look.pressDim * depth)\n const fontSize = height * look.fontSize\n\n return {\n width,\n height,\n look,\n depth,\n fill,\n fontSize,\n scale: 1 - look.pressScale * depth,\n radius: (height / 2) * (custom ? this.radius : look.radius),\n ink: custom && this.ink !== \"\" ? this.ink : readableInk(fill),\n iconSize: this.icon === \"none\" ? 0 : fontSize * look.iconSize,\n gap: this.icon === \"none\" ? 0 : fontSize * look.iconGap,\n }\n }\n\n protected override renderSelf(ctx: RenderContext2D): void {\n const m = this.metrics()\n if (m.width <= 0 || m.height <= 0) return\n\n // The whole drawing shrinks about the node's own centre rather than each\n // part about its own bounding box, which is why the pivot is stated: three\n // separate draws each scaled about themselves would slide apart under the\n // press instead of settling together.\n const pivot = { x: 0, y: 0 }\n\n const face = new Graphics2D().rect({\n x: 0,\n y: 0,\n width: m.width,\n height: m.height,\n cornerRadius: m.radius,\n })\n face.fill(m.fill)\n if (m.look.outline > 0) {\n face.stroke({\n weight: m.height * m.look.outline,\n // A rim a shade darker than the face, which is what a hairline border\n // is on every filled control that has one — a *colour* here would be a\n // second accent nobody asked for.\n fill: mixHex(m.fill, \"#000000\", 0.18),\n align: \"inside\",\n })\n }\n if (m.look.shadow > 0) {\n face.shadow({\n blur: m.height * m.look.shadow,\n offset: { x: 0, y: -m.height * m.look.shadow * 0.3 },\n fill: \"rgba(0, 0, 0, 0.18)\",\n })\n }\n ctx.draw(face.scale(m.scale, pivot))\n\n // Where the icon-and-label pair sits. The pair is centred as a unit, so the\n // label shifts right by half the room the icon takes and the icon sits in\n // the gap that opens on the left.\n const labelWidth = estimateWidth(this.label, m.fontSize)\n const block = m.iconSize + m.gap\n const left = -(labelWidth + block) / 2\n\n if (m.iconSize > 0) {\n this.drawIcon(ctx, left + m.iconSize / 2, m.iconSize, m.ink, m.scale)\n }\n\n if (this.label !== \"\") {\n ctx.draw(\n new Graphics2D()\n .text({\n text: this.label,\n x: left + block + labelWidth / 2,\n // Centred on the baseline's own box rather than nudged: the text\n // descriptor centres its box on the point it is given, which is\n // already the vertical middle of the node.\n y: 0,\n width: labelWidth,\n fontSize: m.fontSize,\n // Already resolved: the props mapper fills an empty family in from\n // the host (see `nodes/kit/text-defaults`), so the node never has to\n // know which typeface this application ships.\n fontFamily: this.fontFamily,\n fontWeight: m.look.fontWeight,\n letterSpacing: m.fontSize * m.look.letterSpacing,\n textAlign: \"center\",\n wrap: false,\n })\n .fill(m.ink)\n .scale(m.scale, pivot)\n )\n }\n }\n\n /** The leading mark — see {@link Switch.drawIcon}, which this mirrors. */\n private drawIcon(\n ctx: RenderContext2D,\n x: number,\n size: number,\n color: string,\n scale: number\n ): void {\n const art = controlIconPixels(this.icon, size)\n const shift = (p: { x: number; y: number }) => ({ x: p.x + x, y: p.y })\n const pivot = { x: 0, y: 0 }\n\n if (art.fills.length > 0) {\n ctx.draw(\n new Graphics2D()\n .path({ data: pathOps.anchorCommands(art.fills, shift) })\n .fill(color)\n .scale(scale, pivot)\n )\n }\n if (art.strokes.length > 0) {\n ctx.draw(\n new Graphics2D()\n .path({ data: pathOps.anchorCommands(art.strokes, shift) })\n .stroke({\n weight: art.weight,\n fill: color,\n cap: \"round\",\n join: \"round\",\n align: \"center\",\n })\n .scale(scale, pivot)\n )\n }\n }\n\n}\n\n/**\n * How wide `label` comes out, in pixels, near enough to centre it by.\n *\n * A per-character average rather than a shaping pass, which this package has no\n * way to run — see the class note. `0.55em` is the mean advance of a UI sans at\n * mixed case; the error on a real label is a few percent, and it lands as a few\n * pixels of off-centre on a control whose text is centred inside its own box\n * anyway.\n *\n * The whole label rather than its longest line, because a button's label does\n * not wrap (`wrap: false` above) — a caption that needs two lines is a caption,\n * not a button.\n */\nfunction estimateWidth(label: string, fontSize: number): number {\n return label.length * fontSize * 0.55\n}\n", "import {\n node,\n Graphics2D,\n Rect,\n property,\n type InsetsResolved,\n type RectProps,\n type RenderContext2D,\n type ShadowResolved,\n Vector2,\n} from \"@motionscript/core\"\n\nimport {\n DEFAULT_WINDOW_STYLE,\n windowLook,\n WINDOW_STYLES,\n type WindowLook,\n type WindowStyle,\n} from \"./window-style\"\n\n/** A window's own props, over the box and layout every `Rect` has. */\nexport interface WindowProps extends RectProps {\n /** Which operating system's chrome to draw — see {@link WindowStyle}. */\n style: WindowStyle\n /**\n * Draw the chrome's **dark** appearance.\n *\n * Inert on the two vintage styles, which shipped one appearance each and so\n * have no dark twin to switch to — see `WINDOW_DARK_STYLES`, which the panel\n * reads to hide the control rather than offer a switch that does nothing.\n */\n dark: boolean\n /** The words in the title bar. Empty draws none. */\n title: string\n /** The face they are set in. Empty falls back to the built-in. */\n fontFamily: string\n /** The client area behind the window's children. */\n body: string\n /**\n * How much bigger than life the chrome is drawn.\n *\n * `1` is the real operating system's own pixels — a 28pt macOS title bar is 28\n * units tall. The field exists because a window in a 1080p composition is\n * usually shown *larger* than life, and everything that makes chrome read as\n * chrome (the title bar's height, the traffic lights' 12px, a bevel's 2px) is\n * an absolute measurement rather than a fraction of the frame. Scaling the\n * node instead would scale its contents too.\n */\n chromeScale: number\n /** Draw the caption buttons. Off is a sheet, a palette, a dialog. */\n controls: boolean\n}\n\n/**\n * A **window**, as a node.\n *\n * The frame every software demo is shot inside, and the third thing the UI shelf\n * needed: the cursor is the person, the switch and the button are what they\n * touch, and this is the thing all of it is happening *in*.\n *\n * ## Why it is a `Rect`\n *\n * Alone among the controls here — the switch and the button both extend\n * {@link ControlNode}, which is a bare `Node2D`. A window is different in kind\n * because it has an **inside**: the whole point of one is to put a screenshot, a\n * chart or a caption in it, and that means real layout, real padding and a real\n * clip against the frame's own rounded corners. `Rect` is the one class in\n * motion-script that does all three, so the window is a Rect whose paint is\n * replaced rather than a node that draws a rectangle.\n *\n * {@link shapeGraphics} is overridden along with {@link renderSelf}, and that is\n * the half that makes the clip work: the silhouette a child is cut against has\n * to be the window's *rounded* outline, not the square box it was laid out in.\n *\n * ## Chrome is drawn in real pixels\n *\n * Everything else on this shelf states its metrics as fractions of its own box,\n * because a switch's box *is* its design. A window's is not: a title bar is 28\n * points tall on a small window and on a large one, which is exactly why a\n * window can be dragged to any size and still look like a window. So the look's\n * numbers are the operating system's own (see `model/window-style.ts`) and the\n * only thing between them and the canvas is {@link WindowProps.chromeScale}.\n *\n * That is also why this node is **not aspect-locked** where the switch is. Its\n * shape is not part of its identity — being resizable is.\n *\n * ## The chrome is padding\n *\n * A window's children belong in its **body**, not in its box: a child set to\n * fill its container should fill the client area and stop at the title bar,\n * because that is what filling a window means to everyone who has ever used one.\n * That is stated as {@link effectivePadding} — the same seam `Rect` already uses\n * to keep children clear of its stroke, extended with the frame's border and the\n * title bar's height. Saying it there rather than in the drawing is what makes it\n * true of *measurement* as well as of paint: `hug` sizes around it, `fill` sizes\n * inside it, and a child never has to know a title bar exists.\n *\n * ## No commands\n *\n * Deliberately none of its own. A switch flips and a button is pressed; a window\n * *is*, and everything anyone would animate about one — where it sits, how big\n * it is, whether it has faded in — is a property of the box that `to`, `moveTo`\n * and `scaleTo` already reach.\n */\n@node({\n key: \"window\",\n parentKey: \"node\",\n forkable: true,\n layout: {\n children: \"flow\",\n defaultWidthMode: \"fixed\",\n defaultHeightMode: \"fixed\",\n acceptsChildren: true,\n },\n seed: {\n width: 720,\n height: 480,\n },\n})\nexport class Window extends Rect<WindowProps> {\n @property({ default: DEFAULT_WINDOW_STYLE, kind: \"enum\", options: WINDOW_STYLES })\n declare style: WindowStyle\n @property({ default: false }) declare dark: boolean\n @property({ default: \"\" }) declare title: string\n @property({ default: \"\" }) declare fontFamily: string\n @property({ default: \"#ffffff\", kind: \"color\" }) declare body: string\n @property({ default: 1, min: 0.5, max: 3, step: 0.05, unit: \"×\" })\n declare chromeScale: number\n @property({ default: true }) declare controls: boolean\n\n /**\n * Every measurement the drawing needs, in the node's own pixels.\n *\n * The look's numbers times the scale, resolved once — three near-identical\n * `look.titleBar * scale` expressions is how a title bar and the thing drawn\n * inside it end up a pixel apart.\n *\n * The title bar is capped at half the window, so a window dragged shorter than\n * its own chrome degrades to a bar with a sliver under it rather than to a\n * client area with a negative height.\n */\n private metrics() {\n const bounds = this.layoutBounds\n const width = bounds?.width ?? 0\n const height = bounds?.height ?? 0\n const look = windowLook(this.style, this.dark)\n const scale = Math.max(0.01, this.chromeScale)\n\n const titleBar = Math.min(look.titleBar * scale, height / 2)\n return {\n width,\n height,\n look,\n scale,\n titleBar,\n radius: Math.min(look.radius * scale, width / 2, height / 2),\n border: look.border * scale,\n /** The top edge in the node's own y-up space. */\n top: height / 2,\n left: -width / 2,\n }\n }\n\n /**\n * The chrome, as padding — so children are laid out in the **body**.\n *\n * `Rect` already treats the part of its stroke that intrudes on the content\n * box as padding, for exactly this reason: a child must not be laid out under\n * paint the parent is going to draw over it. A window's chrome is the same\n * problem several sizes larger — a title bar is 28 points of the box that\n * belongs to the operating system, not to the author's content — so it is\n * answered in the same place, and everything downstream follows without\n * knowing why. A child at `width: \"fill\"` fills the client area; one at\n * `height: \"fill\"` stops under the title bar; a window that hugs hugs around\n * its chrome rather than inside it.\n *\n * The title bar's *uncapped* height is used, unlike everywhere in the drawing:\n * layout runs before this node has a box (that is what it is computing), and\n * the cap in {@link metrics} is a function of the box. A window shorter than\n * its own chrome therefore lays its children out in a content box of zero\n * height, which is the honest answer — there is no client area in a window\n * that is all title bar.\n */\n override effectivePadding(): InsetsResolved {\n const base = super.effectivePadding()\n const look = windowLook(this.style, this.dark)\n const scale = Math.max(0.01, this.chromeScale)\n // The frame's own border, doubled where the chrome is a two-ring bevel —\n // the same inset the title bar is drawn at, so the two cannot disagree.\n const inset = look.border * scale * (look.bevel ? 2 : 1)\n return {\n left: base.left + inset,\n right: base.right + inset,\n top: base.top + inset + look.titleBar * scale,\n bottom: base.bottom + inset,\n }\n }\n\n /**\n * The window's outline — what a nested child is clipped against.\n *\n * Overridden so the clip follows the *rounded* frame. `Rect`'s own answer is\n * the same shape with this node's `cornerRadius`, which a window does not\n * carry: its corner is the operating system's, not the author's.\n */\n protected override shapeGraphics(): Graphics2D {\n const m = this.metrics()\n return new Graphics2D().rect({\n x: 0,\n y: 0,\n width: m.width,\n height: m.height,\n cornerRadius: m.radius,\n })\n }\n\n protected override renderSelf(ctx: RenderContext2D): void {\n const m = this.metrics()\n if (m.width <= 0 || m.height <= 0) return\n\n this.drawFrame(ctx, m)\n this.drawTitleBar(ctx, m)\n if (m.look.pinstripe) this.drawPinstripes(ctx, m)\n this.drawTitle(ctx, m)\n if (this.controls) this.drawControls(ctx, m)\n }\n\n /** The client area, its outer border, and the shadow the whole thing casts. */\n private drawFrame(ctx: RenderContext2D, m: Metrics): void {\n const frame = new Graphics2D().rect({\n x: 0,\n y: 0,\n width: m.width,\n height: m.height,\n cornerRadius: m.radius,\n })\n const authored = this.shadow as ShadowResolved[]\n if (authored.length > 0) {\n frame.shadow(authored)\n } else if (m.look.shadow > 0) {\n frame.shadow(this.chromeShadow(m))\n }\n frame.fill(this.body)\n if (m.border > 0 && !m.look.bevel) {\n frame.stroke({\n weight: m.border,\n fill: m.look.borderColor,\n align: \"inside\",\n })\n }\n ctx.draw(frame)\n\n // Windows 95's frame is not a border, it is a **bevel**: two nested rings,\n // light on the top-left and dark on the bottom-right, which is what makes\n // the whole window read as a raised physical object. There is no stroke that\n // draws that — a stroke has one colour all the way round — so it is four\n // strips a side, drawn as two L-shaped pairs.\n if (m.look.bevel) this.drawBevel(ctx, m)\n }\n\n /**\n * The drop the chrome itself comes with, drawn when the author has not put a\n * stack of their own in the Shadows section.\n *\n * The one place a window's paint slots do **not** behave exactly like a\n * shape's, and the deviation is worth stating: on a `Rect` an empty Shadows\n * section means no shadow, whereas here it means *the chrome's own*. Both\n * modern chromes ship a drop — a macOS window is separated from the desktop by\n * nothing else — so an empty stack meaning \"flat\" would make every window look\n * wrong until the author discovered a section they had no reason to open, and\n * would make the two vintage chromes (which really are flat) indistinguishable\n * from a bug. Adding a shadow replaces it, wholesale, which is the behaviour\n * that lets an author light a window however they like; removing the last one\n * hands the chrome back its own.\n *\n * Its offset is a quarter of its blur, downward — negative in this y-up space\n * — which is what every OS's own drop uses.\n */\n private chromeShadow(m: Metrics): {\n blur: number\n offset: { x: number; y: number }\n fill: string\n } {\n const blur = m.look.shadow * m.scale\n return {\n blur,\n offset: { x: 0, y: -blur * 0.25 },\n fill: \"rgba(0, 0, 0, 0.28)\",\n }\n }\n\n /**\n * A raised (or, with `depth` negative, a sunken) 3D edge — Windows 95's whole\n * visual language, and the one piece of chrome here that cannot be a stroke.\n *\n * Two rings: an outer one in white / black and an inner one in light grey /\n * dark grey, each drawn as an L along the top-and-left and another along the\n * bottom-and-right. Reused for the caption chips, which are the same edge at\n * a smaller radius.\n */\n private drawBevel(\n ctx: RenderContext2D,\n m: Metrics,\n box = { x: 0, y: 0, width: m.width, height: m.height },\n raised = true\n ): void {\n const t = Math.max(1, m.scale)\n const half = { w: box.width / 2, h: box.height / 2 }\n const light = raised ? \"#ffffff\" : \"#808080\"\n const dark = raised ? \"#0a0a0a\" : \"#ffffff\"\n const innerLight = raised ? \"#dfdfdf\" : \"#808080\"\n const innerDark = raised ? \"#808080\" : \"#dfdfdf\"\n\n /** One L: a horizontal strip and a vertical one, meeting at `corner`. */\n const edge = (inset: number, colour: string, corner: \"tl\" | \"br\") => {\n const g = new Graphics2D()\n const top = corner === \"tl\"\n const y = top\n ? box.y + half.h - inset - t / 2\n : box.y - half.h + inset + t / 2\n const x = top\n ? box.x - half.w + inset + t / 2\n : box.x + half.w - inset - t / 2\n const span = { w: box.width - inset * 2, h: box.height - inset * 2 }\n g.rect({ x: box.x, y, width: span.w, height: t })\n g.rect({ x, y: box.y, width: t, height: span.h })\n ctx.draw(g.fill(colour))\n }\n\n edge(0, light, \"tl\")\n edge(0, dark, \"br\")\n edge(t, innerLight, \"tl\")\n edge(t, innerDark, \"br\")\n }\n\n /** The bar across the top, flat or gradient. */\n private drawTitleBar(ctx: RenderContext2D, m: Metrics): void {\n if (m.titleBar <= 0) return\n // Inside the bevel where there is one, so the navy bar sits *within* the\n // grey frame rather than running under it — which is what Windows 95 does\n // and what makes the frame read as surrounding the bar.\n const inset = m.look.bevel ? m.border * 2 : m.border\n const barWidth = m.width - inset * 2\n const bar = new Graphics2D().rect({\n x: 0,\n y: m.top - inset - m.titleBar / 2,\n width: barWidth,\n height: m.titleBar,\n // Only the top corners are the window's; the bottom two are square where\n // the bar meets the client area. Stated as a per-corner bag rather than\n // clipped, which would need a whole mask scope for two corners.\n cornerRadius: {\n topLeft: Math.max(0, m.radius - inset),\n topRight: Math.max(0, m.radius - inset),\n bottomLeft: 0,\n bottomRight: 0,\n },\n })\n\n if (m.look.titleBarTo !== \"\") {\n // Left to right, which is Windows 95's own direction. The endpoints are\n // in the renderer's own gradient space — −1…+1 from the box's centre,\n // y up — rather than in pixels; see `toGradientPoint` in `motion-paints`,\n // which exists because those two spaces are easy to confuse.\n bar.fill({\n type: \"linear-gradient\",\n colors: [m.look.titleBarColor, m.look.titleBarTo],\n stops: [0, 1],\n start: { x: -1, y: 0 },\n end: { x: 1, y: 0 },\n })\n } else {\n bar.fill(m.look.titleBarColor)\n }\n ctx.draw(bar)\n\n if (m.look.separator !== \"\") {\n ctx.draw(\n new Graphics2D()\n .rect({\n x: 0,\n y: m.top - inset - m.titleBar,\n width: barWidth,\n height: Math.max(1, m.scale),\n })\n .fill(m.look.separator)\n )\n }\n }\n\n /**\n * System 7's horizontal stripes — six lines across the bar, every other pixel.\n *\n * Drawn as real 1px rules at 1× rather than as a texture, because at\n * `chromeScale: 2` a texture would blur where the real thing simply had\n * thicker lines. The run stops short of the controls and of the title, which\n * is how the bar was actually drawn: the stripes are the *background*, and\n * anything sitting on the bar interrupts them.\n */\n private drawPinstripes(ctx: RenderContext2D, m: Metrics): void {\n const inset = m.border\n const weight = Math.max(1, m.scale)\n const pitch = weight * 2\n const stripes = new Graphics2D()\n const barTop = m.top - inset\n // Two pixels of clearance top and bottom, as System 7 left.\n for (\n let y = barTop - pitch * 2;\n y > barTop - m.titleBar + weight;\n y -= pitch\n ) {\n stripes.rect({\n x: 0,\n y,\n width: m.width - inset * 2,\n height: weight,\n })\n }\n ctx.draw(stripes.fill(m.look.titleColor))\n }\n\n /**\n * The title, and — on a pinstriped bar — the gap it sits in.\n *\n * The gap is the detail that makes System 7 read as System 7: the words do not\n * sit *on* the stripes, they sit in a hole punched through them. Drawn as a\n * bar-coloured rectangle behind the text, sized from the same width estimate\n * the button's label uses (see `estimateWidth` there for why an estimate).\n */\n private drawTitle(ctx: RenderContext2D, m: Metrics): void {\n if (this.title === \"\" || m.titleBar <= 0) return\n const inset = m.look.bevel ? m.border * 2 : m.border\n const size = m.look.titleSize * m.scale\n const y = m.top - inset - m.titleBar / 2\n const width = this.title.length * size * 0.55\n\n // Where the title starts. Centred on both Macs; on Windows it follows the\n // left edge, clearing the controls only when they are on that side.\n const x =\n m.look.titleAlign === \"center\"\n ? 0\n : m.left + inset + m.look.controlInset * m.scale + width / 2\n\n if (m.look.pinstripe) {\n const pad = size * 0.6\n ctx.draw(\n new Graphics2D()\n .rect({\n x,\n y,\n width: width + pad * 2,\n height: m.titleBar - m.scale * 4,\n })\n .fill(m.look.titleBarColor)\n )\n }\n\n ctx.draw(\n new Graphics2D()\n .text({\n text: this.title,\n x,\n y,\n width,\n fontSize: size,\n // Already resolved: the props mapper fills an empty family in from\n // the host (see `nodes/kit/text-defaults`), so the node never has to\n // know which typeface this application ships.\n fontFamily: this.fontFamily,\n fontWeight: m.look.titleWeight,\n textAlign: \"center\",\n wrap: false,\n })\n .fill(m.look.titleColor)\n )\n }\n\n /** The three caption buttons, in whichever of the four idioms the style uses. */\n private drawControls(ctx: RenderContext2D, m: Metrics): void {\n if (m.titleBar <= 0) return\n const inset = m.look.bevel ? m.border * 2 : m.border\n const size = m.look.controlSize * m.scale\n const height = m.look.controlHeight * m.scale\n const gap = m.look.controlGap * m.scale\n const edge = m.look.controlInset * m.scale\n const y = m.top - inset - m.titleBar / 2\n const rightward = m.look.controlsSide === \"left\"\n\n /** The centre of control `i`, counting outward from the window's near edge. */\n const centreOf = (i: number): number => {\n const first = rightward\n ? m.left + inset + edge + size / 2\n : -m.left - inset - edge - size / 2\n const step = gap || size * 1.6\n return rightward ? first + step * i : first - step * i\n }\n\n // Which button sits where, counting **outward from the corner the controls\n // are pinned to** — which is the only ordering both sides have in common.\n //\n // Close is nearest that corner in both idioms: leftmost on a Mac, rightmost\n // on Windows. What differs is the other two. A Mac runs close, minimise,\n // zoom left to right; Windows runs minimise, maximise, close — so counting\n // in from its right-hand corner gives close, maximise, minimise. The colour\n // list is always in close/minimise/maximise order, so it is the order that\n // is stated here rather than the list that is rebuilt.\n const order = rightward ? [0, 1, 2] : [0, 2, 1]\n\n for (let i = 0; i < 3; i++) {\n const slot = order[i]\n const x = centreOf(i)\n const colour = m.look.controlColors[slot]\n\n switch (m.look.controlsShape) {\n case \"lights\":\n ctx.draw(\n new Graphics2D()\n .ellipse({ x, y, width: size, height: size })\n .fill(colour)\n )\n break\n\n case \"boxes\":\n // System 7 drew a close box at the left and a zoom box at the right,\n // and nothing in between — so the middle slot is skipped rather than\n // drawn as a third square the OS never had.\n if (i === 1) break\n ctx.draw(\n new Graphics2D()\n .rect({\n x: i === 0 ? x : -centreOf(0),\n y,\n width: size,\n height: height,\n })\n .fill(m.look.titleBarColor)\n .stroke({ weight: m.scale, fill: colour, align: \"inside\" })\n )\n break\n\n case \"chips\":\n ctx.draw(\n new Graphics2D().rect({ x, y, width: size, height }).fill(\"#c0c0c0\")\n )\n this.drawBevel(ctx, m, { x, y, width: size, height }, true)\n this.drawGlyph(ctx, slot, x, y, size * 0.42, \"#000000\", m.scale)\n break\n\n case \"glyphs\":\n this.drawGlyph(ctx, slot, x, y, size, colour, m.scale)\n break\n }\n }\n }\n\n /**\n * A caption glyph: a minus, a square, or a cross.\n *\n * Drawn from strokes rather than typed as text, because these are the same\n * three marks in every chrome that has them and a font would put whatever the\n * renderer happened to shape into a 10px box. `slot` is close / minimise /\n * maximise, matching the colour list's order.\n */\n private drawGlyph(\n ctx: RenderContext2D,\n slot: number,\n x: number,\n y: number,\n size: number,\n colour: string,\n scale: number\n ): void {\n const r = size / 2\n const g = new Graphics2D()\n const weight = Math.max(1, scale)\n /** One stroked rule, drawn on its own — see the cross below for why. */\n const rule = (from: Vector2, to: Vector2) =>\n ctx.draw(\n new Graphics2D()\n .line({ points: [from, to] })\n .stroke({ weight, fill: colour, align: \"center\", cap: \"butt\" })\n )\n\n if (slot === 0) {\n // The cross, as two crossing rules — and each is its own draw.\n //\n // A `Graphics2D` unions every shape declared before a paint call and\n // paints them as one surface, which is right for two rects making an L\n // (see {@link drawBevel}) and wrong for two *lines*: the union of two open\n // strokes is not a shape the stroker can trace, and what came out was\n // nothing at all — a close button that was positioned, coloured and\n // invisible. One draw each is the whole fix; the marks do not need to\n // share a paint, since they are one colour by construction.\n rule({ x: x - r, y: y - r }, { x: x + r, y: y + r })\n rule({ x: x - r, y: y + r }, { x: x + r, y: y - r })\n return\n }\n\n if (slot === 1) {\n // The minimise rule, sitting on the baseline rather than the middle in the\n // chrome that draws it low — but centred reads correctly at every size and\n // is what both modern shells do.\n rule({ x: x - r, y }, { x: x + r, y })\n return\n }\n\n // The maximise square, hollow.\n g.rect({ x, y, width: size, height: size })\n ctx.draw(g.stroke({ weight, fill: colour, align: \"center\", cap: \"butt\" }))\n }\n}\n\n\n\n/** What {@link Window.metrics} hands round — named so the helpers can take it. */\ntype Metrics = {\n width: number\n height: number\n look: WindowLook\n scale: number\n titleBar: number\n radius: number\n border: number\n top: number\n left: number\n}\n", "/**\n * The window chromes — four operating systems' idea of a window, two of them\n * from the nineties.\n *\n * ## Why the vintage ones name a release\n *\n * \"Old Mac window\" is not a design; it is a decade of them. System 6's title bar\n * is not System 7's, and Platinum threw both out in 1997. The same is true on\n * the other side: Windows 3.1, 95 and XP are three different chromes people\n * remember as one. So each vintage look here names the **release it was measured\n * from** — in the enum value, in its docblock and in the label the dropdown\n * prints — because a reader who thinks it looks wrong needs to be able to check\n * it against something, and \"old Mac\" is not something you can check against.\n *\n * The two modern ones do not carry a year for the opposite reason: they are the\n * current thing, they move, and pinning today's build number into a label would\n * make it read as historical the moment it shipped.\n *\n * ## Everything is in real pixels\n *\n * A title bar is 28 points tall on macOS whatever size the window is, which is\n * the one way window chrome differs from every other control in this package: a\n * switch's knob is a fraction of its box, and a window's title bar is *not* a\n * fraction of anything. So the numbers below are the real ones at 1×, and\n * {@link WindowLook} is scaled by the node's own `chromeScale` — 1 draws chrome\n * at life size, 2 draws it as it appears in a Retina capture. That is what lets\n * a window be dragged to any size at all and still look like the window it is\n * imitating, which is the whole reason the node is not aspect-locked the way the\n * switch is (see `model/natural-size.ts`).\n *\n * ## Dark is an appearance, not a fifth chrome\n *\n * The two modern chromes each have a dark twin, reached with a boolean rather\n * than by adding `macosDark` and `windowsDark` to the enum above. A chrome is\n * *which operating system* — the thing that decides where the buttons are and\n * how tall the bar is — and light versus dark is a question you ask **of** one,\n * the same one the OS itself asks. Two more enum entries would make the dropdown\n * say there are six operating systems here, and would let a fifth measurement\n * table drift from the one it was copied out of. See {@link WINDOW_DARK_LOOKS},\n * which states each dark chrome as a repaint of its light twin and nothing else.\n */\n\n/** The chromes, in the order the dropdown offers them: new, old, new, old. */\nexport const WINDOW_STYLES = [\n \"macos\",\n \"macosClassic\",\n \"windows\",\n \"windowsClassic\",\n] as const\n\nexport type WindowStyle = (typeof WINDOW_STYLES)[number]\n\n/** What a new Window opens as. */\nexport const DEFAULT_WINDOW_STYLE: WindowStyle = \"macos\"\n\n/**\n * The chromes that have a **dark** variant — the two modern ones, and only\n * them.\n *\n * Not an omission on the vintage pair: System 7 and Windows 95 shipped one\n * appearance each and had no dark mode to imitate, so a dark System 7 would be\n * the same invention as a repainted Windows 95 title bar — a window from an\n * operating system that never existed, which is precisely what the colour fields\n * on this node are withheld to prevent. The list is exported because the panel\n * gates the Dark switch on it (see the `showWhen` on the window's schema entry):\n * a control that is present and does nothing is worse than one that isn't there.\n */\nexport const WINDOW_DARK_STYLES: readonly WindowStyle[] = [\"macos\", \"windows\"]\n\n/** Whether `style` has a dark chrome to switch to. */\nexport function hasDarkWindowStyle(style: WindowStyle): boolean {\n return WINDOW_DARK_STYLES.includes(style)\n}\n\n/** Whether a stored value names a chrome this build knows. */\nexport function isWindowStyle(value: unknown): value is WindowStyle {\n return (\n typeof value === \"string\" &&\n (WINDOW_STYLES as readonly string[]).includes(value)\n )\n}\n\n/** Where a chrome puts its window controls, and what shape they are. */\nexport type ControlsSide = \"left\" | \"right\"\n\n/**\n * How the caption buttons are drawn.\n *\n * Three genuinely different things rather than three colour schemes: macOS's\n * traffic lights are filled circles carrying no glyph, System 7's are hollow\n * squares with a bevel drawn inside them, Windows 11's are bare glyphs on no\n * button at all, and Windows 95's are raised grey chips with a glyph on top.\n */\nexport type ControlsShape = \"lights\" | \"boxes\" | \"glyphs\" | \"chips\"\n\n/** One window chrome, in real pixels at 1×. */\nexport interface WindowLook {\n /** Outer corner radius, px. `0` is a square-cornered window. */\n radius: number\n /** Title bar height, px. */\n titleBar: number\n /** Outer border weight, px. `0` draws none. */\n border: number\n /** The outer border's colour. */\n borderColor: string\n /** The title bar's fill. A two-stop gradient where {@link titleBarTo} is set. */\n titleBarColor: string\n /** The far end of a gradient title bar, or `\"\"` for a flat one. */\n titleBarTo: string\n /** A hairline under the title bar, or `\"\"` for none. */\n separator: string\n /** The client area behind the window's children. */\n body: string\n /** The title's ink. */\n titleColor: string\n /** Title size in px, and its weight. */\n titleSize: number\n titleWeight: number\n /** `center` on the Macs, `left` on both Windows. */\n titleAlign: \"left\" | \"center\"\n /** Which end the caption buttons sit at. */\n controlsSide: ControlsSide\n /** How they are drawn — see {@link ControlsShape}. */\n controlsShape: ControlsShape\n /** Each control's diameter (a light) or width (everything else), px. */\n controlSize: number\n /** A chip's height where it differs from {@link controlSize}, px. */\n controlHeight: number\n /** Centre-to-centre spacing between controls, px. */\n controlGap: number\n /** Distance from the window edge to the first control's near side, px. */\n controlInset: number\n /** The three controls' colours, in close / minimise / maximise order. */\n controlColors: [string, string, string]\n /** Drop-shadow blur, px. `0` draws none. */\n shadow: number\n /**\n * Whether the frame carries a **3D bevel** — the raised grey border that is\n * the whole visual language of Windows 95 and of nothing else here.\n */\n bevel: boolean\n /** Horizontal pinstripes across the title bar, as System 7 drew them. */\n pinstripe: boolean\n}\n\n/**\n * The four chromes.\n *\n * Measured from the real thing in each case, and the two vintage entries say\n * which release. Where a number could not be measured exactly it is the one that\n * makes the drawing read correctly at video sizes rather than a guess dressed up\n * as a fact — the traffic lights' 12px and their 20px pitch are Apple's, the\n * Windows 95 title bar's 18px and its 16×14 buttons are Microsoft's.\n */\nexport const WINDOW_LOOKS: Record<WindowStyle, WindowLook> = {\n /**\n * **macOS**, as of Sonoma (2023) and unchanged since Big Sur redrew it in\n * 2020: a 10px-rounded frame, a light chrome bar with the title centred in it,\n * and the three traffic lights at the left.\n */\n macos: {\n radius: 10,\n titleBar: 28,\n border: 1,\n borderColor: \"#00000026\",\n titleBarColor: \"#ececec\",\n titleBarTo: \"\",\n separator: \"#d5d5d5\",\n body: \"#ffffff\",\n titleColor: \"#4d4d4d\",\n titleSize: 13,\n titleWeight: 600,\n titleAlign: \"center\",\n controlsSide: \"left\",\n controlsShape: \"lights\",\n controlSize: 12,\n controlHeight: 12,\n controlGap: 20,\n controlInset: 14,\n controlColors: [\"#ff5f57\", \"#febc2e\", \"#28c840\"],\n shadow: 40,\n bevel: false,\n pinstripe: false,\n },\n\n /**\n * **Mac OS System 7 (1991)** — the pinstriped title bar.\n *\n * Six horizontal lines across the bar, a hollow close box at the left and a\n * zoom box at the right, a 1px black frame and square corners. The title sits\n * centred in a gap that *interrupts* the pinstripes rather than sitting over\n * them, which is the detail everybody remembers without being able to name.\n *\n * System 7 rather than System 6 (whose bar had no zoom box) or Platinum\n * (Mac OS 8, 1997 — which replaced the stripes with a grey gradient).\n */\n macosClassic: {\n radius: 0,\n titleBar: 20,\n border: 1,\n borderColor: \"#000000\",\n titleBarColor: \"#ffffff\",\n titleBarTo: \"\",\n separator: \"#000000\",\n body: \"#ffffff\",\n titleColor: \"#000000\",\n titleSize: 12,\n titleWeight: 700,\n titleAlign: \"center\",\n controlsSide: \"left\",\n controlsShape: \"boxes\",\n controlSize: 11,\n controlHeight: 11,\n controlGap: 0,\n controlInset: 8,\n controlColors: [\"#000000\", \"#000000\", \"#000000\"],\n shadow: 0,\n bevel: false,\n pinstripe: true,\n },\n\n /**\n * **Windows 11** (2021): 8px-rounded corners, a Mica-grey chrome, the title\n * left-aligned, and three flat caption glyphs at the right on no button at\n * all until you hover one.\n */\n windows: {\n radius: 8,\n titleBar: 32,\n border: 1,\n borderColor: \"#00000026\",\n titleBarColor: \"#f3f3f3\",\n titleBarTo: \"\",\n separator: \"\",\n body: \"#ffffff\",\n titleColor: \"#1a1a1a\",\n titleSize: 12,\n titleWeight: 400,\n titleAlign: \"left\",\n controlsSide: \"right\",\n controlsShape: \"glyphs\",\n controlSize: 10,\n controlHeight: 10,\n controlGap: 46,\n controlInset: 18,\n controlColors: [\"#1a1a1a\", \"#1a1a1a\", \"#1a1a1a\"],\n shadow: 32,\n bevel: false,\n pinstripe: false,\n },\n\n /**\n * **Windows 95 (1995)** — the grey bevel and the navy gradient.\n *\n * A `#c0c0c0` frame raised by a two-tone 3D border (white and light grey on\n * the top and left, dark grey and black on the bottom and right), an 18px\n * title bar running `#000080` → `#1084d0` left to right with the title in\n * white bold at the left, and three 16×14 raised chips at the right.\n *\n * Windows 95 rather than 98 (visually the same chrome) or 3.1 (whose title bar\n * was flat navy with no gradient, and whose buttons sat at both ends). The\n * gradient is the tell: it arrived with 95.\n */\n windowsClassic: {\n radius: 0,\n titleBar: 18,\n border: 2,\n borderColor: \"#c0c0c0\",\n titleBarColor: \"#000080\",\n titleBarTo: \"#1084d0\",\n separator: \"\",\n body: \"#ffffff\",\n titleColor: \"#ffffff\",\n titleSize: 11,\n titleWeight: 700,\n titleAlign: \"left\",\n controlsSide: \"right\",\n controlsShape: \"chips\",\n controlSize: 16,\n controlHeight: 14,\n controlGap: 18,\n controlInset: 4,\n controlColors: [\"#c0c0c0\", \"#c0c0c0\", \"#c0c0c0\"],\n shadow: 0,\n bevel: true,\n pinstripe: false,\n },\n}\n\n/**\n * The dark chromes, each stated as **what changes** about its light twin.\n *\n * Spread from the light entry rather than written out, and that is the point:\n * everything a dark window shares with a light one is a *measurement* — the\n * 28pt title bar, the traffic lights' 12px and their 20px pitch, Windows 11's\n * 32pt bar and 46px caption pitch — and a measurement that got copied would be\n * a measurement free to drift. Dark mode is a repaint, so only paint appears\n * below.\n *\n * Two things worth naming. macOS keeps its traffic lights *exactly* as they are:\n * the red, amber and green are the same three colours in both appearances, which\n * is half of why they read as traffic lights rather than as decoration. And\n * Windows 11's caption glyphs simply invert — they are ink on the bar, never a\n * button, until you hover one.\n */\nexport const WINDOW_DARK_LOOKS: Partial<Record<WindowStyle, WindowLook>> = {\n /**\n * **macOS**, dark appearance: a near-black chrome bar, a light-grey title, a\n * hairline border that is now a highlight rather than a shade, and a deeper\n * drop — a dark window on a dark desktop is separated by its shadow alone.\n */\n macos: {\n ...WINDOW_LOOKS.macos,\n borderColor: \"#ffffff26\",\n titleBarColor: \"#2d2d2d\",\n separator: \"#1c1c1c\",\n body: \"#1e1e1e\",\n titleColor: \"#d8d8d8\",\n shadow: 48,\n },\n\n /**\n * **Windows 11**, dark: the Mica grey inverts to `#202020` with a `#272727`\n * client area — the two are deliberately *not* the same value, which is how\n * the bar stays legible as a bar with no separator drawn under it.\n */\n windows: {\n ...WINDOW_LOOKS.windows,\n borderColor: \"#ffffff1f\",\n titleBarColor: \"#202020\",\n body: \"#272727\",\n titleColor: \"#ffffff\",\n controlColors: [\"#ffffff\", \"#ffffff\", \"#ffffff\"],\n },\n}\n\n/**\n * The chrome a style names, in the appearance asked for — falling back for a\n * value this build doesn't know, and for `dark` on a style that has no dark\n * variant (see {@link WINDOW_DARK_STYLES}).\n */\nexport function windowLook(style: WindowStyle, dark = false): WindowLook {\n const light = WINDOW_LOOKS[style] ?? WINDOW_LOOKS[DEFAULT_WINDOW_STYLE]\n if (!dark) return light\n return WINDOW_DARK_LOOKS[style] ?? light\n}\n\n/**\n * The client area a window opens with — white, which is the light chromes' own.\n *\n * Named rather than inlined because two places have to agree on it and they are\n * in different packages: the appearance schema declares it as the `body` field's\n * default, and `windowMotionProps` reads it back to decide whether the author has\n * chosen a client colour at all. See that mapper for why the question is asked\n * there and not here.\n */\nexport const DEFAULT_WINDOW_BODY = \"#ffffff\"\n\n/**\n * The proportions a new Window is seeded at — 720 × 480, a plain 3 : 2.\n *\n * Not a locked ratio, and that is the point: a window is the one thing on this\n * shelf whose whole nature is being dragged to whatever size you like, and its\n * chrome is drawn in real pixels precisely so that stays true. Contrast the\n * switch, whose box *is* its design (see `model/natural-size.ts`).\n */\nexport const WINDOW_SIZE = { width: 720, height: 480 }\n", "import {\n command,\n driveCommand,\n easeInOut,\n node,\n Clip,\n type Command,\n type CommandArgs,\n Graphics2D,\n Node2D,\n type Node2DProps,\n pathOps,\n property,\n type Node,\n type NodeConfig,\n type PathBounds,\n type PathCommand,\n type RenderContext2D,\n type VectorPath,\n} from \"@motionscript/core\"\n\nimport {\n cursorArtworkPixels,\n cursorLook,\n CURSOR_STYLES,\n CURSOR_TYPES,\n DEFAULT_CURSOR_STYLE,\n DEFAULT_CURSOR_TYPE,\n type CursorStyle,\n type CursorType,\n} from \"../kit/cursor-outline\"\nimport {\n arcPointAt,\n resolveClickArc,\n type ClickArc,\n type ClickPoint,\n} from \"../kit/click-path\"\nimport { pressDepth } from \"../kit/press\"\n\n/** A cursor's own props, over the box every node has. */\nexport interface CursorProps extends Node2DProps {\n /** Which glyph to draw — see {@link CursorType}. */\n cursor: CursorType\n /** Which look to draw it in — see {@link CursorStyle}. */\n style: CursorStyle\n /**\n * How many clicks deep the chain is — a **monotonic phase**, not a depth.\n *\n * The one prop {@link Cursor.click} animates, and it climbs by exactly one\n * per click rather than going down and coming back: a tween can only run one\n * way, and a click is two. `pressDepth` turns the fractional part into the\n * down-and-up envelope, which is zero at both whole numbers — so a finished\n * click leaves the pointer at rest by arithmetic rather than by a reset the\n * command would have to remember to write. Exactly `Button.press`, and the\n * same function drives both.\n */\n press: number\n /** Seconds a {@link Cursor.click} runs for when its row states no duration. */\n clickDuration: number\n}\n\n/** How far the glyph shrinks at the bottom of a press. */\nconst PRESS_DIP = 0.12\n/** The ring's radius at the end of a click, as a fraction of the glyph's box. */\nconst RIPPLE_REACH = 1.4\nconst RIPPLE_OPACITY = 0.5\n\n/**\n * A mouse cursor, as a node.\n *\n * The one thing a screen recording has that a motion-graphics scene doesn't:\n * something on screen standing in for the person. Every explainer that shows an\n * interface needs one, and the alternatives are all worse — a screenshot of a\n * cursor pastes in at one size, and a hand-drawn path is a shape nobody can\n * change their mind about.\n *\n * Which pointer, whose, and how far through a click it is. Where it sits and\n * how big it is are the transform every node has; the contours and both colours\n * come out of the first two (see `kit/cursor-outline`, which holds the artwork\n * and the reasoning).\n *\n * ## A `Node2D`, and a leaf\n *\n * Not a `ShapeNode`, on the reasoning `Code` records in its own changeset: the\n * paint slots a shape carries are a surface this node cannot honour. A cursor's\n * colours are not a matter of taste — the Windows arrow is white with a black\n * keyline — so an author asked to set them by hand would be re-deriving a fact\n * rather than making a choice, and a cursor with the wrong two colours is not a\n * stylized cursor, it is a mistake. Declaring `fill`/`overlay`/`stroke`/\n * `shadow` and then ignoring them was a surface a host had no way to know was\n * inert; not declaring them is one it cannot offer.\n *\n * It refuses children for the same kind of reason. Nothing goes *inside* a\n * pointer, and a drop target on one is a place to lose a node.\n *\n * ## Why it paints itself, in two passes\n *\n * The keyline and the body, in that order: the outer contour filled black, then\n * the inner one filled white over it, leaving the keyline showing as the gap\n * between them. Not a fill with a stroke round it, because a stroke's weight is\n * a pixel count and this glyph is stretched onto whatever box the node has —\n * see the module note on `kit/cursor-outline`.\n *\n * ## The hotspot is the box's top-left\n *\n * Both the click ripple and the press dip are anchored there rather than at the\n * node's centre, because that is where a pointer actually points. A dip about\n * the centre would walk the tip half a pixel down and right at the bottom of\n * every press, which reads as the cursor sliding rather than pressing.\n *\n * ## Animating one\n *\n * `cursor` and `style` are ordinary props, so a `to` command can change either.\n * Neither has a tween function, and motion-script's fallback for a value it\n * can't interpolate is to hold the old one until the command ends and then\n * snap — which is exactly right here: there is no halfway between an arrow and\n * a pointing hand, and a cursor that changes shape *at the moment it reaches\n * the link* is what the author is after. A zero-duration `to` is the instant\n * switch.\n */\n@node({\n key: \"cursor\",\n parentKey: \"node\",\n forkable: true,\n layout: {\n children: \"freeform\",\n defaultWidthMode: \"fixed\",\n defaultHeightMode: \"fixed\",\n acceptsChildren: false,\n },\n seed: {\n width: 32,\n height: 32,\n },\n})\nexport class Cursor extends Node2D<CursorProps> {\n @property({ default: DEFAULT_CURSOR_TYPE, kind: \"enum\", options: CURSOR_TYPES })\n declare cursor: CursorType\n\n @property({ default: DEFAULT_CURSOR_STYLE, kind: \"enum\", options: CURSOR_STYLES })\n declare style: CursorStyle\n\n @property({ default: 0, min: 0 }) declare press: number\n\n @property({ default: 0.5, min: 0, step: 0.05, unit: \"s\" })\n declare clickDuration: number\n\n constructor(props: NodeConfig<Cursor, CursorProps>) {\n super(props as NodeConfig<Node2D, Node2DProps>)\n }\n\n /**\n * Travel to a control, press it, and let go — the whole gesture as one row.\n *\n * **`target` is a node id, and it is resolved when the command is built.**\n * That is the point of the command: the destination and the curve to it are\n * derived from where the control *currently* is, so moving a switch on the\n * canvas re-aims the animation with nothing to re-author. A baked path is\n * correct only for the two points it was baked against, which is what made\n * this a chore worth removing. An id rather than a `key` because `NodeSpec.key`\n * is optional and never generated, so it is not a handle every node has; an\n * expression is not an option either, since a command that does not `bind`\n * refuses one outright (`SceneTimeline._assertNoExpressions`).\n *\n * Naming nothing — or naming a node absent at this moment — clicks in place,\n * which is the same answer the document's own node references give for\n * something not there. A click is a legitimate beat over a rectangle or an\n * image, so refusing would cost more than it caught.\n *\n * `activate` drives the control itself: a switch throws, a button takes a\n * press. Both are written straight to the other node, which the timeline\n * restores along with this one because it records what a command writes\n * rather than what it targets.\n */\n @command({\n args: [\n { key: \"target\", kind: \"nodeRef\" },\n {\n key: \"clickShape\",\n kind: \"arc\",\n properties: {\n bow: { kind: \"number\", min: -1, max: 1, step: 0.01 },\n lean: { kind: \"number\", min: 0, max: 1, step: 0.01 },\n },\n },\n { key: \"activate\", kind: \"boolean\", default: true },\n ],\n })\n click(\n args: CommandArgs<{\n target?: string\n clickShape?: Partial<ClickArc>\n activate?: boolean\n }> & { duration?: number },\n ): Command<CursorProps> {\n const duration = args.duration && args.duration > 0 ? args.duration : this.clickDuration\n const easing = args.easing ?? easeInOut()\n const arc = resolveClickArc(args.data?.clickShape)\n const target = this._controlNamed(args.data?.target)\n\n const from: ClickPoint = { x: this.x, y: this.y }\n const to = target ? this._reach(target) : from\n const press = Math.floor(this.press)\n const activated =\n args.data?.activate === false ? null : target && activateOf(target)\n\n return driveCommand(duration, (t) => {\n // Travel over the lead-in, then hold still while the click happens: a\n // pointer that is still moving as it presses reads as a drag.\n const travel = easing(Math.min(1, t / TRAVEL))\n const at = arcPointAt(from, to, arc, travel)\n const phase = t <= TRAVEL ? 0 : (t - TRAVEL) / (1 - TRAVEL)\n this.set({ x: at.x, y: at.y, press: press + phase } as Partial<CursorProps>)\n activated?.(phase)\n }) as unknown as Command<CursorProps>\n }\n\n /**\n * Keyline, then body — the two-tone cursor, in the style's own colours, with\n * the click ripple behind them.\n *\n * The glyph is one `Graphics2D` with two shape groups rather than two\n * submissions: `fill()` scopes to the shapes declared since the last one, so\n * the second `path()` starts a group of its own and the two paint in order.\n * The ripple is a submission of its own because it carries a group opacity,\n * and `Graphics2D.opacity` composites the whole list — chaining it would fade\n * the pointer along with the ring.\n */\n protected override renderSelf(ctx: RenderContext2D): void {\n const depth = pressDepth(this.press)\n this._drawRipple(ctx, depth)\n\n const scale = 1 - PRESS_DIP * depth\n const { keyline, body } = this.artwork(scale)\n if (keyline.length === 0) return\n const look = cursorLook(this.style)\n const centerBounds = this.centerBounds(scale)\n ctx.draw(\n new Graphics2D()\n .path({ data: keyline, centerBounds })\n .fill(look.keyline)\n .path({ data: body, centerBounds })\n .fill(look.body)\n )\n }\n\n /** Nothing goes inside a pointer — see the class note. */\n protected override acceptsChild(_child: Node): boolean {\n return false\n }\n\n /**\n * The node's laid-out rect as a clip outline, so `clip` and any backdrop\n * effect are confined to the box rather than to nothing.\n *\n * Picking needs no help here: `Node2D` already grabs on the layout box, which\n * is the answer a cursor wants. An arrow occupies about a third of its box,\n * so picking on the silhouette would mean clicking one required hitting the\n * arrow itself — which on a node whose whole job is to be small on screen\n * reads as the app ignoring you.\n */\n protected override clipSelf(): Clip {\n return new Clip().rect({\n width: this.layoutBounds.width,\n height: this.layoutBounds.height,\n })\n }\n\n /**\n * The expanding ring a click leaves, centred on the hotspot.\n *\n * Drawn from the press envelope rather than from a phase of its own, so it\n * cannot drift out of step with the dip — and so a cursor at rest draws\n * nothing at all, which is the common case on every frame.\n */\n private _drawRipple(ctx: RenderContext2D, depth: number): void {\n if (depth <= 0) return\n const { width, height } = this.layoutBounds\n const span = Math.max(width, height)\n if (span <= 0) return\n const tip = this._hotspot()\n const radius = span * RIPPLE_REACH * depth\n ctx.draw(\n new Graphics2D()\n .ellipse({\n x: tip.x,\n y: tip.y,\n width: radius * 2,\n height: radius * 2,\n })\n .stroke({\n weight: Math.max(1, span * 0.06 * (1 - depth)),\n fill: cursorLook(this.style).keyline,\n align: \"center\",\n })\n .opacity(RIPPLE_OPACITY * (1 - depth))\n )\n }\n\n /** Where the pointer points, in the node's own y-up local space. */\n private _hotspot(): ClickPoint {\n return { x: -this.layoutBounds.width / 2, y: this.layoutBounds.height / 2 }\n }\n\n /**\n * Where this cursor has to sit for its hotspot to land on `target`'s centre,\n * in this node's **parent** space.\n *\n * `global` is world-space and `x`/`y` are parent-relative, so the parent's own\n * world contribution comes back off — the conversion `Node2D.global` documents.\n * The half-box offset is what makes the tip, rather than the cursor's centre,\n * the thing that arrives.\n */\n private _reach(target: Node2D): ClickPoint {\n const parent = this.parent2D\n const origin = parent ? parent.global : { x: 0, y: 0 }\n const at = target.global\n const tip = this._hotspot()\n return { x: at.x - origin.x - tip.x, y: at.y - origin.y - tip.y }\n }\n\n /** The node `id` names, from the root of this cursor's own tree. */\n private _controlNamed(id: string | undefined): Node2D | undefined {\n if (id === undefined || id === \"\") return undefined\n let root: Node = this\n while (root.parent) root = root.parent\n return findNode(root, id)\n }\n\n /**\n * The glyph's two contours in the renderer's local space.\n *\n * `cursorArtworkPixels` works y-**down** (the convention the author box and a\n * stored `d` string share), and a node's own space is y-up — hence the flip,\n * which is the whole of the difference between the two.\n */\n private artwork(scale = 1): { keyline: PathCommand[]; body: PathCommand[] } {\n const flip = (p: { x: number; y: number }) => ({ x: p.x, y: -p.y })\n const pixels = cursorArtworkPixels(\n this.cursor,\n this.style,\n this.layoutBounds.width * scale,\n this.layoutBounds.height * scale\n )\n const commands = (path: VectorPath) => pathOps.anchorCommands(path, flip)\n return { keyline: commands(pixels.keyline), body: commands(pixels.body) }\n }\n\n /**\n * The frame both contours are centred against: the node's own box, or the\n * dipped box pinned to the hotspot while a press is under way.\n *\n * Stated rather than left to the descriptor's default, which centres a path\n * on *its own* bounding box. Here that default would break the drawing\n * outright — the body is smaller than the keyline, so each would be centred\n * on a different rectangle and the two would come apart.\n */\n private centerBounds(scale = 1): PathBounds {\n const w = (this.layoutBounds.width * scale) / 2\n const h = (this.layoutBounds.height * scale) / 2\n const tip = this._hotspot()\n // Pinned to the tip, so the dip shrinks the glyph in place rather than\n // walking it down and to the right.\n const cx = tip.x + w\n const cy = tip.y - h\n return [cx - w, cy - h, cx + w, cy + h]\n }\n}\n\n/** The fraction of a click spent travelling, before the press itself. */\nconst TRAVEL = 0.62\n\n/** Depth-first by document id; `undefined` for a node not in this tree. */\nfunction findNode(at: Node, id: string): Node2D | undefined {\n if (at.id === id) return at as Node2D\n for (const child of at._allChildren) {\n const found = findNode(child, id)\n if (found) return found\n }\n return undefined\n}\n\n/**\n * How `target` answers a click, as a function of the press phase — or `null`\n * for a node that has no answer, which is every type but the two controls.\n *\n * Duck-typed on the prop rather than on the class, because a **forked**\n * component is a different class carrying the same source and an `instanceof`\n * against the original is false for it. That is the same argument\n * `Registry.nodeCommand` makes for dispatching on the method name.\n */\nfunction activateOf(target: Node2D): ((phase: number) => void) | null {\n const props = target as unknown as Record<string, unknown>\n\n if (typeof props.progress === \"number\" && \"accent\" in props) {\n // A switch throws once, at the bottom of the press, and stays thrown —\n // read when the command is *built*, matching the timeline's own order, so\n // two clicks at the same `at` agree rather than alternating.\n const from = props.progress\n const to = from > 0.5 ? 0 : 1\n return (phase) => {\n const t = Math.min(1, Math.max(0, (phase - PRESS_DOWN) / (1 - PRESS_DOWN)))\n target.set({ progress: from + (to - from) * t } as never)\n }\n }\n\n if (typeof props.press === \"number\" && \"label\" in props) {\n // A button takes the pointer's own envelope, one whole press per click.\n const base = Math.floor(props.press)\n return (phase) => target.set({ press: base + phase } as never)\n }\n\n return null\n}\n\n/** Where in the press the control actually reacts — the bottom of the travel. */\nconst PRESS_DOWN = 1 / 3\n", "import { Switch } from \"./switch\";\nimport { Button } from \"./button\";\nimport { Window } from \"./window\";\nimport { Cursor } from \"./cursor\";\n\n/**\n * Every node type this package publishes.\n *\n * A host registers these by handing the array to an engine —\n * `new Engine(platform, { nodes: NODES })` — which reads each class's `@node()`\n * key. The decorator only *declares* that key: nothing is registered by the mere\n * act of importing a module, so a registry holds exactly what its owner asked\n * for and two engines in one process can differ about it.\n *\n * Listing class **values** is also what survives bundling. This package declares\n * `sideEffects: false`, which licenses a bundler to drop a module nothing\n * imports a value from, and a document names a node type by string — so\n * `import \"./switch\"` for its side effect is exactly what gets shaken out, where an\n * array of bindings is a data dependency that cannot be.\n */\nexport const NODES = [Switch, Button, Window, Cursor];\n"],
|
|
5
|
-
"mappings": "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",
|
|
6
|
-
"names": ["command", "easeInOut", "node", "Graphics2D", "property", "pathOps", "Switch", "ControlNode", "props", "_child", "args", "toggleState", "duration", "easing", "easeInOut", "to", "bounds", "width", "height", "look", "switchLook", "custom", "p", "trackHeight", "widest", "margin", "knobSize", "travel", "ctx", "m", "track", "Graphics2D", "mixHex", "knob", "icon", "x", "color", "opacity", "art", "controlIconPixels", "shift", "pathOps", "__decorate", "property", "CONTROL_STYLES", "DEFAULT_CONTROL_ICON", "CONTROL_ICONS", "command", "node", "
|
|
4
|
+
"sourcesContent": ["import {\n command,\n easeInOut,\n node,\n type Command,\n type CommandArgs,\n Graphics2D,\n property,\n type Node,\n type NodeConfig,\n type RenderContext2D,\n} from \"@motionscript/sdk\"\n\nimport {\n CONTROL_ICONS,\n controlIconPixels,\n DEFAULT_CONTROL_ICON,\n type ControlIcon,\n} from \"../kit/control-icons\"\nimport {\n CONTROL_STYLES,\n DEFAULT_CONTROL_STYLE,\n mixHex,\n switchLook,\n type ControlStyle,\n} from \"../kit/control-style\"\nimport { pathOps } from \"@motionscript/sdk\"\nimport { ControlNode, type ControlNodeProps } from \"../kit/control-node\"\n\n/** A switch's own props, over the box and the sound schedule every control has. */\nexport interface SwitchProps extends ControlNodeProps {\n /** Which design language to draw in — see {@link ControlStyle}. */\n style: ControlStyle\n /**\n * Where the knob is, `0` (off) to `1` (on).\n *\n * The **whole** of the node's animated state, and an ordinary tweenable number\n * rather than a boolean, which is the point: a switch's flip is a slide, and\n * \"half a boolean\" is not a frame anyone could draw. The document's Toggle\n * commands are lowered to tweens of this by the builder, each toward the\n * concrete state its chain resolves to (see `nodes/ui-kit/commands.ts`).\n */\n progress: number\n /** The colour the track turns when the switch is on. */\n accent: string\n /** The mark inside the knob when on. */\n iconOn: ControlIcon\n /** …and when off. */\n iconOff: ControlIcon\n /**\n * The track when off, the knob, and the mark's ink — honoured **only** in the\n * `custom` style, where they are what \"custom\" means. In the three named looks\n * these are ignored in favour of the platform's own, because a Material switch\n * with someone's grey in it is not a Material switch. See `model/control-style`.\n */\n track: string\n knob: string\n ink: string\n /** Corner radius as a fraction of half the track's height. `custom` only. */\n radius: number\n /** Seconds a {@link Switch.toggle} runs for when its row states no duration. */\n toggleDuration: number\n}\n\n/**\n * A **switch**, as a node.\n *\n * The control every product demo turns on at some point, and — like the cursor\n * one shelf over — a thing that was previously either a screen recording or four\n * rectangles animated by hand. Four rectangles is the interesting failure: it\n * works until you want the knob to arrive a beat later, at which point you are\n * keeping three tweens in sync by eye.\n *\n * ## One control, not thirty\n *\n * Its whole surface is a style, an accent, two marks and a resting state.\n * Everything else — the knob's diameter, its inset, whether it grows on the way\n * across, whether the track carries an outline — comes out of the style, because\n * those are facts about iOS and Material rather than decisions anybody making a\n * video wants to take. `model/control-style` has that argument at length, and\n * the `custom` style is the escape hatch for the case none of the three covers.\n *\n * ## Why it paints itself\n *\n * A `node` in the registry rather than a `shape`, on the cursor's reasoning with\n * one addition. A switch is *five* painted parts — track, outline, knob, and a\n * mark on each side of the fade — and a Fills section could only ever reach one\n * of them, so it would be a control whose effect an author would have to\n * discover by experiment. What it takes instead is the accent, which is the one\n * paint decision a switch actually has.\n *\n * ## Drawing, in four passes\n *\n * Track, knob, then the two marks. Four `draw` calls rather than one chained\n * `Graphics2D` because three of them carry a group opacity or a shadow of their\n * own, and `Graphics2D.opacity` composites the *whole* list — chaining them\n * would fade the track along with the mark it was meant to be cross-fading.\n */\n\n/** What {@link Switch.toggle} should leave the switch in. */\nexport type ToggleState = \"toggle\" | \"on\" | \"off\"\n\n@node({\n key: \"switch\",\n parentKey: \"node\",\n forkable: true,\n layout: {\n children: \"freeform\",\n defaultWidthMode: \"fixed\",\n defaultHeightMode: \"fixed\",\n acceptsChildren: false,\n },\n seed: {\n width: 204,\n height: 124,\n },\n})\nexport class Switch extends ControlNode<SwitchProps> {\n @property({ default: DEFAULT_CONTROL_STYLE, kind: \"enum\", options: CONTROL_STYLES })\n declare style: ControlStyle\n @property({ default: 0, min: 0, max: 1, step: 0.01 }) declare progress: number\n @property({ default: \"#34c759\", kind: \"color\" }) declare accent: string\n @property({ default: DEFAULT_CONTROL_ICON, kind: \"enum\", options: CONTROL_ICONS })\n declare iconOn: ControlIcon\n @property({ default: DEFAULT_CONTROL_ICON, kind: \"enum\", options: CONTROL_ICONS })\n declare iconOff: ControlIcon\n @property({ default: \"#e9e9eb\", kind: \"color\" }) declare track: string\n @property({ default: \"#ffffff\", kind: \"color\" }) declare knob: string\n @property({ default: \"#8e8e93\", kind: \"color\" }) declare ink: string\n @property({ default: 1, min: 0, max: 1, step: 0.01 }) declare radius: number\n @property({ default: 0.28, min: 0, step: 0.05, unit: \"s\" })\n declare toggleDuration: number\n\n constructor(props: NodeConfig<Switch, SwitchProps>) {\n super(props)\n }\n\n /** A switch is a control, not a container — its five painted parts are its own. */\n protected override acceptsChild(_child: Node): boolean {\n return false\n }\n\n /**\n * Throw the switch. `\"on\"`/`\"off\"` are absolute; `\"toggle\"` reads the knob's\n * current position and goes the other way — read when the **command is\n * built**, matching the timeline's own order, so two `toggle`s at the same\n * `at` agree rather than alternating.\n */\n @command({ args: [{ key: \"toggleState\", kind: \"enum\", default: \"toggle\", options: [\"toggle\", \"on\", \"off\"] }] })\n toggle(args: CommandArgs<{ toggleState?: ToggleState }> & { duration?: number }): Command<SwitchProps> {\n const toggleState = args.data?.toggleState ?? \"toggle\"\n const duration = args.duration && args.duration > 0 ? args.duration : this.toggleDuration\n const easing = args.easing ?? easeInOut()\n const to =\n toggleState === \"on\" ? 1 : toggleState === \"off\" ? 0 : this.progress > 0.5 ? 0 : 1\n return this.to({ data: { progress: to } as Partial<SwitchProps>, duration, easing })\n }\n\n /**\n * Every measurement the drawing needs, in pixels of the node's own box.\n *\n * Resolved in one place rather than at each use so the knob, its travel and\n * the mark inside it cannot disagree about where the knob is — three\n * near-identical expressions is how a mark ends up a pixel behind the thing it\n * is drawn on.\n */\n private metrics() {\n const bounds = this.layoutBounds\n const width = bounds?.width ?? 0\n const height = bounds?.height ?? 0\n const look = switchLook(this.style)\n const custom = this.style === \"custom\"\n const p = Math.min(1, Math.max(0, this.progress))\n\n const trackHeight = height * look.track\n // The margin is taken from the *largest* the knob ever gets, so a knob that\n // grows (Material's does) grows in place rather than dragging its own travel\n // with it — which would make the same switch stop at a different place\n // depending on how far through the flip it was.\n const widest = trackHeight * Math.max(look.knobOff, look.knobOn)\n const margin = (trackHeight - widest) / 2\n const knobSize =\n trackHeight * (look.knobOff + (look.knobOn - look.knobOff) * p)\n const travel = width / 2 - margin - widest / 2\n\n return {\n width,\n height,\n look,\n p,\n trackHeight,\n trackRadius: (trackHeight / 2) * (custom ? this.radius : look.radius),\n outline: trackHeight * look.outline,\n knobSize,\n knobX: -travel + travel * 2 * p,\n offTrack: custom ? this.track : look.offTrack,\n offKnob: custom ? this.knob : look.offKnob,\n offIcon: custom ? this.ink : look.offIcon,\n }\n }\n\n protected override renderSelf(ctx: RenderContext2D): void {\n const m = this.metrics()\n if (m.width <= 0 || m.trackHeight <= 0) return\n\n // The track, and its outline where the look has one. Both interpolate\n // toward the accent, which is what makes Material's outline disappear as it\n // turns on without needing a second rule for the weight.\n const track = new Graphics2D().rect({\n x: 0,\n y: 0,\n width: m.width,\n height: m.trackHeight,\n cornerRadius: m.trackRadius,\n })\n track.fill(mixHex(m.offTrack, this.accent, m.p))\n if (m.outline > 0) {\n track.stroke({\n weight: m.outline,\n fill: mixHex(m.look.offOutline, this.accent, m.p),\n align: \"inside\",\n })\n }\n ctx.draw(track)\n\n // The knob. Its shadow is the look's rather than a paint stack's, for the\n // same reason its diameter is: an iOS knob has a soft shadow and a Material\n // one does not, and that is part of what tells them apart.\n const knob = new Graphics2D().ellipse({\n x: m.knobX,\n y: 0,\n width: m.knobSize,\n height: m.knobSize,\n })\n if (m.look.shadow > 0) {\n knob.shadow({\n blur: m.trackHeight * m.look.shadow,\n offset: { x: 0, y: -m.trackHeight * m.look.shadow * 0.25 },\n fill: \"rgba(0, 0, 0, 0.22)\",\n })\n }\n knob.fill(mixHex(m.offKnob, m.look.onKnob, m.p))\n ctx.draw(knob)\n\n // The two marks, cross-fading. Drawn even at zero opacity — skipping them\n // would be a branch that saves nothing and puts a pop at each end of the\n // fade the moment a renderer rounds the alpha differently.\n this.drawIcon(ctx, this.iconOff, m.knobX, m.knobSize, m.offIcon, 1 - m.p)\n this.drawIcon(ctx, this.iconOn, m.knobX, m.knobSize, this.accent, m.p)\n }\n\n /**\n * One mark, centred in the knob.\n *\n * Its ink is the *other* side's colour by construction — the off mark takes\n * the look's own grey against a light knob, and the on mark takes the accent,\n * which the knob has turned white behind by the time it is visible. That is\n * why neither is a field: the pairing is what makes the mark legible, and an\n * author free to set it is an author free to make it invisible.\n *\n * Stroked and filled contours are two draws because they are two paints; a\n * glyph carries one or the other and never both, so the second is a no-op for\n * every mark in the catalogue today (see `model/control-icons`).\n */\n private drawIcon(\n ctx: RenderContext2D,\n icon: ControlIcon,\n x: number,\n knobSize: number,\n color: string,\n opacity: number\n ): void {\n if (icon === \"none\" || opacity <= 0 || knobSize <= 0) return\n // Just over half the knob, which is where every platform sets a glyph\n // inside a circular target: larger reads as a badge, smaller as a speck.\n const art = controlIconPixels(icon, knobSize * 0.56)\n const shift = (p: { x: number; y: number }) => ({ x: p.x + x, y: p.y })\n\n if (art.fills.length > 0) {\n ctx.draw(\n new Graphics2D()\n .path({ data: pathOps.anchorCommands(art.fills, shift) })\n .fill(color)\n .opacity(opacity)\n )\n }\n if (art.strokes.length > 0) {\n ctx.draw(\n new Graphics2D()\n .path({ data: pathOps.anchorCommands(art.strokes, shift) })\n .stroke({\n weight: art.weight,\n fill: color,\n cap: \"round\",\n join: \"round\",\n align: \"center\",\n })\n .opacity(opacity)\n )\n }\n }\n\n}\n", "import {\n clamp,\n command,\n driveCommand,\n node,\n type Command,\n type CommandArgs,\n Graphics2D,\n property,\n type Node,\n type NodeConfig,\n type RenderContext2D,\n} from \"@motionscript/sdk\"\n\nimport {\n CONTROL_ICONS,\n controlIconPixels,\n DEFAULT_CONTROL_ICON,\n type ControlIcon,\n} from \"../kit/control-icons\"\nimport {\n buttonLook,\n CONTROL_STYLES,\n DEFAULT_CONTROL_STYLE,\n mixHex,\n readableInk,\n type ControlStyle,\n} from \"../kit/control-style\"\nimport { pathOps } from \"@motionscript/sdk\"\nimport { pressDepth } from \"../kit/press\"\nimport { ControlNode, type ControlNodeProps } from \"../kit/control-node\"\n\n/** A button's own props, over the box and the sound schedule every control has. */\nexport interface ButtonProps extends ControlNodeProps {\n /** Which design language to draw in — see {@link ControlStyle}. */\n style: ControlStyle\n /** The words on it. */\n label: string\n /** The face they are set in. Empty falls back to the built-in. */\n fontFamily: string\n /** The button's fill, and the colour the label is chosen to read against. */\n accent: string\n /** A mark before the label. `none` draws none and takes no room. */\n icon: ControlIcon\n /**\n * How many presses deep the chain is — a **monotonic phase**, not a depth.\n *\n * The one prop the Press command animates, and it climbs by exactly one per\n * press rather than going down and coming back: a tween can only run one way,\n * and a press is two. {@link pressDepth} turns the fractional part into the\n * down-and-up envelope, which is zero at both whole numbers — so a finished\n * press leaves the button at rest by arithmetic rather than by a reset the\n * builder would have to remember to write. See {@link Button.pulse}.\n */\n press: number\n /**\n * The label's ink and the corner radius — honoured **only** in the `custom`\n * style, for the reason {@link SwitchProps.track} states. Empty ink means\n * \"work it out\", which is what every named style does.\n */\n ink: string\n radius: number\n /** Seconds a press runs for when its row states no duration. */\n pressDuration: number\n}\n\n/**\n * A **button**, as a node.\n *\n * The Switch's sibling and the other half of \"a video about an interface\": the\n * thing the cursor moves to before the switch flips. Same bargain — one style\n * question rather than thirty controls, an accent, a mark — and one difference\n * that runs all the way through it: a button's whole state is *transient*. A\n * switch is on or off between commands; a button is at rest, and a Press is a\n * moment rather than a change.\n *\n * ## The label's colour is not a control\n *\n * It is computed from the fill ({@link readableInk}), because it is not a\n * decision — it is a consequence, and the only decisions available are the\n * correct one and a mistake. The `custom` style exposes it for the case where\n * the fill is an image or the author is matching a real product's off-spec\n * button; every other style works it out and is right.\n *\n * ## The label is placed, not measured\n *\n * A `Graphics2D.text` hands back no metrics, so the icon-plus-label pair is\n * centred against an **estimate** of the label's width — the same trade the\n * grapher's axis labels already make (see `labelWidthEm`), and acceptable for\n * the same reason: being a few pixels off centre on a control the author is\n * sizing by eye is a smaller error than the alternatives, which are measuring\n * text in a package with no DOM or composing the button out of real nodes and\n * giving up the press animation.\n */\n@node({\n key: \"button\",\n parentKey: \"node\",\n forkable: true,\n layout: {\n children: \"freeform\",\n defaultWidthMode: \"fixed\",\n defaultHeightMode: \"fixed\",\n acceptsChildren: false,\n },\n seed: {\n width: 282,\n height: 88,\n },\n})\nexport class Button extends ControlNode<ButtonProps> {\n @property({ default: DEFAULT_CONTROL_STYLE, kind: \"enum\", options: CONTROL_STYLES })\n declare style: ControlStyle\n @property({ default: \"Button\" }) declare label: string\n @property({ default: \"\" }) declare fontFamily: string\n @property({ default: \"#007aff\", kind: \"color\" }) declare accent: string\n @property({ default: DEFAULT_CONTROL_ICON, kind: \"enum\", options: CONTROL_ICONS })\n declare icon: ControlIcon\n @property({ default: 0, min: 0 }) declare press: number\n @property({ default: \"\", kind: \"color\" }) declare ink: string\n @property({ default: 0.48, min: 0, max: 1, step: 0.01 }) declare radius: number\n @property({ default: 0.24, min: 0, step: 0.05, unit: \"s\" })\n declare pressDuration: number\n\n constructor(props: NodeConfig<Button, ButtonProps>) {\n super(props)\n }\n\n /** A button is a control, not a container — its label and mark are its own. */\n protected override acceptsChild(_child: Node): boolean {\n return false\n }\n\n /**\n * Press and release the button: `press` climbs from the whole number it\n * starts at to the next, and {@link pressDepth} draws that as one press. The\n * phase only climbs, as `Cursor.click` writes it, so the row never undoes\n * the count an earlier press left.\n *\n * Named `pulse` and published as `press` to avoid shadowing the `press` prop\n * this drives. Same split `Code.erase` makes to avoid shadowing `Node.remove`.\n */\n @command({ key: \"press\" })\n pulse(args: CommandArgs<Record<string, never>> & { duration?: number }): Command<ButtonProps> {\n const duration = args.duration && args.duration > 0 ? args.duration : this.pressDuration\n const base = Math.floor(this.press)\n return driveCommand(\n duration,\n // Clamped so an overshooting easing cannot carry the phase into a second press.\n (phase) => this.set({ press: base + clamp(phase, 0, 1) } as Partial<ButtonProps>),\n args.easing\n ) as Command<ButtonProps>\n }\n\n /** Every measurement the drawing needs, resolved once — see {@link Switch.metrics}. */\n private metrics() {\n const bounds = this.layoutBounds\n const width = bounds?.width ?? 0\n const height = bounds?.height ?? 0\n const look = buttonLook(this.style)\n const custom = this.style === \"custom\"\n const depth = pressDepth(this.press)\n\n // The pressed fill, which the ink is then chosen against rather than against\n // the resting one: a button dark enough at rest to want white text is darker\n // still while held, so deriving from the resting colour would be deriving\n // from a frame that isn't on screen.\n const fill = mixHex(this.accent, \"#000000\", look.pressDim * depth)\n const fontSize = height * look.fontSize\n\n return {\n width,\n height,\n look,\n depth,\n fill,\n fontSize,\n scale: 1 - look.pressScale * depth,\n radius: (height / 2) * (custom ? this.radius : look.radius),\n ink: custom && this.ink !== \"\" ? this.ink : readableInk(fill),\n iconSize: this.icon === \"none\" ? 0 : fontSize * look.iconSize,\n gap: this.icon === \"none\" ? 0 : fontSize * look.iconGap,\n }\n }\n\n protected override renderSelf(ctx: RenderContext2D): void {\n const m = this.metrics()\n if (m.width <= 0 || m.height <= 0) return\n\n // The whole drawing shrinks about the node's own centre rather than each\n // part about its own bounding box, which is why the pivot is stated: three\n // separate draws each scaled about themselves would slide apart under the\n // press instead of settling together.\n const pivot = { x: 0, y: 0 }\n\n const face = new Graphics2D().rect({\n x: 0,\n y: 0,\n width: m.width,\n height: m.height,\n cornerRadius: m.radius,\n })\n face.fill(m.fill)\n if (m.look.outline > 0) {\n face.stroke({\n weight: m.height * m.look.outline,\n // A rim a shade darker than the face, which is what a hairline border\n // is on every filled control that has one — a *colour* here would be a\n // second accent nobody asked for.\n fill: mixHex(m.fill, \"#000000\", 0.18),\n align: \"inside\",\n })\n }\n if (m.look.shadow > 0) {\n face.shadow({\n blur: m.height * m.look.shadow,\n offset: { x: 0, y: -m.height * m.look.shadow * 0.3 },\n fill: \"rgba(0, 0, 0, 0.18)\",\n })\n }\n ctx.draw(face.scale(m.scale, pivot))\n\n // Where the icon-and-label pair sits. The pair is centred as a unit, so the\n // label shifts right by half the room the icon takes and the icon sits in\n // the gap that opens on the left.\n const labelWidth = estimateWidth(this.label, m.fontSize)\n const block = m.iconSize + m.gap\n const left = -(labelWidth + block) / 2\n\n if (m.iconSize > 0) {\n this.drawIcon(ctx, left + m.iconSize / 2, m.iconSize, m.ink, m.scale)\n }\n\n if (this.label !== \"\") {\n ctx.draw(\n new Graphics2D()\n .text({\n text: this.label,\n x: left + block + labelWidth / 2,\n // Centred on the baseline's own box rather than nudged: the text\n // descriptor centres its box on the point it is given, which is\n // already the vertical middle of the node.\n y: 0,\n width: labelWidth,\n fontSize: m.fontSize,\n // Already resolved: the props mapper fills an empty family in from\n // the host (see `nodes/kit/text-defaults`), so the node never has to\n // know which typeface this application ships.\n fontFamily: this.fontFamily,\n fontWeight: m.look.fontWeight,\n letterSpacing: m.fontSize * m.look.letterSpacing,\n textAlign: \"center\",\n wrap: false,\n })\n .fill(m.ink)\n .scale(m.scale, pivot)\n )\n }\n }\n\n /** The leading mark — see {@link Switch.drawIcon}, which this mirrors. */\n private drawIcon(\n ctx: RenderContext2D,\n x: number,\n size: number,\n color: string,\n scale: number\n ): void {\n const art = controlIconPixels(this.icon, size)\n const shift = (p: { x: number; y: number }) => ({ x: p.x + x, y: p.y })\n const pivot = { x: 0, y: 0 }\n\n if (art.fills.length > 0) {\n ctx.draw(\n new Graphics2D()\n .path({ data: pathOps.anchorCommands(art.fills, shift) })\n .fill(color)\n .scale(scale, pivot)\n )\n }\n if (art.strokes.length > 0) {\n ctx.draw(\n new Graphics2D()\n .path({ data: pathOps.anchorCommands(art.strokes, shift) })\n .stroke({\n weight: art.weight,\n fill: color,\n cap: \"round\",\n join: \"round\",\n align: \"center\",\n })\n .scale(scale, pivot)\n )\n }\n }\n\n}\n\n/**\n * How wide `label` comes out, in pixels, near enough to centre it by.\n *\n * A per-character average rather than a shaping pass, which this package has no\n * way to run — see the class note. `0.55em` is the mean advance of a UI sans at\n * mixed case; the error on a real label is a few percent, and it lands as a few\n * pixels of off-centre on a control whose text is centred inside its own box\n * anyway.\n *\n * The whole label rather than its longest line, because a button's label does\n * not wrap (`wrap: false` above) — a caption that needs two lines is a caption,\n * not a button.\n */\nfunction estimateWidth(label: string, fontSize: number): number {\n return label.length * fontSize * 0.55\n}\n", "import {\n node,\n Graphics2D,\n Rect,\n property,\n type InsetsResolved,\n type RectProps,\n type RenderContext2D,\n type ShadowResolved,\n Vector2,\n} from \"@motionscript/sdk\"\n\nimport {\n DEFAULT_WINDOW_STYLE,\n windowLook,\n WINDOW_STYLES,\n type WindowLook,\n type WindowStyle,\n} from \"./window-style\"\n\n/** A window's own props, over the box and layout every `Rect` has. */\nexport interface WindowProps extends RectProps {\n /** Which operating system's chrome to draw — see {@link WindowStyle}. */\n style: WindowStyle\n /**\n * Draw the chrome's **dark** appearance.\n *\n * Inert on the two vintage styles, which shipped one appearance each and so\n * have no dark twin to switch to — see `WINDOW_DARK_STYLES`, which the panel\n * reads to hide the control rather than offer a switch that does nothing.\n */\n dark: boolean\n /** The words in the title bar. Empty draws none. */\n title: string\n /** The face they are set in. Empty falls back to the built-in. */\n fontFamily: string\n /** The client area behind the window's children. */\n body: string\n /**\n * How much bigger than life the chrome is drawn.\n *\n * `1` is the real operating system's own pixels — a 28pt macOS title bar is 28\n * units tall. The field exists because a window in a 1080p composition is\n * usually shown *larger* than life, and everything that makes chrome read as\n * chrome (the title bar's height, the traffic lights' 12px, a bevel's 2px) is\n * an absolute measurement rather than a fraction of the frame. Scaling the\n * node instead would scale its contents too.\n */\n chromeScale: number\n /** Draw the caption buttons. Off is a sheet, a palette, a dialog. */\n controls: boolean\n}\n\n/**\n * A **window**, as a node.\n *\n * The frame every software demo is shot inside, and the third thing the UI shelf\n * needed: the cursor is the person, the switch and the button are what they\n * touch, and this is the thing all of it is happening *in*.\n *\n * ## Why it is a `Rect`\n *\n * Alone among the controls here — the switch and the button both extend\n * {@link ControlNode}, which is a bare `Node2D`. A window is different in kind\n * because it has an **inside**: the whole point of one is to put a screenshot, a\n * chart or a caption in it, and that means real layout, real padding and a real\n * clip against the frame's own rounded corners. `Rect` is the one class in\n * motion-script that does all three, so the window is a Rect whose paint is\n * replaced rather than a node that draws a rectangle.\n *\n * {@link shapeGraphics} is overridden along with {@link renderSelf}, and that is\n * the half that makes the clip work: the silhouette a child is cut against has\n * to be the window's *rounded* outline, not the square box it was laid out in.\n *\n * ## Chrome is drawn in real pixels\n *\n * Everything else on this shelf states its metrics as fractions of its own box,\n * because a switch's box *is* its design. A window's is not: a title bar is 28\n * points tall on a small window and on a large one, which is exactly why a\n * window can be dragged to any size and still look like a window. So the look's\n * numbers are the operating system's own (see `model/window-style.ts`) and the\n * only thing between them and the canvas is {@link WindowProps.chromeScale}.\n *\n * That is also why this node is **not aspect-locked** where the switch is. Its\n * shape is not part of its identity — being resizable is.\n *\n * ## The chrome is padding\n *\n * A window's children belong in its **body**, not in its box: a child set to\n * fill its container should fill the client area and stop at the title bar,\n * because that is what filling a window means to everyone who has ever used one.\n * That is stated as {@link effectivePadding} — the same seam `Rect` already uses\n * to keep children clear of its stroke, extended with the frame's border and the\n * title bar's height. Saying it there rather than in the drawing is what makes it\n * true of *measurement* as well as of paint: `hug` sizes around it, `fill` sizes\n * inside it, and a child never has to know a title bar exists.\n *\n * ## No commands\n *\n * Deliberately none of its own. A switch flips and a button is pressed; a window\n * *is*, and everything anyone would animate about one — where it sits, how big\n * it is, whether it has faded in — is a property of the box that `to`, `moveTo`\n * and `scaleTo` already reach.\n */\n@node({\n key: \"window\",\n parentKey: \"node\",\n forkable: true,\n layout: {\n children: \"flow\",\n defaultWidthMode: \"fixed\",\n defaultHeightMode: \"fixed\",\n acceptsChildren: true,\n },\n seed: {\n width: 720,\n height: 480,\n },\n})\nexport class Window extends Rect<WindowProps> {\n @property({ default: DEFAULT_WINDOW_STYLE, kind: \"enum\", options: WINDOW_STYLES })\n declare style: WindowStyle\n @property({ default: false }) declare dark: boolean\n @property({ default: \"\" }) declare title: string\n @property({ default: \"\" }) declare fontFamily: string\n @property({ default: \"#ffffff\", kind: \"color\" }) declare body: string\n @property({ default: 1, min: 0.5, max: 3, step: 0.05, unit: \"×\" })\n declare chromeScale: number\n @property({ default: true }) declare controls: boolean\n\n /**\n * Every measurement the drawing needs, in the node's own pixels.\n *\n * The look's numbers times the scale, resolved once — three near-identical\n * `look.titleBar * scale` expressions is how a title bar and the thing drawn\n * inside it end up a pixel apart.\n *\n * The title bar is capped at half the window, so a window dragged shorter than\n * its own chrome degrades to a bar with a sliver under it rather than to a\n * client area with a negative height.\n */\n private metrics() {\n const bounds = this.layoutBounds\n const width = bounds?.width ?? 0\n const height = bounds?.height ?? 0\n const look = windowLook(this.style, this.dark)\n const scale = Math.max(0.01, this.chromeScale)\n\n const titleBar = Math.min(look.titleBar * scale, height / 2)\n return {\n width,\n height,\n look,\n scale,\n titleBar,\n radius: Math.min(look.radius * scale, width / 2, height / 2),\n border: look.border * scale,\n /** The top edge in the node's own y-up space. */\n top: height / 2,\n left: -width / 2,\n }\n }\n\n /**\n * The chrome, as padding — so children are laid out in the **body**.\n *\n * `Rect` already treats the part of its stroke that intrudes on the content\n * box as padding, for exactly this reason: a child must not be laid out under\n * paint the parent is going to draw over it. A window's chrome is the same\n * problem several sizes larger — a title bar is 28 points of the box that\n * belongs to the operating system, not to the author's content — so it is\n * answered in the same place, and everything downstream follows without\n * knowing why. A child at `width: \"fill\"` fills the client area; one at\n * `height: \"fill\"` stops under the title bar; a window that hugs hugs around\n * its chrome rather than inside it.\n *\n * The title bar's *uncapped* height is used, unlike everywhere in the drawing:\n * layout runs before this node has a box (that is what it is computing), and\n * the cap in {@link metrics} is a function of the box. A window shorter than\n * its own chrome therefore lays its children out in a content box of zero\n * height, which is the honest answer — there is no client area in a window\n * that is all title bar.\n */\n override effectivePadding(): InsetsResolved {\n const base = super.effectivePadding()\n const look = windowLook(this.style, this.dark)\n const scale = Math.max(0.01, this.chromeScale)\n // The frame's own border, doubled where the chrome is a two-ring bevel —\n // the same inset the title bar is drawn at, so the two cannot disagree.\n const inset = look.border * scale * (look.bevel ? 2 : 1)\n return {\n left: base.left + inset,\n right: base.right + inset,\n top: base.top + inset + look.titleBar * scale,\n bottom: base.bottom + inset,\n }\n }\n\n /**\n * The window's outline — what a nested child is clipped against.\n *\n * Overridden so the clip follows the *rounded* frame. `Rect`'s own answer is\n * the same shape with this node's `cornerRadius`, which a window does not\n * carry: its corner is the operating system's, not the author's.\n */\n protected override shapeGraphics(): Graphics2D {\n const m = this.metrics()\n return new Graphics2D().rect({\n x: 0,\n y: 0,\n width: m.width,\n height: m.height,\n cornerRadius: m.radius,\n })\n }\n\n protected override renderSelf(ctx: RenderContext2D): void {\n const m = this.metrics()\n if (m.width <= 0 || m.height <= 0) return\n\n this.drawFrame(ctx, m)\n this.drawTitleBar(ctx, m)\n if (m.look.pinstripe) this.drawPinstripes(ctx, m)\n this.drawTitle(ctx, m)\n if (this.controls) this.drawControls(ctx, m)\n }\n\n /** The client area, its outer border, and the shadow the whole thing casts. */\n private drawFrame(ctx: RenderContext2D, m: Metrics): void {\n const frame = new Graphics2D().rect({\n x: 0,\n y: 0,\n width: m.width,\n height: m.height,\n cornerRadius: m.radius,\n })\n const authored = this.shadow as ShadowResolved[]\n if (authored.length > 0) {\n frame.shadow(authored)\n } else if (m.look.shadow > 0) {\n frame.shadow(this.chromeShadow(m))\n }\n frame.fill(this.body)\n if (m.border > 0 && !m.look.bevel) {\n frame.stroke({\n weight: m.border,\n fill: m.look.borderColor,\n align: \"inside\",\n })\n }\n ctx.draw(frame)\n\n // Windows 95's frame is not a border, it is a **bevel**: two nested rings,\n // light on the top-left and dark on the bottom-right, which is what makes\n // the whole window read as a raised physical object. There is no stroke that\n // draws that — a stroke has one colour all the way round — so it is four\n // strips a side, drawn as two L-shaped pairs.\n if (m.look.bevel) this.drawBevel(ctx, m)\n }\n\n /**\n * The drop the chrome itself comes with, drawn when the author has not put a\n * stack of their own in the Shadows section.\n *\n * The one place a window's paint slots do **not** behave exactly like a\n * shape's, and the deviation is worth stating: on a `Rect` an empty Shadows\n * section means no shadow, whereas here it means *the chrome's own*. Both\n * modern chromes ship a drop — a macOS window is separated from the desktop by\n * nothing else — so an empty stack meaning \"flat\" would make every window look\n * wrong until the author discovered a section they had no reason to open, and\n * would make the two vintage chromes (which really are flat) indistinguishable\n * from a bug. Adding a shadow replaces it, wholesale, which is the behaviour\n * that lets an author light a window however they like; removing the last one\n * hands the chrome back its own.\n *\n * Its offset is a quarter of its blur, downward — negative in this y-up space\n * — which is what every OS's own drop uses.\n */\n private chromeShadow(m: Metrics): {\n blur: number\n offset: { x: number; y: number }\n fill: string\n } {\n const blur = m.look.shadow * m.scale\n return {\n blur,\n offset: { x: 0, y: -blur * 0.25 },\n fill: \"rgba(0, 0, 0, 0.28)\",\n }\n }\n\n /**\n * A raised (or, with `depth` negative, a sunken) 3D edge — Windows 95's whole\n * visual language, and the one piece of chrome here that cannot be a stroke.\n *\n * Two rings: an outer one in white / black and an inner one in light grey /\n * dark grey, each drawn as an L along the top-and-left and another along the\n * bottom-and-right. Reused for the caption chips, which are the same edge at\n * a smaller radius.\n */\n private drawBevel(\n ctx: RenderContext2D,\n m: Metrics,\n box = { x: 0, y: 0, width: m.width, height: m.height },\n raised = true\n ): void {\n const t = Math.max(1, m.scale)\n const half = { w: box.width / 2, h: box.height / 2 }\n const light = raised ? \"#ffffff\" : \"#808080\"\n const dark = raised ? \"#0a0a0a\" : \"#ffffff\"\n const innerLight = raised ? \"#dfdfdf\" : \"#808080\"\n const innerDark = raised ? \"#808080\" : \"#dfdfdf\"\n\n /** One L: a horizontal strip and a vertical one, meeting at `corner`. */\n const edge = (inset: number, colour: string, corner: \"tl\" | \"br\") => {\n const g = new Graphics2D()\n const top = corner === \"tl\"\n const y = top\n ? box.y + half.h - inset - t / 2\n : box.y - half.h + inset + t / 2\n const x = top\n ? box.x - half.w + inset + t / 2\n : box.x + half.w - inset - t / 2\n const span = { w: box.width - inset * 2, h: box.height - inset * 2 }\n g.rect({ x: box.x, y, width: span.w, height: t })\n g.rect({ x, y: box.y, width: t, height: span.h })\n ctx.draw(g.fill(colour))\n }\n\n edge(0, light, \"tl\")\n edge(0, dark, \"br\")\n edge(t, innerLight, \"tl\")\n edge(t, innerDark, \"br\")\n }\n\n /** The bar across the top, flat or gradient. */\n private drawTitleBar(ctx: RenderContext2D, m: Metrics): void {\n if (m.titleBar <= 0) return\n // Inside the bevel where there is one, so the navy bar sits *within* the\n // grey frame rather than running under it — which is what Windows 95 does\n // and what makes the frame read as surrounding the bar.\n const inset = m.look.bevel ? m.border * 2 : m.border\n const barWidth = m.width - inset * 2\n const bar = new Graphics2D().rect({\n x: 0,\n y: m.top - inset - m.titleBar / 2,\n width: barWidth,\n height: m.titleBar,\n // Only the top corners are the window's; the bottom two are square where\n // the bar meets the client area. Stated as a per-corner bag rather than\n // clipped, which would need a whole mask scope for two corners.\n cornerRadius: {\n topLeft: Math.max(0, m.radius - inset),\n topRight: Math.max(0, m.radius - inset),\n bottomLeft: 0,\n bottomRight: 0,\n },\n })\n\n if (m.look.titleBarTo !== \"\") {\n // Left to right, which is Windows 95's own direction. The endpoints are\n // in the renderer's own gradient space — −1…+1 from the box's centre,\n // y up — rather than in pixels; see `toGradientPoint` in `motion-paints`,\n // which exists because those two spaces are easy to confuse.\n bar.fill({\n type: \"linear-gradient\",\n colors: [m.look.titleBarColor, m.look.titleBarTo],\n stops: [0, 1],\n start: { x: -1, y: 0 },\n end: { x: 1, y: 0 },\n })\n } else {\n bar.fill(m.look.titleBarColor)\n }\n ctx.draw(bar)\n\n if (m.look.separator !== \"\") {\n ctx.draw(\n new Graphics2D()\n .rect({\n x: 0,\n y: m.top - inset - m.titleBar,\n width: barWidth,\n height: Math.max(1, m.scale),\n })\n .fill(m.look.separator)\n )\n }\n }\n\n /**\n * System 7's horizontal stripes — six lines across the bar, every other pixel.\n *\n * Drawn as real 1px rules at 1× rather than as a texture, because at\n * `chromeScale: 2` a texture would blur where the real thing simply had\n * thicker lines. The run stops short of the controls and of the title, which\n * is how the bar was actually drawn: the stripes are the *background*, and\n * anything sitting on the bar interrupts them.\n */\n private drawPinstripes(ctx: RenderContext2D, m: Metrics): void {\n const inset = m.border\n const weight = Math.max(1, m.scale)\n const pitch = weight * 2\n const stripes = new Graphics2D()\n const barTop = m.top - inset\n // Two pixels of clearance top and bottom, as System 7 left.\n for (\n let y = barTop - pitch * 2;\n y > barTop - m.titleBar + weight;\n y -= pitch\n ) {\n stripes.rect({\n x: 0,\n y,\n width: m.width - inset * 2,\n height: weight,\n })\n }\n ctx.draw(stripes.fill(m.look.titleColor))\n }\n\n /**\n * The title, and — on a pinstriped bar — the gap it sits in.\n *\n * The gap is the detail that makes System 7 read as System 7: the words do not\n * sit *on* the stripes, they sit in a hole punched through them. Drawn as a\n * bar-coloured rectangle behind the text, sized from the same width estimate\n * the button's label uses (see `estimateWidth` there for why an estimate).\n */\n private drawTitle(ctx: RenderContext2D, m: Metrics): void {\n if (this.title === \"\" || m.titleBar <= 0) return\n const inset = m.look.bevel ? m.border * 2 : m.border\n const size = m.look.titleSize * m.scale\n const y = m.top - inset - m.titleBar / 2\n const width = this.title.length * size * 0.55\n\n // Where the title starts. Centred on both Macs; on Windows it follows the\n // left edge, clearing the controls only when they are on that side.\n const x =\n m.look.titleAlign === \"center\"\n ? 0\n : m.left + inset + m.look.controlInset * m.scale + width / 2\n\n if (m.look.pinstripe) {\n const pad = size * 0.6\n ctx.draw(\n new Graphics2D()\n .rect({\n x,\n y,\n width: width + pad * 2,\n height: m.titleBar - m.scale * 4,\n })\n .fill(m.look.titleBarColor)\n )\n }\n\n ctx.draw(\n new Graphics2D()\n .text({\n text: this.title,\n x,\n y,\n width,\n fontSize: size,\n // Already resolved: the props mapper fills an empty family in from\n // the host (see `nodes/kit/text-defaults`), so the node never has to\n // know which typeface this application ships.\n fontFamily: this.fontFamily,\n fontWeight: m.look.titleWeight,\n textAlign: \"center\",\n wrap: false,\n })\n .fill(m.look.titleColor)\n )\n }\n\n /** The three caption buttons, in whichever of the four idioms the style uses. */\n private drawControls(ctx: RenderContext2D, m: Metrics): void {\n if (m.titleBar <= 0) return\n const inset = m.look.bevel ? m.border * 2 : m.border\n const size = m.look.controlSize * m.scale\n const height = m.look.controlHeight * m.scale\n const gap = m.look.controlGap * m.scale\n const edge = m.look.controlInset * m.scale\n const y = m.top - inset - m.titleBar / 2\n const rightward = m.look.controlsSide === \"left\"\n\n /** The centre of control `i`, counting outward from the window's near edge. */\n const centreOf = (i: number): number => {\n const first = rightward\n ? m.left + inset + edge + size / 2\n : -m.left - inset - edge - size / 2\n const step = gap || size * 1.6\n return rightward ? first + step * i : first - step * i\n }\n\n // Which button sits where, counting **outward from the corner the controls\n // are pinned to** — which is the only ordering both sides have in common.\n //\n // Close is nearest that corner in both idioms: leftmost on a Mac, rightmost\n // on Windows. What differs is the other two. A Mac runs close, minimise,\n // zoom left to right; Windows runs minimise, maximise, close — so counting\n // in from its right-hand corner gives close, maximise, minimise. The colour\n // list is always in close/minimise/maximise order, so it is the order that\n // is stated here rather than the list that is rebuilt.\n const order = rightward ? [0, 1, 2] : [0, 2, 1]\n\n for (let i = 0; i < 3; i++) {\n const slot = order[i]\n const x = centreOf(i)\n const colour = m.look.controlColors[slot]\n\n switch (m.look.controlsShape) {\n case \"lights\":\n ctx.draw(\n new Graphics2D()\n .ellipse({ x, y, width: size, height: size })\n .fill(colour)\n )\n break\n\n case \"boxes\":\n // System 7 drew a close box at the left and a zoom box at the right,\n // and nothing in between — so the middle slot is skipped rather than\n // drawn as a third square the OS never had.\n if (i === 1) break\n ctx.draw(\n new Graphics2D()\n .rect({\n x: i === 0 ? x : -centreOf(0),\n y,\n width: size,\n height: height,\n })\n .fill(m.look.titleBarColor)\n .stroke({ weight: m.scale, fill: colour, align: \"inside\" })\n )\n break\n\n case \"chips\":\n ctx.draw(\n new Graphics2D().rect({ x, y, width: size, height }).fill(\"#c0c0c0\")\n )\n this.drawBevel(ctx, m, { x, y, width: size, height }, true)\n this.drawGlyph(ctx, slot, x, y, size * 0.42, \"#000000\", m.scale)\n break\n\n case \"glyphs\":\n this.drawGlyph(ctx, slot, x, y, size, colour, m.scale)\n break\n }\n }\n }\n\n /**\n * A caption glyph: a minus, a square, or a cross.\n *\n * Drawn from strokes rather than typed as text, because these are the same\n * three marks in every chrome that has them and a font would put whatever the\n * renderer happened to shape into a 10px box. `slot` is close / minimise /\n * maximise, matching the colour list's order.\n */\n private drawGlyph(\n ctx: RenderContext2D,\n slot: number,\n x: number,\n y: number,\n size: number,\n colour: string,\n scale: number\n ): void {\n const r = size / 2\n const g = new Graphics2D()\n const weight = Math.max(1, scale)\n /** One stroked rule, drawn on its own — see the cross below for why. */\n const rule = (from: Vector2, to: Vector2) =>\n ctx.draw(\n new Graphics2D()\n .line({ points: [from, to] })\n .stroke({ weight, fill: colour, align: \"center\", cap: \"butt\" })\n )\n\n if (slot === 0) {\n // The cross, as two crossing rules — and each is its own draw.\n //\n // A `Graphics2D` unions every shape declared before a paint call and\n // paints them as one surface, which is right for two rects making an L\n // (see {@link drawBevel}) and wrong for two *lines*: the union of two open\n // strokes is not a shape the stroker can trace, and what came out was\n // nothing at all — a close button that was positioned, coloured and\n // invisible. One draw each is the whole fix; the marks do not need to\n // share a paint, since they are one colour by construction.\n rule({ x: x - r, y: y - r }, { x: x + r, y: y + r })\n rule({ x: x - r, y: y + r }, { x: x + r, y: y - r })\n return\n }\n\n if (slot === 1) {\n // The minimise rule, sitting on the baseline rather than the middle in the\n // chrome that draws it low — but centred reads correctly at every size and\n // is what both modern shells do.\n rule({ x: x - r, y }, { x: x + r, y })\n return\n }\n\n // The maximise square, hollow.\n g.rect({ x, y, width: size, height: size })\n ctx.draw(g.stroke({ weight, fill: colour, align: \"center\", cap: \"butt\" }))\n }\n}\n\n\n\n/** What {@link Window.metrics} hands round — named so the helpers can take it. */\ntype Metrics = {\n width: number\n height: number\n look: WindowLook\n scale: number\n titleBar: number\n radius: number\n border: number\n top: number\n left: number\n}\n", "/**\n * The window chromes — four operating systems' idea of a window, two of them\n * from the nineties.\n *\n * ## Why the vintage ones name a release\n *\n * \"Old Mac window\" is not a design; it is a decade of them. System 6's title bar\n * is not System 7's, and Platinum threw both out in 1997. The same is true on\n * the other side: Windows 3.1, 95 and XP are three different chromes people\n * remember as one. So each vintage look here names the **release it was measured\n * from** — in the enum value, in its docblock and in the label the dropdown\n * prints — because a reader who thinks it looks wrong needs to be able to check\n * it against something, and \"old Mac\" is not something you can check against.\n *\n * The two modern ones do not carry a year for the opposite reason: they are the\n * current thing, they move, and pinning today's build number into a label would\n * make it read as historical the moment it shipped.\n *\n * ## Everything is in real pixels\n *\n * A title bar is 28 points tall on macOS whatever size the window is, which is\n * the one way window chrome differs from every other control in this package: a\n * switch's knob is a fraction of its box, and a window's title bar is *not* a\n * fraction of anything. So the numbers below are the real ones at 1×, and\n * {@link WindowLook} is scaled by the node's own `chromeScale` — 1 draws chrome\n * at life size, 2 draws it as it appears in a Retina capture. That is what lets\n * a window be dragged to any size at all and still look like the window it is\n * imitating, which is the whole reason the node is not aspect-locked the way the\n * switch is (see `model/natural-size.ts`).\n *\n * ## Dark is an appearance, not a fifth chrome\n *\n * The two modern chromes each have a dark twin, reached with a boolean rather\n * than by adding `macosDark` and `windowsDark` to the enum above. A chrome is\n * *which operating system* — the thing that decides where the buttons are and\n * how tall the bar is — and light versus dark is a question you ask **of** one,\n * the same one the OS itself asks. Two more enum entries would make the dropdown\n * say there are six operating systems here, and would let a fifth measurement\n * table drift from the one it was copied out of. See {@link WINDOW_DARK_LOOKS},\n * which states each dark chrome as a repaint of its light twin and nothing else.\n */\n\n/** The chromes, in the order the dropdown offers them: new, old, new, old. */\nexport const WINDOW_STYLES = [\n \"macos\",\n \"macosClassic\",\n \"windows\",\n \"windowsClassic\",\n] as const\n\nexport type WindowStyle = (typeof WINDOW_STYLES)[number]\n\n/** What a new Window opens as. */\nexport const DEFAULT_WINDOW_STYLE: WindowStyle = \"macos\"\n\n/**\n * The chromes that have a **dark** variant — the two modern ones, and only\n * them.\n *\n * Not an omission on the vintage pair: System 7 and Windows 95 shipped one\n * appearance each and had no dark mode to imitate, so a dark System 7 would be\n * the same invention as a repainted Windows 95 title bar — a window from an\n * operating system that never existed, which is precisely what the colour fields\n * on this node are withheld to prevent. The list is exported because the panel\n * gates the Dark switch on it (see the `showWhen` on the window's schema entry):\n * a control that is present and does nothing is worse than one that isn't there.\n */\nexport const WINDOW_DARK_STYLES: readonly WindowStyle[] = [\"macos\", \"windows\"]\n\n/** Whether `style` has a dark chrome to switch to. */\nexport function hasDarkWindowStyle(style: WindowStyle): boolean {\n return WINDOW_DARK_STYLES.includes(style)\n}\n\n/** Whether a stored value names a chrome this build knows. */\nexport function isWindowStyle(value: unknown): value is WindowStyle {\n return (\n typeof value === \"string\" &&\n (WINDOW_STYLES as readonly string[]).includes(value)\n )\n}\n\n/** Where a chrome puts its window controls, and what shape they are. */\nexport type ControlsSide = \"left\" | \"right\"\n\n/**\n * How the caption buttons are drawn.\n *\n * Three genuinely different things rather than three colour schemes: macOS's\n * traffic lights are filled circles carrying no glyph, System 7's are hollow\n * squares with a bevel drawn inside them, Windows 11's are bare glyphs on no\n * button at all, and Windows 95's are raised grey chips with a glyph on top.\n */\nexport type ControlsShape = \"lights\" | \"boxes\" | \"glyphs\" | \"chips\"\n\n/** One window chrome, in real pixels at 1×. */\nexport interface WindowLook {\n /** Outer corner radius, px. `0` is a square-cornered window. */\n radius: number\n /** Title bar height, px. */\n titleBar: number\n /** Outer border weight, px. `0` draws none. */\n border: number\n /** The outer border's colour. */\n borderColor: string\n /** The title bar's fill. A two-stop gradient where {@link titleBarTo} is set. */\n titleBarColor: string\n /** The far end of a gradient title bar, or `\"\"` for a flat one. */\n titleBarTo: string\n /** A hairline under the title bar, or `\"\"` for none. */\n separator: string\n /** The client area behind the window's children. */\n body: string\n /** The title's ink. */\n titleColor: string\n /** Title size in px, and its weight. */\n titleSize: number\n titleWeight: number\n /** `center` on the Macs, `left` on both Windows. */\n titleAlign: \"left\" | \"center\"\n /** Which end the caption buttons sit at. */\n controlsSide: ControlsSide\n /** How they are drawn — see {@link ControlsShape}. */\n controlsShape: ControlsShape\n /** Each control's diameter (a light) or width (everything else), px. */\n controlSize: number\n /** A chip's height where it differs from {@link controlSize}, px. */\n controlHeight: number\n /** Centre-to-centre spacing between controls, px. */\n controlGap: number\n /** Distance from the window edge to the first control's near side, px. */\n controlInset: number\n /** The three controls' colours, in close / minimise / maximise order. */\n controlColors: [string, string, string]\n /** Drop-shadow blur, px. `0` draws none. */\n shadow: number\n /**\n * Whether the frame carries a **3D bevel** — the raised grey border that is\n * the whole visual language of Windows 95 and of nothing else here.\n */\n bevel: boolean\n /** Horizontal pinstripes across the title bar, as System 7 drew them. */\n pinstripe: boolean\n}\n\n/**\n * The four chromes.\n *\n * Measured from the real thing in each case, and the two vintage entries say\n * which release. Where a number could not be measured exactly it is the one that\n * makes the drawing read correctly at video sizes rather than a guess dressed up\n * as a fact — the traffic lights' 12px and their 20px pitch are Apple's, the\n * Windows 95 title bar's 18px and its 16×14 buttons are Microsoft's.\n */\nexport const WINDOW_LOOKS: Record<WindowStyle, WindowLook> = {\n /**\n * **macOS**, as of Sonoma (2023) and unchanged since Big Sur redrew it in\n * 2020: a 10px-rounded frame, a light chrome bar with the title centred in it,\n * and the three traffic lights at the left.\n */\n macos: {\n radius: 10,\n titleBar: 28,\n border: 1,\n borderColor: \"#00000026\",\n titleBarColor: \"#ececec\",\n titleBarTo: \"\",\n separator: \"#d5d5d5\",\n body: \"#ffffff\",\n titleColor: \"#4d4d4d\",\n titleSize: 13,\n titleWeight: 600,\n titleAlign: \"center\",\n controlsSide: \"left\",\n controlsShape: \"lights\",\n controlSize: 12,\n controlHeight: 12,\n controlGap: 20,\n controlInset: 14,\n controlColors: [\"#ff5f57\", \"#febc2e\", \"#28c840\"],\n shadow: 40,\n bevel: false,\n pinstripe: false,\n },\n\n /**\n * **Mac OS System 7 (1991)** — the pinstriped title bar.\n *\n * Six horizontal lines across the bar, a hollow close box at the left and a\n * zoom box at the right, a 1px black frame and square corners. The title sits\n * centred in a gap that *interrupts* the pinstripes rather than sitting over\n * them, which is the detail everybody remembers without being able to name.\n *\n * System 7 rather than System 6 (whose bar had no zoom box) or Platinum\n * (Mac OS 8, 1997 — which replaced the stripes with a grey gradient).\n */\n macosClassic: {\n radius: 0,\n titleBar: 20,\n border: 1,\n borderColor: \"#000000\",\n titleBarColor: \"#ffffff\",\n titleBarTo: \"\",\n separator: \"#000000\",\n body: \"#ffffff\",\n titleColor: \"#000000\",\n titleSize: 12,\n titleWeight: 700,\n titleAlign: \"center\",\n controlsSide: \"left\",\n controlsShape: \"boxes\",\n controlSize: 11,\n controlHeight: 11,\n controlGap: 0,\n controlInset: 8,\n controlColors: [\"#000000\", \"#000000\", \"#000000\"],\n shadow: 0,\n bevel: false,\n pinstripe: true,\n },\n\n /**\n * **Windows 11** (2021): 8px-rounded corners, a Mica-grey chrome, the title\n * left-aligned, and three flat caption glyphs at the right on no button at\n * all until you hover one.\n */\n windows: {\n radius: 8,\n titleBar: 32,\n border: 1,\n borderColor: \"#00000026\",\n titleBarColor: \"#f3f3f3\",\n titleBarTo: \"\",\n separator: \"\",\n body: \"#ffffff\",\n titleColor: \"#1a1a1a\",\n titleSize: 12,\n titleWeight: 400,\n titleAlign: \"left\",\n controlsSide: \"right\",\n controlsShape: \"glyphs\",\n controlSize: 10,\n controlHeight: 10,\n controlGap: 46,\n controlInset: 18,\n controlColors: [\"#1a1a1a\", \"#1a1a1a\", \"#1a1a1a\"],\n shadow: 32,\n bevel: false,\n pinstripe: false,\n },\n\n /**\n * **Windows 95 (1995)** — the grey bevel and the navy gradient.\n *\n * A `#c0c0c0` frame raised by a two-tone 3D border (white and light grey on\n * the top and left, dark grey and black on the bottom and right), an 18px\n * title bar running `#000080` → `#1084d0` left to right with the title in\n * white bold at the left, and three 16×14 raised chips at the right.\n *\n * Windows 95 rather than 98 (visually the same chrome) or 3.1 (whose title bar\n * was flat navy with no gradient, and whose buttons sat at both ends). The\n * gradient is the tell: it arrived with 95.\n */\n windowsClassic: {\n radius: 0,\n titleBar: 18,\n border: 2,\n borderColor: \"#c0c0c0\",\n titleBarColor: \"#000080\",\n titleBarTo: \"#1084d0\",\n separator: \"\",\n body: \"#ffffff\",\n titleColor: \"#ffffff\",\n titleSize: 11,\n titleWeight: 700,\n titleAlign: \"left\",\n controlsSide: \"right\",\n controlsShape: \"chips\",\n controlSize: 16,\n controlHeight: 14,\n controlGap: 18,\n controlInset: 4,\n controlColors: [\"#c0c0c0\", \"#c0c0c0\", \"#c0c0c0\"],\n shadow: 0,\n bevel: true,\n pinstripe: false,\n },\n}\n\n/**\n * The dark chromes, each stated as **what changes** about its light twin.\n *\n * Spread from the light entry rather than written out, and that is the point:\n * everything a dark window shares with a light one is a *measurement* — the\n * 28pt title bar, the traffic lights' 12px and their 20px pitch, Windows 11's\n * 32pt bar and 46px caption pitch — and a measurement that got copied would be\n * a measurement free to drift. Dark mode is a repaint, so only paint appears\n * below.\n *\n * Two things worth naming. macOS keeps its traffic lights *exactly* as they are:\n * the red, amber and green are the same three colours in both appearances, which\n * is half of why they read as traffic lights rather than as decoration. And\n * Windows 11's caption glyphs simply invert — they are ink on the bar, never a\n * button, until you hover one.\n */\nexport const WINDOW_DARK_LOOKS: Partial<Record<WindowStyle, WindowLook>> = {\n /**\n * **macOS**, dark appearance: a near-black chrome bar, a light-grey title, a\n * hairline border that is now a highlight rather than a shade, and a deeper\n * drop — a dark window on a dark desktop is separated by its shadow alone.\n */\n macos: {\n ...WINDOW_LOOKS.macos,\n borderColor: \"#ffffff26\",\n titleBarColor: \"#2d2d2d\",\n separator: \"#1c1c1c\",\n body: \"#1e1e1e\",\n titleColor: \"#d8d8d8\",\n shadow: 48,\n },\n\n /**\n * **Windows 11**, dark: the Mica grey inverts to `#202020` with a `#272727`\n * client area — the two are deliberately *not* the same value, which is how\n * the bar stays legible as a bar with no separator drawn under it.\n */\n windows: {\n ...WINDOW_LOOKS.windows,\n borderColor: \"#ffffff1f\",\n titleBarColor: \"#202020\",\n body: \"#272727\",\n titleColor: \"#ffffff\",\n controlColors: [\"#ffffff\", \"#ffffff\", \"#ffffff\"],\n },\n}\n\n/**\n * The chrome a style names, in the appearance asked for — falling back for a\n * value this build doesn't know, and for `dark` on a style that has no dark\n * variant (see {@link WINDOW_DARK_STYLES}).\n */\nexport function windowLook(style: WindowStyle, dark = false): WindowLook {\n const light = WINDOW_LOOKS[style] ?? WINDOW_LOOKS[DEFAULT_WINDOW_STYLE]\n if (!dark) return light\n return WINDOW_DARK_LOOKS[style] ?? light\n}\n\n/**\n * The client area a window opens with — white, which is the light chromes' own.\n *\n * Named rather than inlined because two places have to agree on it and they are\n * in different packages: the appearance schema declares it as the `body` field's\n * default, and `windowMotionProps` reads it back to decide whether the author has\n * chosen a client colour at all. See that mapper for why the question is asked\n * there and not here.\n */\nexport const DEFAULT_WINDOW_BODY = \"#ffffff\"\n\n/**\n * The proportions a new Window is seeded at — 720 × 480, a plain 3 : 2.\n *\n * Not a locked ratio, and that is the point: a window is the one thing on this\n * shelf whose whole nature is being dragged to whatever size you like, and its\n * chrome is drawn in real pixels precisely so that stays true. Contrast the\n * switch, whose box *is* its design (see `model/natural-size.ts`).\n */\nexport const WINDOW_SIZE = { width: 720, height: 480 }\n", "import {\n command,\n driveCommand,\n easeInOut,\n node,\n Clip,\n type Command,\n type CommandArgs,\n Graphics2D,\n Node2D,\n type Node2DProps,\n pathOps,\n property,\n type Node,\n type NodeConfig,\n type PathBounds,\n type PathCommand,\n type RenderContext2D,\n type VectorPath,\n} from \"@motionscript/sdk\"\n\nimport {\n cursorArtworkPixels,\n cursorLook,\n CURSOR_STYLES,\n CURSOR_TYPES,\n DEFAULT_CURSOR_STYLE,\n DEFAULT_CURSOR_TYPE,\n type CursorStyle,\n type CursorType,\n} from \"../kit/cursor-outline\"\nimport {\n arcPointAt,\n resolveClickArc,\n type ClickArc,\n type ClickPoint,\n} from \"../kit/click-path\"\nimport { pressDepth } from \"../kit/press\"\n\n/** A cursor's own props, over the box every node has. */\nexport interface CursorProps extends Node2DProps {\n /** Which glyph to draw — see {@link CursorType}. */\n cursor: CursorType\n /** Which look to draw it in — see {@link CursorStyle}. */\n style: CursorStyle\n /**\n * How many clicks deep the chain is — a **monotonic phase**, not a depth.\n *\n * The one prop {@link Cursor.click} animates, and it climbs by exactly one\n * per click rather than going down and coming back: a tween can only run one\n * way, and a click is two. `pressDepth` turns the fractional part into the\n * down-and-up envelope, which is zero at both whole numbers — so a finished\n * click leaves the pointer at rest by arithmetic rather than by a reset the\n * command would have to remember to write. Exactly `Button.press`, and the\n * same function drives both.\n */\n press: number\n /** Seconds a {@link Cursor.click} runs for when its row states no duration. */\n clickDuration: number\n}\n\n/** How far the glyph shrinks at the bottom of a press. */\nconst PRESS_DIP = 0.12\n/** The ring's radius at the end of a click, as a fraction of the glyph's box. */\nconst RIPPLE_REACH = 1.4\nconst RIPPLE_OPACITY = 0.5\n\n/**\n * A mouse cursor, as a node.\n *\n * The one thing a screen recording has that a motion-graphics scene doesn't:\n * something on screen standing in for the person. Every explainer that shows an\n * interface needs one, and the alternatives are all worse — a screenshot of a\n * cursor pastes in at one size, and a hand-drawn path is a shape nobody can\n * change their mind about.\n *\n * Which pointer, whose, and how far through a click it is. Where it sits and\n * how big it is are the transform every node has; the contours and both colours\n * come out of the first two (see `kit/cursor-outline`, which holds the artwork\n * and the reasoning).\n *\n * ## A `Node2D`, and a leaf\n *\n * Not a `ShapeNode`, on the reasoning `Code` records in its own changeset: the\n * paint slots a shape carries are a surface this node cannot honour. A cursor's\n * colours are not a matter of taste — the Windows arrow is white with a black\n * keyline — so an author asked to set them by hand would be re-deriving a fact\n * rather than making a choice, and a cursor with the wrong two colours is not a\n * stylized cursor, it is a mistake. Declaring `fill`/`overlay`/`stroke`/\n * `shadow` and then ignoring them was a surface a host had no way to know was\n * inert; not declaring them is one it cannot offer.\n *\n * It refuses children for the same kind of reason. Nothing goes *inside* a\n * pointer, and a drop target on one is a place to lose a node.\n *\n * ## Why it paints itself, in two passes\n *\n * The keyline and the body, in that order: the outer contour filled black, then\n * the inner one filled white over it, leaving the keyline showing as the gap\n * between them. Not a fill with a stroke round it, because a stroke's weight is\n * a pixel count and this glyph is stretched onto whatever box the node has —\n * see the module note on `kit/cursor-outline`.\n *\n * ## The hotspot is the box's top-left\n *\n * Both the click ripple and the press dip are anchored there rather than at the\n * node's centre, because that is where a pointer actually points. A dip about\n * the centre would walk the tip half a pixel down and right at the bottom of\n * every press, which reads as the cursor sliding rather than pressing.\n *\n * ## Animating one\n *\n * `cursor` and `style` are ordinary props, so a `to` command can change either.\n * Neither has a tween function, and motion-script's fallback for a value it\n * can't interpolate is to hold the old one until the command ends and then\n * snap — which is exactly right here: there is no halfway between an arrow and\n * a pointing hand, and a cursor that changes shape *at the moment it reaches\n * the link* is what the author is after. A zero-duration `to` is the instant\n * switch.\n */\n@node({\n key: \"cursor\",\n parentKey: \"node\",\n forkable: true,\n layout: {\n children: \"freeform\",\n defaultWidthMode: \"fixed\",\n defaultHeightMode: \"fixed\",\n acceptsChildren: false,\n },\n seed: {\n width: 32,\n height: 32,\n },\n})\nexport class Cursor extends Node2D<CursorProps> {\n @property({ default: DEFAULT_CURSOR_TYPE, kind: \"enum\", options: CURSOR_TYPES })\n declare cursor: CursorType\n\n @property({ default: DEFAULT_CURSOR_STYLE, kind: \"enum\", options: CURSOR_STYLES })\n declare style: CursorStyle\n\n @property({ default: 0, min: 0 }) declare press: number\n\n @property({ default: 0.5, min: 0, step: 0.05, unit: \"s\" })\n declare clickDuration: number\n\n constructor(props: NodeConfig<Cursor, CursorProps>) {\n super(props as NodeConfig<Node2D, Node2DProps>)\n }\n\n /**\n * Travel to a control, press it, and let go — the whole gesture as one row.\n *\n * **`target` is a node id, and it is resolved when the command is built.**\n * That is the point of the command: the destination and the curve to it are\n * derived from where the control *currently* is, so moving a switch on the\n * canvas re-aims the animation with nothing to re-author. A baked path is\n * correct only for the two points it was baked against, which is what made\n * this a chore worth removing. An id rather than a `key` because `NodeSpec.key`\n * is optional and never generated, so it is not a handle every node has; an\n * expression is not an option either, since a command that does not `bind`\n * refuses one outright (`SceneTimeline._assertNoExpressions`).\n *\n * Naming nothing — or naming a node absent at this moment — clicks in place,\n * which is the same answer the document's own node references give for\n * something not there. A click is a legitimate beat over a rectangle or an\n * image, so refusing would cost more than it caught.\n *\n * `activate` drives the control itself: a switch throws, a button takes a\n * press. Both are written straight to the other node, which the timeline\n * restores along with this one because it records what a command writes\n * rather than what it targets.\n */\n @command({\n args: [\n { key: \"target\", kind: \"nodeRef\" },\n {\n key: \"clickShape\",\n kind: \"arc\",\n properties: {\n bow: { kind: \"number\", min: -1, max: 1, step: 0.01 },\n lean: { kind: \"number\", min: 0, max: 1, step: 0.01 },\n },\n },\n { key: \"activate\", kind: \"boolean\", default: true },\n ],\n })\n click(\n args: CommandArgs<{\n target?: string\n clickShape?: Partial<ClickArc>\n activate?: boolean\n }> & { duration?: number },\n ): Command<CursorProps> {\n const duration = args.duration && args.duration > 0 ? args.duration : this.clickDuration\n const easing = args.easing ?? easeInOut()\n const arc = resolveClickArc(args.data?.clickShape)\n const target = this._controlNamed(args.data?.target)\n\n const from: ClickPoint = { x: this.x, y: this.y }\n const to = target ? this._reach(target) : from\n const press = Math.floor(this.press)\n const activated =\n args.data?.activate === false ? null : target && activateOf(target)\n\n return driveCommand(duration, (t) => {\n // Travel over the lead-in, then hold still while the click happens: a\n // pointer that is still moving as it presses reads as a drag.\n const travel = easing(Math.min(1, t / TRAVEL))\n const at = arcPointAt(from, to, arc, travel)\n const phase = t <= TRAVEL ? 0 : (t - TRAVEL) / (1 - TRAVEL)\n this.set({ x: at.x, y: at.y, press: press + phase } as Partial<CursorProps>)\n activated?.(phase)\n }) as unknown as Command<CursorProps>\n }\n\n /**\n * Keyline, then body — the two-tone cursor, in the style's own colours, with\n * the click ripple behind them.\n *\n * The glyph is one `Graphics2D` with two shape groups rather than two\n * submissions: `fill()` scopes to the shapes declared since the last one, so\n * the second `path()` starts a group of its own and the two paint in order.\n * The ripple is a submission of its own because it carries a group opacity,\n * and `Graphics2D.opacity` composites the whole list — chaining it would fade\n * the pointer along with the ring.\n */\n protected override renderSelf(ctx: RenderContext2D): void {\n const depth = pressDepth(this.press)\n this._drawRipple(ctx, depth)\n\n const scale = 1 - PRESS_DIP * depth\n const { keyline, body } = this.artwork(scale)\n if (keyline.length === 0) return\n const look = cursorLook(this.style)\n const centerBounds = this.centerBounds(scale)\n ctx.draw(\n new Graphics2D()\n .path({ data: keyline, centerBounds })\n .fill(look.keyline)\n .path({ data: body, centerBounds })\n .fill(look.body)\n )\n }\n\n /** Nothing goes inside a pointer — see the class note. */\n protected override acceptsChild(_child: Node): boolean {\n return false\n }\n\n /**\n * The node's laid-out rect as a clip outline, so `clip` and any backdrop\n * effect are confined to the box rather than to nothing.\n *\n * Picking needs no help here: `Node2D` already grabs on the layout box, which\n * is the answer a cursor wants. An arrow occupies about a third of its box,\n * so picking on the silhouette would mean clicking one required hitting the\n * arrow itself — which on a node whose whole job is to be small on screen\n * reads as the app ignoring you.\n */\n protected override clipSelf(): Clip {\n return new Clip().rect({\n width: this.layoutBounds.width,\n height: this.layoutBounds.height,\n })\n }\n\n /**\n * The expanding ring a click leaves, centred on the hotspot.\n *\n * Drawn from the press envelope rather than from a phase of its own, so it\n * cannot drift out of step with the dip — and so a cursor at rest draws\n * nothing at all, which is the common case on every frame.\n */\n private _drawRipple(ctx: RenderContext2D, depth: number): void {\n if (depth <= 0) return\n const { width, height } = this.layoutBounds\n const span = Math.max(width, height)\n if (span <= 0) return\n const tip = this._hotspot()\n const radius = span * RIPPLE_REACH * depth\n ctx.draw(\n new Graphics2D()\n .ellipse({\n x: tip.x,\n y: tip.y,\n width: radius * 2,\n height: radius * 2,\n })\n .stroke({\n weight: Math.max(1, span * 0.06 * (1 - depth)),\n fill: cursorLook(this.style).keyline,\n align: \"center\",\n })\n .opacity(RIPPLE_OPACITY * (1 - depth))\n )\n }\n\n /** Where the pointer points, in the node's own y-up local space. */\n private _hotspot(): ClickPoint {\n return { x: -this.layoutBounds.width / 2, y: this.layoutBounds.height / 2 }\n }\n\n /**\n * Where this cursor has to sit for its hotspot to land on `target`'s centre,\n * in this node's **parent** space.\n *\n * `global` is world-space and `x`/`y` are parent-relative, so the parent's own\n * world contribution comes back off — the conversion `Node2D.global` documents.\n * The half-box offset is what makes the tip, rather than the cursor's centre,\n * the thing that arrives.\n */\n private _reach(target: Node2D): ClickPoint {\n const parent = this.parent2D\n const origin = parent ? parent.global : { x: 0, y: 0 }\n const at = target.global\n const tip = this._hotspot()\n return { x: at.x - origin.x - tip.x, y: at.y - origin.y - tip.y }\n }\n\n /** The node `id` names, from the root of this cursor's own tree. */\n private _controlNamed(id: string | undefined): Node2D | undefined {\n if (id === undefined || id === \"\") return undefined\n let root: Node = this\n while (root.parent) root = root.parent\n return findNode(root, id)\n }\n\n /**\n * The glyph's two contours in the renderer's local space.\n *\n * `cursorArtworkPixels` works y-**down** (the convention the author box and a\n * stored `d` string share), and a node's own space is y-up — hence the flip,\n * which is the whole of the difference between the two.\n */\n private artwork(scale = 1): { keyline: PathCommand[]; body: PathCommand[] } {\n const flip = (p: { x: number; y: number }) => ({ x: p.x, y: -p.y })\n const pixels = cursorArtworkPixels(\n this.cursor,\n this.style,\n this.layoutBounds.width * scale,\n this.layoutBounds.height * scale\n )\n const commands = (path: VectorPath) => pathOps.anchorCommands(path, flip)\n return { keyline: commands(pixels.keyline), body: commands(pixels.body) }\n }\n\n /**\n * The frame both contours are centred against: the node's own box, or the\n * dipped box pinned to the hotspot while a press is under way.\n *\n * Stated rather than left to the descriptor's default, which centres a path\n * on *its own* bounding box. Here that default would break the drawing\n * outright — the body is smaller than the keyline, so each would be centred\n * on a different rectangle and the two would come apart.\n */\n private centerBounds(scale = 1): PathBounds {\n const w = (this.layoutBounds.width * scale) / 2\n const h = (this.layoutBounds.height * scale) / 2\n const tip = this._hotspot()\n // Pinned to the tip, so the dip shrinks the glyph in place rather than\n // walking it down and to the right.\n const cx = tip.x + w\n const cy = tip.y - h\n return [cx - w, cy - h, cx + w, cy + h]\n }\n}\n\n/** The fraction of a click spent travelling, before the press itself. */\nconst TRAVEL = 0.62\n\n/** Depth-first by document id; `undefined` for a node not in this tree. */\nfunction findNode(at: Node, id: string): Node2D | undefined {\n if (at.id === id) return at as Node2D\n for (const child of at._allChildren) {\n const found = findNode(child, id)\n if (found) return found\n }\n return undefined\n}\n\n/**\n * How `target` answers a click, as a function of the press phase — or `null`\n * for a node that has no answer, which is every type but the two controls.\n *\n * Duck-typed on the prop rather than on the class, because a **forked**\n * component is a different class carrying the same source and an `instanceof`\n * against the original is false for it. That is the same argument\n * `Registry.nodeCommand` makes for dispatching on the method name.\n */\nfunction activateOf(target: Node2D): ((phase: number) => void) | null {\n const props = target as unknown as Record<string, unknown>\n\n if (typeof props.progress === \"number\" && \"accent\" in props) {\n // A switch throws once, at the bottom of the press, and stays thrown —\n // read when the command is *built*, matching the timeline's own order, so\n // two clicks at the same `at` agree rather than alternating.\n const from = props.progress\n const to = from > 0.5 ? 0 : 1\n return (phase) => {\n const t = Math.min(1, Math.max(0, (phase - PRESS_DOWN) / (1 - PRESS_DOWN)))\n target.set({ progress: from + (to - from) * t } as never)\n }\n }\n\n if (typeof props.press === \"number\" && \"label\" in props) {\n // A button takes the pointer's own envelope, one whole press per click.\n const base = Math.floor(props.press)\n return (phase) => target.set({ press: base + phase } as never)\n }\n\n return null\n}\n\n/** Where in the press the control actually reacts — the bottom of the travel. */\nconst PRESS_DOWN = 1 / 3\n", "import { Switch } from \"./switch\";\nimport { Button } from \"./button\";\nimport { Window } from \"./window\";\nimport { Cursor } from \"./cursor\";\n\n/**\n * Every node type this package publishes.\n *\n * A host registers these by handing the array to an engine —\n * `new Engine(platform, { nodes: NODES })` — which reads each class's `@node()`\n * key. The decorator only *declares* that key: nothing is registered by the mere\n * act of importing a module, so a registry holds exactly what its owner asked\n * for and two engines in one process can differ about it.\n *\n * Listing class **values** is also what survives bundling. This package declares\n * `sideEffects: false`, which licenses a bundler to drop a module nothing\n * imports a value from, and a document names a node type by string — so\n * `import \"./switch\"` for its side effect is exactly what gets shaken out, where an\n * array of bindings is a data dependency that cannot be.\n */\nexport const NODES = [Switch, Button, Window, Cursor];\n"],
|
|
5
|
+
"mappings": "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",
|
|
6
|
+
"names": ["command", "easeInOut", "node", "Graphics2D", "property", "pathOps", "Switch", "ControlNode", "props", "_child", "args", "toggleState", "duration", "easing", "easeInOut", "to", "bounds", "width", "height", "look", "switchLook", "custom", "p", "trackHeight", "widest", "margin", "knobSize", "travel", "ctx", "m", "track", "Graphics2D", "mixHex", "knob", "icon", "x", "color", "opacity", "art", "controlIconPixels", "shift", "pathOps", "__decorate", "property", "CONTROL_STYLES", "DEFAULT_CONTROL_ICON", "CONTROL_ICONS", "command", "node", "clamp", "command", "driveCommand", "node", "Graphics2D", "property", "pathOps", "Button", "ControlNode", "props", "_child", "args", "duration", "base", "driveCommand", "phase", "clamp", "bounds", "width", "height", "look", "buttonLook", "custom", "depth", "pressDepth", "fill", "mixHex", "fontSize", "readableInk", "ctx", "m", "pivot", "face", "Graphics2D", "labelWidth", "estimateWidth", "block", "left", "x", "size", "color", "scale", "art", "controlIconPixels", "shift", "p", "pathOps", "__decorate", "property", "CONTROL_STYLES", "DEFAULT_CONTROL_ICON", "CONTROL_ICONS", "command", "node", "estimateWidth", "label", "fontSize", "node", "Graphics2D", "Rect", "property", "WINDOW_STYLES", "DEFAULT_WINDOW_STYLE", "WINDOW_DARK_STYLES", "hasDarkWindowStyle", "style", "isWindowStyle", "value", "WINDOW_LOOKS", "WINDOW_DARK_LOOKS", "windowLook", "dark", "light", "DEFAULT_WINDOW_BODY", "WINDOW_SIZE", "Window", "Rect", "bounds", "width", "height", "look", "windowLook", "scale", "titleBar", "base", "inset", "m", "Graphics2D", "ctx", "frame", "authored", "blur", "box", "raised", "t", "half", "light", "dark", "innerLight", "innerDark", "edge", "colour", "corner", "g", "top", "y", "x", "span", "barWidth", "bar", "weight", "pitch", "stripes", "barTop", "size", "pad", "gap", "rightward", "centreOf", "i", "first", "step", "order", "slot", "r", "rule", "from", "to", "__decorate", "property", "DEFAULT_WINDOW_STYLE", "WINDOW_STYLES", "node", "command", "driveCommand", "easeInOut", "node", "Clip", "Graphics2D", "Node2D", "pathOps", "property", "PRESS_DIP", "RIPPLE_REACH", "RIPPLE_OPACITY", "Cursor", "Node2D", "props", "args", "duration", "easing", "easeInOut", "arc", "resolveClickArc", "target", "from", "to", "press", "activated", "activateOf", "driveCommand", "t", "travel", "TRAVEL", "at", "arcPointAt", "phase", "ctx", "depth", "pressDepth", "scale", "keyline", "body", "look", "cursorLook", "centerBounds", "Graphics2D", "_child", "Clip", "width", "height", "span", "tip", "radius", "parent", "origin", "id", "root", "findNode", "flip", "p", "pixels", "cursorArtworkPixels", "commands", "path", "pathOps", "w", "h", "cx", "cy", "__decorate", "property", "DEFAULT_CURSOR_TYPE", "CURSOR_TYPES", "DEFAULT_CURSOR_STYLE", "CURSOR_STYLES", "command", "node", "TRAVEL", "findNode", "at", "id", "child", "found", "activateOf", "target", "props", "from", "to", "phase", "t", "PRESS_DOWN", "base", "NODES", "Switch", "Button", "Window", "Cursor"]
|
|
7
7
|
}
|
package/dist/browser/kit.js
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
import{A,B,C,D,E,F,G,H,I,J,K,L,M,a,b,c,d,e,f,g,h,i,j,k,l,m,n,o,p,q,r,s,t,u,v,w,x,y,z}from"./chunks/chunk-
|
|
1
|
+
import{A,B,C,D,E,F,G,H,I,J,K,L,M,a,b,c,d,e,f,g,h,i,j,k,l,m,n,o,p,q,r,s,t,u,v,w,x,y,z}from"./chunks/chunk-SFBJ6J3M.js";export{o as BUTTON_ASPECT,m as BUTTON_LOOKS,F as CLICK_ARC_DEFAULT,H as CLICK_ARC_MAX_LEAN,G as CLICK_ARC_MIN_LEAN,a as CONTROL_ICONS,c as CONTROL_ICON_UNITS,g as CONTROL_STYLES,z as CURSOR_LOOKS,y as CURSOR_SIZE,u as CURSOR_STYLES,t as CURSOR_TYPES,x as CURSOR_UNITS,r as ControlNode,b as DEFAULT_CONTROL_ICON,h as DEFAULT_CONTROL_STYLE,w as DEFAULT_CURSOR_STYLE,v as DEFAULT_CURSOR_TYPE,l as SWITCH_ASPECT,j as SWITCH_LOOKS,J as arcControlPoint,K as arcCrestPoint,L as arcFromCrest,M as arcPointAt,n as buttonLook,e as controlIconData,d as controlIconPixels,C as cursorArtworkData,B as cursorArtworkPixels,A as cursorLook,f as isControlIcon,i as isControlStyle,E as isCursorStyle,D as isCursorType,p as mixHex,s as pressDepth,q as readableInk,I as resolveClickArc,k as switchLook};
|
|
2
2
|
//# sourceMappingURL=kit.js.map
|
|
@@ -1,12 +1,12 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@motionscript/ui",
|
|
3
|
-
"version": "0.1.0-alpha.
|
|
3
|
+
"version": "0.1.0-alpha.3",
|
|
4
4
|
"exports": {
|
|
5
5
|
".": "dist/browser/index.js",
|
|
6
6
|
"./kit": "dist/browser/kit.js"
|
|
7
7
|
},
|
|
8
8
|
"peers": [
|
|
9
|
-
"@motionscript/
|
|
9
|
+
"@motionscript/sdk"
|
|
10
10
|
],
|
|
11
11
|
"assets": {}
|
|
12
12
|
}
|
package/dist/button/button.d.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { type Command, type CommandArgs, type Node, type NodeConfig, type RenderContext2D } from "@motionscript/
|
|
1
|
+
import { type Command, type CommandArgs, type Node, type NodeConfig, type RenderContext2D } from "@motionscript/sdk";
|
|
2
2
|
import { type ControlIcon } from "../kit/control-icons.js";
|
|
3
3
|
import { type ControlStyle } from "../kit/control-style.js";
|
|
4
4
|
import { ControlNode, type ControlNodeProps } from "../kit/control-node.js";
|
|
@@ -22,7 +22,7 @@ export interface ButtonProps extends ControlNodeProps {
|
|
|
22
22
|
* and a press is two. {@link pressDepth} turns the fractional part into the
|
|
23
23
|
* down-and-up envelope, which is zero at both whole numbers — so a finished
|
|
24
24
|
* press leaves the button at rest by arithmetic rather than by a reset the
|
|
25
|
-
* builder would have to remember to write. See
|
|
25
|
+
* builder would have to remember to write. See {@link Button.pulse}.
|
|
26
26
|
*/
|
|
27
27
|
press: number;
|
|
28
28
|
/**
|
|
@@ -77,8 +77,10 @@ export declare class Button extends ControlNode<ButtonProps> {
|
|
|
77
77
|
/** A button is a control, not a container — its label and mark are its own. */
|
|
78
78
|
protected acceptsChild(_child: Node): boolean;
|
|
79
79
|
/**
|
|
80
|
-
* Press and release the button
|
|
81
|
-
*
|
|
80
|
+
* Press and release the button: `press` climbs from the whole number it
|
|
81
|
+
* starts at to the next, and {@link pressDepth} draws that as one press. The
|
|
82
|
+
* phase only climbs, as `Cursor.click` writes it, so the row never undoes
|
|
83
|
+
* the count an earlier press left.
|
|
82
84
|
*
|
|
83
85
|
* Named `pulse` and published as `press` to avoid shadowing the `press` prop
|
|
84
86
|
* this drives. Same split `Code.erase` makes to avoid shadowing `Node.remove`.
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"button.d.ts","sourceRoot":"","sources":["../../src/button/button.ts"],"names":[],"mappings":"AAAA,OAAO,EAKL,KAAK,OAAO,EACZ,KAAK,WAAW,EAGhB,KAAK,IAAI,EACT,KAAK,UAAU,EACf,KAAK,eAAe,EACrB,MAAM,
|
|
1
|
+
{"version":3,"file":"button.d.ts","sourceRoot":"","sources":["../../src/button/button.ts"],"names":[],"mappings":"AAAA,OAAO,EAKL,KAAK,OAAO,EACZ,KAAK,WAAW,EAGhB,KAAK,IAAI,EACT,KAAK,UAAU,EACf,KAAK,eAAe,EACrB,MAAM,mBAAmB,CAAA;AAE1B,OAAO,EAIL,KAAK,WAAW,EACjB,MAAM,sBAAsB,CAAA;AAC7B,OAAO,EAML,KAAK,YAAY,EAClB,MAAM,sBAAsB,CAAA;AAG7B,OAAO,EAAE,WAAW,EAAE,KAAK,gBAAgB,EAAE,MAAM,qBAAqB,CAAA;AAExE,mFAAmF;AACnF,MAAM,WAAW,WAAY,SAAQ,gBAAgB;IACnD,mEAAmE;IACnE,KAAK,EAAE,YAAY,CAAA;IACnB,uBAAuB;IACvB,KAAK,EAAE,MAAM,CAAA;IACb,kEAAkE;IAClE,UAAU,EAAE,MAAM,CAAA;IAClB,6EAA6E;IAC7E,MAAM,EAAE,MAAM,CAAA;IACd,oEAAoE;IACpE,IAAI,EAAE,WAAW,CAAA;IACjB;;;;;;;;;OASG;IACH,KAAK,EAAE,MAAM,CAAA;IACb;;;;OAIG;IACH,GAAG,EAAE,MAAM,CAAA;IACX,MAAM,EAAE,MAAM,CAAA;IACd,gEAAgE;IAChE,aAAa,EAAE,MAAM,CAAA;CACtB;AAED;;;;;;;;;;;;;;;;;;;;;;;;;;;GA2BG;AACH,qBAea,MAAO,SAAQ,WAAW,CAAC,WAAW,CAAC;IAE1C,KAAK,EAAE,YAAY,CAAA;IACc,KAAK,EAAE,MAAM,CAAA;IACnB,UAAU,EAAE,MAAM,CAAA;IACI,MAAM,EAAE,MAAM,CAAA;IAE/D,IAAI,EAAE,WAAW,CAAA;IACiB,KAAK,EAAE,MAAM,CAAA;IACL,GAAG,EAAE,MAAM,CAAA;IACI,MAAM,EAAE,MAAM,CAAA;IAEvE,aAAa,EAAE,MAAM,CAAA;gBAEjB,KAAK,EAAE,UAAU,CAAC,MAAM,EAAE,WAAW,CAAC;IAIlD,+EAA+E;cAC5D,YAAY,CAAC,MAAM,EAAE,IAAI,GAAG,OAAO;IAItD;;;;;;;;OAQG;IAEH,KAAK,CAAC,IAAI,EAAE,WAAW,CAAC,MAAM,CAAC,MAAM,EAAE,KAAK,CAAC,CAAC,GAAG;QAAE,QAAQ,CAAC,EAAE,MAAM,CAAA;KAAE,GAAG,OAAO,CAAC,WAAW,CAAC;IAW7F,uFAAuF;IACvF,OAAO,CAAC,OAAO;cA8BI,UAAU,CAAC,GAAG,EAAE,eAAe,GAAG,IAAI;IA2EzD,0EAA0E;IAC1E,OAAO,CAAC,QAAQ;CAmCjB"}
|