Comparing version 0.7.8-beta.6 to 0.7.8-beta.7
import * as rc from 'rive-canvas'; | ||
/** | ||
* Generic type for a parameterless void callback | ||
*/ | ||
export declare type VoidCallback = () => void; | ||
export declare enum Fit { | ||
@@ -154,6 +158,5 @@ Cover = "cover", | ||
export interface Task { | ||
action: ActionCallback; | ||
action: VoidCallback; | ||
event?: Event; | ||
} | ||
export declare type ActionCallback = () => void; | ||
declare class TaskQueueManager { | ||
@@ -192,3 +195,2 @@ private eventManager; | ||
private readonly canvas; | ||
private autoplay; | ||
private src; | ||
@@ -200,4 +202,15 @@ private buffer; | ||
private renderer; | ||
private playState; | ||
/** | ||
* Flag to active/deactivate renderer | ||
*/ | ||
private isRendererActive; | ||
private loaded; | ||
/** | ||
* Tracks if a Rive file is loaded; we need this in addition to loaded as some | ||
* commands (e.g. contents) can be called as soon as the file is loaded. | ||
* However, playback commands need to be queued and run in order once initial | ||
* animations and autoplay has been sorted out. This applies to play, pause, | ||
* and start. | ||
*/ | ||
private readyForPlaying; | ||
private runtime; | ||
@@ -218,2 +231,10 @@ private artboard; | ||
private frameRequestId; | ||
/** | ||
* Used be draw to track when a second of active rendering time has passed. Used for debugging purposes | ||
*/ | ||
private renderSecondTimer; | ||
/** | ||
* Draw rendering loop; renders animation frames at the correct time interval. | ||
* @param time the time at which to render a frame | ||
*/ | ||
private draw; | ||
@@ -224,3 +245,3 @@ /** | ||
private alignRenderer; | ||
play(animationNames?: string | string[]): void; | ||
play(animationNames?: string | string[], autoplay?: true): void; | ||
pause(animationNames?: string | string[]): void; | ||
@@ -248,8 +269,2 @@ stop(animationNames?: string | string[] | undefined): void; | ||
/** | ||
* Gets a runtime state machine object by name | ||
* @param name the name of the state machine | ||
* @returns a runtime state machine | ||
*/ | ||
private getRuntimeStateMachine; | ||
/** | ||
* Returns the inputs for the specified instanced state machine, or an empty | ||
@@ -264,2 +279,7 @@ * list if the name is invalid or the state machine is not instanced | ||
get pausedAnimationNames(): string[]; | ||
/** | ||
* Returns a list of paused machine names | ||
* @returns a list of state machine names that are paused | ||
*/ | ||
get pausedStateMachineNames(): string[]; | ||
get isPlaying(): boolean; | ||
@@ -287,2 +307,15 @@ get isPaused(): boolean; | ||
/** | ||
* Stops the rendering loop; this is different from pausing in that it doesn't | ||
* change the state of any animation. It stops rendering from occurring. This | ||
* is designed for situations such as when Rive isn't visible. | ||
* | ||
* The only way to start rendering again is to call `startRendering`/ | ||
*/ | ||
stopRendering(): void; | ||
/** | ||
* Starts the rendering loop if it has been previously stopped. If the | ||
* renderer is already active, then this will have zero effect. | ||
*/ | ||
startRendering(): void; | ||
/** | ||
* Returns the contents of a Rive file: the artboards, animations, and state machines | ||
@@ -289,0 +322,0 @@ */ |
{ | ||
"name": "rive-js", | ||
"version": "0.7.8-beta.6", | ||
"version": "0.7.8-beta.7", | ||
"description": "Rive's web api.", | ||
@@ -5,0 +5,0 @@ "main": "dist/rive.dev.js", |
@@ -207,3 +207,3 @@ # Rive.js -- Rive's JS runtime | ||
r.on('load', () => { | ||
console.log('Animations ' + rive.animationNames()); | ||
console.log('Animations ' + r.animationNames()); | ||
}); | ||
@@ -210,0 +210,0 @@ |
541
src/rive.ts
import * as rc from 'rive-canvas'; | ||
/** | ||
* Generic type for a parameterless void callback | ||
*/ | ||
export type VoidCallback = () => void; | ||
// Tracks playback states; numbers map to the runtime's numerical values | ||
@@ -210,3 +215,2 @@ // i.e. play: 0, pause: 1, stop: 2 | ||
public loopCount: number = 0; | ||
public paused: boolean = false; | ||
public readonly instance: rc.LinearAnimationInstance; | ||
@@ -219,3 +223,3 @@ /** | ||
*/ | ||
constructor(private animation: rc.LinearAnimation, runtime: rc.RiveCanvas) { | ||
constructor(private animation: rc.LinearAnimation, runtime: rc.RiveCanvas, public playing: boolean) { | ||
this.instance = new runtime.LinearAnimationInstance(animation); | ||
@@ -291,7 +295,2 @@ } | ||
/** | ||
* Is the state machine paused | ||
*/ | ||
public paused: boolean = false; | ||
/** | ||
* Runtime state machine instance | ||
@@ -306,3 +305,3 @@ */ | ||
*/ | ||
constructor(private stateMachine: rc.StateMachine, runtime: rc.RiveCanvas) { | ||
constructor(private stateMachine: rc.StateMachine, runtime: rc.RiveCanvas, public playing: boolean) { | ||
this.instance = new runtime.StateMachineInstance(stateMachine); | ||
@@ -365,7 +364,6 @@ this.initInputs(runtime); | ||
private artboard: rc.Artboard, | ||
private eventManager: EventManager, | ||
public readonly animations: Animation[] = [], | ||
public readonly stateMachines: StateMachine[] = []) {} | ||
/** | ||
@@ -378,32 +376,76 @@ * Adds animations and state machines by their names. If names are shared | ||
*/ | ||
public add(animatables: string | string[]): string[] { | ||
public add(animatables: string | string[], playing: boolean, fireEvent = true): string[] { | ||
animatables = mapToStringArray(animatables); | ||
const instancedAnimationNames = this.animations.map(a => a.name); | ||
const instancedMachineNames = this.stateMachines.map(m => m.name); | ||
for (const i in animatables) { | ||
const aIndex = instancedAnimationNames.indexOf(animatables[i]); | ||
const mIndex = instancedMachineNames.indexOf(animatables[i]); | ||
if (aIndex >= 0) { | ||
// Animation is instanced, unpause it | ||
this.animations[aIndex].paused = false; | ||
} if (mIndex >= 0) { | ||
// State machine is instanced, unpause it | ||
this.stateMachines[mIndex].paused = false; | ||
} else { | ||
// Try to create a new animation instance | ||
const anim = this.artboard.animationByName(animatables[i]); | ||
if(anim) { | ||
this.animations.push(new Animation(anim, this.runtime)); | ||
} else { | ||
const sm = this.artboard.stateMachineByName(animatables[i]); | ||
if (sm) { | ||
this.stateMachines.push(new StateMachine(sm, this.runtime)); | ||
// If animatables is empty, play or pause everything | ||
if (animatables.length === 0) { | ||
this.animations.forEach(a => a.playing = playing); | ||
this.stateMachines.forEach(m => m.playing = playing); | ||
} else { | ||
// Play/pause already instanced items, or create new instances | ||
const instancedAnimationNames = this.animations.map(a => a.name); | ||
const instancedMachineNames = this.stateMachines.map(m => m.name); | ||
for (const i in animatables) { | ||
const aIndex = instancedAnimationNames.indexOf(animatables[i]); | ||
const mIndex = instancedMachineNames.indexOf(animatables[i]); | ||
if (aIndex >= 0 || mIndex >= 0) { | ||
if (aIndex >= 0) { | ||
// Animation is instanced, play/pause it | ||
this.animations[aIndex].playing = playing; | ||
} else { | ||
// State machine is instanced, play/pause it | ||
this.stateMachines[mIndex].playing = playing; | ||
} | ||
} else { | ||
// Try to create a new animation instance | ||
const anim = this.artboard.animationByName(animatables[i]); | ||
if(anim) { | ||
this.animations.push(new Animation(anim, this.runtime, playing)); | ||
} else { | ||
// Try to create a new state machine instance | ||
const sm = this.artboard.stateMachineByName(animatables[i]); | ||
if (sm) { | ||
this.stateMachines.push(new StateMachine(sm, this.runtime, playing)); | ||
} | ||
} | ||
} | ||
} | ||
} | ||
return this.playing; | ||
// Fire play/paused events for animations | ||
if (fireEvent) { | ||
if (playing) { | ||
this.eventManager.fire({ | ||
type: EventType.Play, | ||
data: this.playing, | ||
}); | ||
} else { | ||
this.eventManager.fire({ | ||
type: EventType.Pause, | ||
data: this.paused, | ||
}); | ||
} | ||
} | ||
return playing ? this.playing : this.paused; | ||
} | ||
/** | ||
* Play the named animations/state machines | ||
* @param animatables the names of the animations/machines to play; plays all if empty | ||
* @returns a list of the playing items | ||
*/ | ||
public play(animatables: string | string[]): string[] { | ||
return this.add(animatables, true); | ||
} | ||
/** | ||
* Pauses named aninimations and state machines, or everything if nothing is | ||
* specified | ||
* @param animatables names of the animations and state machines to pause | ||
* @returns a list of names of the animations and state machines paused | ||
*/ | ||
public pause(animatables: string[]): string[] { | ||
return this.add(animatables, false); | ||
} | ||
/** | ||
* Returns a list of names of all animations and state machines currently | ||
@@ -413,4 +455,4 @@ * playing | ||
public get playing(): string[] { | ||
return this.animations.filter(a => !a.paused).map(a => a.name).concat( | ||
this.stateMachines.filter(m => !m.paused).map(m => m.name) | ||
return this.animations.filter(a => a.playing).map(a => a.name).concat( | ||
this.stateMachines.filter(m => m.playing).map(m => m.name) | ||
); | ||
@@ -420,10 +462,23 @@ } | ||
/** | ||
* Removes all named animations and state machines | ||
* Returns a list of names of all animations and state machines currently | ||
* paused | ||
*/ | ||
public get paused(): string[] { | ||
return this.animations.filter(a => !a.playing).map(a => a.name).concat( | ||
this.stateMachines.filter(m => !m.playing).map(m => m.name) | ||
); | ||
} | ||
/** | ||
* Stops and removes all named animations and state machines | ||
* @param animatables animations and state machines to remove | ||
* @returns a list of names of removed items | ||
*/ | ||
public remove(animatables?: string[]): string[] { | ||
public stop(animatables?: string[] | string): string[] { | ||
animatables = mapToStringArray(animatables); | ||
// If nothing's specified, wipe them out, all of them | ||
if (!animatables) { | ||
const names = this.animations.map(a => a.name).concat( | ||
let removedNames: string[] = []; | ||
if (animatables.length === 0) { | ||
removedNames = this.animations.map(a => a.name).concat( | ||
this.stateMachines.map(m => m.name) | ||
@@ -433,53 +488,50 @@ ); | ||
this.stateMachines.splice(0, this.stateMachines.length); | ||
return names; | ||
} else { | ||
// Remove only the named animations/state machines | ||
const animationsToRemove = this.animations.filter( | ||
a => animatables.includes(a.name) | ||
); | ||
animationsToRemove.forEach(a => | ||
this.animations.splice(this.animations.indexOf(a), 1) | ||
); | ||
const machinesToRemove = this.stateMachines.filter( | ||
m => animatables.includes(m.name) | ||
); | ||
machinesToRemove.forEach(m => | ||
this.stateMachines.splice(this.stateMachines.indexOf(m), 1) | ||
); | ||
removedNames = animationsToRemove.map(a => a.name).concat( | ||
machinesToRemove.map(m => m.name)); | ||
} | ||
// Remove only the named animations/state machines | ||
const animationsToRemove = this.animations.filter( | ||
a => animatables.includes(a.name) | ||
); | ||
animationsToRemove.forEach(a => | ||
this.animations.splice(this.animations.indexOf(a), 1) | ||
); | ||
const machinesToRemove = this.stateMachines.filter( | ||
m => animatables.includes(m.name) | ||
); | ||
machinesToRemove.forEach(m => | ||
this.stateMachines.splice(this.stateMachines.indexOf(m), 1) | ||
); | ||
this.eventManager.fire({ | ||
type: EventType.Stop, | ||
data: removedNames, | ||
}); | ||
// Return the list of animations removed | ||
return animationsToRemove.map(a => a.name).concat( | ||
machinesToRemove.map(m => m.name) | ||
); | ||
return removedNames; | ||
} | ||
/** | ||
* Pauses named aninimations and state machines, or everything if nothing is | ||
* specified | ||
* @param animatables names of the animations and state machines to pause | ||
* @returns a list of names of the animations and state machines paused | ||
* Returns true if at least one animation is active | ||
*/ | ||
public pause(animatables: string[]): string[] { | ||
const pausedNames: string[] = []; | ||
public get isPlaying(): boolean { | ||
return this.animations.reduce((acc, curr) => acc || curr.playing, false) | ||
|| this.stateMachines.reduce((acc, curr) => acc || curr.playing, false); | ||
} | ||
this.animations.forEach((a) => { | ||
if (animatables.includes(a.name)) { | ||
a.paused = true; | ||
pausedNames.push(a.name); | ||
} | ||
}); | ||
this.stateMachines.forEach((m) => { | ||
if (animatables.includes(m.name)) { | ||
m.paused = true; | ||
pausedNames.push(m.name); | ||
} | ||
}); | ||
return pausedNames; | ||
/** | ||
* Returns true if all animations are paused and there's at least one animation | ||
*/ | ||
public get isPaused(): boolean { | ||
return !this.isPlaying && | ||
(this.animations.length > 0 || this.stateMachines.length > 0); | ||
} | ||
/** | ||
* Returns true if at least one animation is active | ||
* Returns true if there are no playing or paused animations/state machines | ||
*/ | ||
public get isPlaying(): boolean { | ||
return this.animations.reduce((acc, curr) => acc || !curr.paused, false) | ||
|| this.stateMachines.reduce((acc, curr) => acc || !curr.paused, false); | ||
public get isStopped(): boolean { | ||
return this.animations.length === 0 && this.stateMachines.length === 0; | ||
} | ||
@@ -489,17 +541,48 @@ | ||
* If there are no animations or state machines, add the first one found | ||
* @returns the name of the animation or state machine instanced | ||
*/ | ||
public atLeastOne(): void { | ||
public atLeastOne(playing: boolean, fireEvent = true): string { | ||
let instancedName: string; | ||
if (this.animations.length === 0 && this.stateMachines.length === 0) { | ||
if(this.artboard.animationCount() > 0) { | ||
// Add the first animation | ||
this.animations.push(new Animation( | ||
this.artboard.animationByIndex(0), this.runtime)); | ||
this.add([instancedName = this.artboard.animationByIndex(0).name], playing, fireEvent); | ||
} else if(this.artboard.stateMachineCount() > 0) { | ||
// Add the first state machine | ||
this.stateMachines.push(new StateMachine( | ||
this.artboard.stateMachineByIndex(0), this.runtime)); | ||
this.add([instancedName = this.artboard.stateMachineByIndex(0).name], playing, fireEvent); | ||
} | ||
} | ||
return instancedName; | ||
} | ||
/** | ||
* Checks if any animations have looped and if so, fire the appropriate event | ||
*/ | ||
public handleLooping() { | ||
for (const animation of this.animations) { | ||
// Emit if the animation looped | ||
if (animation.loopValue === 0 && animation.loopCount) { | ||
animation.loopCount = 0; | ||
// This is a one-shot; if it has ended, delete the instance | ||
this.stop(animation.name); | ||
} | ||
else if (animation.loopValue === 1 && animation.loopCount) { | ||
this.eventManager.fire({ | ||
type: EventType.Loop, | ||
data: { animation: animation.name, type: LoopType.Loop } | ||
}); | ||
animation.loopCount = 0; | ||
} | ||
// Wasm indicates a loop at each time the animation | ||
// changes direction, so a full loop/lap occurs every | ||
// two loop counts | ||
else if (animation.loopValue === 2 && animation.loopCount > 1) { | ||
this.eventManager.fire({ | ||
type: EventType.Loop, | ||
data: { animation: animation.name, type: LoopType.PingPong } | ||
}); | ||
animation.loopCount = 0; | ||
} | ||
} | ||
} | ||
} | ||
@@ -628,9 +711,6 @@ | ||
export interface Task { | ||
action: ActionCallback, | ||
action: VoidCallback, | ||
event?: Event, | ||
} | ||
// Callback type for task actions | ||
export type ActionCallback = () => void; | ||
// Manages a queue of tasks | ||
@@ -696,5 +776,2 @@ class TaskQueueManager { | ||
// Should the animations autoplay? | ||
private autoplay: boolean; | ||
// A url to a Rive file; may be undefined if a buffer is specified | ||
@@ -719,4 +796,6 @@ private src: string; | ||
// Tracks the playback state | ||
private playState: PlaybackState = PlaybackState.Stop; | ||
/** | ||
* Flag to active/deactivate renderer | ||
*/ | ||
private isRendererActive = true; | ||
@@ -726,2 +805,11 @@ // Tracks if a Rive file is loaded | ||
/** | ||
* Tracks if a Rive file is loaded; we need this in addition to loaded as some | ||
* commands (e.g. contents) can be called as soon as the file is loaded. | ||
* However, playback commands need to be queued and run in order once initial | ||
* animations and autoplay has been sorted out. This applies to play, pause, | ||
* and start. | ||
*/ | ||
private readyForPlaying: boolean = false; | ||
// Wasm runtime | ||
@@ -751,3 +839,2 @@ private runtime: rc.RiveCanvas; | ||
this.canvas = params.canvas; | ||
this.autoplay = params.autoplay ?? false; | ||
this.src = params.src; | ||
@@ -776,3 +863,3 @@ this.buffer = params.buffer; | ||
buffer: this.buffer, | ||
autoplay: this.autoplay, | ||
autoplay: params.autoplay, | ||
animations: params.animations, | ||
@@ -794,3 +881,2 @@ stateMachines: params.stateMachines, | ||
this.buffer = buffer; | ||
this.autoplay = autoplay; | ||
@@ -808,15 +894,6 @@ // If no source file url specified, it's a bust | ||
// Queue up play action and event if necessary | ||
if (this.autoplay) { | ||
this.taskQueue.add({ action: () => this.play() }); | ||
} | ||
// Ensure loaded is marked as false if loading new file | ||
this.loaded = false; | ||
this.readyForPlaying = false; | ||
// Queue up play action and event if necessary | ||
if (this.autoplay) { | ||
this.taskQueue.add({ action: () => this.play() }); | ||
} | ||
// Ensure the runtime is loaded | ||
@@ -826,3 +903,3 @@ RuntimeLoader.awaitInstance().then((runtime) => { | ||
// Load Rive data from a source uri or a data buffer | ||
this.initData(artboard, startingAnimationNames, startingStateMachineNames).catch(e => { | ||
this.initData(artboard, startingAnimationNames, startingStateMachineNames, autoplay).catch(e => { | ||
console.error(e); | ||
@@ -836,3 +913,8 @@ }); | ||
// Initializes runtime with Rive data and preps for playing | ||
private async initData(artboardName: string, animationNames: string[], stateMachineNames: string[]): Promise<void> { | ||
private async initData( | ||
artboardName: string, | ||
animationNames: string[], | ||
stateMachineNames: string[], | ||
autoplay: boolean | ||
): Promise<void> { | ||
// Load the buffer from the src if provided | ||
@@ -845,7 +927,7 @@ if (this.src) { | ||
if (this.file) { | ||
this.loaded = true; | ||
// Initialize and draw frame | ||
this.initArtboard(artboardName, animationNames, stateMachineNames); | ||
this.drawFrame(); | ||
this.initArtboard(artboardName, animationNames, stateMachineNames, autoplay); | ||
// Everything's set up, emit a load event | ||
this.loaded = true; | ||
this.eventManager.fire({ | ||
@@ -855,4 +937,10 @@ type: EventType.Load, | ||
}); | ||
// Clear the task queue | ||
// Flag ready for playback commands and clear the task queue; this order | ||
// is important or it may infinitely recurse | ||
this.readyForPlaying = true; | ||
this.taskQueue.process(); | ||
this.drawFrame(); | ||
return Promise.resolve(); | ||
@@ -868,3 +956,8 @@ } else { | ||
// Initialize for playback | ||
private initArtboard(artboardName: string, animationNames: string[], stateMachineNames: string[]): void { | ||
private initArtboard( | ||
artboardName: string, | ||
animationNames: string[], | ||
stateMachineNames: string[], | ||
autoplay: boolean | ||
): void { | ||
this.artboard = artboardName ? | ||
@@ -889,3 +982,3 @@ this.file.artboardByName(artboardName) : | ||
// Initialize the animator | ||
this.animator = new Animator(this.runtime, this.artboard); | ||
this.animator = new Animator(this.runtime, this.artboard, this.eventManager); | ||
@@ -895,11 +988,22 @@ // Get the canvas where you want to render the animation and create a renderer | ||
// Initialize the animations | ||
if (animationNames.length > 0) { | ||
this.animator.add(animationNames); | ||
} | ||
// Initialize the state machines | ||
if (stateMachineNames.length > 0) { | ||
this.animator.add(stateMachineNames); | ||
// Initialize the animations; as loaded hasn't happened yet, we need to | ||
// suppress firing the play/pause events until the load event has fired. To | ||
// do this we tell the animator to suppress firing events, and add event | ||
// firing to the task queue. | ||
let instanceNames: string[]; | ||
if (animationNames.length > 0 || stateMachineNames.length > 0) { | ||
instanceNames = animationNames.concat(stateMachineNames); | ||
this.animator.add(instanceNames, autoplay, false); | ||
} else { | ||
instanceNames = [this.animator.atLeastOne(autoplay, false)]; | ||
} | ||
// Queue up firing the playback events | ||
this.taskQueue.add({ | ||
action: () => {}, | ||
event: { | ||
type: autoplay ? EventType.Play : EventType.Pause, | ||
data: instanceNames, | ||
} | ||
}); | ||
} | ||
@@ -909,11 +1013,3 @@ | ||
public drawFrame() { | ||
// Advance to the first frame and draw the artboard | ||
this.artboard.advance(0); | ||
// Update the renderer's alignment if necessary | ||
this.alignRenderer(); | ||
const bounds = this.artboard.bounds; | ||
this.ctx.clearRect(bounds.minX, bounds.minY, bounds.maxX, bounds.maxY); | ||
this.artboard.draw(this.renderer); | ||
this.startRendering(); | ||
} | ||
@@ -926,6 +1022,17 @@ | ||
// Tracks the current animation frame request | ||
private frameRequestId: number; | ||
private frameRequestId: number | null; | ||
// Draw rendering loop; renders animation frames at the correct time interval. | ||
private draw(time: number): void { | ||
/** | ||
* Used be draw to track when a second of active rendering time has passed. Used for debugging purposes | ||
*/ | ||
private renderSecondTimer: number = 0; | ||
/** | ||
* Draw rendering loop; renders animation frames at the correct time interval. | ||
* @param time the time at which to render a frame | ||
*/ | ||
private draw(time: number, onSecond?: VoidCallback): void { | ||
// Clear the frameRequestId, as we're now rendering a fresh frame | ||
this.frameRequestId = null; | ||
// On the first pass, make sure lastTime has a valid value | ||
@@ -935,2 +1042,10 @@ if (!this.lastRenderTime) { | ||
} | ||
// Handle the onSecond callback | ||
this.renderSecondTimer += (time - this.lastRenderTime); | ||
if (this.renderSecondTimer > 5000) { | ||
this.renderSecondTimer = 0; | ||
onSecond?.(); | ||
} | ||
// Calculate the elapsed time between frames in seconds | ||
@@ -941,3 +1056,3 @@ const elapsedTime = (time - this.lastRenderTime) / 1000; | ||
// Advance non-paused animations by the elapsed number of seconds | ||
const activeAnimations = this.animator.animations.filter(a => !a.paused); | ||
const activeAnimations = this.animator.animations.filter(a => a.playing); | ||
for (const animation of activeAnimations) { | ||
@@ -952,3 +1067,3 @@ animation.instance.advance(elapsedTime); | ||
// Advance non-paused state machines by the elapsed number of seconds | ||
const activeStateMachines = this.animator.stateMachines.filter(a => !a.paused); | ||
const activeStateMachines = this.animator.stateMachines.filter(a => a.playing); | ||
for (const stateMachine of activeStateMachines) { | ||
@@ -970,36 +1085,13 @@ stateMachine.instance.advance(elapsedTime); | ||
for (const animation of this.animator.animations) { | ||
// Emit if the animation looped | ||
if (animation.loopValue === 0 && animation.loopCount) { | ||
animation.loopCount = 0; | ||
// This is a one-shot; if it has ended, delete the instance | ||
this.stop(animation.name); | ||
} | ||
else if (animation.loopValue === 1 && animation.loopCount) { | ||
this.eventManager.fire({ | ||
type: EventType.Loop, | ||
data: { animation: animation.name, type: LoopType.Loop } | ||
}); | ||
animation.loopCount = 0; | ||
} | ||
// Wasm indicates a loop at each time the animation | ||
// changes direction, so a full loop/lap occurs every | ||
// two loop counts | ||
else if (animation.loopValue === 2 && animation.loopCount > 1) { | ||
this.eventManager.fire({ | ||
type: EventType.Loop, | ||
data: { animation: animation.name, type: LoopType.PingPong } | ||
}); | ||
animation.loopCount = 0; | ||
} | ||
} | ||
this.animator.handleLooping(); | ||
// Calling requestAnimationFrame will rerun draw() at the correct rate: | ||
// https://developer.mozilla.org/en-US/docs/Web/API/Canvas_API/Tutorial/Basic_animations | ||
if (this.playState === PlaybackState.Play) { | ||
this.frameRequestId = requestAnimationFrame(this.draw.bind(this)); | ||
} else if (this.playState === PlaybackState.Pause) { | ||
if (this.animator.isPlaying) { | ||
// Request a new rendering frame | ||
this.startRendering(); | ||
} else if (this.animator.isPaused) { | ||
// Reset the end time so on playback it starts at the correct frame | ||
this.lastRenderTime = 0; | ||
} else if (this.playState === PlaybackState.Stop) { | ||
} else if (this.animator.isStopped) { | ||
// Reset animation instances, artboard and time | ||
@@ -1041,22 +1133,15 @@ // TODO: implement this properly when we have instancing | ||
// Plays specified animations; if none specified, it plays paused ones. | ||
public play(animationNames?: string | string[]): void { | ||
// Plays specified animations; if none specified, it unpauses everything. | ||
public play(animationNames?: string | string[], autoplay?: true): void { | ||
animationNames = mapToStringArray(animationNames); | ||
// If the file's not loaded, queue up the play | ||
if (!this.loaded) { | ||
if (!this.readyForPlaying) { | ||
this.taskQueue.add({ | ||
action: () => this.play(animationNames), | ||
action: () => this.play(animationNames, autoplay), | ||
}); | ||
return; | ||
} | ||
const playingAnimations = this.animator.add(animationNames); | ||
this.animator.atLeastOne(); | ||
this.playState = PlaybackState.Play; | ||
this.frameRequestId = requestAnimationFrame(this.draw.bind(this)); | ||
this.eventManager.fire({ | ||
type: EventType.Play, | ||
data: this.playingAnimationNames | ||
}); | ||
this.animator.play(animationNames); | ||
this.startRendering(); | ||
} | ||
@@ -1069,14 +1154,9 @@ | ||
// If the file's not loaded, early out, nothing to pause | ||
if (!this.loaded) { | ||
if (!this.readyForPlaying) { | ||
this.taskQueue.add({ | ||
action: () => this.pause(animationNames), | ||
}); | ||
return; | ||
} | ||
this.animator.pause(animationNames); | ||
if (!this.animator.isPlaying || animationNames.length === 0) { | ||
this.playState = PlaybackState.Pause; | ||
} | ||
this.eventManager.fire({ | ||
type: EventType.Pause, | ||
data: this.pausedAnimationNames, | ||
}); | ||
} | ||
@@ -1087,23 +1167,10 @@ | ||
animationNames = mapToStringArray(animationNames); | ||
// If the file's not loaded, early out, nothing to pause | ||
if (!this.loaded) { | ||
if (!this.readyForPlaying) { | ||
this.taskQueue.add({ | ||
action: () => this.stop(animationNames), | ||
}); | ||
return; | ||
} | ||
const stoppedAnimationNames: string[] = animationNames.length === 0 ? | ||
this.animator.remove() : | ||
this.animator.remove(animationNames); | ||
if (!this.animator.isPlaying || animationNames.length === 0) { | ||
// Immediately cancel the next frame draw; if we don't do this, | ||
// strange things will happen if the Rive file/buffer is | ||
// reloaded. | ||
cancelAnimationFrame(this.frameRequestId); | ||
this.playState = PlaybackState.Stop; | ||
} | ||
this.eventManager.fire({ | ||
type: EventType.Stop, | ||
data: stoppedAnimationNames, | ||
}); | ||
this.animator.stop(animationNames); | ||
} | ||
@@ -1191,16 +1258,2 @@ | ||
/** | ||
* Gets a runtime state machine object by name | ||
* @param name the name of the state machine | ||
* @returns a runtime state machine | ||
*/ | ||
private getRuntimeStateMachine(name: string): rc.StateMachine { | ||
for (let i = 0; i < this.artboard.stateMachineCount(); i++) { | ||
const stateMachine = this.artboard.stateMachineByIndex(i); | ||
if (stateMachine.name === name) { | ||
return stateMachine; | ||
} | ||
} | ||
} | ||
/** | ||
* Returns the inputs for the specified instanced state machine, or an empty | ||
@@ -1220,3 +1273,3 @@ * list if the name is invalid or the state machine is not instanced | ||
// Returns a list of playing animation names | ||
// Returns a list of playing machine names | ||
public get playingStateMachineNames(): string[] { | ||
@@ -1228,8 +1281,6 @@ // If the file's not loaded, we got nothing to return | ||
return this.animator.stateMachines | ||
.filter(m => !m.paused) | ||
.filter(m => m.playing) | ||
.map(m => m.name); | ||
} | ||
// Returns a list of playing animation names | ||
@@ -1242,3 +1293,3 @@ public get playingAnimationNames(): string[] { | ||
return this.animator.animations | ||
.filter(a => !a.paused) | ||
.filter(a => a.playing) | ||
.map(a => a.name); | ||
@@ -1255,9 +1306,23 @@ } | ||
return this.animator.animations | ||
.filter(a => a.paused) | ||
.filter(a => !a.playing) | ||
.map(a => a.name); | ||
} | ||
/** | ||
* Returns a list of paused machine names | ||
* @returns a list of state machine names that are paused | ||
*/ | ||
public get pausedStateMachineNames(): string[] { | ||
// If the file's not loaded, we got nothing to return | ||
if (!this.loaded) { | ||
return []; | ||
} | ||
return this.animator.stateMachines | ||
.filter(m => !m.playing) | ||
.map(m => m.name); | ||
} | ||
// Returns true if playing | ||
public get isPlaying(): boolean { | ||
return this.playState === PlaybackState.Play; | ||
return this.animator.isPlaying; | ||
} | ||
@@ -1267,3 +1332,3 @@ | ||
public get isPaused(): boolean { | ||
return this.playState === PlaybackState.Pause; | ||
return this.animator.isPaused; | ||
} | ||
@@ -1273,3 +1338,3 @@ | ||
public get isStopped(): boolean { | ||
return this.playState === PlaybackState.Stop; | ||
return this.animator.isStopped; | ||
} | ||
@@ -1311,2 +1376,24 @@ | ||
/** | ||
* Stops the rendering loop; this is different from pausing in that it doesn't | ||
* change the state of any animation. It stops rendering from occurring. This | ||
* is designed for situations such as when Rive isn't visible. | ||
* | ||
* The only way to start rendering again is to call `startRendering`/ | ||
*/ | ||
public stopRendering() { | ||
cancelAnimationFrame(this.frameRequestId); | ||
this.frameRequestId = null; | ||
} | ||
/** | ||
* Starts the rendering loop if it has been previously stopped. If the | ||
* renderer is already active, then this will have zero effect. | ||
*/ | ||
public startRendering() { | ||
if (!this.frameRequestId) { | ||
this.frameRequestId = requestAnimationFrame(this.draw.bind(this)); | ||
} | ||
} | ||
/** | ||
* Returns the contents of a Rive file: the artboards, animations, and state machines | ||
@@ -1410,3 +1497,3 @@ */ | ||
// #region exports for testing | ||
// #region testing utilities | ||
@@ -1413,0 +1500,0 @@ // Exports to only be used for tests |
Sorry, the diff of this file is too big to display
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is too big to display
Sorry, the diff of this file is not supported yet
1567562
7758