Skip to content
MotionActor
Esc
↑↓navigate↵open⌘Jpreview
On this page

Desktop

A real library actor end to end, with signals for state, derived chains for presentation, slots for hosted apps and generator commands for motion.

Desktop animates an operating system desktop: a dock, windows that open out of their dock icon, and a cursor. It ships in the component library and is large enough to show how the patterns hold up in a real actor. The code below is trimmed from its source. The helpers that compute dock metrics and window transforms come from an import, and it keeps one or two of each kind of command.

import {
  function derived<TThis, T>(fn: (this: TThis) => T): DerivedDefinition<T, TThis>derived,
  
const Easing: {
    readonly linear: EasingFunction;
    readonly inQuad: EasingFunction;
    readonly outQuad: EasingFunction;
    readonly inOutQuad: EasingFunction;
    readonly inCubic: EasingFunction;
    readonly outCubic: EasingFunction;
    readonly inOutCubic: EasingFunction;
    readonly inQuart: EasingFunction;
    readonly outQuart: EasingFunction;
    readonly inOutQuart: EasingFunction;
    readonly bezier: (x1: number, y1: number, x2: number, y2: number) => EasingFunction;
    readonly wiggle: (count: number, type?: WiggleType, amplitude?: number) => EasingFunction;
    readonly in: (easing: EasingFunction) => EasingFunction;
    readonly out: (easing: EasingFunction) => EasingFunction;
    readonly inOut: (easing: EasingFunction) => EasingFunction;
}
Easing
,
function group<T extends Record<string, unknown>>(value: T): T & SignalGroupDefinitiongroup, class LayoutActor<TEvents extends ActorEventMap = {}>LayoutActor,
function signal<T>(): RequiredSignal<T> (+1 overload)
module signal
A timeline value on an actor, and an input to every spawn of it. Without an initial value the signal is required: `ctx.spawn` must supply it.
signal
,
function slot<const TAccepts extends SlotAccepts>(options: {
    accepts: TAccepts;
    multiple?: false;
}): OnePlaceField<TAccepts> (+1 overload)
Declare a place: a named position this actor's children are hosted in. Written as a field; the field reads as a handle once the actor spawns. ```ts screen = slot({ accepts: LayoutActor }); // this.screen.current letters = slot({ accepts: Letter, multiple: true }); // this.letters.all() ```
slot
,
type interface ActorClass<TInstance extends object = object>ActorClass, type type Duration = numberDuration, } from "@motionactor/core"; import { const animateWindowField: (window: DesktopWindowRecord, progress: number, updates: Partial<Record<WindowField, number>>) => DesktopWindowRecordanimateWindowField,
const buildWindowTransform: (window: DesktopWindowRecord, dockCenter: {
    x: number;
    y: number;
} | null) => string
buildWindowTransform
,
const clamp01: (value: number) => numberclamp01, const mapWindowById: (windows: DesktopWindowRecord[], windowId: string, update: (window: DesktopWindowRecord) => DesktopWindowRecord) => DesktopWindowRecord[]mapWindowById, const moveWindowToFront: (windows: DesktopWindowRecord[], windowId: string) => DesktopWindowRecord[]moveWindowToFront, const resolveDockMetrics: (skin: DesktopSkin, itemCount: number, width: number, height: number) => DesktopDockMetricsresolveDockMetrics, type
type DesktopActorWindowOptions = Pick<DesktopWindowRecord, "x" | "y" | "width" | "height" | "appId" | "title"> & {
    windowId: string;
}
DesktopActorWindowOptions
,
type
type DesktopDockItem = {
    id: string;
    label: string;
    icon?: string;
}
DesktopDockItem
,
type
type DesktopDockMetrics = {
    iconSize: number;
    gap: number;
    paddingX: number;
    dockWidth: number;
    left: number;
    centerY: number;
}
DesktopDockMetrics
,
type type DesktopSkin = "aqua" | "retro"DesktopSkin, type
type DesktopWindowRecord = {
    id: string;
    appId: string;
    title: string;
    x: number;
    y: number;
    width: number;
    height: number;
    isOpen: boolean;
    isMinimized: boolean;
    openProgress: number;
    minimizeProgress: number;
    content: {
        type: "hosted";
    } | {
        type: "blank";
    };
}
DesktopWindowRecord
,
type
type DesktopWindowVisual = {
    id: string;
    transform: string;
    opacity: number;
    zIndex: number;
    isFocused: boolean;
    visible: boolean;
}
DesktopWindowVisual
,
} from "./desktop-helpers"; export class class DesktopWindowHostDesktopWindowHost extends class LayoutActor<TEvents extends ActorEventMap = {}>LayoutActor { DesktopWindowHost.content: OnePlaceField<typeof LayoutActor>content =
slot<typeof LayoutActor>(options: {
    accepts: typeof LayoutActor;
    multiple?: false;
}): OnePlaceField<typeof LayoutActor> (+1 overload)
Declare a place: a named position this actor's children are hosted in. Written as a field; the field reads as a handle once the actor spawns. ```ts screen = slot({ accepts: LayoutActor }); // this.screen.current letters = slot({ accepts: Letter, multiple: true }); // this.letters.all() ```
slot
({ accepts: typeof LayoutActoraccepts: class LayoutActor<TEvents extends ActorEventMap = {}>LayoutActor });
DesktopWindowHost.window: {
    windowId: ActorFieldSignal<string>;
} & SignalGroupDefinition
window
=
group<{
    windowId: ActorFieldSignal<string>;
}>(value: {
    windowId: ActorFieldSignal<string>;
}): {
    windowId: ActorFieldSignal<string>;
} & SignalGroupDefinition
group
({ windowId: ActorFieldSignal<string>windowId: signal<string>(initial: string): ActorFieldSignal<string> (+1 overload)
A timeline value on an actor, and an input to every spawn of it. Without an initial value the signal is required: `ctx.spawn` must supply it.
signal
("") });
} export class class DesktopDesktop extends class LayoutActor<TEvents extends ActorEventMap = {}>LayoutActor { Desktop.windowHosts: ManyPlaceField<typeof DesktopWindowHost>windowHosts =
slot<typeof DesktopWindowHost>(options: {
    accepts: typeof DesktopWindowHost;
    multiple: true;
}): ManyPlaceField<typeof DesktopWindowHost> (+1 overload)
Declare a place: a named position this actor's children are hosted in. Written as a field; the field reads as a handle once the actor spawns. ```ts screen = slot({ accepts: LayoutActor }); // this.screen.current letters = slot({ accepts: Letter, multiple: true }); // this.letters.all() ```
slot
({ accepts: typeof DesktopWindowHostaccepts: class DesktopWindowHostDesktopWindowHost, multiple: truemultiple: true });
Desktop.skin: ActorFieldSignal<DesktopSkin>skin = signal<DesktopSkin>(initial: DesktopSkin): ActorFieldSignal<DesktopSkin> (+1 overload)
A timeline value on an actor, and an input to every spawn of it. Without an initial value the signal is required: `ctx.spawn` must supply it.
signal
<type DesktopSkin = "aqua" | "retro"DesktopSkin>("aqua");
Desktop.windows: ActorFieldSignal<DesktopWindowRecord[]>windows = signal<DesktopWindowRecord[]>(initial: DesktopWindowRecord[]): ActorFieldSignal<DesktopWindowRecord[]> (+1 overload)
A timeline value on an actor, and an input to every spawn of it. Without an initial value the signal is required: `ctx.spawn` must supply it.
signal
<
type DesktopWindowRecord = {
    id: string;
    appId: string;
    title: string;
    x: number;
    y: number;
    width: number;
    height: number;
    isOpen: boolean;
    isMinimized: boolean;
    openProgress: number;
    minimizeProgress: number;
    content: {
        type: "hosted";
    } | {
        type: "blank";
    };
}
DesktopWindowRecord
[]>([]);
Desktop.focusedWindowId: ActorFieldSignal<string>focusedWindowId = signal<string>(initial: string): ActorFieldSignal<string> (+1 overload)
A timeline value on an actor, and an input to every spawn of it. Without an initial value the signal is required: `ctx.spawn` must supply it.
signal
("");
Desktop.dock: {
    items: ActorFieldSignal<DesktopDockItem[]>;
    highlights: ActorFieldSignal<Record<string, number>>;
} & SignalGroupDefinition
dock
=
group<{
    items: ActorFieldSignal<DesktopDockItem[]>;
    highlights: ActorFieldSignal<Record<string, number>>;
}>(value: {
    items: ActorFieldSignal<DesktopDockItem[]>;
    highlights: ActorFieldSignal<Record<string, number>>;
}): {
    items: ActorFieldSignal<DesktopDockItem[]>;
    highlights: ActorFieldSignal<Record<string, number>>;
} & SignalGroupDefinition
group
({
items: ActorFieldSignal<DesktopDockItem[]>items: signal<DesktopDockItem[]>(initial: DesktopDockItem[]): ActorFieldSignal<DesktopDockItem[]> (+1 overload)
A timeline value on an actor, and an input to every spawn of it. Without an initial value the signal is required: `ctx.spawn` must supply it.
signal
<
type DesktopDockItem = {
    id: string;
    label: string;
    icon?: string;
}
DesktopDockItem
[]>([]),
highlights: ActorFieldSignal<Record<string, number>>highlights: signal<Record<string, number>>(initial: Record<string, number>): ActorFieldSignal<Record<string, number>> (+1 overload)
A timeline value on an actor, and an input to every spawn of it. Without an initial value the signal is required: `ctx.spawn` must supply it.
signal
<type Record<K extends keyof any, T> = { [P in K]: T; }
Construct a type with a set of properties K of type T
Record
<string, number>>({}),
});
Desktop.mouse: {
    x: ActorFieldSignal<number>;
    y: ActorFieldSignal<number>;
    clickProgress: ActorFieldSignal<number>;
} & SignalGroupDefinition
mouse
=
group<{
    x: ActorFieldSignal<number>;
    y: ActorFieldSignal<number>;
    clickProgress: ActorFieldSignal<number>;
}>(value: {
    x: ActorFieldSignal<number>;
    y: ActorFieldSignal<number>;
    clickProgress: ActorFieldSignal<number>;
}): {
    x: ActorFieldSignal<number>;
    y: ActorFieldSignal<number>;
    clickProgress: ActorFieldSignal<number>;
} & SignalGroupDefinition
group
({
x: ActorFieldSignal<number>x: signal<number>(initial: number): ActorFieldSignal<number> (+1 overload)
A timeline value on an actor, and an input to every spawn of it. Without an initial value the signal is required: `ctx.spawn` must supply it.
signal
(0),
y: ActorFieldSignal<number>y: signal<number>(initial: number): ActorFieldSignal<number> (+1 overload)
A timeline value on an actor, and an input to every spawn of it. Without an initial value the signal is required: `ctx.spawn` must supply it.
signal
(0),
clickProgress: ActorFieldSignal<number>clickProgress: signal<number>(initial: number): ActorFieldSignal<number> (+1 overload)
A timeline value on an actor, and an input to every spawn of it. Without an initial value the signal is required: `ctx.spawn` must supply it.
signal
(0),
}); Desktop.dockMetrics: DerivedDefinition<DesktopDockMetrics, unknown>dockMetrics = derived<unknown, DesktopDockMetrics>(fn: (this: unknown) => DesktopDockMetrics): DerivedDefinition<DesktopDockMetrics, unknown>derived(():
type DesktopDockMetrics = {
    iconSize: number;
    gap: number;
    paddingX: number;
    dockWidth: number;
    left: number;
    centerY: number;
}
DesktopDockMetrics
=> {
const { const width: numberwidth, const height: numberheight } = this.LayoutActor<{}>.localLayout(): ResolvedLayoutBoxlocalLayout(); return function resolveDockMetrics(skin: DesktopSkin, itemCount: number, width: number, height: number): DesktopDockMetricsresolveDockMetrics(this.Desktop.skin: () => DesktopSkinskin(), this.
Desktop.dock: {
    items: ActorFieldSignal<DesktopDockItem[]>;
    highlights: ActorFieldSignal<Record<string, number>>;
} & SignalGroupDefinition
dock
.items: () => DesktopDockItem[]items().Array<T>.length: number
Gets or sets the length of the array. This is a number one higher than the highest index in the array.
length
, const width: numberwidth, const height: numberheight);
}); /** appId to the screen point a window opens from and minimizes into. */
Desktop.dockIconCenters: DerivedDefinition<Record<string, {
    x: number;
    y: number;
}>, unknown>
appId to the screen point a window opens from and minimizes into.
dockIconCenters
=
derived<unknown, Record<string, {
    x: number;
    y: number;
}>>(fn: (this: unknown) => Record<string, {
    x: number;
    y: number;
}>): DerivedDefinition<Record<string, {
    x: number;
    y: number;
}>, unknown>
derived
(() => {
const { const iconSize: numbericonSize, const gap: numbergap, const paddingX: numberpaddingX, const left: numberleft, const centerY: numbercenterY } = this.
Desktop.dockMetrics: DerivedDefinition
() => DesktopDockMetrics
dockMetrics
();
const
const centers: Record<string, {
    x: number;
    y: number;
}>
centers
: type Record<K extends keyof any, T> = { [P in K]: T; }
Construct a type with a set of properties K of type T
Record
<string, { x: numberx: number; y: numbery: number }> = {};
this.
Desktop.dock: {
    items: ActorFieldSignal<DesktopDockItem[]>;
    highlights: ActorFieldSignal<Record<string, number>>;
} & SignalGroupDefinition
dock
.items: () => DesktopDockItem[]items().Array<DesktopDockItem>.forEach(callbackfn: (value: DesktopDockItem, index: number, array: DesktopDockItem[]) => void, thisArg?: any): void
Performs the specified action for each element in an array.
@paramcallbackfn A function that accepts up to three arguments. forEach calls the callbackfn function one time for each element in the array.@paramthisArg An object to which the this keyword can refer in the callbackfn function. If thisArg is omitted, undefined is used as the this value.
forEach
((item: DesktopDockItemitem, index: numberindex) => {
const centers: Record<string, {
    x: number;
    y: number;
}>
centers
[item: DesktopDockItemitem.id: stringid] = {
x: numberx: const left: numberleft + const paddingX: numberpaddingX + index: numberindex * (const iconSize: numbericonSize + const gap: numbergap) + const iconSize: numbericonSize / 2, y: numbery: const centerY: numbercenterY, }; }); return
const centers: Record<string, {
    x: number;
    y: number;
}>
centers
;
}); Desktop.windowVisuals: DerivedDefinition<DesktopWindowVisual[], unknown>windowVisuals = derived<unknown, DesktopWindowVisual[]>(fn: (this: unknown) => DesktopWindowVisual[]): DerivedDefinition<DesktopWindowVisual[], unknown>derived(():
type DesktopWindowVisual = {
    id: string;
    transform: string;
    opacity: number;
    zIndex: number;
    isFocused: boolean;
    visible: boolean;
}
DesktopWindowVisual
[] => {
const
const centers: Record<string, {
    x: number;
    y: number;
}>
centers
= this.
Desktop.dockIconCenters: DerivedDefinition
() => Record<string, {
    x: number;
    y: number;
}>
appId to the screen point a window opens from and minimizes into.
dockIconCenters
();
const const focused: stringfocused = this.Desktop.focusedWindowId: () => stringfocusedWindowId(); return this.Desktop.windows: () => DesktopWindowRecord[]windows().
Array<DesktopWindowRecord>.map<{
    id: string;
    transform: string;
    opacity: number;
    zIndex: number;
    isFocused: boolean;
    visible: boolean;
}>(callbackfn: (value: DesktopWindowRecord, index: number, array: DesktopWindowRecord[]) => {
    id: string;
    transform: string;
    opacity: number;
    zIndex: number;
    isFocused: boolean;
    visible: boolean;
}, thisArg?: any): {
    id: string;
    transform: string;
    opacity: number;
    zIndex: number;
    isFocused: boolean;
    visible: boolean;
}[]
Calls a defined callback function on each element of an array, and returns an array that contains the results.
@paramcallbackfn A function that accepts up to three arguments. The map method calls the callbackfn function one time for each element in the array.@paramthisArg An object to which the this keyword can refer in the callbackfn function. If thisArg is omitted, undefined is used as the this value.
map
((window: DesktopWindowRecordwindow, index: numberindex) => {
const const opacity: numberopacity = function clamp01(value: number): numberclamp01(window: DesktopWindowRecordwindow.openProgress: numberopenProgress) * (1 - function clamp01(value: number): numberclamp01(window: DesktopWindowRecordwindow.minimizeProgress: numberminimizeProgress)); return { id: stringid: window: DesktopWindowRecordwindow.id: stringid, transform: stringtransform:
function buildWindowTransform(window: DesktopWindowRecord, dockCenter: {
    x: number;
    y: number;
} | null): string
buildWindowTransform
(window: DesktopWindowRecordwindow,
const centers: Record<string, {
    x: number;
    y: number;
}>
centers
[window: DesktopWindowRecordwindow.appId: stringappId] ?? null),
opacity: numberopacity, zIndex: numberzIndex: 100 + index: numberindex, isFocused: booleanisFocused: const focused: stringfocused === window: DesktopWindowRecordwindow.id: stringid, visible: booleanvisible: (window: DesktopWindowRecordwindow.isOpen: booleanisOpen || window: DesktopWindowRecordwindow.openProgress: numberopenProgress > 0) && const opacity: numberopacity > 0.001, }; }); }); Desktop.setDockItems(items: DesktopDockItem[]): voidsetDockItems(items: DesktopDockItem[]items:
type DesktopDockItem = {
    id: string;
    label: string;
    icon?: string;
}
DesktopDockItem
[]) {
this.
Desktop.dock: {
    items: ActorFieldSignal<DesktopDockItem[]>;
    highlights: ActorFieldSignal<Record<string, number>>;
} & SignalGroupDefinition
dock
.items: ActorFieldSignal<DesktopDockItem[]>items.LiveSignal<DesktopDockItem[]>.set(value: DesktopDockItem[]): void
Set value instantly at current cursor (no yield needed)
set
(items: DesktopDockItem[]items);
} Desktop.spawnActorWindow<T extends LayoutActor>(definition: ActorClass<T>, window: DesktopActorWindowOptions): TspawnActorWindow<function (type parameter) T in Desktop.spawnActorWindow<T extends LayoutActor>(definition: ActorClass<T>, window: DesktopActorWindowOptions): TT extends class LayoutActor<TEvents extends ActorEventMap = {}>LayoutActor>( definition: ActorClass<T>definition: interface ActorClass<TInstance extends object = object>ActorClass<function (type parameter) T in Desktop.spawnActorWindow<T extends LayoutActor>(definition: ActorClass<T>, window: DesktopActorWindowOptions): TT>, window: DesktopActorWindowOptionswindow:
type DesktopActorWindowOptions = Pick<DesktopWindowRecord, "x" | "y" | "width" | "height" | "appId" | "title"> & {
    windowId: string;
}
DesktopActorWindowOptions
,
): function (type parameter) T in Desktop.spawnActorWindow<T extends LayoutActor>(definition: ActorClass<T>, window: DesktopActorWindowOptions): TT; Desktop.spawnActorWindow<T extends LayoutActor>(definition: ActorClass<T>, window: DesktopActorWindowOptions): TspawnActorWindow( definition: ActorClass<LayoutActor<{}>>definition: interface ActorClass<TInstance extends object = object>ActorClass<class LayoutActor<TEvents extends ActorEventMap = {}>LayoutActor>, window: DesktopActorWindowOptionswindow:
type DesktopActorWindowOptions = Pick<DesktopWindowRecord, "x" | "y" | "width" | "height" | "appId" | "title"> & {
    windowId: string;
}
DesktopActorWindowOptions
,
): unknown { const const host: DesktopWindowHosthost = this.Desktop.windowHosts: ManyPlaceField<typeof DesktopWindowHost>windowHosts.
function spawn(overrides?: {
    measuredBox?: undefined;
    transform?: {
        scale?: SignalInputValue<number> | undefined;
        rotation?: SignalInputValue<number> | undefined;
    } | undefined;
    id?: undefined;
    window?: {
        windowId?: SignalInputValue<string> | undefined;
    } | undefined;
    layout?: {
        x?: SignalInputValue<LayoutValue> | undefined;
        y?: SignalInputValue<LayoutValue> | undefined;
        width?: Readonly<{
            kind: "layout.percent";
            value: number;
        }> | SignalInputValue<number | `${number}%` | Readonly<...>> | undefined;
        height?: Readonly<...> | ... 1 more ... | undefined;
    } | undefined;
    ... 18 more ...;
    parent?: undefined;
} | undefined): DesktopWindowHost (+1 overload)
Host a child of the one declared class.
spawn
({
window?: {
    windowId?: SignalInputValue<string> | undefined;
} | undefined
window
: { windowId?: SignalInputValue<string> | undefinedwindowId: window: DesktopActorWindowOptionswindow.windowId: stringwindowId },
}); const const actor: LayoutActor<{}>actor = const host: DesktopWindowHosthost.DesktopWindowHost.content: OnePlaceField<typeof LayoutActor>content.
ClassSpawn<typeof LayoutActor>.spawn<ActorClass<LayoutActor<{}>>>(blueprint: ActorClass<LayoutActor<{}>>, overrides?: {
    measuredBox?: undefined;
    transform?: {
        scale?: SignalInputValue<number> | undefined;
        rotation?: SignalInputValue<number> | undefined;
    } | undefined;
    id?: undefined;
    layout?: {
        x?: SignalInputValue<LayoutValue> | undefined;
        y?: SignalInputValue<LayoutValue> | undefined;
        width?: Readonly<{
            kind: "layout.percent";
            value: number;
        }> | SignalInputValue<...> | undefined;
        height?: Readonly<...> | ... 1 more ... | undefined;
    } | undefined;
    ... 18 more ...;
    parent?: undefined;
} | undefined): LayoutActor<...>
Host a child of `blueprint`, which must be an accepted class.
spawn
(definition: ActorClass<LayoutActor<{}>>definition);
const { const windowId: stringwindowId, ...
const placement: {
    x: number;
    y: number;
    width: number;
    height: number;
    appId: string;
    title: string;
}
placement
} = window: DesktopActorWindowOptionswindow;
this.Desktop.windows: ActorFieldSignal<DesktopWindowRecord[]>windows.LiveSignal<DesktopWindowRecord[]>.set(value: DesktopWindowRecord[]): void
Set value instantly at current cursor (no yield needed)
set
([
...this.Desktop.windows: ActorFieldSignal<DesktopWindowRecord[]>windows.ReadSignal<DesktopWindowRecord[]>.peek(): DesktopWindowRecord[]
Read current value at compile cursor or current resolve frame
peek
(),
{ ...
const placement: {
    x: number;
    y: number;
    width: number;
    height: number;
    appId: string;
    title: string;
}
placement
,
id: stringid: const windowId: stringwindowId, isOpen: booleanisOpen: false, isMinimized: booleanisMinimized: false, openProgress: numberopenProgress: 0, minimizeProgress: numberminimizeProgress: 0,
content: {
    type: "hosted";
} | {
    type: "blank";
}
content
: { type: "hosted"type: "hosted" },
}, ]); return const actor: LayoutActor<{}>actor; } *
Desktop.moveCursor({ x, y }: {
    x: number;
    y: number;
}, transition?: Duration): Generator<number, void, unknown>
moveCursor
({ x: numberx, y: numbery }: { x: numberx: number; y: numbery: number }, transition: numbertransition: type Duration = numberDuration = 0.6667) {
yield* this.Actor<{}>.runtime: ActorRuntimeOrchestration<this>runtime.ActorRuntimeOrchestration<this>.all(...generators: Generator<Frame, unknown, unknown>[]): Generator<Frame, void, unknown>all( this.
Desktop.mouse: {
    x: ActorFieldSignal<number>;
    y: ActorFieldSignal<number>;
    clickProgress: ActorFieldSignal<number>;
} & SignalGroupDefinition
mouse
.x: ActorFieldSignal<number>x.LiveSignal<number>.tween(target: number, options: number | SignalTweenOptions<number>): Generator<Frame, void, unknown>
Tween to target over authored seconds — must yield*
tween
(x: numberx, { SignalTweenOptions<number>.duration: numberduration: transition: numbertransition, SignalTweenOptions<number>.easing?: EasingFunction | undefinedeasing:
const Easing: {
    readonly linear: EasingFunction;
    readonly inQuad: EasingFunction;
    readonly outQuad: EasingFunction;
    readonly inOutQuad: EasingFunction;
    readonly inCubic: EasingFunction;
    readonly outCubic: EasingFunction;
    readonly inOutCubic: EasingFunction;
    readonly inQuart: EasingFunction;
    readonly outQuart: EasingFunction;
    readonly inOutQuart: EasingFunction;
    readonly bezier: (x1: number, y1: number, x2: number, y2: number) => EasingFunction;
    readonly wiggle: (count: number, type?: WiggleType, amplitude?: number) => EasingFunction;
    readonly in: (easing: EasingFunction) => EasingFunction;
    readonly out: (easing: EasingFunction) => EasingFunction;
    readonly inOut: (easing: EasingFunction) => EasingFunction;
}
Easing
.inOutCubic: EasingFunctioninOutCubic }),
this.
Desktop.mouse: {
    x: ActorFieldSignal<number>;
    y: ActorFieldSignal<number>;
    clickProgress: ActorFieldSignal<number>;
} & SignalGroupDefinition
mouse
.y: ActorFieldSignal<number>y.LiveSignal<number>.tween(target: number, options: number | SignalTweenOptions<number>): Generator<Frame, void, unknown>
Tween to target over authored seconds — must yield*
tween
(y: numbery, { SignalTweenOptions<number>.duration: numberduration: transition: numbertransition, SignalTweenOptions<number>.easing?: EasingFunction | undefinedeasing:
const Easing: {
    readonly linear: EasingFunction;
    readonly inQuad: EasingFunction;
    readonly outQuad: EasingFunction;
    readonly inOutQuad: EasingFunction;
    readonly inCubic: EasingFunction;
    readonly outCubic: EasingFunction;
    readonly inOutCubic: EasingFunction;
    readonly inQuart: EasingFunction;
    readonly outQuart: EasingFunction;
    readonly inOutQuart: EasingFunction;
    readonly bezier: (x1: number, y1: number, x2: number, y2: number) => EasingFunction;
    readonly wiggle: (count: number, type?: WiggleType, amplitude?: number) => EasingFunction;
    readonly in: (easing: EasingFunction) => EasingFunction;
    readonly out: (easing: EasingFunction) => EasingFunction;
    readonly inOut: (easing: EasingFunction) => EasingFunction;
}
Easing
.inOutCubic: EasingFunctioninOutCubic }),
); } *Desktop.openWindow(windowId: string, duration?: Duration): Generator<number, void, unknown>openWindow(windowId: stringwindowId: string, duration: numberduration: type Duration = numberDuration = 0.6667) { const const baseline: DesktopWindowRecord[]baseline = function moveWindowToFront(windows: DesktopWindowRecord[], windowId: string): DesktopWindowRecord[]moveWindowToFront( function mapWindowById(windows: DesktopWindowRecord[], windowId: string, update: (window: DesktopWindowRecord) => DesktopWindowRecord): DesktopWindowRecord[]mapWindowById(this.Desktop.windows: ActorFieldSignal<DesktopWindowRecord[]>windows.ReadSignal<DesktopWindowRecord[]>.peek(): DesktopWindowRecord[]
Read current value at compile cursor or current resolve frame
peek
(), windowId: stringwindowId, (window: DesktopWindowRecordwindow) => ({
...window: DesktopWindowRecordwindow, isOpen: booleanisOpen: true, isMinimized: booleanisMinimized: false, minimizeProgress: numberminimizeProgress: 0, })), windowId: stringwindowId, ); this.Desktop.windows: ActorFieldSignal<DesktopWindowRecord[]>windows.LiveSignal<DesktopWindowRecord[]>.set(value: DesktopWindowRecord[]): void
Set value instantly at current cursor (no yield needed)
set
(const baseline: DesktopWindowRecord[]baseline);
this.Desktop.focusedWindowId: ActorFieldSignal<string>focusedWindowId.LiveSignal<string>.set(value: string): void
Set value instantly at current cursor (no yield needed)
set
(windowId: stringwindowId);
yield* this.Desktop.windows: ActorFieldSignal<DesktopWindowRecord[]>windows.LiveSignal<DesktopWindowRecord[]>.transition(fn: (progress: number, from: DesktopWindowRecord[]) => DesktopWindowRecord[], options: number | SignalTransitionOptions<DesktopWindowRecord[]>): Generator<Frame, void, unknown>
Custom transition — must yield*
transition
(
(progress: numberprogress) => const baseline: DesktopWindowRecord[]baseline.Array<DesktopWindowRecord>.map<DesktopWindowRecord>(callbackfn: (value: DesktopWindowRecord, index: number, array: DesktopWindowRecord[]) => DesktopWindowRecord, thisArg?: any): DesktopWindowRecord[]
Calls a defined callback function on each element of an array, and returns an array that contains the results.
@paramcallbackfn A function that accepts up to three arguments. The map method calls the callbackfn function one time for each element in the array.@paramthisArg An object to which the this keyword can refer in the callbackfn function. If thisArg is omitted, undefined is used as the this value.
map
((window: DesktopWindowRecordwindow) =>
window: DesktopWindowRecordwindow.id: stringid === windowId: stringwindowId ? function animateWindowField(window: DesktopWindowRecord, progress: number, updates: Partial<Record<WindowField, number>>): DesktopWindowRecordanimateWindowField(window: DesktopWindowRecordwindow, progress: numberprogress, { openProgress?: number | undefinedopenProgress: 1 }) : window: DesktopWindowRecordwindow, ), { SignalTransitionOptions<DesktopWindowRecord[]>.duration: numberduration, SignalTransitionOptions<DesktopWindowRecord[]>.easing?: EasingFunction | undefinedeasing:
const Easing: {
    readonly linear: EasingFunction;
    readonly inQuad: EasingFunction;
    readonly outQuad: EasingFunction;
    readonly inOutQuad: EasingFunction;
    readonly inCubic: EasingFunction;
    readonly outCubic: EasingFunction;
    readonly inOutCubic: EasingFunction;
    readonly inQuart: EasingFunction;
    readonly outQuart: EasingFunction;
    readonly inOutQuart: EasingFunction;
    readonly bezier: (x1: number, y1: number, x2: number, y2: number) => EasingFunction;
    readonly wiggle: (count: number, type?: WiggleType, amplitude?: number) => EasingFunction;
    readonly in: (easing: EasingFunction) => EasingFunction;
    readonly out: (easing: EasingFunction) => EasingFunction;
    readonly inOut: (easing: EasingFunction) => EasingFunction;
}
Easing
.out: (easing: EasingFunction) => EasingFunctionout(
const Easing: {
    readonly linear: EasingFunction;
    readonly inQuad: EasingFunction;
    readonly outQuad: EasingFunction;
    readonly inOutQuad: EasingFunction;
    readonly inCubic: EasingFunction;
    readonly outCubic: EasingFunction;
    readonly inOutCubic: EasingFunction;
    readonly inQuart: EasingFunction;
    readonly outQuart: EasingFunction;
    readonly inOutQuart: EasingFunction;
    readonly bezier: (x1: number, y1: number, x2: number, y2: number) => EasingFunction;
    readonly wiggle: (count: number, type?: WiggleType, amplitude?: number) => EasingFunction;
    readonly in: (easing: EasingFunction) => EasingFunction;
    readonly out: (easing: EasingFunction) => EasingFunction;
    readonly inOut: (easing: EasingFunction) => EasingFunction;
}
Easing
.inCubic: EasingFunctioninCubic) },
); } }

