mirror of
https://github.com/motion-canvas/motion-canvas.git
synced 2026-01-11 23:07:57 -05:00
feat(2d): improve property declarations
This PR splits property declarations into separate decorators. `@initial()`, `interpolation()`, and `wrapper()` were added. The following declaration: ```ts @compound(['x', 'y'], Vector2) @property(undefined, Vector2.lerp, Vector2) public declare readonly position: Signal<Vector2, this>; ``` becomes: ```ts @compound(['x', 'y']) @wrapper(Vector2) @property() public declare readonly position: Signal<Vector2, this>; ``` Eliminating the need to repeat `Vector2` in both decorators.
This commit is contained in:
@@ -1,4 +1,4 @@
|
||||
import {computed, property} from '../decorators';
|
||||
import {computed, initial, property} from '../decorators';
|
||||
import {Signal, SignalValue} from '@motion-canvas/core/lib/utils';
|
||||
import {Shape, ShapeProps} from './Shape';
|
||||
import {CodeTree, parse, diff, ready, MorphToken} from 'code-fns';
|
||||
@@ -16,7 +16,8 @@ export interface CodeProps extends ShapeProps {
|
||||
}
|
||||
|
||||
export class CodeBlock extends Shape {
|
||||
@property('')
|
||||
@initial('')
|
||||
@property()
|
||||
public declare readonly code: Signal<CodeTree, this>;
|
||||
|
||||
private progress: number | null = null;
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import {Signal, SignalValue} from '@motion-canvas/core/lib/utils';
|
||||
import {computed, property} from '../decorators';
|
||||
import {computed, initial, property} from '../decorators';
|
||||
import {Rect as RectType, Vector2} from '@motion-canvas/core/lib/types';
|
||||
import Color from 'colorjs.io';
|
||||
import {drawImage} from '../utils';
|
||||
@@ -15,10 +15,12 @@ export class Image extends Rect {
|
||||
@property()
|
||||
public declare readonly src: Signal<string, this>;
|
||||
|
||||
@property(1)
|
||||
@initial(1)
|
||||
@property()
|
||||
public declare readonly alpha: Signal<number, this>;
|
||||
|
||||
@property(true)
|
||||
@initial(true)
|
||||
@property()
|
||||
public declare readonly smoothing: Signal<boolean, this>;
|
||||
|
||||
protected readonly image: HTMLImageElement;
|
||||
|
||||
@@ -1,4 +1,12 @@
|
||||
import {compound, computed, Property, property} from '../decorators';
|
||||
import {
|
||||
compound,
|
||||
computed,
|
||||
initial,
|
||||
interpolation,
|
||||
Property,
|
||||
property,
|
||||
wrapper,
|
||||
} from '../decorators';
|
||||
import {
|
||||
Origin,
|
||||
PossibleSpacing,
|
||||
@@ -87,100 +95,129 @@ export interface LayoutProps extends NodeProps {
|
||||
}
|
||||
|
||||
export class Layout extends Node {
|
||||
@property(null)
|
||||
@initial(null)
|
||||
@property()
|
||||
public declare readonly layout: Signal<LayoutMode, this>;
|
||||
|
||||
@property(null)
|
||||
@initial(null)
|
||||
@property()
|
||||
public declare readonly maxWidth: Signal<Length, this>;
|
||||
@property(null)
|
||||
@initial(null)
|
||||
@property()
|
||||
public declare readonly maxHeight: Signal<Length, this>;
|
||||
@property(null)
|
||||
@initial(null)
|
||||
@property()
|
||||
public declare readonly minWidth: Signal<Length, this>;
|
||||
@property(null)
|
||||
@initial(null)
|
||||
@property()
|
||||
public declare readonly minHeight: Signal<Length, this>;
|
||||
@property(null)
|
||||
@initial(null)
|
||||
@property()
|
||||
public declare readonly ratio: Signal<number | null, this>;
|
||||
|
||||
@property(0)
|
||||
@initial(0)
|
||||
@property()
|
||||
public declare readonly marginTop: Signal<number, this>;
|
||||
@property(0)
|
||||
@initial(0)
|
||||
@property()
|
||||
public declare readonly marginBottom: Signal<number, this>;
|
||||
@property(0)
|
||||
@initial(0)
|
||||
@property()
|
||||
public declare readonly marginLeft: Signal<number, this>;
|
||||
@property(0)
|
||||
@initial(0)
|
||||
@property()
|
||||
public declare readonly marginRight: Signal<number, this>;
|
||||
@compound(
|
||||
{
|
||||
top: 'marginTop',
|
||||
bottom: 'marginBottom',
|
||||
left: 'marginLeft',
|
||||
right: 'marginRight',
|
||||
},
|
||||
Spacing,
|
||||
)
|
||||
@property(undefined, Spacing.lerp, Spacing)
|
||||
@compound({
|
||||
top: 'marginTop',
|
||||
bottom: 'marginBottom',
|
||||
left: 'marginLeft',
|
||||
right: 'marginRight',
|
||||
})
|
||||
@wrapper(Spacing)
|
||||
@property()
|
||||
public declare readonly margin: Property<PossibleSpacing, Spacing, this>;
|
||||
|
||||
@property(0)
|
||||
@initial(0)
|
||||
@property()
|
||||
public declare readonly paddingTop: Signal<number, this>;
|
||||
@property(0)
|
||||
@initial(0)
|
||||
@property()
|
||||
public declare readonly paddingBottom: Signal<number, this>;
|
||||
@property(0)
|
||||
@initial(0)
|
||||
@property()
|
||||
public declare readonly paddingLeft: Signal<number, this>;
|
||||
@property(0)
|
||||
@initial(0)
|
||||
@property()
|
||||
public declare readonly paddingRight: Signal<number, this>;
|
||||
@compound(
|
||||
{
|
||||
top: 'paddingTop',
|
||||
bottom: 'paddingBottom',
|
||||
left: 'paddingLeft',
|
||||
right: 'paddingRight',
|
||||
},
|
||||
Spacing,
|
||||
)
|
||||
@property(undefined, Spacing.lerp, Spacing)
|
||||
@compound({
|
||||
top: 'paddingTop',
|
||||
bottom: 'paddingBottom',
|
||||
left: 'paddingLeft',
|
||||
right: 'paddingRight',
|
||||
})
|
||||
@wrapper(Spacing)
|
||||
@property()
|
||||
public declare readonly padding: Property<PossibleSpacing, Spacing, this>;
|
||||
|
||||
@property('row')
|
||||
@initial('row')
|
||||
@property()
|
||||
public declare readonly direction: Signal<FlexDirection, this>;
|
||||
@property(null)
|
||||
@initial(null)
|
||||
@property()
|
||||
public declare readonly basis: Signal<FlexBasis, this>;
|
||||
@property(0)
|
||||
@initial(0)
|
||||
@property()
|
||||
public declare readonly grow: Signal<number, this>;
|
||||
@property(1)
|
||||
@initial(1)
|
||||
@property()
|
||||
public declare readonly shrink: Signal<number, this>;
|
||||
@property('nowrap')
|
||||
@initial('nowrap')
|
||||
@property()
|
||||
public declare readonly wrap: Signal<FlexWrap, this>;
|
||||
|
||||
@property('normal')
|
||||
@initial('normal')
|
||||
@property()
|
||||
public declare readonly justifyContent: Signal<FlexJustify, this>;
|
||||
@property('normal')
|
||||
@initial('normal')
|
||||
@property()
|
||||
public declare readonly alignItems: Signal<FlexAlign, this>;
|
||||
@property(null)
|
||||
@initial(null)
|
||||
@property()
|
||||
public declare readonly gap: Signal<Length, this>;
|
||||
@property(null)
|
||||
@initial(null)
|
||||
@property()
|
||||
public declare readonly rowGap: Signal<Length, this>;
|
||||
@property(null)
|
||||
@initial(null)
|
||||
@property()
|
||||
public declare readonly columnGap: Signal<Length, this>;
|
||||
|
||||
@property(null)
|
||||
@initial(null)
|
||||
@property()
|
||||
public declare readonly fontFamily: Signal<string | null, this>;
|
||||
@property(null)
|
||||
@initial(null)
|
||||
@property()
|
||||
public declare readonly fontSize: Signal<number | null, this>;
|
||||
@property(null)
|
||||
@initial(null)
|
||||
@property()
|
||||
public declare readonly fontStyle: Signal<string | null, this>;
|
||||
@property(null)
|
||||
@initial(null)
|
||||
@property()
|
||||
public declare readonly fontWeight: Signal<number | null, this>;
|
||||
@property(null)
|
||||
@initial(null)
|
||||
@property()
|
||||
public declare readonly lineHeight: Signal<number | null, this>;
|
||||
@property(null)
|
||||
@initial(null)
|
||||
@property()
|
||||
public declare readonly letterSpacing: Signal<number | null, this>;
|
||||
@property(null)
|
||||
@initial(null)
|
||||
@property()
|
||||
public declare readonly textWrap: Signal<boolean | null, this>;
|
||||
|
||||
@property(0)
|
||||
@initial(0)
|
||||
@property()
|
||||
protected declare readonly customX: Signal<number, this>;
|
||||
@property(0)
|
||||
@initial(0)
|
||||
@property()
|
||||
public declare readonly x: Signal<number, this>;
|
||||
protected getX(): number {
|
||||
if (this.isLayoutRoot()) {
|
||||
@@ -193,9 +230,11 @@ export class Layout extends Node {
|
||||
this.customX(value);
|
||||
}
|
||||
|
||||
@property(0)
|
||||
@initial(0)
|
||||
@property()
|
||||
protected declare readonly customY: Signal<number, this>;
|
||||
@property(0)
|
||||
@initial(0)
|
||||
@property()
|
||||
public declare readonly y: Signal<number, this>;
|
||||
protected getY(): number {
|
||||
if (this.isLayoutRoot()) {
|
||||
@@ -208,9 +247,11 @@ export class Layout extends Node {
|
||||
this.customY(value);
|
||||
}
|
||||
|
||||
@property(null)
|
||||
@initial(null)
|
||||
@property()
|
||||
protected declare readonly customWidth: Signal<Length, this>;
|
||||
@property(null)
|
||||
@initial(null)
|
||||
@property()
|
||||
public declare readonly width: Property<Length, number, this>;
|
||||
protected getWidth(): number {
|
||||
return this.computedSize().width;
|
||||
@@ -252,9 +293,11 @@ export class Layout extends Node {
|
||||
lock && this.releaseSize();
|
||||
}
|
||||
|
||||
@property(null)
|
||||
@initial(null)
|
||||
@property()
|
||||
protected declare readonly customHeight: Signal<Length, this>;
|
||||
@property(null)
|
||||
@initial(null)
|
||||
@property()
|
||||
public declare readonly height: Property<Length, number, this>;
|
||||
protected getHeight(): number {
|
||||
return this.computedSize().height;
|
||||
@@ -297,8 +340,9 @@ export class Layout extends Node {
|
||||
lock && this.releaseSize();
|
||||
}
|
||||
|
||||
@compound(['width', 'height'], Vector2)
|
||||
@property(undefined, Vector2.lerp, Vector2)
|
||||
@compound({x: 'width', y: 'height'})
|
||||
@wrapper(Vector2)
|
||||
@property()
|
||||
public declare readonly size: Property<
|
||||
{width: Length; height: Length},
|
||||
Vector2,
|
||||
@@ -349,30 +393,38 @@ export class Layout extends Node {
|
||||
this.size(value);
|
||||
}
|
||||
|
||||
@property(0)
|
||||
@initial(0)
|
||||
@property()
|
||||
public declare readonly rotation: Signal<number, this>;
|
||||
|
||||
@property(0)
|
||||
@initial(0)
|
||||
@property()
|
||||
public declare readonly offsetX: Signal<number, this>;
|
||||
|
||||
@property(0)
|
||||
@initial(0)
|
||||
@property()
|
||||
public declare readonly offsetY: Signal<number, this>;
|
||||
|
||||
@compound({x: 'offsetX', y: 'offsetY'}, Vector2)
|
||||
@property(undefined, Vector2.lerp, Vector2)
|
||||
@compound({x: 'offsetX', y: 'offsetY'})
|
||||
@wrapper(Vector2)
|
||||
@property()
|
||||
public declare readonly offset: Signal<Vector2, this>;
|
||||
|
||||
@property(1)
|
||||
@initial(1)
|
||||
@property()
|
||||
public declare readonly scaleX: Signal<number, this>;
|
||||
|
||||
@property(1)
|
||||
@initial(1)
|
||||
@property()
|
||||
public declare readonly scaleY: Signal<number, this>;
|
||||
|
||||
@compound({x: 'scaleX', y: 'scaleY'}, Vector2)
|
||||
@property(undefined, Vector2.lerp, Vector2)
|
||||
@compound({x: 'scaleX', y: 'scaleY'})
|
||||
@wrapper(Vector2)
|
||||
@property()
|
||||
public declare readonly scale: Signal<Vector2, this>;
|
||||
|
||||
@property(undefined, Vector2.lerp, Vector2)
|
||||
@wrapper(Vector2)
|
||||
@property()
|
||||
public declare readonly absoluteScale: Signal<Vector2, this>;
|
||||
|
||||
protected getAbsoluteScale(): Vector2 {
|
||||
@@ -396,11 +448,13 @@ export class Layout extends Node {
|
||||
return scale.div(parentScale);
|
||||
}
|
||||
|
||||
@compound(['x', 'y'], Vector2)
|
||||
@property(undefined, Vector2.lerp, Vector2)
|
||||
@compound(['x', 'y'])
|
||||
@wrapper(Vector2)
|
||||
@property()
|
||||
public declare readonly position: Signal<Vector2, this>;
|
||||
|
||||
@property(undefined, Vector2.lerp, Vector2)
|
||||
@wrapper(Vector2)
|
||||
@property()
|
||||
public declare readonly absolutePosition: Signal<Vector2, this>;
|
||||
|
||||
protected getAbsolutePosition(): Vector2 {
|
||||
@@ -432,13 +486,15 @@ export class Layout extends Node {
|
||||
}
|
||||
}
|
||||
|
||||
@property(false)
|
||||
@initial(false)
|
||||
@property()
|
||||
public declare readonly clip: Signal<boolean, this>;
|
||||
|
||||
public readonly element: HTMLElement;
|
||||
public readonly styles: CSSStyleDeclaration;
|
||||
|
||||
@property(0)
|
||||
@initial(0)
|
||||
@property()
|
||||
protected declare readonly sizeLockCounter: Signal<number, this>;
|
||||
|
||||
public constructor({tagName = 'div', ...props}: LayoutProps) {
|
||||
|
||||
@@ -1,4 +1,12 @@
|
||||
import {compound, computed, initialize, property} from '../decorators';
|
||||
import {
|
||||
compound,
|
||||
computed,
|
||||
initial,
|
||||
initialize,
|
||||
interpolation,
|
||||
property,
|
||||
wrapper,
|
||||
} from '../decorators';
|
||||
import {Vector2, Rect, transformScalar} from '@motion-canvas/core/lib/types';
|
||||
import {
|
||||
createSignal,
|
||||
@@ -38,13 +46,16 @@ export interface NodeProps {
|
||||
export class Node implements Promisable<Node> {
|
||||
public declare isClass: boolean;
|
||||
|
||||
@property(false)
|
||||
@initial(false)
|
||||
@property()
|
||||
public declare readonly cache: Signal<boolean, this>;
|
||||
|
||||
@property(false)
|
||||
@initial(false)
|
||||
@property()
|
||||
public declare readonly composite: Signal<boolean, this>;
|
||||
|
||||
@property('source-over')
|
||||
@initial('source-over')
|
||||
@property()
|
||||
public declare readonly compositeOperation: Signal<
|
||||
GlobalCompositeOperation,
|
||||
this
|
||||
@@ -70,7 +81,8 @@ export class Node implements Promisable<Node> {
|
||||
}
|
||||
}
|
||||
|
||||
@property(1)
|
||||
@initial(1)
|
||||
@property()
|
||||
public declare readonly opacity: Signal<number, this>;
|
||||
|
||||
@computed()
|
||||
@@ -78,44 +90,57 @@ export class Node implements Promisable<Node> {
|
||||
return (this.parent()?.absoluteOpacity() ?? 1) * this.opacity();
|
||||
}
|
||||
|
||||
@property(0)
|
||||
@initial(0)
|
||||
@property()
|
||||
public declare readonly blur: Signal<number, this>;
|
||||
|
||||
@property(1)
|
||||
@initial(1)
|
||||
@property()
|
||||
public declare readonly brightness: Signal<number, this>;
|
||||
|
||||
@property(1)
|
||||
@initial(1)
|
||||
@property()
|
||||
public declare readonly contrast: Signal<number, this>;
|
||||
|
||||
@property(0)
|
||||
@initial(0)
|
||||
@property()
|
||||
public declare readonly grayscale: Signal<number, this>;
|
||||
|
||||
@property(0)
|
||||
@initial(0)
|
||||
@property()
|
||||
public declare readonly hue: Signal<number, this>;
|
||||
|
||||
@property(0)
|
||||
@initial(0)
|
||||
@property()
|
||||
public declare readonly invert: Signal<number, this>;
|
||||
|
||||
@property(1)
|
||||
@initial(1)
|
||||
@property()
|
||||
public declare readonly saturate: Signal<number, this>;
|
||||
|
||||
@property(0)
|
||||
@initial(0)
|
||||
@property()
|
||||
public declare readonly sepia: Signal<number, this>;
|
||||
|
||||
@property('')
|
||||
@initial('')
|
||||
@property()
|
||||
public declare readonly shadowColor: Signal<string, this>;
|
||||
|
||||
@property(0)
|
||||
@initial(0)
|
||||
@property()
|
||||
public declare readonly shadowBlur: Signal<number, this>;
|
||||
|
||||
@property(0)
|
||||
@initial(0)
|
||||
@property()
|
||||
public declare readonly shadowOffsetX: Signal<number, this>;
|
||||
|
||||
@property(0)
|
||||
@initial(0)
|
||||
@property()
|
||||
public declare readonly shadowOffsetY: Signal<number, this>;
|
||||
|
||||
@compound({x: 'shadowOffsetX', y: 'shadowOffsetY'}, Vector2)
|
||||
@property(undefined, Vector2.lerp, Vector2)
|
||||
@compound({x: 'shadowOffsetX', y: 'shadowOffsetY'})
|
||||
@wrapper(Vector2)
|
||||
@property()
|
||||
public declare readonly shadowOffset: Signal<Vector2, this>;
|
||||
|
||||
@computed()
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import {Signal, SignalValue} from '@motion-canvas/core/lib/utils';
|
||||
import {Rect as RectType} from '@motion-canvas/core/lib/types';
|
||||
import {Shape, ShapeProps} from './Shape';
|
||||
import {property} from '../decorators';
|
||||
import {initial, property} from '../decorators';
|
||||
import {drawRoundRect} from '../utils';
|
||||
|
||||
export interface RectProps extends ShapeProps {
|
||||
@@ -9,7 +9,8 @@ export interface RectProps extends ShapeProps {
|
||||
}
|
||||
|
||||
export class Rect extends Shape {
|
||||
@property(0)
|
||||
@initial(0)
|
||||
@property()
|
||||
public declare readonly radius: Signal<number, this>;
|
||||
|
||||
public constructor(props: RectProps) {
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import {CanvasStyle} from '../partials';
|
||||
import {computed, property} from '../decorators';
|
||||
import {computed, initial, property} from '../decorators';
|
||||
import {createSignal, Signal, SignalValue} from '@motion-canvas/core/lib/utils';
|
||||
import {Rect} from '@motion-canvas/core/lib/types';
|
||||
import {Layout, LayoutProps} from './Layout';
|
||||
@@ -19,21 +19,29 @@ export interface ShapeProps extends LayoutProps {
|
||||
}
|
||||
|
||||
export abstract class Shape extends Layout {
|
||||
@property(null)
|
||||
@initial(null)
|
||||
@property()
|
||||
public declare readonly fill: Signal<CanvasStyle, this>;
|
||||
@property(null)
|
||||
@initial(null)
|
||||
@property()
|
||||
public declare readonly stroke: Signal<CanvasStyle, this>;
|
||||
@property(false)
|
||||
@initial(false)
|
||||
@property()
|
||||
public declare readonly strokeFirst: Signal<boolean, this>;
|
||||
@property(0)
|
||||
@initial(0)
|
||||
@property()
|
||||
public declare readonly lineWidth: Signal<number, this>;
|
||||
@property('miter')
|
||||
@initial('miter')
|
||||
@property()
|
||||
public declare readonly lineJoin: Signal<CanvasLineJoin, this>;
|
||||
@property('butt')
|
||||
@initial('butt')
|
||||
@property()
|
||||
public declare readonly lineCap: Signal<CanvasLineCap, this>;
|
||||
@property([])
|
||||
@initial([])
|
||||
@property()
|
||||
public declare readonly lineDash: Signal<number[], this>;
|
||||
@property(0)
|
||||
@initial(0)
|
||||
@property()
|
||||
public declare readonly lineDashOffset: Signal<number, this>;
|
||||
|
||||
protected readonly rippleStrength = createSignal<number, this>(0);
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import {property} from '../decorators';
|
||||
import {initial, interpolation, property} from '../decorators';
|
||||
import {Signal, SignalValue} from '@motion-canvas/core/lib/utils';
|
||||
import {textLerp} from '@motion-canvas/core/lib/tweening';
|
||||
import {Shape, ShapeProps} from './Shape';
|
||||
@@ -22,7 +22,9 @@ export class Text extends Shape {
|
||||
}
|
||||
}
|
||||
|
||||
@property('', textLerp)
|
||||
@initial('')
|
||||
@interpolation(textLerp)
|
||||
@property()
|
||||
public declare readonly text: Signal<string, this>;
|
||||
|
||||
public constructor({children, ...rest}: TextProps) {
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import {SignalValue, isReactive} from '@motion-canvas/core/lib/utils';
|
||||
import {capitalize} from './property';
|
||||
import {capitalize, PropertyMetadata} from './property';
|
||||
import {addInitializer} from './initializers';
|
||||
|
||||
/**
|
||||
@@ -40,33 +40,25 @@ import {addInitializer} from './initializers';
|
||||
* @param mapping - An array of signals to turn into a compound property or a
|
||||
* record mapping the property in the compound object to the
|
||||
* corresponding signal.
|
||||
* @param klass - A class used to instantiate the returned value.
|
||||
*/
|
||||
export function compound(
|
||||
mapping: string[] | Record<string, string>,
|
||||
klass?: new (value: any) => any,
|
||||
): PropertyDecorator {
|
||||
return (target: any, key) => {
|
||||
const metaKey = `meta${capitalize(key.toString())}`;
|
||||
const meta: PropertyMetadata<any> = target[metaKey];
|
||||
|
||||
const entries = Array.isArray(mapping)
|
||||
? mapping.map(key => [key, key])
|
||||
: Object.entries(mapping);
|
||||
|
||||
if (klass) {
|
||||
target.constructor.prototype[`get${capitalize(key.toString())}`] =
|
||||
function () {
|
||||
const object = Object.fromEntries(
|
||||
entries.map(([key, property]) => [key, this[property]()]),
|
||||
);
|
||||
return new klass(object);
|
||||
};
|
||||
} else {
|
||||
target.constructor.prototype[`get${capitalize(key.toString())}`] =
|
||||
function () {
|
||||
return Object.fromEntries(
|
||||
entries.map(([key, property]) => [key, this[property]()]),
|
||||
);
|
||||
};
|
||||
}
|
||||
target.constructor.prototype[`get${capitalize(key.toString())}`] =
|
||||
function () {
|
||||
const object = Object.fromEntries(
|
||||
entries.map(([key, property]) => [key, this[property]()]),
|
||||
);
|
||||
return meta?.wrapper ? new meta.wrapper(object) : object;
|
||||
};
|
||||
|
||||
target.constructor.prototype[`set${capitalize(key.toString())}`] =
|
||||
function set(value: SignalValue<any>) {
|
||||
|
||||
@@ -11,15 +11,8 @@ export function addInitializer<T>(target: any, initializer: Initializer<T>) {
|
||||
// and it's not the target object itself
|
||||
!Object.prototype.hasOwnProperty.call(target, INITIALIZERS)
|
||||
) {
|
||||
const props = [];
|
||||
let base = Object.getPrototypeOf(target);
|
||||
while (base) {
|
||||
if (Object.prototype.hasOwnProperty.call(base, INITIALIZERS)) {
|
||||
props.push(...base[INITIALIZERS]);
|
||||
}
|
||||
base = Object.getPrototypeOf(base);
|
||||
}
|
||||
target[INITIALIZERS] = props;
|
||||
const base = Object.getPrototypeOf(target);
|
||||
target[INITIALIZERS] = [...base[INITIALIZERS]];
|
||||
}
|
||||
|
||||
target[INITIALIZERS].push(initializer);
|
||||
|
||||
@@ -20,6 +20,12 @@ export function capitalize<T extends string>(value: T): Capitalize<T> {
|
||||
return <Capitalize<T>>(value[0].toUpperCase() + value.slice(1));
|
||||
}
|
||||
|
||||
export interface PropertyMetadata<T> {
|
||||
default?: T;
|
||||
interpolationFunction?: InterpolationFunction<T>;
|
||||
wrapper?: new (value: any) => T;
|
||||
}
|
||||
|
||||
export interface Property<
|
||||
TSetterValue,
|
||||
TGetterValue extends TSetterValue,
|
||||
@@ -157,6 +163,18 @@ export function createProperty<
|
||||
return handler;
|
||||
}
|
||||
|
||||
const PROPERTIES = Symbol.for('properties');
|
||||
|
||||
export function getPropertiesOf(
|
||||
value: any,
|
||||
): Record<string, PropertyMetadata<any>> {
|
||||
if (value && typeof value === 'object') {
|
||||
return value[PROPERTIES] ?? {};
|
||||
}
|
||||
|
||||
return {};
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a signal property decorator.
|
||||
*
|
||||
@@ -174,44 +192,136 @@ export function createProperty<
|
||||
* ```ts
|
||||
* class Example {
|
||||
* \@property()
|
||||
* public declare color: Signal<Color, this>;
|
||||
*
|
||||
* \@customProperty()
|
||||
* public declare colorString: Signal<string, this>;
|
||||
*
|
||||
* protected getColorString() {
|
||||
* return this.color().toString();
|
||||
* }
|
||||
*
|
||||
* protected setColorString(value: SignalValue<string>) {
|
||||
* this.color(
|
||||
* isReactive(value)
|
||||
* ? () => new Color(value())
|
||||
* : new Color(value)
|
||||
* );
|
||||
* }
|
||||
* public declare length: Signal<number, this>;
|
||||
* }
|
||||
* ```
|
||||
*
|
||||
* @param initial - An option initial value of the property.
|
||||
* @param interpolationFunction - The default function used to interpolate
|
||||
* between values.
|
||||
* @param klass - A class used to instantiate the returned value.
|
||||
*/
|
||||
export function property<T>(
|
||||
initial?: T,
|
||||
interpolationFunction?: InterpolationFunction<T>,
|
||||
klass?: new (value: any) => T,
|
||||
): PropertyDecorator {
|
||||
export function property<T>(): PropertyDecorator {
|
||||
return (target: any, key) => {
|
||||
let lookup: Record<string | symbol, PropertyMetadata<T>>;
|
||||
if (!target[PROPERTIES]) {
|
||||
target[PROPERTIES] = lookup = {};
|
||||
} else if (
|
||||
target[PROPERTIES] &&
|
||||
!Object.prototype.hasOwnProperty.call(target, PROPERTIES)
|
||||
) {
|
||||
target[PROPERTIES] = lookup = Object.fromEntries<PropertyMetadata<T>>(
|
||||
Object.entries(
|
||||
<Record<string | symbol, PropertyMetadata<T>>>target[PROPERTIES],
|
||||
).map(([key, meta]) => [key, {...meta}]),
|
||||
);
|
||||
} else {
|
||||
lookup = target[PROPERTIES];
|
||||
}
|
||||
|
||||
const meta = (lookup[key] = lookup[key] ?? {});
|
||||
addInitializer(target, (instance: any, context: any) => {
|
||||
instance[key] = createProperty(
|
||||
instance,
|
||||
<string>key,
|
||||
context.defaults[key] ?? initial,
|
||||
interpolationFunction ?? deepLerp,
|
||||
klass,
|
||||
context.defaults[key] ?? meta.default,
|
||||
meta.interpolationFunction ?? deepLerp,
|
||||
meta.wrapper,
|
||||
);
|
||||
});
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Create an initial property value decorator.
|
||||
*
|
||||
* @remarks
|
||||
* This decorator specifies the initial value of a property.
|
||||
* Must be specified before the {@link property} decorator.
|
||||
*
|
||||
* @example
|
||||
* ```ts
|
||||
* class Example {
|
||||
* \@initial(1)
|
||||
* \@property()
|
||||
* public declare length: Signal<number, this>;
|
||||
* }
|
||||
* ```
|
||||
*
|
||||
* @param value - The initial value of the property.
|
||||
*/
|
||||
export function initial<T>(value: T): PropertyDecorator {
|
||||
return (target: any, key) => {
|
||||
const meta: PropertyMetadata<T> = target[PROPERTIES]?.[key];
|
||||
if (!meta) {
|
||||
console.error(`Missing property decorator for "${key.toString()}"`);
|
||||
return;
|
||||
}
|
||||
meta.default = value;
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a property interpolation function decorator.
|
||||
*
|
||||
* @remarks
|
||||
* This decorator specifies the interpolation function of a property.
|
||||
* The interpolation function is used when tweening between different values of
|
||||
* the property.
|
||||
* Must be specified before the {@link property} decorator.
|
||||
*
|
||||
* @example
|
||||
* ```ts
|
||||
* class Example {
|
||||
* \@interpolation(textLerp)
|
||||
* \@property()
|
||||
* public declare text: Signal<string, this>;
|
||||
* }
|
||||
* ```
|
||||
*
|
||||
* @param value - The interpolation function for the property.
|
||||
*/
|
||||
export function interpolation<T>(
|
||||
value: InterpolationFunction<T>,
|
||||
): PropertyDecorator {
|
||||
return (target: any, key) => {
|
||||
const meta: PropertyMetadata<T> = target[PROPERTIES]?.[key];
|
||||
if (!meta) {
|
||||
console.error(`Missing property decorator for "${key.toString()}"`);
|
||||
return;
|
||||
}
|
||||
meta.interpolationFunction = value;
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a property wrapper decorator.
|
||||
*
|
||||
* @remarks
|
||||
* This decorator specifies the wrapper of a property.
|
||||
* Instead of returning the raw value of the property, an instance of the
|
||||
* wrapper is returned. The actual value is passed as the first parameter to the
|
||||
* constructor.
|
||||
* Must be specified before the {@link property} decorator.
|
||||
*
|
||||
* @example
|
||||
* ```ts
|
||||
* class Example {
|
||||
* \@wrapper(Vector2)
|
||||
* \@property()
|
||||
* public declare offset: Signal<Vector2, this>;
|
||||
* }
|
||||
* ```
|
||||
*
|
||||
* @param value - The wrapper class for the property.
|
||||
*/
|
||||
export function wrapper<T>(
|
||||
value: (new (value: any) => T) & {lerp?: InterpolationFunction<T>},
|
||||
): PropertyDecorator {
|
||||
return (target: any, key) => {
|
||||
const meta: PropertyMetadata<T> = target[PROPERTIES]?.[key];
|
||||
if (!meta) {
|
||||
console.error(`Missing property decorator for "${key.toString()}"`);
|
||||
return;
|
||||
}
|
||||
meta.wrapper = value;
|
||||
if ('lerp' in value) {
|
||||
meta.interpolationFunction ??= value.lerp;
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
@@ -1,4 +1,12 @@
|
||||
import {compound, computed, initialize, property} from '../decorators';
|
||||
import {
|
||||
compound,
|
||||
computed,
|
||||
initial,
|
||||
initialize,
|
||||
interpolation,
|
||||
property,
|
||||
wrapper,
|
||||
} from '../decorators';
|
||||
import {Vector2} from '@motion-canvas/core/lib/types';
|
||||
import {Signal} from '@motion-canvas/core/lib/utils';
|
||||
|
||||
@@ -24,32 +32,43 @@ export interface GradientProps {
|
||||
}
|
||||
|
||||
export class Gradient {
|
||||
@property('linear')
|
||||
@initial('linear')
|
||||
@property()
|
||||
public declare readonly type: Signal<GradientType, this>;
|
||||
|
||||
@property(0)
|
||||
@initial(0)
|
||||
@property()
|
||||
public declare readonly fromX: Signal<number, this>;
|
||||
@property(0)
|
||||
@initial(0)
|
||||
@property()
|
||||
public declare readonly fromY: Signal<number, this>;
|
||||
@compound({x: 'fromX', y: 'fromY'})
|
||||
@property(undefined, Vector2.lerp, Vector2)
|
||||
@wrapper(Vector2)
|
||||
@property()
|
||||
public declare readonly from: Signal<Vector2, this>;
|
||||
|
||||
@property(0)
|
||||
@initial(0)
|
||||
@property()
|
||||
public declare readonly toX: Signal<number, this>;
|
||||
@property(0)
|
||||
@initial(0)
|
||||
@property()
|
||||
public declare readonly toY: Signal<number, this>;
|
||||
@compound({x: 'toX', y: 'toY'})
|
||||
@property(undefined, Vector2.lerp, Vector2)
|
||||
@wrapper(Vector2)
|
||||
@property()
|
||||
public declare readonly to: Signal<Vector2, this>;
|
||||
|
||||
@property(0)
|
||||
@initial(0)
|
||||
@property()
|
||||
public declare readonly angle: Signal<number, this>;
|
||||
@property(0)
|
||||
@initial(0)
|
||||
@property()
|
||||
public declare readonly fromRadius: Signal<number, this>;
|
||||
@property(0)
|
||||
@initial(0)
|
||||
@property()
|
||||
public declare readonly toRadius: Signal<number, this>;
|
||||
@property([])
|
||||
@initial([])
|
||||
@property()
|
||||
public declare readonly stops: Signal<GradientStop[], this>;
|
||||
|
||||
public constructor(props: GradientProps) {
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import {computed, initialize, property} from '../decorators';
|
||||
import {computed, initial, initialize, property} from '../decorators';
|
||||
import {Signal} from '@motion-canvas/core/lib/utils';
|
||||
|
||||
export type CanvasRepetition =
|
||||
@@ -17,7 +17,8 @@ export interface PatternProps {
|
||||
export class Pattern {
|
||||
@property()
|
||||
public declare readonly image: Signal<CanvasImageSource, this>;
|
||||
@property(null)
|
||||
@initial(null)
|
||||
@property()
|
||||
public declare readonly repetition: Signal<CanvasRepetition, this>;
|
||||
|
||||
public constructor(props: PatternProps) {
|
||||
|
||||
@@ -144,7 +144,7 @@ export class Vector2 {
|
||||
|
||||
if (Array.isArray(one)) {
|
||||
this.x = one[0];
|
||||
this.y = one[0];
|
||||
this.y = one[1];
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
@@ -18,7 +18,7 @@
|
||||
"paths": {
|
||||
"@motion-canvas/core/lib/jsx-runtime": ["jsx-runtime.ts"]
|
||||
},
|
||||
"types": ["node", "prismjs", "three", "dom-webcodecs", "jest"]
|
||||
"types": ["node", "dom-webcodecs", "jest"]
|
||||
},
|
||||
"include": ["src"]
|
||||
}
|
||||
|
||||
@@ -12,7 +12,7 @@
|
||||
},
|
||||
"dependencies": {
|
||||
"@motion-canvas/core": "*",
|
||||
"@motion-canvas/legacy": "*"
|
||||
"@motion-canvas/2d": "*"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@motion-canvas/ui": "*",
|
||||
|
||||
Reference in New Issue
Block a user