/** 
 | 
 * 动画主控制器 
 | 
 * @config target 动画对象,可以是数组,如果是数组的话会批量分发onframe等事件 
 | 
 * @config life(1000) 动画时长 
 | 
 * @config delay(0) 动画延迟时间 
 | 
 * @config loop(true) 
 | 
 * @config onframe 
 | 
 * @config easing(optional) 
 | 
 * @config ondestroy(optional) 
 | 
 * @config onrestart(optional) 
 | 
 * 
 | 
 * TODO pause 
 | 
 */ 
 | 
  
 | 
import easingFuncs, {AnimationEasing} from './easing'; 
 | 
import type Animation from './Animation'; 
 | 
import { isFunction, noop } from '../core/util'; 
 | 
import { createCubicEasingFunc } from './cubicEasing'; 
 | 
  
 | 
type OnframeCallback = (percent: number) => void; 
 | 
type ondestroyCallback = () => void 
 | 
type onrestartCallback = () => void 
 | 
  
 | 
export type DeferredEventTypes = 'destroy' | 'restart' 
 | 
// type DeferredEventKeys = 'ondestroy' | 'onrestart' 
 | 
  
 | 
export interface ClipProps { 
 | 
    life?: number 
 | 
    delay?: number 
 | 
    loop?: boolean 
 | 
    easing?: AnimationEasing 
 | 
  
 | 
    onframe?: OnframeCallback 
 | 
    ondestroy?: ondestroyCallback 
 | 
    onrestart?: onrestartCallback 
 | 
} 
 | 
  
 | 
export default class Clip { 
 | 
  
 | 
    private _life: number 
 | 
    private _delay: number 
 | 
  
 | 
    private _inited: boolean = false 
 | 
    private _startTime = 0 // 开始时间单位毫秒 
 | 
  
 | 
    private _pausedTime = 0 
 | 
    private _paused = false 
 | 
  
 | 
    animation: Animation 
 | 
  
 | 
    loop: boolean 
 | 
  
 | 
    easing: AnimationEasing 
 | 
    easingFunc: (p: number) => number 
 | 
  
 | 
    // For linked list. Readonly 
 | 
    next: Clip 
 | 
    prev: Clip 
 | 
  
 | 
    onframe: OnframeCallback 
 | 
    ondestroy: ondestroyCallback 
 | 
    onrestart: onrestartCallback 
 | 
  
 | 
    constructor(opts: ClipProps) { 
 | 
  
 | 
        this._life = opts.life || 1000; 
 | 
        this._delay = opts.delay || 0; 
 | 
  
 | 
        this.loop = opts.loop || false; 
 | 
  
 | 
        this.onframe = opts.onframe || noop; 
 | 
        this.ondestroy = opts.ondestroy || noop; 
 | 
        this.onrestart = opts.onrestart || noop; 
 | 
  
 | 
        opts.easing && this.setEasing(opts.easing); 
 | 
    } 
 | 
  
 | 
    step(globalTime: number, deltaTime: number): boolean { 
 | 
        // Set startTime on first step, or _startTime may has milleseconds different between clips 
 | 
        // PENDING 
 | 
        if (!this._inited) { 
 | 
            this._startTime = globalTime + this._delay; 
 | 
            this._inited = true; 
 | 
        } 
 | 
  
 | 
        if (this._paused) { 
 | 
            this._pausedTime += deltaTime; 
 | 
            return; 
 | 
        } 
 | 
  
 | 
        const life = this._life; 
 | 
        let elapsedTime = globalTime - this._startTime - this._pausedTime; 
 | 
        let percent = elapsedTime / life; 
 | 
  
 | 
        // PENDING: Not begin yet. Still run the loop. 
 | 
        // In the case callback needs to be invoked. 
 | 
        // Or want to update to the begin state at next frame when `setToFinal` and `delay` are both used. 
 | 
        // To avoid the unexpected blink. 
 | 
        if (percent < 0) { 
 | 
            percent = 0; 
 | 
        } 
 | 
  
 | 
        percent = Math.min(percent, 1); 
 | 
  
 | 
        const easingFunc = this.easingFunc; 
 | 
        const schedule = easingFunc ? easingFunc(percent) : percent; 
 | 
  
 | 
        this.onframe(schedule); 
 | 
  
 | 
        // 结束 
 | 
        if (percent === 1) { 
 | 
            if (this.loop) { 
 | 
                // Restart 
 | 
                const remainder = elapsedTime % life; 
 | 
                this._startTime = globalTime - remainder; 
 | 
                this._pausedTime = 0; 
 | 
  
 | 
                this.onrestart(); 
 | 
            } 
 | 
            else { 
 | 
                return true; 
 | 
            } 
 | 
        } 
 | 
  
 | 
        return false; 
 | 
    } 
 | 
  
 | 
    pause() { 
 | 
        this._paused = true; 
 | 
    } 
 | 
  
 | 
    resume() { 
 | 
        this._paused = false; 
 | 
    } 
 | 
  
 | 
    setEasing(easing: AnimationEasing) { 
 | 
        this.easing = easing; 
 | 
        this.easingFunc = isFunction(easing) 
 | 
            ? easing 
 | 
            : easingFuncs[easing] || createCubicEasingFunc(easing); 
 | 
    } 
 | 
} 
 |