State

Everything the scene writes is a signal, with related fields namespaced by group. The open windows are one signal holding an array. Opening a window changes several of its fields as one step, and the window list changes as a whole, so one value with one writer is simpler than a nested reactiveSignal.

Presentation

The renderers draw finished numbers. windowVisuals reads dockIconCenters, which reads dockMetrics, which reads the resolved box and the dock’s signals. Each is a derived, recomputed for the frame being resolved. The Aqua and retro skins read the same windowVisuals, and a test can check where a window minimizes to without drawing it.

Hosting

Window chrome belongs to the desktop. What a window shows is any other actor. Each window is a DesktopWindowHost in the desktop’s windowHosts slot, and the app goes into that host’s content slot. accepts: LayoutActor lets any actor that can be laid out be a window’s content, so the desktop hosts apps it does not import.

spawnActorWindow does all three steps: spawn a host, spawn the app into it and add the window’s record to windows. The record starts closed, and openWindow animates it.

Commands

moveCursor moves both axes together with this.runtime.all. openWindow writes the structural facts first: the window is open, frontmost and focused from the first frame of its animation. Then a transition moves openProgress inside the window list, so the list changes as one value each frame.

A scene

import { function createScene<TSceneExports, const I extends SceneInputs = Readonly<Record<never, SceneInput>>, const L extends SceneLayers = Readonly<Record<never, SceneLayer<BackendRef<string>>>>>(script: SceneScript<I, L, TSceneExports>, options?: SceneDefinitionOptions<I, L>): SceneDefinition<I, L, TSceneExports>
Declare a scene. Nothing compiles until a host builds it: `scene.build({ viewport, fps, inputs })` runs the script as `(ctx, inputs, viewport, layers)` and returns the compiled scene. `ctx.spawn` places a root actor in the default layer; `layers.<key>.spawn` places it in a declared one. `ctx.stage` stages another scene on this scene's clock, and a staged scene may stage again.
createScene
, class LayoutActor<TEvents extends ActorEventMap = {}>LayoutActor,
function signal<T>(): RequiredSignal<T> (+1 overload)
module signal
A timeline value on an actor, and an input to every spawn of it. Without an initial value the signal is required: `ctx.spawn` must supply it.
signal
} from "@motionactor/core";
import { class DesktopDesktop } from "./desktop"; class class TerminalTerminal extends class LayoutActor<TEvents extends ActorEventMap = {}>LayoutActor { Terminal.output: ActorFieldSignal<string>output = signal<string>(initial: string): ActorFieldSignal<string> (+1 overload)
A timeline value on an actor, and an input to every spawn of it. Without an initial value the signal is required: `ctx.spawn` must supply it.
signal
("");
} export const
const compiled: CompiledScene<{
    desktop: Desktop;
    terminal: Terminal;
}, CompiledLayersOf<Readonly<Record<never, SceneLayer<BackendRef<string>>>>>>
compiled
=
createScene<{
    desktop: Desktop;
    terminal: Terminal;
}, Readonly<Record<never, SceneInput>>, Readonly<Record<never, SceneLayer<BackendRef<string>>>>>(script: SceneScript<Readonly<Record<never, SceneInput>>, Readonly<Record<never, SceneLayer<BackendRef<string>>>>, {
    desktop: Desktop;
    terminal: Terminal;
}>, options?: SceneDefinitionOptions<Readonly<Record<never, SceneInput>>, Readonly<...>> | undefined): SceneDefinition<...>
Declare a scene. Nothing compiles until a host builds it: `scene.build({ viewport, fps, inputs })` runs the script as `(ctx, inputs, viewport, layers)` and returns the compiled scene. `ctx.spawn` places a root actor in the default layer; `layers.<key>.spawn` places it in a declared one. `ctx.stage` stages another scene on this scene's clock, and a staged scene may stage again.
createScene
(function* (ctx: SceneContextctx) {
const const desktop: Desktopdesktop = ctx: SceneContextctx.
CompileContext.spawn<Desktop>(definition: ActorClass<Desktop>, overrides?: {
    measuredBox?: undefined;
    layout?: {
        x?: SignalInputValue<LayoutValue> | undefined;
        y?: SignalInputValue<LayoutValue> | undefined;
        width?: Readonly<{
            kind: "layout.percent";
            value: number;
        }> | SignalInputValue<number | `${number}%` | Readonly<{
            kind: "layout.percent";
            value: number;
        }>> | undefined;
        height?: Readonly<{
            kind: "layout.percent";
            value: number;
        }> | SignalInputValue<...> | undefined;
    } | undefined;
    ... 32 more ...;
    parent?: undefined;
} | undefined, options?: {
    ...;
} | undefined): Desktop (+1 overload)
spawn
(class DesktopDesktop, {
layout?: {
    x?: SignalInputValue<LayoutValue> | undefined;
    y?: SignalInputValue<LayoutValue> | undefined;
    width?: Readonly<{
        kind: "layout.percent";
        value: number;
    }> | SignalInputValue<number | `${number}%` | Readonly<{
        kind: "layout.percent";
        value: number;
    }>> | undefined;
    height?: Readonly<{
        kind: "layout.percent";
        value: number;
    }> | SignalInputValue<number | `${number}%` | Readonly<{
        kind: "layout.percent";
        value: number;
    }>> | undefined;
} | undefined
layout
: {
width?: Readonly<{
    kind: "layout.percent";
    value: number;
}> | SignalInputValue<number | `${number}%` | Readonly<{
    kind: "layout.percent";
    value: number;
}>> | undefined
width
: 1920,
height?: Readonly<{
    kind: "layout.percent";
    value: number;
}> | SignalInputValue<number | `${number}%` | Readonly<{
    kind: "layout.percent";
    value: number;
}>> | undefined
height
: 1080 },
}); const desktop: Desktopdesktop.Desktop.setDockItems(items: DesktopDockItem[]): voidsetDockItems([ { id: stringid: "browser", label: stringlabel: "Browser" }, { id: stringid: "terminal", label: stringlabel: "Terminal" }, ]); const const terminal: Terminalterminal = const desktop: Desktopdesktop.Desktop.spawnActorWindow<Terminal>(definition: ActorClass<Terminal>, window: DesktopActorWindowOptions): TerminalspawnActorWindow(class TerminalTerminal, { windowId: stringwindowId: "terminal-window", appId: stringappId: "terminal", title: stringtitle: "Terminal", x: numberx: 460, y: numbery: 210, width: numberwidth: 760, height: numberheight: 420, }); yield* const desktop: Desktopdesktop.
Desktop.moveCursor({ x, y }: {
    x: number;
    y: number;
}, transition?: Duration): Generator<number, void, unknown>
moveCursor
({ x: numberx: 960, y: numbery: 1030 }, 0.5);
yield* const desktop: Desktopdesktop.Desktop.openWindow(windowId: string, duration?: Duration): Generator<number, void, unknown>openWindow("terminal-window", 0.5); const terminal: Terminalterminal.Terminal.output: ActorFieldSignal<string>output.LiveSignal<string>.set(value: string): void
Set value instantly at current cursor (no yield needed)
set
("$ pnpm test");
yield* ctx: SceneContextctx.CompileContext.wait(duration: Duration): Generator<Frame, void, unknown>wait(1); return { desktop: Desktopdesktop, terminal: Terminalterminal }; }).
SceneDefinition<Readonly<Record<never, SceneInput>>, Readonly<Record<never, SceneLayer<BackendRef<string>>>>, { ...; }>.build(args: SceneBuildInput<Readonly<Record<never, SceneInput>>>): CompiledScene<{
    desktop: Desktop;
    terminal: Terminal;
}, CompiledLayersOf<Readonly<Record<never, SceneLayer<BackendRef<string>>>>>>
build
({ viewport: SceneViewportviewport: { SceneViewport.width: numberwidth: 1920, SceneViewport.height: numberheight: 1080 }, fps: numberfps: 30 });

The terminal is an ordinary actor with its own state, written on the same timeline as the desktop’s commands. Nothing coordinates them beyond the scene’s single cursor.

Was this page helpful?