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:
Jacob
2022-11-16 22:55:46 +01:00
committed by GitHub
parent ad346f118d
commit 7c6e584aca
9 changed files with 145 additions and 31 deletions

View File

@@ -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;
}
}

View File

@@ -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.
*

View File

@@ -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>) {

View File

@@ -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();

View File

@@ -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';

View File

@@ -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
);
}
}

View File

@@ -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);
}
}

View File

@@ -1,5 +1,6 @@
export * from './createComputed';
export * from './createSignal';
export * from './range';
export * from './useAnimator';
export * from './useProject';
export * from './useRef';

View 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()];
}