mirror of
https://github.com/motion-canvas/motion-canvas.git
synced 2026-01-11 14:57:56 -05:00
feat: directional padding and margin
This commit is contained in:
@@ -1,5 +1,6 @@
|
||||
import {IRect, Vector2d} from 'konva/lib/types';
|
||||
import mixColor from 'mix-color';
|
||||
import {PossibleSpacing, Spacing} from "../types";
|
||||
|
||||
export class TimeTween {
|
||||
public constructor(public value: number) {}
|
||||
@@ -103,6 +104,15 @@ export class TimeTween {
|
||||
};
|
||||
}
|
||||
|
||||
public spacing(from: Spacing, to: Spacing, value?: number): PossibleSpacing {
|
||||
return [
|
||||
TimeTween.map(from.top, to.top, value ?? this.value),
|
||||
TimeTween.map(from.right, to.right, value ?? this.value),
|
||||
TimeTween.map(from.bottom, to.bottom, value ?? this.value),
|
||||
TimeTween.map(from.left, to.left, value ?? this.value),
|
||||
];
|
||||
}
|
||||
|
||||
public rectArc(
|
||||
from: Partial<IRect>,
|
||||
to: Partial<IRect>,
|
||||
|
||||
@@ -1,7 +1,8 @@
|
||||
import {Node} from 'konva/lib/Node';
|
||||
import {Project} from '../Project';
|
||||
import {Surface} from "MC/components/Surface";
|
||||
import {TimeTween} from "MC/animations/TimeTween";
|
||||
import {Surface} from "../components/Surface";
|
||||
import {TimeTween} from "./TimeTween";
|
||||
import {Spacing} from "../types";
|
||||
|
||||
export function showTop(this: Project, node: Node): [Generator, Generator] {
|
||||
const to = node.offsetY();
|
||||
@@ -22,6 +23,7 @@ export function showTop(this: Project, node: Node): [Generator, Generator] {
|
||||
}
|
||||
|
||||
export function showSurface(this: Project, surface: Surface): Generator {
|
||||
const marginFrom = new Spacing();
|
||||
const margin = surface.getMargin();
|
||||
const toMask = surface.getMask();
|
||||
const fromMask = {
|
||||
@@ -41,7 +43,7 @@ export function showSurface(this: Project, surface: Surface): Generator {
|
||||
height: value.easeInOutCubic(fromMask.height, toMask.height),
|
||||
}
|
||||
)
|
||||
surface.setMargin(value.easeInOutCubic(0, margin));
|
||||
surface.setMargin(value.spacing(marginFrom, margin, value.easeInOutCubic()));
|
||||
surface.opacity(TimeTween.clampRemap(0.3, 1, 0, 1, value.value));
|
||||
});
|
||||
}
|
||||
@@ -1,4 +1,4 @@
|
||||
import {Size, Origin, Direction} from '../types';
|
||||
import {Size, Origin, Direction, PossibleSpacing, Spacing} from '../types';
|
||||
import {Node} from 'konva/lib/Node';
|
||||
import {LayoutGroup} from 'MC/components/LayoutGroup';
|
||||
import {LayoutShape} from 'MC/components/LayoutShape';
|
||||
@@ -10,16 +10,16 @@ export const LAYOUT_CHANGE_EVENT = 'layoutChange';
|
||||
|
||||
export interface LayoutAttrs {
|
||||
radius: number;
|
||||
margin: number;
|
||||
padding: number;
|
||||
margin: PossibleSpacing;
|
||||
padd: PossibleSpacing;
|
||||
color: string;
|
||||
origin: Origin;
|
||||
}
|
||||
|
||||
export interface ILayoutNode {
|
||||
getRadius(): number;
|
||||
getMargin(): number;
|
||||
getPadding(): number;
|
||||
getMargin(): Spacing;
|
||||
getPadd(): Spacing;
|
||||
getColor(): string;
|
||||
getOrigin(): Origin;
|
||||
getOriginOffset(custom?: Partial<LayoutAttrs>): Vector2d;
|
||||
|
||||
@@ -20,10 +20,10 @@ export class Layout extends LayoutGroup {
|
||||
}
|
||||
|
||||
getLayoutSize(): Size {
|
||||
return {
|
||||
width: (this.contentSize?.width ?? 0) + this.getPadding() * 2,
|
||||
height: (this.contentSize?.height ?? 0) + this.getPadding() * 2,
|
||||
};
|
||||
return this.getPadd().apply({
|
||||
width: this.contentSize?.width ?? 0,
|
||||
height: this.contentSize?.height ?? 0,
|
||||
});
|
||||
}
|
||||
|
||||
//TODO Recalculate upon removing children as well.
|
||||
@@ -48,9 +48,9 @@ export class Layout extends LayoutGroup {
|
||||
const scale = child.getAbsoluteScale(this);
|
||||
this.contentSize.width = Math.max(
|
||||
this.contentSize.width,
|
||||
(size.width + margin * 2) * scale.x,
|
||||
(size.width + margin.x) * scale.x,
|
||||
);
|
||||
this.contentSize.height += (size.height + margin * 2) * scale.y;
|
||||
this.contentSize.height += (size.height + margin.y) * scale.y;
|
||||
}
|
||||
|
||||
let height = this.contentSize.height / -2;
|
||||
@@ -62,9 +62,9 @@ export class Layout extends LayoutGroup {
|
||||
|
||||
child.position({
|
||||
x: -offset.x * scale.x,
|
||||
y: height + (-offset.y + margin) * scale.y,
|
||||
y: height + (-offset.y + margin.top) * scale.y,
|
||||
});
|
||||
height += (size.height + margin * 2) * scale.y;
|
||||
height += (size.height + margin.y) * scale.y;
|
||||
}
|
||||
this.offset(this.getOriginOffset());
|
||||
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import {Group} from 'konva/lib/Group';
|
||||
import {Container, ContainerConfig} from 'konva/lib/Container';
|
||||
import {Origin, Size} from '../types';
|
||||
import {Origin, Size, PossibleSpacing, Spacing} from '../types';
|
||||
import {
|
||||
getClientRect,
|
||||
getOriginDelta,
|
||||
@@ -18,8 +18,6 @@ import {Project} from '../Project';
|
||||
export type LayoutGroupConfig = Partial<LayoutAttrs> & ContainerConfig;
|
||||
|
||||
export abstract class LayoutGroup extends Group implements ILayoutNode {
|
||||
public attrs: LayoutGroupConfig;
|
||||
|
||||
public get project(): Project {
|
||||
return <Project>this.getStage();
|
||||
}
|
||||
@@ -45,22 +43,22 @@ export abstract class LayoutGroup extends Group implements ILayoutNode {
|
||||
return this.attrs.radius ?? 0;
|
||||
}
|
||||
|
||||
public setMargin(value: number): this {
|
||||
this.attrs.margin = value;
|
||||
public setMargin(value: PossibleSpacing): this {
|
||||
this.attrs.margin = new Spacing(value);
|
||||
return this;
|
||||
}
|
||||
|
||||
public getMargin(): Origin {
|
||||
return this.attrs.margin ?? 0;
|
||||
public getMargin(): Spacing {
|
||||
return this.attrs.margin ?? new Spacing();
|
||||
}
|
||||
|
||||
public setPadding(value: number): this {
|
||||
this.attrs.padding = value;
|
||||
public setPadd(value: PossibleSpacing): this {
|
||||
this.attrs.padd = new Spacing(value);
|
||||
return this;
|
||||
}
|
||||
|
||||
public getPadding(): number {
|
||||
return this.attrs.padding ?? 0;
|
||||
public getPadd(): Spacing {
|
||||
return this.attrs.padd ?? new Spacing();
|
||||
}
|
||||
|
||||
public setColor(value: string): this {
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
import {Container} from 'konva/lib/Container';
|
||||
import {Origin} from 'MC/types/Origin';
|
||||
import {Shape, ShapeConfig} from 'konva/lib/Shape';
|
||||
import {
|
||||
getOriginDelta,
|
||||
@@ -10,15 +9,13 @@ import {
|
||||
LayoutAttrs,
|
||||
getClientRect,
|
||||
} from './ILayoutNode';
|
||||
import {Size} from '../types';
|
||||
import {Origin, Size, PossibleSpacing, Spacing} from '../types';
|
||||
import {IRect, Vector2d} from 'konva/lib/types';
|
||||
import {Project} from "../Project";
|
||||
|
||||
export type LayoutShapeConfig = Partial<LayoutAttrs> & ShapeConfig;
|
||||
|
||||
export abstract class LayoutShape extends Shape implements ILayoutNode {
|
||||
public attrs: LayoutShapeConfig;
|
||||
|
||||
public get project(): Project {
|
||||
return <Project>this.getStage();
|
||||
}
|
||||
@@ -40,22 +37,22 @@ export abstract class LayoutShape extends Shape implements ILayoutNode {
|
||||
return this.attrs.radius ?? 0;
|
||||
}
|
||||
|
||||
public setMargin(value: number): this {
|
||||
this.attrs.margin = value;
|
||||
public setMargin(value: PossibleSpacing): this {
|
||||
this.attrs.margin = new Spacing(value);
|
||||
return this;
|
||||
}
|
||||
|
||||
public getMargin(): Origin {
|
||||
return this.attrs.margin ?? 0;
|
||||
public getMargin(): Spacing {
|
||||
return this.attrs.margin ?? new Spacing();
|
||||
}
|
||||
|
||||
public setPadding(value: number): this {
|
||||
this.attrs.padding = value;
|
||||
public setPadd(value: PossibleSpacing): this {
|
||||
this.attrs.padd = new Spacing(value);
|
||||
return this;
|
||||
}
|
||||
|
||||
public getPadding(): number {
|
||||
return this.attrs.padding ?? 0;
|
||||
public getPadd(): Spacing {
|
||||
return this.attrs.padd ?? new Spacing();
|
||||
}
|
||||
|
||||
public setColor(value: string): this {
|
||||
|
||||
@@ -10,7 +10,7 @@ import {
|
||||
LayoutAttrs,
|
||||
} from './ILayoutNode';
|
||||
import {Project} from '../Project';
|
||||
import {Origin, Size} from '../types';
|
||||
import {Origin, Size, PossibleSpacing, Spacing} from '../types';
|
||||
|
||||
export interface LayoutTextConfig extends Partial<LayoutAttrs>, TextConfig {
|
||||
minWidth?: number;
|
||||
@@ -28,7 +28,7 @@ export class LayoutText extends Text implements ILayoutNode {
|
||||
super({
|
||||
color: '#c0b3a3',
|
||||
radius: 40,
|
||||
padding: 30,
|
||||
padd: new Spacing(30),
|
||||
align: 'center',
|
||||
verticalAlign: 'middle',
|
||||
height: 20,
|
||||
@@ -44,13 +44,14 @@ export class LayoutText extends Text implements ILayoutNode {
|
||||
}
|
||||
|
||||
public getLayoutSize(custom?: LayoutTextConfig): Size {
|
||||
const padding = this.getPadd();
|
||||
const size = this.measureSize(custom?.text ?? this.text());
|
||||
return {
|
||||
width: Math.max(
|
||||
custom?.minWidth ?? this.getMinWidth(),
|
||||
this.overrideWidth ?? (size.width + this.getPadding() * 2),
|
||||
this.overrideWidth ?? (size.width + padding.x),
|
||||
),
|
||||
height: (this.isConstructed ? this.getHeight() : 0) + this.getPadding() * 2,
|
||||
height: (this.isConstructed ? this.getHeight() : 0) + padding.y,
|
||||
};
|
||||
}
|
||||
|
||||
@@ -63,22 +64,22 @@ export class LayoutText extends Text implements ILayoutNode {
|
||||
return this.attrs.radius ?? 0;
|
||||
}
|
||||
|
||||
public setMargin(value: number): this {
|
||||
this.attrs.margin = value;
|
||||
public setMargin(value: PossibleSpacing): this {
|
||||
this.attrs.margin = new Spacing(value);
|
||||
return this;
|
||||
}
|
||||
|
||||
public getMargin(): Origin {
|
||||
return this.attrs.margin ?? 0;
|
||||
public getMargin(): Spacing {
|
||||
return this.attrs.margin ?? new Spacing();
|
||||
}
|
||||
|
||||
public setPadding(value: number): this {
|
||||
this.attrs.padding = value;
|
||||
public setPadd(value: PossibleSpacing): this {
|
||||
this.attrs.padd = new Spacing(value);
|
||||
return this;
|
||||
}
|
||||
|
||||
public getPadding(): number {
|
||||
return this.attrs.padding ?? 0;
|
||||
public getPadd(): Spacing {
|
||||
return this.attrs.padd ?? new Spacing();
|
||||
}
|
||||
|
||||
public setColor(value: string): this {
|
||||
@@ -99,7 +100,7 @@ export class LayoutText extends Text implements ILayoutNode {
|
||||
return this.attrs.minWidth ?? 0;
|
||||
}
|
||||
|
||||
setText(text: string): this {
|
||||
public setText(text: string): this {
|
||||
super.setText(text);
|
||||
this.offset(this.getOriginOffset());
|
||||
this.fireLayoutChange();
|
||||
@@ -130,10 +131,11 @@ export class LayoutText extends Text implements ILayoutNode {
|
||||
}
|
||||
|
||||
public getOriginOffset(custom?: LayoutTextConfig): Vector2d {
|
||||
const padding = this.getPadd();
|
||||
const size = this.getLayoutSize({minWidth: 0, ...custom});
|
||||
const offset = getOriginOffset(size, custom?.origin ?? this.getOrigin());
|
||||
offset.x += size.width / 2;
|
||||
offset.y += size.height / 2 - this.getPadding();
|
||||
offset.x += size.width / 2 - padding.left;
|
||||
offset.y += size.height / 2 - padding.top;
|
||||
|
||||
return offset;
|
||||
}
|
||||
|
||||
@@ -152,7 +152,7 @@ export class Surface extends LayoutGroup {
|
||||
const contentMargin = this.child.getMargin();
|
||||
const scale = Math.min(
|
||||
1,
|
||||
data.width / (contentSize.width + contentMargin * 2),
|
||||
data.width / (contentSize.width + contentMargin.x),
|
||||
);
|
||||
|
||||
this.mask = data;
|
||||
@@ -178,7 +178,7 @@ export class Surface extends LayoutGroup {
|
||||
color: '#F0F',
|
||||
height: 0,
|
||||
width: 0,
|
||||
padding: 0,
|
||||
padd: 0,
|
||||
margin: 0,
|
||||
radius: 0,
|
||||
};
|
||||
@@ -188,12 +188,12 @@ export class Surface extends LayoutGroup {
|
||||
const size = this.child.getLayoutSize();
|
||||
const margin = this.child.getMargin();
|
||||
const scale = this.child.getAbsoluteScale(this);
|
||||
const padding = this.getPadding();
|
||||
const padding = this.getPadd();
|
||||
|
||||
this.layoutData = {
|
||||
...this.layoutData,
|
||||
width: (size.width + margin * 2 + padding * 2) * scale.x,
|
||||
height: (size.height + margin * 2 + padding * 2) * scale.y,
|
||||
width: (size.width + margin.x + padding.x) * scale.x,
|
||||
height: (size.height + margin.y + padding.y) * scale.y,
|
||||
radius: this.child.getRadius(),
|
||||
color: this.child.getColor(),
|
||||
};
|
||||
|
||||
@@ -84,6 +84,7 @@ export class ThreeView extends LayoutShape {
|
||||
|
||||
public setCanvasSize(value: Size): this {
|
||||
this.attrs.canvasSize = value;
|
||||
this.handleCanvasSizeChange();
|
||||
return this;
|
||||
}
|
||||
|
||||
@@ -95,6 +96,7 @@ export class ThreeView extends LayoutShape {
|
||||
|
||||
public setCameraScale(value: number): this {
|
||||
this.attrs.cameraScale = value;
|
||||
this.handleCanvasSizeChange();
|
||||
return this;
|
||||
}
|
||||
|
||||
@@ -104,6 +106,7 @@ export class ThreeView extends LayoutShape {
|
||||
|
||||
public setQuality(value: number): this {
|
||||
this.attrs.quality = value;
|
||||
this.handleCanvasSizeChange();
|
||||
return this;
|
||||
}
|
||||
|
||||
|
||||
75
src/types/Spacing.ts
Normal file
75
src/types/Spacing.ts
Normal file
@@ -0,0 +1,75 @@
|
||||
import {Size} from './Size';
|
||||
|
||||
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 apply(size: Size): Size {
|
||||
return {
|
||||
width: size.width + this.x,
|
||||
height: size.height + this.y,
|
||||
};
|
||||
}
|
||||
}
|
||||
@@ -1,2 +1,3 @@
|
||||
export * from './Size';
|
||||
export * from './Origin';
|
||||
export * from './Origin';
|
||||
export * from './Spacing';
|
||||
Reference in New Issue
Block a user