261 lines
5.2 KiB
Plaintext
261 lines
5.2 KiB
Plaintext
var Ease = use('ease')
|
|
var time = use('time')
|
|
|
|
var rate = 1/240
|
|
|
|
function make_engine(default_clock) {
|
|
return {
|
|
tweens: [],
|
|
default_clock: default_clock || null,
|
|
add(tween) {
|
|
this.tweens.push(tween)
|
|
},
|
|
remove(tween) {
|
|
this.tweens = filter(this.tweens, t => t != tween)
|
|
},
|
|
update(current_time) {
|
|
if (current_time == null) {
|
|
current_time = this.default_clock ? this.default_clock() : time.number()
|
|
}
|
|
arrfor(this.tweens, function(tween) {
|
|
tween._update(current_time)
|
|
})
|
|
},
|
|
clear() {
|
|
this.tweens = []
|
|
}
|
|
}
|
|
}
|
|
|
|
// Global fire-and-forget engine
|
|
var TweenEngine = make_engine(null)
|
|
|
|
var TweenProto = {
|
|
obj: null,
|
|
startVals: null,
|
|
endVals: null,
|
|
duration: 0,
|
|
easing: null,
|
|
startTime: 0,
|
|
onCompleteCallback: null,
|
|
onUpdateCallback: null,
|
|
engine: null,
|
|
|
|
to: function(props, duration, start_time) {
|
|
for (var key in props) {
|
|
var value = props[key]
|
|
if (is_object(value)) {
|
|
for (var subkey in value) {
|
|
var flatKey = key + '.' + subkey
|
|
this.startVals[flatKey] = this.obj[key] ? this.obj[key][subkey] : undefined
|
|
this.endVals[flatKey] = value[subkey]
|
|
}
|
|
} else {
|
|
this.startVals[key] = this.obj[key]
|
|
this.endVals[key] = value
|
|
}
|
|
}
|
|
|
|
this.duration = duration
|
|
|
|
this.engine = this.engine || TweenEngine
|
|
|
|
if (start_time == null) {
|
|
this.startTime = this.engine.default_clock ? this.engine.default_clock() : time.number()
|
|
} else {
|
|
this.startTime = start_time
|
|
}
|
|
|
|
this.engine.add(this)
|
|
return this
|
|
},
|
|
|
|
ease: function(easingFn) {
|
|
this.easing = easingFn
|
|
return this
|
|
},
|
|
|
|
onComplete: function(callback) {
|
|
this.onCompleteCallback = callback
|
|
return this
|
|
},
|
|
|
|
onUpdate: function(cb) {
|
|
this.onUpdateCallback = cb
|
|
return this
|
|
},
|
|
|
|
_update: function(now) {
|
|
this.seek(now)
|
|
this.onUpdateCallback?.()
|
|
},
|
|
|
|
seek: function(global_time) {
|
|
var elapsed = global_time - this.startTime
|
|
var t = min(max(elapsed / this.duration, 0), 1)
|
|
var eased = this.easing(t)
|
|
|
|
for (var key in this.endVals) {
|
|
var start = this.startVals[key]
|
|
var end = this.endVals[key]
|
|
var value = start + (end - start) * eased
|
|
|
|
if (search(key, '.') != null) {
|
|
var parts = array(key, '.')
|
|
var objKey = parts[0]
|
|
var subKey = parts[1]
|
|
|
|
if (!this.obj[objKey]) {
|
|
this.obj[objKey] = {}
|
|
}
|
|
|
|
this.obj[objKey][subKey] = value
|
|
} else {
|
|
this.obj[key] = value
|
|
}
|
|
}
|
|
|
|
if (t == 1 && this.engine) {
|
|
this.onCompleteCallback()
|
|
this.engine.remove(this)
|
|
}
|
|
},
|
|
|
|
cancel: function() {
|
|
if (this.engine) {
|
|
this.engine.remove(this)
|
|
}
|
|
},
|
|
|
|
toJSON: function() {
|
|
return {
|
|
startVals: this.startVals,
|
|
endVals: this.endVals,
|
|
duration: this.duration,
|
|
startTime: this.startTime,
|
|
easing: this.easing.name || 'linear'
|
|
}
|
|
}
|
|
}
|
|
|
|
function create_tween(obj) {
|
|
var tw = meme(TweenProto)
|
|
tw.obj = obj
|
|
tw.startVals = {}
|
|
tw.endVals = {}
|
|
tw.duration = 0
|
|
tw.easing = Ease.linear
|
|
tw.startTime = 0
|
|
tw.onCompleteCallback = function() {}
|
|
tw.onUpdateCallback = null
|
|
tw.engine = null
|
|
return tw
|
|
}
|
|
|
|
var TimelineProto = {
|
|
current_time: 0,
|
|
events: null,
|
|
playing: false,
|
|
last_tick: 0,
|
|
engine: null,
|
|
|
|
add_event: function(time_value, fn) {
|
|
this.events.push({ time: time_value, fn, fired: false })
|
|
},
|
|
|
|
add_tween: function(obj, props, duration, start_time) {
|
|
var tw = create_tween(obj)
|
|
tw.engine = this.engine
|
|
return tw.to(props, duration, start_time)
|
|
},
|
|
|
|
play: function() {
|
|
this.playing = true
|
|
this.last_tick = time.number()
|
|
|
|
var self = this
|
|
var loop = () => {
|
|
if (!self.playing) return
|
|
var now = time.number()
|
|
var dt = now - self.last_tick
|
|
self.last_tick = now
|
|
self.current_time += dt
|
|
self.seek(self.current_time)
|
|
$delay(loop, rate)
|
|
}
|
|
|
|
loop()
|
|
},
|
|
|
|
pause: function() {
|
|
this.playing = false
|
|
},
|
|
|
|
seek: function(t) {
|
|
this.current_time = t
|
|
|
|
// Update all tweens in this timeline
|
|
this.engine.update(t)
|
|
|
|
// Fire any events
|
|
arrfor(this.events, function(ev) {
|
|
if (!ev.fired && t >= ev.time) {
|
|
ev.fn()
|
|
ev.fired = true
|
|
} else if (ev.fired && t < ev.time) {
|
|
ev.fired = false
|
|
}
|
|
})
|
|
},
|
|
|
|
toJSON: function() {
|
|
return {
|
|
current_time: this.current_time,
|
|
events: array(this.events, e => ({ time: e.time, fired: e.fired })),
|
|
tweens: array(this.engine.tweens, t => t.toJSON())
|
|
}
|
|
}
|
|
}
|
|
|
|
function Timeline() {
|
|
var tl = meme(TimelineProto)
|
|
tl.current_time = 0
|
|
tl.events = []
|
|
tl.playing = false
|
|
tl.last_tick = 0
|
|
tl.engine = make_engine(() => tl.current_time)
|
|
return tl
|
|
}
|
|
|
|
// Live update loop for fire-and-forget tweens
|
|
function live_update_loop() {
|
|
TweenEngine.update()
|
|
$delay(live_update_loop, rate)
|
|
}
|
|
|
|
function tween(obj, engine) {
|
|
var tw = create_tween(obj)
|
|
if (engine) tw.engine = engine
|
|
return tw
|
|
}
|
|
|
|
function init(default_clock) {
|
|
TweenEngine.default_clock = default_clock || (() => time.number())
|
|
live_update_loop()
|
|
}
|
|
|
|
$delay(() => {
|
|
if (!TweenEngine.default_clock) {
|
|
init()
|
|
}
|
|
}, 0)
|
|
|
|
var tween_ret = {
|
|
init,
|
|
Timeline,
|
|
TweenEngine,
|
|
tween
|
|
}
|
|
|
|
return tween_ret
|