mirror of
https://github.com/motion-canvas/motion-canvas.git
synced 2026-01-12 15:28:03 -05:00
feat: unify core types (#71)
Unifies all types currently available in core. Each of them is now represented by a separate class.
This commit is contained in:
@@ -11,6 +11,8 @@
|
||||
"core:test": "npm run test -w packages/core",
|
||||
"2d:build": "npm run build -w packages/2d",
|
||||
"2d:watch": "npm run watch -w packages/2d",
|
||||
"legacy:build": "npm run build -w packages/legacy",
|
||||
"legacy:watch": "npm run watch -w packages/legacy",
|
||||
"ui:build": "npm run build -w packages/ui",
|
||||
"ui:dev": "npm run dev -w packages/ui",
|
||||
"template:serve": "npm run serve -w packages/template",
|
||||
|
||||
@@ -7,13 +7,10 @@ import {
|
||||
} from '../decorators';
|
||||
import {
|
||||
Vector2,
|
||||
transformPoint,
|
||||
transformAngle,
|
||||
Rect,
|
||||
Size,
|
||||
rect,
|
||||
transformScalar,
|
||||
transformVector,
|
||||
} from '@motion-canvas/core/lib/types';
|
||||
import {
|
||||
createSignal,
|
||||
@@ -23,8 +20,6 @@ import {
|
||||
SignalValue,
|
||||
} from '@motion-canvas/core/lib/utils';
|
||||
import {
|
||||
vector2dLerp,
|
||||
sizeLerp,
|
||||
InterpolationFunction,
|
||||
TimingFunction,
|
||||
tween,
|
||||
@@ -190,8 +185,8 @@ export class Node<TProps extends NodeProps = NodeProps>
|
||||
this.layout.releaseSize();
|
||||
}
|
||||
|
||||
@compound(['width', 'height'])
|
||||
@property(undefined, sizeLerp)
|
||||
@compound(['width', 'height'], Size)
|
||||
@property(undefined, Size.lerp)
|
||||
public declare readonly size: Property<
|
||||
{width: Length; height: Length},
|
||||
Size,
|
||||
@@ -251,8 +246,8 @@ export class Node<TProps extends NodeProps = NodeProps>
|
||||
@property(0)
|
||||
public declare readonly offsetY: Signal<number, this>;
|
||||
|
||||
@compound({x: 'offsetX', y: 'offsetY'})
|
||||
@property(undefined, vector2dLerp)
|
||||
@compound({x: 'offsetX', y: 'offsetY'}, Vector2)
|
||||
@property(undefined, Vector2.lerp)
|
||||
public declare readonly offset: Signal<Vector2, this>;
|
||||
|
||||
@property(1)
|
||||
@@ -261,8 +256,8 @@ export class Node<TProps extends NodeProps = NodeProps>
|
||||
@property(1)
|
||||
public declare readonly scaleY: Signal<number, this>;
|
||||
|
||||
@compound({x: 'scaleX', y: 'scaleY'})
|
||||
@property(undefined, vector2dLerp)
|
||||
@compound({x: 'scaleX', y: 'scaleY'}, Vector2)
|
||||
@property(undefined, Vector2.lerp)
|
||||
public declare readonly scale: Signal<Vector2, this>;
|
||||
|
||||
@property(false)
|
||||
@@ -318,8 +313,8 @@ export class Node<TProps extends NodeProps = NodeProps>
|
||||
@property(0)
|
||||
public declare readonly shadowOffsetY: Signal<number, this>;
|
||||
|
||||
@compound({x: 'shadowOffsetX', y: 'shadowOffsetY'})
|
||||
@property(undefined, vector2dLerp)
|
||||
@compound({x: 'shadowOffsetX', y: 'shadowOffsetY'}, Vector2)
|
||||
@property(undefined, Vector2.lerp)
|
||||
public declare readonly shadowOffset: Signal<Vector2, this>;
|
||||
|
||||
@computed()
|
||||
@@ -386,23 +381,23 @@ export class Node<TProps extends NodeProps = NodeProps>
|
||||
return filters;
|
||||
}
|
||||
|
||||
@compound(['x', 'y'])
|
||||
@property(undefined, vector2dLerp)
|
||||
@compound(['x', 'y'], Vector2)
|
||||
@property(undefined, Vector2.lerp)
|
||||
public declare readonly position: Signal<Vector2, this>;
|
||||
|
||||
@property(undefined, vector2dLerp)
|
||||
@property(undefined, Vector2.lerp)
|
||||
public declare readonly absolutePosition: Signal<Vector2, this>;
|
||||
|
||||
protected getAbsolutePosition() {
|
||||
protected getAbsolutePosition(): Vector2 {
|
||||
const matrix = this.localToWorld();
|
||||
return {x: matrix.m41, y: matrix.m42};
|
||||
return new Vector2(matrix.m41, matrix.m42);
|
||||
}
|
||||
|
||||
protected setAbsolutePosition(value: SignalValue<Vector2>) {
|
||||
if (isReactive(value)) {
|
||||
this.position(() => transformPoint(value(), this.worldToParent()));
|
||||
this.position(() => value().transformAsPoint(this.worldToParent()));
|
||||
} else {
|
||||
this.position(transformPoint(value, this.worldToParent()));
|
||||
this.position(value.transformAsPoint(this.worldToParent()));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -552,20 +547,17 @@ export class Node<TProps extends NodeProps = NodeProps>
|
||||
protected computedPosition(): Vector2 {
|
||||
const mode = this.mode();
|
||||
if (mode !== 'enabled') {
|
||||
return {
|
||||
x: this.customX(),
|
||||
y: this.customY(),
|
||||
};
|
||||
return new Vector2(this.customX(), this.customY());
|
||||
}
|
||||
|
||||
this.requestLayoutUpdate();
|
||||
this.requestFontUpdate();
|
||||
const rect = this.layout.getComputedLayout();
|
||||
|
||||
const position = {
|
||||
x: rect.x + (rect.width / 2) * this.offsetX(),
|
||||
y: rect.y + (rect.height / 2) * this.offsetY(),
|
||||
};
|
||||
const position = new Vector2(
|
||||
rect.x + (rect.width / 2) * this.offsetX(),
|
||||
rect.y + (rect.height / 2) * this.offsetY(),
|
||||
);
|
||||
|
||||
const parent = this.parent();
|
||||
if (parent) {
|
||||
@@ -583,10 +575,7 @@ export class Node<TProps extends NodeProps = NodeProps>
|
||||
this.requestFontUpdate();
|
||||
const rect = this.layout.getComputedLayout();
|
||||
|
||||
return {
|
||||
width: rect.width,
|
||||
height: rect.height,
|
||||
};
|
||||
return new Size(rect);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -737,13 +726,8 @@ export class Node<TProps extends NodeProps = NodeProps>
|
||||
* The returned rectangle should be in local space.
|
||||
*/
|
||||
protected getCacheRect(): Rect {
|
||||
const {width, height} = this.computedSize();
|
||||
return {
|
||||
x: width / -2,
|
||||
y: height / -2,
|
||||
width,
|
||||
height,
|
||||
};
|
||||
const size = this.computedSize();
|
||||
return new Rect(size.vector.scale(-0.5), size);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -755,13 +739,10 @@ export class Node<TProps extends NodeProps = NodeProps>
|
||||
*/
|
||||
protected getFullCacheRect() {
|
||||
const matrix = this.compositeToLocal();
|
||||
const shadowOffset = transformVector(this.shadowOffset(), matrix);
|
||||
const shadowOffset = this.shadowOffset().transform(matrix);
|
||||
const shadowBlur = transformScalar(this.shadowBlur(), matrix);
|
||||
|
||||
const result = rect.expand(
|
||||
this.getCacheRect(),
|
||||
this.blur() * 2 + shadowBlur,
|
||||
);
|
||||
const result = this.getCacheRect().expand(this.blur() * 2 + shadowBlur);
|
||||
|
||||
if (shadowOffset.x < 0) {
|
||||
result.x += shadowOffset.x;
|
||||
@@ -789,31 +770,24 @@ export class Node<TProps extends NodeProps = NodeProps>
|
||||
const cache = this.getCacheRect();
|
||||
const children = this.children();
|
||||
if (!this.overflow() || children.length === 0) {
|
||||
return cache;
|
||||
return cache.pixelPerfect;
|
||||
}
|
||||
|
||||
const points: Vector2[] = rect.corners(cache);
|
||||
const points: Vector2[] = cache.corners;
|
||||
for (const child of children) {
|
||||
const childCache = child.fullCacheRect();
|
||||
const childMatrix = child.localToParent();
|
||||
points.push(
|
||||
...rect.corners(childCache).map(r => transformPoint(r, childMatrix)),
|
||||
...childCache.corners.map(r => r.transformAsPoint(childMatrix)),
|
||||
);
|
||||
}
|
||||
|
||||
const result = rect.fromPoints(...points);
|
||||
|
||||
return {
|
||||
x: Math.floor(result.x),
|
||||
y: Math.floor(result.y),
|
||||
width: Math.ceil(result.width + 1),
|
||||
height: Math.ceil(result.height + 1),
|
||||
};
|
||||
return Rect.fromPoints(...points).pixelPerfect;
|
||||
}
|
||||
|
||||
@computed()
|
||||
protected fullCacheRect(): Rect {
|
||||
return rect.fromRects(this.cacheRect(), this.getFullCacheRect());
|
||||
return Rect.fromRects(this.cacheRect(), this.getFullCacheRect());
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -836,7 +810,7 @@ export class Node<TProps extends NodeProps = NodeProps>
|
||||
}
|
||||
if (this.hasShadow()) {
|
||||
const matrix = this.compositeToWorld();
|
||||
const offset = transformVector(this.shadowOffset(), matrix);
|
||||
const offset = this.shadowOffset().transform(matrix);
|
||||
const blur = transformScalar(this.shadowBlur(), matrix);
|
||||
|
||||
context.shadowColor = this.shadowColor();
|
||||
|
||||
@@ -2,7 +2,7 @@ import {Node, NodeProps} from './Node';
|
||||
import {Gradient, Pattern} from '../partials';
|
||||
import {property} from '../decorators';
|
||||
import {Signal} from '@motion-canvas/core/lib/utils';
|
||||
import {Rect, rect} from '@motion-canvas/core/lib/types';
|
||||
import {Rect} from '@motion-canvas/core/lib/types';
|
||||
|
||||
export type CanvasStyle = null | string | Gradient | Pattern;
|
||||
|
||||
@@ -81,7 +81,7 @@ export abstract class Shape<T extends ShapeProps = ShapeProps> extends Node<T> {
|
||||
}
|
||||
|
||||
protected override getCacheRect(): Rect {
|
||||
return rect.expand(super.getCacheRect(), this.lineWidth() / 2);
|
||||
return super.getCacheRect().expand(this.lineWidth() / 2);
|
||||
}
|
||||
|
||||
protected getPath(): Path2D {
|
||||
|
||||
@@ -2,7 +2,7 @@ import {property} from '../decorators';
|
||||
import {Signal} from '@motion-canvas/core/lib/utils';
|
||||
import {textLerp} from '@motion-canvas/core/lib/tweening';
|
||||
import {Shape, ShapeProps} from './Shape';
|
||||
import {rect, Rect} from '@motion-canvas/core/lib/types';
|
||||
import {Rect} from '@motion-canvas/core/lib/types';
|
||||
|
||||
export interface TextProps extends ShapeProps {
|
||||
children?: string;
|
||||
@@ -42,7 +42,7 @@ export class Text extends Shape<TextProps> {
|
||||
const {width, height} = this.computedSize();
|
||||
const range = document.createRange();
|
||||
let line = '';
|
||||
const lineRect = rect();
|
||||
const lineRect = new Rect();
|
||||
for (const childNode of this.layout.element.childNodes) {
|
||||
if (!childNode.textContent) {
|
||||
continue;
|
||||
|
||||
@@ -40,9 +40,12 @@ 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 (from: any) => any,
|
||||
): PropertyDecorator {
|
||||
return (target: any, key) => {
|
||||
const entries = Array.isArray(mapping)
|
||||
@@ -51,9 +54,10 @@ export function compound(
|
||||
|
||||
target.constructor.prototype[`get${capitalize(key.toString())}`] =
|
||||
function () {
|
||||
return Object.fromEntries(
|
||||
const object = Object.fromEntries(
|
||||
entries.map(([key, property]) => [key, this[property]()]),
|
||||
);
|
||||
return klass ? new klass(object) : object;
|
||||
};
|
||||
|
||||
target.constructor.prototype[`set${capitalize(key.toString())}`] =
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
import {compound, computed, initialize, property} from '../decorators';
|
||||
import {Vector2} from '@motion-canvas/core/lib/types';
|
||||
import {Signal} from '@motion-canvas/core/lib/utils';
|
||||
import {vector2dLerp} from '@motion-canvas/core/lib/tweening';
|
||||
|
||||
export type GradientType = 'linear' | 'conic' | 'radial';
|
||||
|
||||
@@ -32,16 +31,16 @@ export class Gradient {
|
||||
public declare readonly fromX: Signal<number, this>;
|
||||
@property(0)
|
||||
public declare readonly fromY: Signal<number, this>;
|
||||
@compound({x: 'fromX', y: 'fromY'})
|
||||
@property(undefined, vector2dLerp)
|
||||
@compound({x: 'fromX', y: 'fromY'}, Vector2)
|
||||
@property(undefined, Vector2.lerp)
|
||||
public declare readonly from: Signal<Vector2, this>;
|
||||
|
||||
@property(0)
|
||||
public declare readonly toX: Signal<number, this>;
|
||||
@property(0)
|
||||
public declare readonly toY: Signal<number, this>;
|
||||
@compound({x: 'toX', y: 'toY'})
|
||||
@property(undefined, vector2dLerp)
|
||||
@compound({x: 'toX', y: 'toY'}, Vector2)
|
||||
@property(undefined, Vector2.lerp)
|
||||
public declare readonly to: Signal<Vector2, this>;
|
||||
|
||||
@property(0)
|
||||
|
||||
@@ -114,13 +114,7 @@ export class Layout {
|
||||
}
|
||||
|
||||
public getComputedLayout(): Rect {
|
||||
const rect = this.element.getBoundingClientRect();
|
||||
return {
|
||||
width: rect.width,
|
||||
height: rect.height,
|
||||
x: rect.x,
|
||||
y: rect.y,
|
||||
};
|
||||
return new Rect(this.element.getBoundingClientRect());
|
||||
}
|
||||
|
||||
public setWidth(width: Length): this {
|
||||
|
||||
@@ -5,8 +5,8 @@ import {Size, CanvasColorSpace, CanvasOutputMimeType} from './types';
|
||||
import {AudioManager} from './media';
|
||||
import {ifHot} from './utils';
|
||||
|
||||
export const ProjectSize = {
|
||||
FullHD: {width: 1920, height: 1080},
|
||||
export const ProjectSize: Record<string, Size> = {
|
||||
FullHD: new Size(1920, 1080),
|
||||
};
|
||||
|
||||
export interface ProjectConfig {
|
||||
@@ -116,10 +116,7 @@ export class Project {
|
||||
}
|
||||
|
||||
public getSize(): Size {
|
||||
return {
|
||||
width: this.width,
|
||||
height: this.height,
|
||||
};
|
||||
return new Size(this.width, this.height);
|
||||
}
|
||||
|
||||
public readonly name: string;
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
import {Size} from '../types';
|
||||
import {SerializedSize} from '../types';
|
||||
|
||||
const canvas = document.createElement('canvas');
|
||||
const context = canvas.getContext('2d');
|
||||
|
||||
export type ImageDataSource = CanvasImageSource & Size;
|
||||
export type ImageDataSource = CanvasImageSource & SerializedSize;
|
||||
|
||||
export function loadImage(source: string): Promise<HTMLImageElement> {
|
||||
const image = new Image();
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import {Rect, Vector2} from '../types';
|
||||
import {SerializedRect, SerializedVector2} from '../types';
|
||||
|
||||
/**
|
||||
* Represents an element to inspect.
|
||||
@@ -22,22 +22,22 @@ export interface InspectedSize {
|
||||
/**
|
||||
* Bounding box of the element (with padding).
|
||||
*/
|
||||
rect?: Rect;
|
||||
rect?: SerializedRect;
|
||||
|
||||
/**
|
||||
* Bounding box of the content of this element (without padding).
|
||||
*/
|
||||
contentRect?: Rect;
|
||||
contentRect?: SerializedRect;
|
||||
|
||||
/**
|
||||
* Bounding box of the element (with margin).
|
||||
*/
|
||||
marginRect?: Rect;
|
||||
marginRect?: SerializedRect;
|
||||
|
||||
/**
|
||||
* The absolute position of the object's origin.
|
||||
*/
|
||||
position?: Vector2;
|
||||
position?: SerializedVector2;
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -1,13 +1,14 @@
|
||||
import {Direction, originPosition} from '../types';
|
||||
import {easeInOutCubic, tween, deepLerp} from '../tweening';
|
||||
import {Direction, Vector2} from '../types';
|
||||
import {easeInOutCubic, tween} from '../tweening';
|
||||
import {useScene} from '../utils';
|
||||
import {useTransition} from './';
|
||||
import {useTransition} from './useTransition';
|
||||
|
||||
export function slideTransition(direction: Direction = Direction.Top) {
|
||||
const size = useScene().getSize();
|
||||
const position = originPosition(direction, size.width, size.height);
|
||||
let ppos = {x: 0, y: 0};
|
||||
let cpos = {x: 0, y: 0};
|
||||
const position = size.getOriginOffset(direction).scale(2);
|
||||
const inverse = position.scale(-1);
|
||||
let ppos = new Vector2();
|
||||
let cpos = new Vector2();
|
||||
const endTransition = useTransition(
|
||||
ctx => ctx.translate(cpos.x, cpos.y),
|
||||
ctx => ctx.translate(ppos.x, ppos.y),
|
||||
@@ -15,13 +16,8 @@ export function slideTransition(direction: Direction = Direction.Top) {
|
||||
return tween(
|
||||
0.6,
|
||||
value => {
|
||||
ppos = deepLerp(
|
||||
{x: 0, y: 0},
|
||||
{x: -position.x, y: -position.y},
|
||||
easeInOutCubic(value),
|
||||
);
|
||||
|
||||
cpos = deepLerp(position, {x: 0, y: 0}, easeInOutCubic(value));
|
||||
ppos = Vector2.lerp(Vector2.zero, inverse, easeInOutCubic(value));
|
||||
cpos = Vector2.lerp(position, Vector2.zero, easeInOutCubic(value));
|
||||
},
|
||||
endTransition,
|
||||
);
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import {deepLerp, colorLerp, vector2dLerp} from './interpolationFunctions';
|
||||
import type {Vector2} from '../types';
|
||||
import {deepLerp, colorLerp} from './interpolationFunctions';
|
||||
import {Vector2} from '../types';
|
||||
|
||||
describe('deepLerp', () => {
|
||||
test('falls back to primitive tween for numbers', () => {
|
||||
@@ -90,13 +90,13 @@ describe('deepLerp', () => {
|
||||
expect(deepLerp({}, {foo: 5}, 1)).toEqual({foo: 5});
|
||||
});
|
||||
|
||||
test('replaces vector2dLerp', () => {
|
||||
test('invokes native interpolation function', () => {
|
||||
const args: [Vector2, Vector2, number] = [
|
||||
{x: 50, y: 65},
|
||||
{x: 10, y: 100},
|
||||
new Vector2(50, 65),
|
||||
new Vector2(10, 100),
|
||||
1 / 2,
|
||||
];
|
||||
expect(deepLerp(...args)).toEqual(vector2dLerp(...args));
|
||||
expect(deepLerp(...args)).toEqual(Vector2.lerp(...args));
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
@@ -91,6 +91,10 @@ export function deepLerp(from: any, to: any, value: number): any {
|
||||
return textLerp(from, to, value);
|
||||
}
|
||||
|
||||
if ('lerp' in from) {
|
||||
return from.lerp(from, to, value);
|
||||
}
|
||||
|
||||
if (from && to && typeof from === 'object' && typeof to === 'object') {
|
||||
if (Array.isArray(from) && Array.isArray(to)) {
|
||||
if (from.length === to.length) {
|
||||
@@ -155,85 +159,6 @@ export function colorLerp(
|
||||
return range(value).toString();
|
||||
}
|
||||
|
||||
export function vector2dLerp(from: Vector2, to: Vector2, value: number) {
|
||||
return {
|
||||
x: map(from.x, to.x, value),
|
||||
y: map(from.y, to.y, value),
|
||||
};
|
||||
}
|
||||
|
||||
export function sizeLerp(from: Size, to: Size, value: number) {
|
||||
return {
|
||||
width: map(from.width, to.width, value),
|
||||
height: map(from.height, to.height, value),
|
||||
};
|
||||
}
|
||||
|
||||
export function spacingLerp(
|
||||
from: Spacing,
|
||||
to: Spacing,
|
||||
value: number,
|
||||
): PossibleSpacing {
|
||||
return [
|
||||
map(from.top, to.top, value),
|
||||
map(from.right, to.right, value),
|
||||
map(from.bottom, to.bottom, value),
|
||||
map(from.left, to.left, value),
|
||||
];
|
||||
}
|
||||
|
||||
export function rectArcLerp(
|
||||
from: Partial<Rect>,
|
||||
to: Partial<Rect>,
|
||||
value: number,
|
||||
reverse?: boolean,
|
||||
ratio?: number,
|
||||
) {
|
||||
ratio ??= calculateRatio(from, to);
|
||||
|
||||
let flip = reverse;
|
||||
if (ratio > 1) {
|
||||
ratio = 1 / ratio;
|
||||
} else {
|
||||
flip = !flip;
|
||||
}
|
||||
|
||||
const normalized = flip ? Math.acos(1 - value) : Math.asin(value);
|
||||
const radians = map(normalized, map(0, Math.PI / 2, value), ratio);
|
||||
|
||||
let xValue = Math.sin(radians);
|
||||
let yValue = 1 - Math.cos(radians);
|
||||
if (reverse) {
|
||||
[xValue, yValue] = [yValue, xValue];
|
||||
}
|
||||
|
||||
return {
|
||||
x: map(from.x ?? 0, to.x ?? 0, xValue),
|
||||
y: map(from.y ?? 0, to.y ?? 0, yValue),
|
||||
width: map(from.width ?? 0, to.width ?? 0, xValue),
|
||||
height: map(from.height ?? 0, to.height ?? 0, yValue),
|
||||
};
|
||||
}
|
||||
|
||||
export function calculateRatio(from: Partial<Rect>, to: Partial<Rect>): number {
|
||||
let numberOfValues = 0;
|
||||
let ratio = 0;
|
||||
if (from.x) {
|
||||
ratio += Math.abs((from.x - to.x) / (from.y - to.y));
|
||||
numberOfValues++;
|
||||
}
|
||||
if (from.width) {
|
||||
ratio += Math.abs((from.width - to.width) / (from.height - to.height));
|
||||
numberOfValues++;
|
||||
}
|
||||
|
||||
if (numberOfValues) {
|
||||
ratio /= numberOfValues;
|
||||
}
|
||||
|
||||
return isNaN(ratio) ? 1 : ratio;
|
||||
}
|
||||
|
||||
export function map(from: number, to: number, value: number) {
|
||||
return from + (to - from) * value;
|
||||
}
|
||||
|
||||
@@ -1,26 +1,9 @@
|
||||
import {Vector2} from './Vector';
|
||||
|
||||
export function transformPoint(vector: Vector2, matrix: DOMMatrix) {
|
||||
return {
|
||||
x: vector.x * matrix.m11 + vector.y * matrix.m21 + matrix.m41,
|
||||
y: vector.x * matrix.m12 + vector.y * matrix.m22 + matrix.m42,
|
||||
};
|
||||
}
|
||||
|
||||
export function transformVector(vector: Vector2, matrix: DOMMatrix) {
|
||||
return {
|
||||
x: vector.x * matrix.m11 + vector.y * matrix.m21,
|
||||
y: vector.x * matrix.m12 + vector.y * matrix.m22,
|
||||
};
|
||||
}
|
||||
|
||||
export function transformAngle(angle: number, matrix: DOMMatrix) {
|
||||
const radians = (angle / 180) * Math.PI;
|
||||
const vector = transformVector(
|
||||
{x: Math.cos(radians), y: Math.sin(radians)},
|
||||
matrix,
|
||||
);
|
||||
return (Math.atan2(vector.y, vector.x) * 180) / Math.PI;
|
||||
const vector = Vector2.fromRadians(radians).transform(matrix);
|
||||
return (vector.radians * 180) / Math.PI;
|
||||
}
|
||||
|
||||
export function transformScalar(scalar: number, matrix: DOMMatrix) {
|
||||
|
||||
@@ -49,46 +49,3 @@ export function flipOrigin(
|
||||
|
||||
return origin;
|
||||
}
|
||||
|
||||
export function originPosition(
|
||||
origin: Origin | Direction,
|
||||
width = 1,
|
||||
height = 1,
|
||||
): Vector2 {
|
||||
const position: Vector2 = {x: 0, y: 0};
|
||||
|
||||
if (origin === Origin.Middle) {
|
||||
return position;
|
||||
}
|
||||
|
||||
if (origin & Direction.Left) {
|
||||
position.x = -width;
|
||||
} else if (origin & Direction.Right) {
|
||||
position.x = width;
|
||||
}
|
||||
|
||||
if (origin & Direction.Top) {
|
||||
position.y = -height;
|
||||
} else if (origin & Direction.Bottom) {
|
||||
position.y = height;
|
||||
}
|
||||
|
||||
return position;
|
||||
}
|
||||
|
||||
export function getOriginOffset(size: Size, origin: Origin): Vector2 {
|
||||
return originPosition(origin, size.width / 2, size.height / 2);
|
||||
}
|
||||
|
||||
export function getOriginDelta(size: Size, from: Origin, to: Origin) {
|
||||
const fromOffset = getOriginOffset(size, from);
|
||||
if (to === Origin.Middle) {
|
||||
return {x: -fromOffset.x, y: -fromOffset.y};
|
||||
}
|
||||
|
||||
const toOffset = getOriginOffset(size, to);
|
||||
return {
|
||||
x: toOffset.x - fromOffset.x,
|
||||
y: toOffset.y - fromOffset.y,
|
||||
};
|
||||
}
|
||||
|
||||
@@ -1,119 +1,193 @@
|
||||
import {Vector2} from './Vector';
|
||||
import {transformPoint, transformVector} from './Matrix';
|
||||
import {Size} from './Size';
|
||||
import {map} from '../tweening';
|
||||
|
||||
export interface Rect {
|
||||
export type SerializedRect = {
|
||||
x: number;
|
||||
y: number;
|
||||
width: number;
|
||||
height: number;
|
||||
}
|
||||
};
|
||||
|
||||
export function rect(x = 0, y = 0, width = 0, height = 0): Rect {
|
||||
return {x, y, width, height};
|
||||
}
|
||||
export type PossibleRect =
|
||||
| SerializedRect
|
||||
| [number, number, number, number]
|
||||
| Size
|
||||
| Vector2;
|
||||
|
||||
export interface rect {
|
||||
fromPoints: (...points: Vector2[]) => Rect;
|
||||
fromRects: (...rects: Rect[]) => Rect;
|
||||
topLeft: (rect: Rect) => Vector2;
|
||||
topRight: (rect: Rect) => Vector2;
|
||||
bottomLeft: (rect: Rect) => Vector2;
|
||||
bottomRight: (rect: Rect) => Vector2;
|
||||
corners: (rect: Rect) => Vector2[];
|
||||
transform: (rect: Rect, matrix: DOMMatrix) => Rect;
|
||||
expand: (rect: Rect, amount: number) => Rect;
|
||||
}
|
||||
export class Rect {
|
||||
public x = 0;
|
||||
public y = 0;
|
||||
public width = 0;
|
||||
public height = 0;
|
||||
|
||||
rect.fromPoints = (...points: Vector2[]) => {
|
||||
let minX = Infinity;
|
||||
let minY = Infinity;
|
||||
let maxX = -Infinity;
|
||||
let maxY = -Infinity;
|
||||
|
||||
for (const point of points) {
|
||||
if (point.x > maxX) {
|
||||
maxX = point.x;
|
||||
}
|
||||
if (point.x < minX) {
|
||||
minX = point.x;
|
||||
}
|
||||
if (point.y > maxY) {
|
||||
maxY = point.y;
|
||||
}
|
||||
if (point.y < minY) {
|
||||
minY = point.y;
|
||||
}
|
||||
public static lerp(from: Rect, to: Rect, value: number): Rect {
|
||||
return new Rect(
|
||||
map(from.x, to.x, value),
|
||||
map(from.y, to.y, value),
|
||||
map(from.width, to.width, value),
|
||||
map(from.height, to.height, value),
|
||||
);
|
||||
}
|
||||
|
||||
return {
|
||||
x: minX,
|
||||
y: minY,
|
||||
width: maxX - minX,
|
||||
height: maxY - minY,
|
||||
};
|
||||
};
|
||||
public static fromPoints(...points: Vector2[]): Rect {
|
||||
let minX = Infinity;
|
||||
let minY = Infinity;
|
||||
let maxX = -Infinity;
|
||||
let maxY = -Infinity;
|
||||
|
||||
rect.fromRects = (...rects: Rect[]) => {
|
||||
let minX = Infinity;
|
||||
let minY = Infinity;
|
||||
let maxX = -Infinity;
|
||||
let maxY = -Infinity;
|
||||
for (const point of points) {
|
||||
if (point.x > maxX) {
|
||||
maxX = point.x;
|
||||
}
|
||||
if (point.x < minX) {
|
||||
minX = point.x;
|
||||
}
|
||||
if (point.y > maxY) {
|
||||
maxY = point.y;
|
||||
}
|
||||
if (point.y < minY) {
|
||||
minY = point.y;
|
||||
}
|
||||
}
|
||||
|
||||
for (const r of rects) {
|
||||
const right = r.x + r.width;
|
||||
if (right > maxX) {
|
||||
maxX = right;
|
||||
}
|
||||
if (r.x < minX) {
|
||||
minX = r.x;
|
||||
}
|
||||
const bottom = r.y + r.height;
|
||||
if (bottom > maxY) {
|
||||
maxY = bottom;
|
||||
}
|
||||
if (r.y < minY) {
|
||||
minY = r.y;
|
||||
}
|
||||
return new Rect(minX, minY, maxX - minX, maxY - minY);
|
||||
}
|
||||
|
||||
return {
|
||||
x: minX,
|
||||
y: minY,
|
||||
width: maxX - minX,
|
||||
height: maxY - minY,
|
||||
};
|
||||
};
|
||||
public static fromRects(...rects: Rect[]): Rect {
|
||||
let minX = Infinity;
|
||||
let minY = Infinity;
|
||||
let maxX = -Infinity;
|
||||
let maxY = -Infinity;
|
||||
|
||||
rect.topLeft = (rect: Rect) => ({x: rect.x, y: rect.y});
|
||||
rect.topRight = (rect: Rect) => ({x: rect.x + rect.width, y: rect.y});
|
||||
rect.bottomLeft = (rect: Rect) => ({x: rect.x, y: rect.y + rect.height});
|
||||
rect.bottomRight = (rect: Rect) => ({
|
||||
x: rect.x + rect.width,
|
||||
y: rect.y + rect.height,
|
||||
});
|
||||
rect.corners = (value: Rect) => [
|
||||
rect.topLeft(value),
|
||||
rect.topRight(value),
|
||||
rect.bottomRight(value),
|
||||
rect.bottomLeft(value),
|
||||
];
|
||||
for (const r of rects) {
|
||||
const right = r.x + r.width;
|
||||
if (right > maxX) {
|
||||
maxX = right;
|
||||
}
|
||||
if (r.x < minX) {
|
||||
minX = r.x;
|
||||
}
|
||||
const bottom = r.y + r.height;
|
||||
if (bottom > maxY) {
|
||||
maxY = bottom;
|
||||
}
|
||||
if (r.y < minY) {
|
||||
minY = r.y;
|
||||
}
|
||||
}
|
||||
|
||||
rect.transform = (rect: Rect, matrix: DOMMatrix) => {
|
||||
const position = transformPoint(rect, matrix);
|
||||
const size = transformVector({x: rect.width, y: rect.height}, matrix);
|
||||
return new Rect(minX, minY, maxX - minX, maxY - minY);
|
||||
}
|
||||
|
||||
return {
|
||||
...position,
|
||||
width: size.x,
|
||||
height: size.y,
|
||||
};
|
||||
};
|
||||
public get position() {
|
||||
return new Vector2(this.x, this.y);
|
||||
}
|
||||
|
||||
rect.expand = (rect: Rect, amount: number) => {
|
||||
return {
|
||||
x: rect.x - amount,
|
||||
y: rect.y - amount,
|
||||
width: rect.width + amount * 2,
|
||||
height: rect.height + amount * 2,
|
||||
};
|
||||
};
|
||||
public get size() {
|
||||
return new Size(this.width, this.height);
|
||||
}
|
||||
|
||||
public get topLeft() {
|
||||
return this.position;
|
||||
}
|
||||
|
||||
public get topRight() {
|
||||
return new Vector2(this.x + this.width, this.y);
|
||||
}
|
||||
|
||||
public get bottomLeft() {
|
||||
return new Vector2(this.x, this.y + this.height);
|
||||
}
|
||||
|
||||
public get bottomRight() {
|
||||
return new Vector2(this.x + this.width, this.y + this.height);
|
||||
}
|
||||
|
||||
public get corners() {
|
||||
return [this.topLeft, this.topRight, this.bottomRight, this.bottomLeft];
|
||||
}
|
||||
|
||||
public get pixelPerfect() {
|
||||
return new Rect(
|
||||
Math.floor(this.x),
|
||||
Math.floor(this.y),
|
||||
Math.ceil(this.width + 1),
|
||||
Math.ceil(this.height + 1),
|
||||
);
|
||||
}
|
||||
|
||||
public constructor();
|
||||
public constructor(from: PossibleRect);
|
||||
public constructor(position: Vector2, size: Size);
|
||||
public constructor(from: Vector2, to: Vector2);
|
||||
public constructor(x: number, y?: number, width?: number, height?: number);
|
||||
public constructor(
|
||||
one?: PossibleRect | number,
|
||||
two: Vector2 | Size | number = 0,
|
||||
three = 0,
|
||||
four = 0,
|
||||
) {
|
||||
if (one === undefined || one === null) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (typeof one === 'number') {
|
||||
this.x = one;
|
||||
this.y = <number>two;
|
||||
this.width = three;
|
||||
this.height = four;
|
||||
return;
|
||||
}
|
||||
|
||||
if (one instanceof Vector2) {
|
||||
this.x = one.x;
|
||||
this.y = one.y;
|
||||
|
||||
if (two instanceof Size) {
|
||||
this.width = two.width;
|
||||
this.height = two.height;
|
||||
} else if (two instanceof Vector2) {
|
||||
this.width = two.x - one.x;
|
||||
this.height = two.y - one.y;
|
||||
}
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
if (one instanceof Size) {
|
||||
this.width = one.width;
|
||||
this.height = one.height;
|
||||
return;
|
||||
}
|
||||
|
||||
if (Array.isArray(one)) {
|
||||
this.x = one[0];
|
||||
this.y = one[1];
|
||||
this.width = one[2];
|
||||
this.height = one[3];
|
||||
return;
|
||||
}
|
||||
|
||||
this.x = one.x;
|
||||
this.y = one.y;
|
||||
this.width = one.width;
|
||||
this.height = one.height;
|
||||
}
|
||||
|
||||
public transform(matrix: DOMMatrix): Rect {
|
||||
return new Rect(
|
||||
this.position.transformAsPoint(matrix),
|
||||
this.size.transform(matrix),
|
||||
);
|
||||
}
|
||||
|
||||
public expand(amount: number) {
|
||||
return new Rect(
|
||||
this.x - amount,
|
||||
this.y - amount,
|
||||
this.width + amount * 2,
|
||||
this.height + amount * 2,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,4 +1,80 @@
|
||||
export interface Size {
|
||||
import {Vector2} from './Vector';
|
||||
import {Rect} from './Rect';
|
||||
import {Direction, Origin} from './Origin';
|
||||
import {map} from '../tweening';
|
||||
|
||||
export type SerializedSize = {
|
||||
width: number;
|
||||
height: number;
|
||||
};
|
||||
|
||||
export type PossibleSize = SerializedSize | [number, number] | Vector2 | Rect;
|
||||
|
||||
export class Size {
|
||||
public width = 0;
|
||||
public height = 0;
|
||||
|
||||
public static lerp(from: Size, to: Size, value: number) {
|
||||
return new Size(
|
||||
map(from.width, to.width, value),
|
||||
map(from.height, to.height, value),
|
||||
);
|
||||
}
|
||||
|
||||
public get vector() {
|
||||
return new Vector2(this);
|
||||
}
|
||||
|
||||
public get flip() {
|
||||
return new Size(this.height, this.width);
|
||||
}
|
||||
|
||||
public constructor();
|
||||
public constructor(from: PossibleSize);
|
||||
public constructor(width: number, height?: number);
|
||||
public constructor(one?: PossibleSize | number, two = 0) {
|
||||
if (one === undefined || one === null) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (typeof one === 'number') {
|
||||
this.width = one;
|
||||
this.height = two;
|
||||
return;
|
||||
}
|
||||
|
||||
if (one instanceof Vector2) {
|
||||
this.width = one.x;
|
||||
this.height = one.y;
|
||||
return;
|
||||
}
|
||||
|
||||
if (Array.isArray(one)) {
|
||||
this.width = one[0];
|
||||
this.height = one[1];
|
||||
return;
|
||||
}
|
||||
|
||||
this.width = one.width;
|
||||
this.height = one.height;
|
||||
}
|
||||
|
||||
public getOriginOffset(origin: Origin | Direction) {
|
||||
const offset = Vector2.fromOrigin(origin);
|
||||
offset.x *= this.width / 2;
|
||||
offset.y *= this.height / 2;
|
||||
|
||||
return offset;
|
||||
}
|
||||
|
||||
public scale(value: number) {
|
||||
return new Size(this.width * value, this.height * value);
|
||||
}
|
||||
|
||||
public transform(matrix: DOMMatrix) {
|
||||
return new Size(
|
||||
this.width * matrix.m11 + this.height * matrix.m21,
|
||||
this.width * matrix.m12 + this.height * matrix.m22,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,27 +1,37 @@
|
||||
import type {Size} from './Size';
|
||||
import type {Rect} from './Rect';
|
||||
import type {Vector2} from './Vector';
|
||||
import {map} from '../tweening';
|
||||
|
||||
interface ISpacing {
|
||||
export type SerializedSpacing = {
|
||||
top: number;
|
||||
right: number;
|
||||
bottom: number;
|
||||
left: number;
|
||||
}
|
||||
};
|
||||
|
||||
export type PossibleSpacing =
|
||||
| ISpacing
|
||||
| SerializedSpacing
|
||||
| number
|
||||
| [number, number]
|
||||
| [number, number, number]
|
||||
| [number, number, number, number];
|
||||
|
||||
export class Spacing implements ISpacing {
|
||||
export class Spacing {
|
||||
public top = 0;
|
||||
public right = 0;
|
||||
public bottom = 0;
|
||||
public left = 0;
|
||||
|
||||
public static lerp(from: Spacing, to: Spacing, value: number): Spacing {
|
||||
return new Spacing(
|
||||
map(from.top, to.top, value),
|
||||
map(from.right, to.right, value),
|
||||
map(from.bottom, to.bottom, value),
|
||||
map(from.left, to.left, value),
|
||||
);
|
||||
}
|
||||
|
||||
public get x(): number {
|
||||
return this.left + this.right;
|
||||
}
|
||||
@@ -30,76 +40,40 @@ export class Spacing implements ISpacing {
|
||||
return this.top + this.bottom;
|
||||
}
|
||||
|
||||
public constructor(value?: PossibleSpacing) {
|
||||
if (value !== undefined) {
|
||||
this.set(value);
|
||||
}
|
||||
}
|
||||
|
||||
public set(value: PossibleSpacing): this {
|
||||
if (Array.isArray(value)) {
|
||||
switch (value.length) {
|
||||
case 2:
|
||||
this.top = this.bottom = value[0];
|
||||
this.right = this.left = value[1];
|
||||
break;
|
||||
case 3:
|
||||
this.top = value[0];
|
||||
this.right = this.left = value[1];
|
||||
this.bottom = value[2];
|
||||
break;
|
||||
case 4:
|
||||
this.top = value[0];
|
||||
this.right = value[1];
|
||||
this.bottom = value[2];
|
||||
this.left = value[3];
|
||||
break;
|
||||
}
|
||||
} else if (typeof value === 'object') {
|
||||
this.top = value.top ?? 0;
|
||||
this.right = value.right ?? 0;
|
||||
this.bottom = value.bottom ?? 0;
|
||||
this.left = value.left ?? 0;
|
||||
return this;
|
||||
} else {
|
||||
this.top = this.right = this.bottom = this.left = value;
|
||||
public constructor();
|
||||
public constructor(from: PossibleSpacing);
|
||||
public constructor(all: number);
|
||||
public constructor(vertical: number, horizontal: number);
|
||||
public constructor(top: number, horizontal: number, bottom: number);
|
||||
public constructor(top: number, right: number, bottom: number, left: number);
|
||||
public constructor(
|
||||
one: PossibleSpacing = 0,
|
||||
two?: number,
|
||||
three?: number,
|
||||
four?: number,
|
||||
) {
|
||||
if (one === undefined || one === null) {
|
||||
return;
|
||||
}
|
||||
|
||||
return this;
|
||||
}
|
||||
|
||||
public expand<T extends Size | Rect>(value: T): T {
|
||||
const result = {...value};
|
||||
|
||||
result.width += this.x;
|
||||
result.height += this.y;
|
||||
if ('x' in result) {
|
||||
result.x -= this.left;
|
||||
result.y -= this.top;
|
||||
if (Array.isArray(one)) {
|
||||
four = one[3];
|
||||
three = one[2];
|
||||
two = one[1];
|
||||
one = one[0];
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
public shrink<T extends Size | Rect>(value: T): T {
|
||||
const result = {...value};
|
||||
|
||||
result.width -= this.x;
|
||||
result.height -= this.y;
|
||||
if ('x' in result) {
|
||||
result.x += this.left;
|
||||
result.y += this.top;
|
||||
if (typeof one === 'number') {
|
||||
this.top = one;
|
||||
this.right = two !== undefined ? two : one;
|
||||
this.bottom = three !== undefined ? three : one;
|
||||
this.left = four !== undefined ? four : two !== undefined ? two : one;
|
||||
return;
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
public scale(scale: Vector2): Spacing {
|
||||
return new Spacing([
|
||||
this.top * scale.y,
|
||||
this.right * scale.x,
|
||||
this.bottom * scale.y,
|
||||
this.left * scale.x,
|
||||
]);
|
||||
this.top = one.top;
|
||||
this.right = one.right;
|
||||
this.bottom = one.bottom;
|
||||
this.left = one.left;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,10 +1,104 @@
|
||||
export interface Vector2 {
|
||||
x: number;
|
||||
y: number;
|
||||
}
|
||||
import {Size} from './Size';
|
||||
import {Rect} from './Rect';
|
||||
import {map} from '../tweening';
|
||||
import {Direction, Origin} from './Origin';
|
||||
|
||||
export interface Vector3 {
|
||||
export type SerializedVector2 = {
|
||||
x: number;
|
||||
y: number;
|
||||
z: number;
|
||||
};
|
||||
|
||||
export type PossibleVector2 =
|
||||
| SerializedVector2
|
||||
| [number, number]
|
||||
| Size
|
||||
| Rect;
|
||||
|
||||
export class Vector2 {
|
||||
public x = 0;
|
||||
public y = 0;
|
||||
|
||||
public static readonly zero = new Vector2();
|
||||
|
||||
public static lerp(from: Vector2, to: Vector2, value: number) {
|
||||
return new Vector2(map(from.x, to.x, value), map(from.y, to.y, value));
|
||||
}
|
||||
|
||||
public static fromOrigin(origin: Origin | Direction) {
|
||||
const position = new Vector2();
|
||||
|
||||
if (origin === Origin.Middle) {
|
||||
return position;
|
||||
}
|
||||
|
||||
if (origin & Direction.Left) {
|
||||
position.x = -1;
|
||||
} else if (origin & Direction.Right) {
|
||||
position.x = 1;
|
||||
}
|
||||
|
||||
if (origin & Direction.Top) {
|
||||
position.y = -1;
|
||||
} else if (origin & Direction.Bottom) {
|
||||
position.y = 1;
|
||||
}
|
||||
|
||||
return position;
|
||||
}
|
||||
|
||||
public static fromRadians(radians: number) {
|
||||
return new Vector2(Math.cos(radians), Math.sin(radians));
|
||||
}
|
||||
|
||||
public get radians() {
|
||||
return Math.atan2(this.y, this.x);
|
||||
}
|
||||
|
||||
public constructor();
|
||||
public constructor(from: PossibleVector2);
|
||||
public constructor(x: number, y?: number);
|
||||
public constructor(one?: PossibleVector2 | number, two = 0) {
|
||||
if (one === undefined || one === null) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (typeof one === 'number') {
|
||||
this.x = one;
|
||||
this.y = two;
|
||||
return;
|
||||
}
|
||||
|
||||
if (one instanceof Size) {
|
||||
this.x = one.width;
|
||||
this.y = one.height;
|
||||
return;
|
||||
}
|
||||
|
||||
if (Array.isArray(one)) {
|
||||
this.x = one[0];
|
||||
this.y = one[0];
|
||||
return;
|
||||
}
|
||||
|
||||
this.x = one.x;
|
||||
this.y = one.y;
|
||||
}
|
||||
|
||||
public scale(value: number) {
|
||||
return new Vector2(this.x * value, this.y * value);
|
||||
}
|
||||
|
||||
public transformAsPoint(matrix: DOMMatrix) {
|
||||
return new Vector2(
|
||||
this.x * matrix.m11 + this.y * matrix.m21 + matrix.m41,
|
||||
this.x * matrix.m12 + this.y * matrix.m22 + matrix.m42,
|
||||
);
|
||||
}
|
||||
|
||||
public transform(matrix: DOMMatrix) {
|
||||
return new Vector2(
|
||||
this.x * matrix.m11 + this.y * matrix.m21,
|
||||
this.x * matrix.m12 + this.y * matrix.m22,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -4,5 +4,6 @@
|
||||
* @module
|
||||
*/
|
||||
export * from './show';
|
||||
export * from './interpolationFunctions';
|
||||
export * from './surfaceFrom';
|
||||
export * from './surfaceTransition';
|
||||
|
||||
81
packages/legacy/src/animations/interpolationFunctions.ts
Normal file
81
packages/legacy/src/animations/interpolationFunctions.ts
Normal file
@@ -0,0 +1,81 @@
|
||||
import {PossibleSpacing, Rect, Size, Spacing, Vector2} from '../types';
|
||||
import {map} from '@motion-canvas/core/lib/tweening';
|
||||
|
||||
export function vector2dLerp(from: Vector2, to: Vector2, value: number) {
|
||||
return {
|
||||
x: map(from.x, to.x, value),
|
||||
y: map(from.y, to.y, value),
|
||||
};
|
||||
}
|
||||
|
||||
export function sizeLerp(from: Size, to: Size, value: number) {
|
||||
return {
|
||||
width: map(from.width, to.width, value),
|
||||
height: map(from.height, to.height, value),
|
||||
};
|
||||
}
|
||||
|
||||
export function spacingLerp(
|
||||
from: Spacing,
|
||||
to: Spacing,
|
||||
value: number,
|
||||
): PossibleSpacing {
|
||||
return [
|
||||
map(from.top, to.top, value),
|
||||
map(from.right, to.right, value),
|
||||
map(from.bottom, to.bottom, value),
|
||||
map(from.left, to.left, value),
|
||||
];
|
||||
}
|
||||
|
||||
export function rectArcLerp(
|
||||
from: Partial<Rect>,
|
||||
to: Partial<Rect>,
|
||||
value: number,
|
||||
reverse?: boolean,
|
||||
ratio?: number,
|
||||
) {
|
||||
ratio ??= calculateRatio(from, to);
|
||||
|
||||
let flip = reverse;
|
||||
if (ratio > 1) {
|
||||
ratio = 1 / ratio;
|
||||
} else {
|
||||
flip = !flip;
|
||||
}
|
||||
|
||||
const normalized = flip ? Math.acos(1 - value) : Math.asin(value);
|
||||
const radians = map(normalized, map(0, Math.PI / 2, value), ratio);
|
||||
|
||||
let xValue = Math.sin(radians);
|
||||
let yValue = 1 - Math.cos(radians);
|
||||
if (reverse) {
|
||||
[xValue, yValue] = [yValue, xValue];
|
||||
}
|
||||
|
||||
return {
|
||||
x: map(from.x ?? 0, to.x ?? 0, xValue),
|
||||
y: map(from.y ?? 0, to.y ?? 0, yValue),
|
||||
width: map(from.width ?? 0, to.width ?? 0, xValue),
|
||||
height: map(from.height ?? 0, to.height ?? 0, yValue),
|
||||
};
|
||||
}
|
||||
|
||||
export function calculateRatio(from: Partial<Rect>, to: Partial<Rect>): number {
|
||||
let numberOfValues = 0;
|
||||
let ratio = 0;
|
||||
if (from.x) {
|
||||
ratio += Math.abs((from.x - to.x) / (from.y - to.y));
|
||||
numberOfValues++;
|
||||
}
|
||||
if (from.width) {
|
||||
ratio += Math.abs((from.width - to.width) / (from.height - to.height));
|
||||
numberOfValues++;
|
||||
}
|
||||
|
||||
if (numberOfValues) {
|
||||
ratio /= numberOfValues;
|
||||
}
|
||||
|
||||
return isNaN(ratio) ? 1 : ratio;
|
||||
}
|
||||
@@ -1,6 +1,7 @@
|
||||
import type {Node} from 'konva/lib/Node';
|
||||
import {Surface} from '../components';
|
||||
import {Origin, originPosition} from '@motion-canvas/core/lib/types';
|
||||
import {Origin} from '@motion-canvas/core/lib/types';
|
||||
import {originPosition} from '../types';
|
||||
import {all} from '@motion-canvas/core/lib/flow';
|
||||
import {Vector2d} from 'konva/lib/types';
|
||||
import {
|
||||
|
||||
@@ -2,14 +2,13 @@ import type {Vector2d} from 'konva/lib/types';
|
||||
import type {ThreadGenerator} from '@motion-canvas/core/lib/threading';
|
||||
import type {Surface, SurfaceMask} from '../components';
|
||||
import {
|
||||
calculateRatio,
|
||||
colorLerp,
|
||||
easeInOutCubic,
|
||||
easeInOutQuint,
|
||||
rectArcLerp,
|
||||
tween,
|
||||
} from '@motion-canvas/core/lib/tweening';
|
||||
import {decorate, threadable} from '@motion-canvas/core/lib/decorators';
|
||||
import {calculateRatio, rectArcLerp} from './interpolationFunctions';
|
||||
|
||||
/**
|
||||
* Configuration for {@link surfaceFrom}.
|
||||
|
||||
@@ -1,15 +1,14 @@
|
||||
import {Surface} from '../components';
|
||||
import {
|
||||
calculateRatio,
|
||||
clampRemap,
|
||||
colorLerp,
|
||||
easeInOutCubic,
|
||||
easeInOutQuint,
|
||||
rectArcLerp,
|
||||
tween,
|
||||
} from '@motion-canvas/core/lib/tweening';
|
||||
import {decorate, threadable} from '@motion-canvas/core/lib/decorators';
|
||||
import {ThreadGenerator} from '@motion-canvas/core/lib/threading';
|
||||
import {calculateRatio, rectArcLerp} from './interpolationFunctions';
|
||||
|
||||
/**
|
||||
* Configuration for {@link surfaceTransition}.
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import type {Node} from 'konva/lib/Node';
|
||||
import {getOriginDelta, Origin} from '@motion-canvas/core/lib/types';
|
||||
import {Origin} from '@motion-canvas/core/lib/types';
|
||||
import {getOriginDelta} from '../types';
|
||||
import {useScene} from '@motion-canvas/core/lib/utils';
|
||||
|
||||
interface AlignConfig {
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import {Size} from '@motion-canvas/core/lib/types';
|
||||
import {Size} from '../types';
|
||||
import {Group} from 'konva/lib/Group';
|
||||
|
||||
export class LayeredLayout extends Group {
|
||||
|
||||
@@ -1,12 +1,9 @@
|
||||
import {Text, TextConfig} from 'konva/lib/shapes/Text';
|
||||
import {GetSet, IRect, Vector2d} from 'konva/lib/types';
|
||||
import {ShapeGetClientRectConfig} from 'konva/lib/Shape';
|
||||
import {
|
||||
Origin,
|
||||
Size,
|
||||
Spacing,
|
||||
getOriginOffset,
|
||||
} from '@motion-canvas/core/lib/types';
|
||||
import {Origin} from '@motion-canvas/core/lib/types';
|
||||
import {getOriginOffset} from '../types';
|
||||
import {Size, Spacing} from '../types';
|
||||
import {
|
||||
Animator,
|
||||
tween,
|
||||
|
||||
@@ -1,12 +1,9 @@
|
||||
import {Group} from 'konva/lib/Group';
|
||||
import {GetSet} from 'konva/lib/types';
|
||||
import {Shape} from 'konva/lib/Shape';
|
||||
import {
|
||||
Center,
|
||||
getOriginOffset,
|
||||
Origin,
|
||||
Size,
|
||||
} from '@motion-canvas/core/lib/types';
|
||||
import {Center, Origin} from '@motion-canvas/core/lib/types';
|
||||
import {getOriginOffset} from '../types';
|
||||
import {Size} from '../types';
|
||||
import {ContainerConfig} from 'konva/lib/Container';
|
||||
import {KonvaNode, getset} from '../decorators';
|
||||
import {Node} from 'konva/lib/Node';
|
||||
|
||||
@@ -1,11 +1,7 @@
|
||||
import {Group} from 'konva/lib/Group';
|
||||
import {Container, ContainerConfig} from 'konva/lib/Container';
|
||||
import {
|
||||
Center,
|
||||
flipOrigin,
|
||||
getOriginDelta,
|
||||
Origin,
|
||||
} from '@motion-canvas/core/lib/types';
|
||||
import {Center, flipOrigin, Origin} from '@motion-canvas/core/lib/types';
|
||||
import {getOriginDelta} from '../types';
|
||||
import {GetSet, IRect} from 'konva/lib/types';
|
||||
import {KonvaNode, getset} from '../decorators';
|
||||
import {Node} from 'konva/lib/Node';
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import {ContainerConfig} from 'konva/lib/Container';
|
||||
import {getOriginDelta, Origin, Size} from '@motion-canvas/core/lib/types';
|
||||
import {Origin} from '@motion-canvas/core/lib/types';
|
||||
import {getOriginDelta, Size} from '../types';
|
||||
import {CanvasHelper} from '../helpers';
|
||||
import {easeOutExpo, linear, tween} from '@motion-canvas/core/lib/tweening';
|
||||
import {GetSet} from 'konva/lib/types';
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import {PossibleSpacing, Size} from '@motion-canvas/core/lib/types';
|
||||
import {PossibleSpacing, Size} from '../types';
|
||||
import {Util} from 'konva/lib/Util';
|
||||
import {Context} from 'konva/lib/Context';
|
||||
import * as THREE from 'three';
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import type {Context} from 'konva/lib/Context';
|
||||
import {PossibleSpacing, Spacing} from '@motion-canvas/core/lib/types';
|
||||
import {PossibleSpacing, Spacing} from '../types';
|
||||
|
||||
export const CanvasHelper = {
|
||||
roundRect<T extends CanvasRenderingContext2D | Context>(
|
||||
|
||||
@@ -1,12 +1,7 @@
|
||||
import type {Style} from '../styles';
|
||||
import {Node, NodeConfig} from 'konva/lib/Node';
|
||||
import {
|
||||
Origin,
|
||||
PossibleSpacing,
|
||||
Size,
|
||||
Spacing,
|
||||
getOriginDelta,
|
||||
} from '@motion-canvas/core/lib/types';
|
||||
import {Origin} from '@motion-canvas/core/lib/types';
|
||||
import {PossibleSpacing, Size, Spacing, getOriginDelta} from '../types';
|
||||
import {GetSet, IRect, Vector2d} from 'konva/lib/types';
|
||||
import {Factory} from 'konva/lib/Factory';
|
||||
import {Container} from 'konva/lib/Container';
|
||||
|
||||
@@ -18,6 +18,7 @@ import {Util} from 'konva/lib/Util';
|
||||
import {Node} from 'konva/lib/Node';
|
||||
import {Konva} from 'konva/lib/Global';
|
||||
import {NODE_ID} from '@motion-canvas/core/lib';
|
||||
import {Rect, Vector2} from '@motion-canvas/core/lib/types';
|
||||
|
||||
Konva.autoDrawEnabled = false;
|
||||
|
||||
|
||||
46
packages/legacy/src/types/Origin.ts
Normal file
46
packages/legacy/src/types/Origin.ts
Normal file
@@ -0,0 +1,46 @@
|
||||
import type {Vector2} from './Vector';
|
||||
import type {Size} from './Size';
|
||||
import {Direction, Origin} from '@motion-canvas/core/lib/types';
|
||||
|
||||
export function originPosition(
|
||||
origin: Origin | Direction,
|
||||
width = 1,
|
||||
height = 1,
|
||||
): Vector2 {
|
||||
const position: Vector2 = {x: 0, y: 0};
|
||||
|
||||
if (origin === Origin.Middle) {
|
||||
return position;
|
||||
}
|
||||
|
||||
if (origin & Direction.Left) {
|
||||
position.x = -width;
|
||||
} else if (origin & Direction.Right) {
|
||||
position.x = width;
|
||||
}
|
||||
|
||||
if (origin & Direction.Top) {
|
||||
position.y = -height;
|
||||
} else if (origin & Direction.Bottom) {
|
||||
position.y = height;
|
||||
}
|
||||
|
||||
return position;
|
||||
}
|
||||
|
||||
export function getOriginOffset(size: Size, origin: Origin): Vector2 {
|
||||
return originPosition(origin, size.width / 2, size.height / 2);
|
||||
}
|
||||
|
||||
export function getOriginDelta(size: Size, from: Origin, to: Origin) {
|
||||
const fromOffset = getOriginOffset(size, from);
|
||||
if (to === Origin.Middle) {
|
||||
return {x: -fromOffset.x, y: -fromOffset.y};
|
||||
}
|
||||
|
||||
const toOffset = getOriginOffset(size, to);
|
||||
return {
|
||||
x: toOffset.x - fromOffset.x,
|
||||
y: toOffset.y - fromOffset.y,
|
||||
};
|
||||
}
|
||||
6
packages/legacy/src/types/Rect.ts
Normal file
6
packages/legacy/src/types/Rect.ts
Normal file
@@ -0,0 +1,6 @@
|
||||
export interface Rect {
|
||||
x: number;
|
||||
y: number;
|
||||
width: number;
|
||||
height: number;
|
||||
}
|
||||
4
packages/legacy/src/types/Size.ts
Normal file
4
packages/legacy/src/types/Size.ts
Normal file
@@ -0,0 +1,4 @@
|
||||
export interface Size {
|
||||
width: number;
|
||||
height: number;
|
||||
}
|
||||
105
packages/legacy/src/types/Spacing.ts
Normal file
105
packages/legacy/src/types/Spacing.ts
Normal file
@@ -0,0 +1,105 @@
|
||||
import type {Size} from './Size';
|
||||
import type {Rect} from './Rect';
|
||||
import type {Vector2} from './Vector';
|
||||
|
||||
interface ISpacing {
|
||||
top: number;
|
||||
right: number;
|
||||
bottom: number;
|
||||
left: number;
|
||||
}
|
||||
|
||||
export type PossibleSpacing =
|
||||
| ISpacing
|
||||
| number
|
||||
| [number, number]
|
||||
| [number, number, number]
|
||||
| [number, number, number, number];
|
||||
|
||||
export class Spacing implements ISpacing {
|
||||
public top = 0;
|
||||
public right = 0;
|
||||
public bottom = 0;
|
||||
public left = 0;
|
||||
|
||||
public get x(): number {
|
||||
return this.left + this.right;
|
||||
}
|
||||
|
||||
public get y(): number {
|
||||
return this.top + this.bottom;
|
||||
}
|
||||
|
||||
public constructor(value?: PossibleSpacing) {
|
||||
if (value !== undefined) {
|
||||
this.set(value);
|
||||
}
|
||||
}
|
||||
|
||||
public set(value: PossibleSpacing): this {
|
||||
if (Array.isArray(value)) {
|
||||
switch (value.length) {
|
||||
case 2:
|
||||
this.top = this.bottom = value[0];
|
||||
this.right = this.left = value[1];
|
||||
break;
|
||||
case 3:
|
||||
this.top = value[0];
|
||||
this.right = this.left = value[1];
|
||||
this.bottom = value[2];
|
||||
break;
|
||||
case 4:
|
||||
this.top = value[0];
|
||||
this.right = value[1];
|
||||
this.bottom = value[2];
|
||||
this.left = value[3];
|
||||
break;
|
||||
}
|
||||
} else if (typeof value === 'object') {
|
||||
this.top = value.top ?? 0;
|
||||
this.right = value.right ?? 0;
|
||||
this.bottom = value.bottom ?? 0;
|
||||
this.left = value.left ?? 0;
|
||||
return this;
|
||||
} else {
|
||||
this.top = this.right = this.bottom = this.left = value;
|
||||
}
|
||||
|
||||
return this;
|
||||
}
|
||||
|
||||
public expand<T extends Size | Rect>(value: T): T {
|
||||
const result = {...value};
|
||||
|
||||
result.width += this.x;
|
||||
result.height += this.y;
|
||||
if ('x' in result) {
|
||||
result.x -= this.left;
|
||||
result.y -= this.top;
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
public shrink<T extends Size | Rect>(value: T): T {
|
||||
const result = {...value};
|
||||
|
||||
result.width -= this.x;
|
||||
result.height -= this.y;
|
||||
if ('x' in result) {
|
||||
result.x += this.left;
|
||||
result.y += this.top;
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
public scale(scale: Vector2): Spacing {
|
||||
return new Spacing([
|
||||
this.top * scale.y,
|
||||
this.right * scale.x,
|
||||
this.bottom * scale.y,
|
||||
this.left * scale.x,
|
||||
]);
|
||||
}
|
||||
}
|
||||
10
packages/legacy/src/types/Vector.ts
Normal file
10
packages/legacy/src/types/Vector.ts
Normal file
@@ -0,0 +1,10 @@
|
||||
export interface Vector2 {
|
||||
x: number;
|
||||
y: number;
|
||||
}
|
||||
|
||||
export interface Vector3 {
|
||||
x: number;
|
||||
y: number;
|
||||
z: number;
|
||||
}
|
||||
5
packages/legacy/src/types/index.ts
Normal file
5
packages/legacy/src/types/index.ts
Normal file
@@ -0,0 +1,5 @@
|
||||
export * from './Origin';
|
||||
export * from './Rect';
|
||||
export * from './Size';
|
||||
export * from './Spacing';
|
||||
export * from './Vector';
|
||||
@@ -1,5 +1,5 @@
|
||||
import type {Container} from 'konva/lib/Container';
|
||||
import type {Vector2} from '@motion-canvas/core/lib/types';
|
||||
import type {Vector2} from '../types';
|
||||
|
||||
export function slide(container: Container, offset: Vector2): void;
|
||||
export function slide(container: Container, x: number, y?: number): void;
|
||||
|
||||
@@ -13,7 +13,7 @@
|
||||
</head>
|
||||
<body>
|
||||
<script type="module">
|
||||
import project from '@motion-canvas/template/dist/projectA';
|
||||
import project from '@motion-canvas/template/dist/project';
|
||||
import {editor} from '/src/main.tsx';
|
||||
editor(project);
|
||||
</script>
|
||||
|
||||
Reference in New Issue
Block a user