UNPKG

@lightningjs/renderer

Version:
221 lines 8.03 kB
/* * If not stated otherwise in this file or this component's LICENSE file the * following copyright and licenses apply: * * Copyright 2023 Comcast Cable Communications Management, LLC. * * Licensed under the Apache License, Version 2.0 (the License); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ import {} from '../CoreNode.js'; import { getTimingFunction } from '../utils.js'; import { mergeColorProgress } from '../../utils.js'; import { EventEmitter } from '../../common/EventEmitter.js'; let animationIdCounter = 0; export class CoreAnimation extends EventEmitter { node; props; id = ++animationIdCounter; settings; progress = 0; delayFor = 0; delay = 0; timingFunction; propValuesMap = { props: null, shaderProps: null }; constructor(node, props, settings) { super(); this.node = node; this.props = props; for (const key in props) { if (key !== 'shaderProps') { if (this.propValuesMap['props'] === null) { this.propValuesMap['props'] = { keys: [], values: [] }; } this.propValuesMap['props']['keys'].push(key); this.propValuesMap['props']['values'].push({ start: node[key] || 0, target: props[key], isColor: key.indexOf('color') !== -1, }); } else if (key === 'shaderProps' && node.shader !== null) { this.propValuesMap['shaderProps'] = { keys: [], values: [] }; for (const key in props.shaderProps) { let start = node.shader.props[key]; if (Array.isArray(start) === true) { start = start[0]; } this.propValuesMap['shaderProps']['keys'].push(key); this.propValuesMap['shaderProps']['values'].push({ start, target: props.shaderProps[key], isColor: key.indexOf('color') !== -1, }); } } } const easing = settings.easing || 'linear'; const delay = settings.delay ?? 0; this.settings = { duration: settings.duration ?? 0, delay, easing, loop: settings.loop ?? false, repeat: settings.repeat ?? 0, stopMethod: settings.stopMethod ?? false, }; this.timingFunction = typeof easing === 'string' ? getTimingFunction(easing) : easing; this.delayFor = delay; this.delay = delay; } reset() { this.progress = 0; this.delayFor = this.settings.delay || 0; this.update(0); } restoreValues(target, group) { const keys = group.keys; const values = group.values; const length = keys.length; for (let i = 0; i < length; i++) { target[keys[i]] = values[i].start; } } restore() { this.reset(); if (this.propValuesMap['props'] !== null) { this.restoreValues(this.node, this.propValuesMap['props']); } if (this.propValuesMap['shaderProps'] !== null) { this.restoreValues(this.node.shader.props, this.propValuesMap['shaderProps']); } } reverseValues(group) { const values = group.values; const length = values.length; for (let i = 0; i < length; i++) { const value = values[i]; const tmp = value.start; value.start = value.target; value.target = tmp; } } reverse() { this.progress = 0; if (this.propValuesMap['props'] !== null) { this.reverseValues(this.propValuesMap['props']); } if (this.propValuesMap['shaderProps'] !== null) { this.reverseValues(this.propValuesMap['shaderProps']); } // restore stop method if we are not looping if (!this.settings.loop) { this.settings.stopMethod = false; } } applyEasing(p, s, e) { return this.timingFunction(p) * (e - s) + s; } updateValue(isColor, propValue, startValue, easing) { if (this.progress === 1) { return propValue; } if (this.progress === 0) { return startValue; } const endValue = propValue; if (isColor === true) { if (startValue === endValue) { return startValue; } if (easing) { const easingProgressValue = this.timingFunction(this.progress) || this.progress; return mergeColorProgress(startValue, endValue, easingProgressValue); } return mergeColorProgress(startValue, endValue, this.progress); } if (easing) { return this.applyEasing(this.progress, startValue, endValue); } return startValue + (endValue - startValue) * this.progress; } updateValues(target, group, easing) { const keys = group.keys; const values = group.values; const length = keys.length; for (let i = 0; i < length; i++) { const value = values[i]; target[keys[i]] = this.updateValue(value.isColor, value.target, value.start, easing); } } update(dt) { const { duration, loop, easing, stopMethod } = this.settings; const { delayFor } = this; if (this.node.destroyed) { // cleanup this.emit('destroyed', {}); return; } if (duration === 0 && delayFor === 0) { this.emit('finished', {}); return; } if (this.delayFor > 0) { this.delayFor -= dt; if (this.delayFor >= 0) { // Either no or more delay left. Exit. return; } else { // We went beyond the delay time, add it back to dt so we can continue // with the animation. dt = -this.delayFor; this.delayFor = 0; } } if (duration === 0) { // No duration, we are done. this.emit('finished', {}); return; } if (this.progress === 0) { // Progress is 0, we are starting the post-delay part of the animation. this.emit('animating', {}); } this.progress += dt / duration; if (this.progress > 1) { this.progress = loop ? 0 : 1; this.delayFor = this.delay; if (stopMethod) { // If there's a stop method emit finished so the stop method can be applied. // TODO: We should probably reevaluate how stopMethod is implemented as currently // stop method 'reset' does not work when looping. this.emit('finished', {}); return; } } if (this.propValuesMap['props'] !== null) { this.updateValues(this.node, this.propValuesMap['props'], easing); } if (this.propValuesMap['shaderProps'] !== null) { this.updateValues(this.node.shader.props, this.propValuesMap['shaderProps'], easing); } if (this.progress < 1) { this.emit('tick'); } if (this.progress === 1) { this.emit('finished', {}); } } } //# sourceMappingURL=CoreAnimation.js.map