mirror of
https://github.com/motion-canvas/motion-canvas.git
synced 2026-04-22 03:00:03 -04:00
feat: minor improvements (#77)
- add a range helper - make node matrices public - fix compound properties not being wrapped in a class - add methods for mouse hit detection
This commit is contained in:
@@ -109,12 +109,15 @@ export class Layout extends Node {
|
||||
public declare readonly marginLeft: Signal<number, this>;
|
||||
@property(0)
|
||||
public declare readonly marginRight: Signal<number, this>;
|
||||
@compound({
|
||||
top: 'marginTop',
|
||||
bottom: 'marginBottom',
|
||||
left: 'marginLeft',
|
||||
right: 'marginRight',
|
||||
})
|
||||
@compound(
|
||||
{
|
||||
top: 'marginTop',
|
||||
bottom: 'marginBottom',
|
||||
left: 'marginLeft',
|
||||
right: 'marginRight',
|
||||
},
|
||||
Spacing,
|
||||
)
|
||||
@property(undefined, Spacing.lerp, Spacing)
|
||||
public declare readonly margin: Property<PossibleSpacing, Spacing, this>;
|
||||
|
||||
@@ -126,12 +129,15 @@ export class Layout extends Node {
|
||||
public declare readonly paddingLeft: Signal<number, this>;
|
||||
@property(0)
|
||||
public declare readonly paddingRight: Signal<number, this>;
|
||||
@compound({
|
||||
top: 'paddingTop',
|
||||
bottom: 'paddingBottom',
|
||||
left: 'paddingLeft',
|
||||
right: 'paddingRight',
|
||||
})
|
||||
@compound(
|
||||
{
|
||||
top: 'paddingTop',
|
||||
bottom: 'paddingBottom',
|
||||
left: 'paddingLeft',
|
||||
right: 'paddingRight',
|
||||
},
|
||||
Spacing,
|
||||
)
|
||||
@property(undefined, Spacing.lerp, Spacing)
|
||||
public declare readonly padding: Property<PossibleSpacing, Spacing, this>;
|
||||
|
||||
@@ -293,7 +299,7 @@ export class Layout extends Node {
|
||||
lock && this.releaseSize();
|
||||
}
|
||||
|
||||
@compound(['width', 'height'])
|
||||
@compound(['width', 'height'], Size)
|
||||
@property(undefined, Size.lerp, Size)
|
||||
public declare readonly size: Property<
|
||||
{width: Length; height: Length},
|
||||
@@ -354,7 +360,7 @@ export class Layout extends Node {
|
||||
@property(0)
|
||||
public declare readonly offsetY: Signal<number, this>;
|
||||
|
||||
@compound({x: 'offsetX', y: 'offsetY'})
|
||||
@compound({x: 'offsetX', y: 'offsetY'}, Vector2)
|
||||
@property(undefined, Vector2.lerp, Vector2)
|
||||
public declare readonly offset: Signal<Vector2, this>;
|
||||
|
||||
@@ -364,14 +370,29 @@ export class Layout extends Node {
|
||||
@property(1)
|
||||
public declare readonly scaleY: Signal<number, this>;
|
||||
|
||||
@compound({x: 'scaleX', y: 'scaleY'})
|
||||
@compound({x: 'scaleX', y: 'scaleY'}, Vector2)
|
||||
@property(undefined, Vector2.lerp, Vector2)
|
||||
public declare readonly scale: Signal<Vector2, this>;
|
||||
|
||||
@property(undefined, Vector2.lerp, Vector2)
|
||||
public declare readonly absoluteScale: Signal<Vector2, this>;
|
||||
|
||||
protected getAbsoluteScale(): Vector2 {
|
||||
const matrix = this.localToWorld();
|
||||
return new Vector2(
|
||||
Vector2.magnitude(matrix.m11, matrix.m12),
|
||||
Vector2.magnitude(matrix.m21, matrix.m22),
|
||||
);
|
||||
}
|
||||
|
||||
protected setAbsoluteScale(value: SignalValue<Vector2>) {
|
||||
// TODO Implement setter
|
||||
}
|
||||
|
||||
@property(false)
|
||||
public declare readonly overflow: Signal<boolean, this>;
|
||||
|
||||
@compound(['x', 'y'])
|
||||
@compound(['x', 'y'], Vector2)
|
||||
@property(undefined, Vector2.lerp, Vector2)
|
||||
public declare readonly position: Signal<Vector2, this>;
|
||||
|
||||
@@ -444,6 +465,14 @@ export class Layout extends Node {
|
||||
return null;
|
||||
}
|
||||
|
||||
@computed()
|
||||
public anchorPosition() {
|
||||
const size = this.computedSize();
|
||||
const offset = this.offset();
|
||||
|
||||
return size.vector.scale(0.5).mul(offset);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the resolved layout mode of this node.
|
||||
*
|
||||
@@ -469,7 +498,7 @@ export class Layout extends Node {
|
||||
return mode;
|
||||
}
|
||||
|
||||
protected override localToParent(): DOMMatrix {
|
||||
public override localToParent(): DOMMatrix {
|
||||
const matrix = new DOMMatrix();
|
||||
const size = this.computedSize();
|
||||
matrix.translateSelf(this.x(), this.y());
|
||||
@@ -642,4 +671,13 @@ export class Layout extends Node {
|
||||
this.element.style.whiteSpace =
|
||||
wrap === null ? '' : wrap ? 'normal' : 'nowrap';
|
||||
}
|
||||
|
||||
public override hit(position: Vector2): Node | null {
|
||||
const local = position.transformAsPoint(this.localToParent().inverse());
|
||||
if (this.getCacheRect().includes(local)) {
|
||||
return super.hit(position) ?? this;
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -79,7 +79,7 @@ export class Node implements Promisable<Node> {
|
||||
@property(0)
|
||||
public declare readonly shadowOffsetY: Signal<number, this>;
|
||||
|
||||
@compound({x: 'shadowOffsetX', y: 'shadowOffsetY'})
|
||||
@compound({x: 'shadowOffsetX', y: 'shadowOffsetY'}, Vector2)
|
||||
@property(undefined, Vector2.lerp, Vector2)
|
||||
public declare readonly shadowOffset: Signal<Vector2, this>;
|
||||
|
||||
@@ -156,7 +156,7 @@ export class Node implements Promisable<Node> {
|
||||
}
|
||||
|
||||
@computed()
|
||||
protected localToWorld(): DOMMatrix {
|
||||
public localToWorld(): DOMMatrix {
|
||||
const parent = this.parent();
|
||||
return parent
|
||||
? parent.localToWorld().multiply(this.localToParent())
|
||||
@@ -164,17 +164,17 @@ export class Node implements Promisable<Node> {
|
||||
}
|
||||
|
||||
@computed()
|
||||
protected worldToLocal() {
|
||||
public worldToLocal() {
|
||||
return this.localToWorld().inverse();
|
||||
}
|
||||
|
||||
@computed()
|
||||
protected worldToParent(): DOMMatrix {
|
||||
public worldToParent(): DOMMatrix {
|
||||
return this.parent()?.worldToLocal() ?? new DOMMatrix();
|
||||
}
|
||||
|
||||
@computed()
|
||||
protected localToParent(): DOMMatrix {
|
||||
public localToParent(): DOMMatrix {
|
||||
return new DOMMatrix();
|
||||
}
|
||||
|
||||
@@ -330,7 +330,7 @@ export class Node implements Promisable<Node> {
|
||||
* as its children.
|
||||
*/
|
||||
@computed()
|
||||
protected cacheRect(): Rect {
|
||||
public cacheRect(): Rect {
|
||||
const cache = this.getCacheRect();
|
||||
const children = this.children();
|
||||
if (children.length === 0) {
|
||||
@@ -460,6 +460,24 @@ export class Node implements Promisable<Node> {
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Try to find a node intersecting the given position.
|
||||
*
|
||||
* @param position - The searched position.
|
||||
*/
|
||||
public hit(position: Vector2): Node | null {
|
||||
let hit: Node | null = null;
|
||||
const local = position.transformAsPoint(this.localToParent().inverse());
|
||||
for (const child of this.children().reverse()) {
|
||||
hit = child.hit(local);
|
||||
if (hit) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return hit;
|
||||
}
|
||||
|
||||
/**
|
||||
* Wait for any asynchronous resources that this node or its children have.
|
||||
*
|
||||
|
||||
@@ -40,21 +40,33 @@ 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 entries = Array.isArray(mapping)
|
||||
? mapping.map(key => [key, key])
|
||||
: Object.entries(mapping);
|
||||
|
||||
target.constructor.prototype[`get${capitalize(key.toString())}`] =
|
||||
function () {
|
||||
return Object.fromEntries(
|
||||
entries.map(([key, property]) => [key, this[property]()]),
|
||||
);
|
||||
};
|
||||
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[`set${capitalize(key.toString())}`] =
|
||||
function set(value: SignalValue<any>) {
|
||||
|
||||
@@ -4,7 +4,6 @@ import {
|
||||
SceneRenderEvent,
|
||||
} from '@motion-canvas/core/lib/scenes';
|
||||
import {TwoDView} from './TwoDView';
|
||||
import {Node} from '../components';
|
||||
|
||||
export class TwoDScene extends GeneratorScene<TwoDView> {
|
||||
private readonly view = new TwoDView();
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import {Layout} from '../components/Layout';
|
||||
import {Layout} from '../components';
|
||||
|
||||
export class TwoDView extends Layout {
|
||||
public static frameID = 'motion-canvas-2d-frame';
|
||||
|
||||
@@ -190,4 +190,13 @@ export class Rect {
|
||||
this.height + amount * 2,
|
||||
);
|
||||
}
|
||||
|
||||
public includes(point: Vector2): boolean {
|
||||
return (
|
||||
point.x >= this.x &&
|
||||
point.x <= this.x + this.width &&
|
||||
point.y >= this.y &&
|
||||
point.y <= this.y + this.height
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -51,6 +51,14 @@ export class Vector2 {
|
||||
return new Vector2(Math.cos(radians), Math.sin(radians));
|
||||
}
|
||||
|
||||
public static magnitude(x: number, y: number) {
|
||||
return Math.sqrt(x * x + y * y);
|
||||
}
|
||||
|
||||
public get magnitude(): number {
|
||||
return Vector2.magnitude(this.x, this.y);
|
||||
}
|
||||
|
||||
public get radians() {
|
||||
return Math.atan2(this.y, this.x);
|
||||
}
|
||||
@@ -102,4 +110,20 @@ export class Vector2 {
|
||||
this.x * matrix.m12 + this.y * matrix.m22,
|
||||
);
|
||||
}
|
||||
|
||||
public mul(vector: Vector2) {
|
||||
return new Vector2(this.x * vector.x, this.y * vector.y);
|
||||
}
|
||||
|
||||
public add(vector: Vector2) {
|
||||
return new Vector2(this.x + vector.x, this.y + vector.y);
|
||||
}
|
||||
|
||||
public addX(value: number) {
|
||||
return new Vector2(this.x + value, this.y);
|
||||
}
|
||||
|
||||
public addY(value: number) {
|
||||
return new Vector2(this.x, this.y + value);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
export * from './createComputed';
|
||||
export * from './createSignal';
|
||||
export * from './range';
|
||||
export * from './useAnimator';
|
||||
export * from './useProject';
|
||||
export * from './useRef';
|
||||
|
||||
13
packages/core/src/utils/range.ts
Normal file
13
packages/core/src/utils/range.ts
Normal file
@@ -0,0 +1,13 @@
|
||||
/**
|
||||
* Create an array of given length containing its own indices.
|
||||
*
|
||||
* @example
|
||||
* ```ts
|
||||
* const array = range(3); // [0, 1, 2]
|
||||
* ```
|
||||
*
|
||||
* @param length
|
||||
*/
|
||||
export function range(length: number): number[] {
|
||||
return [...Array(length).keys()];
|
||||
}
|
||||
Reference in New Issue
Block a user