mirror of
https://github.com/motion-canvas/motion-canvas.git
synced 2026-01-11 06:48:12 -05:00
feat: project variables (#255)
Adds variables class to store and set project variables signals
This commit is contained in:
@@ -27,6 +27,7 @@ export interface ProjectConfig {
|
||||
canvas?: HTMLCanvasElement;
|
||||
size?: Vector2;
|
||||
background?: string | false;
|
||||
variables?: Record<string, unknown>;
|
||||
}
|
||||
|
||||
export enum PlaybackState {
|
||||
@@ -148,6 +149,15 @@ export class Project {
|
||||
return this.size;
|
||||
}
|
||||
|
||||
public setVariables(variables: Record<string, unknown>) {
|
||||
this.variables = variables;
|
||||
this.currentScene.current?.variables.updateSignals();
|
||||
}
|
||||
|
||||
public getVariables(): {[key: string]: any} {
|
||||
return this.variables;
|
||||
}
|
||||
|
||||
public readonly name: string;
|
||||
public readonly audio = new AudioManager();
|
||||
public readonly logger = new Logger();
|
||||
@@ -170,6 +180,7 @@ export class Project {
|
||||
private previousBufferContext: CanvasRenderingContext2D | null = null;
|
||||
private exportCounter = 0;
|
||||
private size = Vector2.zero;
|
||||
private variables: Record<string, unknown> = {};
|
||||
|
||||
/**
|
||||
* @deprecated Use {@link makeProject} instead.
|
||||
@@ -194,6 +205,7 @@ export class Project {
|
||||
canvas,
|
||||
size = ProjectSize.FullHD,
|
||||
background = false,
|
||||
variables = {},
|
||||
} = typeof name === 'string' ? config! : name;
|
||||
|
||||
this.name = typeof name === 'string' ? name : '';
|
||||
@@ -202,6 +214,7 @@ export class Project {
|
||||
this.creationStack = new Error().stack ?? '';
|
||||
this.setCanvas(canvas);
|
||||
this.setSize(size);
|
||||
this.setVariables(variables);
|
||||
this.background = background;
|
||||
|
||||
if (audio) {
|
||||
|
||||
@@ -7,6 +7,7 @@ import {
|
||||
} from '../threading';
|
||||
import {Meta} from '../Meta';
|
||||
import {TimeEvents} from './TimeEvents';
|
||||
import {Variables} from './Variables';
|
||||
import {EventDispatcher, ValueDispatcher} from '../events';
|
||||
import {Project} from '../Project';
|
||||
import {decorate, threadable} from '../decorators';
|
||||
@@ -41,6 +42,7 @@ export abstract class GeneratorScene<T>
|
||||
public readonly project: Project;
|
||||
public readonly meta: Meta<SceneMetadata>;
|
||||
public readonly timeEvents: TimeEvents;
|
||||
public readonly variables: Variables;
|
||||
public random: Random;
|
||||
public creationStack?: string;
|
||||
|
||||
@@ -113,6 +115,7 @@ export abstract class GeneratorScene<T>
|
||||
|
||||
decorate(this.runnerFactory, threadable(this.name));
|
||||
this.timeEvents = new TimeEvents(this);
|
||||
this.variables = new Variables(this);
|
||||
|
||||
let seed = this.meta.getData().seed;
|
||||
if (typeof seed !== 'number') {
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import type {Project} from '../Project';
|
||||
import {Meta, Metadata} from '../Meta';
|
||||
import {SavedTimeEvent, TimeEvents} from './TimeEvents';
|
||||
import {Variables} from './Variables';
|
||||
import {
|
||||
SubscribableEvent,
|
||||
SubscribableValueEvent,
|
||||
@@ -122,6 +123,7 @@ export interface Scene<T = unknown> {
|
||||
*/
|
||||
readonly project: Project;
|
||||
readonly timeEvents: TimeEvents;
|
||||
readonly variables: Variables;
|
||||
readonly random: Random;
|
||||
readonly meta: Meta<SceneMetadata>;
|
||||
creationStack?: string;
|
||||
|
||||
49
packages/core/src/scenes/Variables.ts
Normal file
49
packages/core/src/scenes/Variables.ts
Normal file
@@ -0,0 +1,49 @@
|
||||
import type {Scene} from './Scene';
|
||||
import {createSignal, SimpleSignal} from '../signals';
|
||||
|
||||
export class Variables {
|
||||
private signals: {[key: string]: SimpleSignal<any>} = {};
|
||||
|
||||
public constructor(private readonly scene: Scene) {
|
||||
scene.onReset.subscribe(this.handleReset);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get variable signal if exists or create signal if not
|
||||
*
|
||||
* @param name - The name of the variable.
|
||||
*/
|
||||
public get<T>(name: string, initial: T): SimpleSignal<T> {
|
||||
const variables = this.scene.project.getVariables();
|
||||
if (!(name in variables)) {
|
||||
this.scene.project.logger.warn(
|
||||
`Variable ${name} has not been set in project ${this.scene.project.name}`,
|
||||
);
|
||||
this.signals[name] = createSignal(initial);
|
||||
}
|
||||
if (!(name in this.signals)) {
|
||||
this.signals[name] = createSignal(variables[name]);
|
||||
}
|
||||
|
||||
return this.signals[name];
|
||||
}
|
||||
|
||||
/**
|
||||
* Update all signals with new project variable values.
|
||||
*/
|
||||
public updateSignals = () => {
|
||||
const variables = this.scene.project.getVariables();
|
||||
Object.keys(variables).map(variableName => {
|
||||
if (variableName in this.signals) {
|
||||
this.signals[variableName](variables[variableName]);
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
* Reset all stored signals.
|
||||
*/
|
||||
public handleReset = () => {
|
||||
this.signals = {};
|
||||
};
|
||||
}
|
||||
@@ -11,4 +11,5 @@ export * from './Scene';
|
||||
export * from './SceneState';
|
||||
export * from './Threadable';
|
||||
export * from './TimeEvents';
|
||||
export * from './Variables';
|
||||
export * from './LifecycleEvents';
|
||||
|
||||
38
packages/docs/docs/advanced/project-variables.mdx
Normal file
38
packages/docs/docs/advanced/project-variables.mdx
Normal file
@@ -0,0 +1,38 @@
|
||||
---
|
||||
slug: /project-variables
|
||||
---
|
||||
|
||||
# Project variables
|
||||
|
||||
Making animations dynamic can be achieved using project variables. These could
|
||||
be used for light/dark themes, changing Node styling or editing text content.
|
||||
Project variables make use of signals - allowing variables to be updated during
|
||||
animations.
|
||||
|
||||
Adding variables via the player component can be done by passing a stringified
|
||||
json object to the variables attribute:
|
||||
|
||||
```html
|
||||
<motion-canvas-player
|
||||
src="/@id/__x00__virtual:template"
|
||||
style="aspect-ratio: 16 / 9"
|
||||
variables='{"circleFill":"red"}'
|
||||
></motion-canvas-player>
|
||||
```
|
||||
|
||||
They can also be added using makeProject():
|
||||
|
||||
```ts
|
||||
export default makeProject({
|
||||
scenes: [example],
|
||||
background: '#141414',
|
||||
variables: {circleFill: 'red'},
|
||||
});
|
||||
```
|
||||
|
||||
Accessing the variables inside of a scene is through
|
||||
[`useScene()`](/api/core/utils#useScene):
|
||||
|
||||
```ts
|
||||
const circleFill = useScene().variables.get('circleFill');
|
||||
```
|
||||
@@ -15,7 +15,7 @@ enum State {
|
||||
|
||||
class MotionCanvasPlayer extends HTMLElement {
|
||||
public static get observedAttributes() {
|
||||
return ['src', 'quality', 'width', 'height', 'auto'];
|
||||
return ['src', 'quality', 'width', 'height', 'auto', 'variables'];
|
||||
}
|
||||
|
||||
private get auto() {
|
||||
@@ -42,6 +42,16 @@ class MotionCanvasPlayer extends HTMLElement {
|
||||
return attr ? parseFloat(attr) : this.defaultHeight;
|
||||
}
|
||||
|
||||
private get variables() {
|
||||
try {
|
||||
const attr = this.getAttribute('variables');
|
||||
return attr ? JSON.parse(attr) : {};
|
||||
} catch {
|
||||
this.project.logger.warn(`Project variables could not be parsed.`);
|
||||
return {};
|
||||
}
|
||||
}
|
||||
|
||||
private readonly root: ShadowRoot;
|
||||
private readonly canvas: HTMLCanvasElement;
|
||||
private readonly overlay: HTMLCanvasElement;
|
||||
@@ -71,6 +81,7 @@ class MotionCanvasPlayer extends HTMLElement {
|
||||
this.overlay.addEventListener('mousemove', this.handleMouseMove);
|
||||
this.overlay.addEventListener('mouseleave', this.handleMouseLeave);
|
||||
this.button.addEventListener('mousedown', this.handleMouseDown);
|
||||
|
||||
this.setState(State.Initial);
|
||||
}
|
||||
|
||||
@@ -170,6 +181,7 @@ class MotionCanvasPlayer extends HTMLElement {
|
||||
await Promise.any([delay, promise]);
|
||||
this.setState(State.Loading);
|
||||
project = (await promise).default();
|
||||
project.setVariables(this.variables);
|
||||
} catch (e) {
|
||||
this.setState(State.Error);
|
||||
return;
|
||||
@@ -245,6 +257,10 @@ class MotionCanvasPlayer extends HTMLElement {
|
||||
this.project.setSize([this.width, this.height]);
|
||||
}
|
||||
break;
|
||||
case 'variables':
|
||||
if (this.project) {
|
||||
this.project.setVariables(this.variables);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user