let nextId = 1 import {raf} from 'rafz' import {MixingValue} from './MixingValue' import {MixingRef} from './MixingRef' import {is, each, eachProp, flush} from './utils' import { Lookup, InferState, AsyncResult, UnknownProps, MixingValues, ControllerUpdate, } from './types' const concat = Array.prototype.concat export type ControllerFlushFn = Controller> = ( ctrl: T, queue: ControllerQueue> ) => AsyncResult export interface ControllerQueue extends Array< ControllerUpdate & { keys: string[] | null } > {} export class Controller { readonly id = nextId++ mixings: MixingValues = {} ref?: MixingRef queue: any = [] private _flush?: ControllerFlushFn private _lastAsyncId = 0 private _active = new Set() private _changed = new Set() private _started = false private _item?: any private _state = { paused: false, pauseQueue: new Set(), resumeQueue: new Set(), timeouts: new Set(), } private _events = { onStart: new Map(), onChange: new Map(), onRest: new Map() } constructor(props: any, flush?: any) { this._onFrame = this._onFrame.bind(this) if (flush) this._flush = flush if (props) this.start({default: true, ...props}) } get item () { return this._item } get (): T & UnknownProps { const values: any = {} eachProp(this.mixings, (mixing, key) => (values[key] = mixing?.get())) return values } set (values: Partial) { eachProp(values, (value, key) => { if (!is.und(value)) this.mixings[key]?.set() // (value) }) } // each (eachFn) { // each(this.queue, eachFn) // return this // } resume (keys?: string | string[]) { if (is.und(keys)) this.start({pause: false}) } pause (...args: any) {} start (props?: ControllerUpdate | null) { let {queue} = this as any if (props) queue = concat(props)//.map(createUpdate) else this.queue = [] if (this._flush) return this._flush(this, queue) prepareKeys(this, queue) return flushUpdateQueue(this, queue) } stop (arg?: boolean, keys?: string | string[]) { if (arg !== !!arg) keys = arg as stirng | string[] if (keys) each(concat(keys), key => this.mixings[key].pause()) else stopAsync(this._state, this._lastAsyncId) each(this.mixings, mixing => mixing.stop(!!arg)) } update (props?: ControllerUpdate | null) { if (props) this.queue.push(createUpdate(props)) } _onFrame () { const {onStart, onChange, onRest} = this._events const active = this._active.size > 0 const changed = this._changed.size > 0 const idle = !active && this._started const values = changed || (idle && onRest.size)? this.get(): null if ((active && !this._started) || (changed && !this._started)) { this._started = true flush (onStart, ([onStart, result]) => { result.value = this.get() onStart(result, this, this._item) }) } if (changed && onChange.size) { flush(onChange, ([onChange, result]) => { result.value = this.get() onChange(result, this, this._item) }) } if (idle) { this._started = false flush(onRest, ([onRest, result]) => { result.value = values onRest(result,this, this._item) }) } } eventObserved (event: any) {// MixingValue.Event) { if (event.type == 'change') { this._changed.add(event.parent) if (!event.idle) this._active.add(event.parent) } else if (event.type == 'idle') { this._active.delete(event.parent) } else return raf.onFrame(this._onFrame) } } export function getMixings( ctrl: Controller>, props?: ControllerUpdate | ControllerUpdate[] ) { const mixings = { ...ctrl.mixings } if (props) { each(concat(props), (props: any) => { if (is.und(props.keys)) props = createUpdate(props) if (!is.obj(props.to)) props = { ...props, to: undefined } prepareMixings(mixings as any, props, key => { return createMixing(key) }) }) } setMixings(ctrl, mixings) return mixings } export function setMixings( ctrl: Controller>, mixings: MixingValue ) { for (const key in mixings) { if (!(ctrl.mixings as any)[key]) { (ctrl.mixings as any)[key] = mixings[key] addFluidObserver(mixings[key], ctrl) } } } function createMixing(key: string, observer?: FluidObserver) { const mixing = new MixingValue() mixing.key = key if (observer) addFluidObserver(mixing, observer) return mixing } function prepareMixings( mixings: MixingValue, props: ControllerQueue[number], create: (key: string) => MixingValue ) { if (props.keys) { each(props.keys, key => { const mixing = mixings[key] || (mixings[key] = create(key)) mixing['_prepareNode'](props) }) } } function createUpdate (...args: any) {}