2
-- A tween transitions a property from one state to another
3
-- in in-game time. A tween instance is designed to manage
4
-- many of these transitions at once, in fact. In order for it
5
-- to work properly, it must receive update events, so it must
6
-- be added somewhere in the current view or app. If you are using
7
-- the <View> class, this is already done for you.
16
-- These are different methods of easing a tween, and
17
-- can be set via the ease property of an individual tween.
18
-- They should be referred to by their key name, not the property
19
-- (e.g. 'linear', no Tweener.easers.linear).
20
-- See http://www.gizma.com/easing/ for details.
24
linear = function (elapsed, start, change, duration)
25
return change * elapsed / duration + start
28
quadIn = function (elapsed, start, change, duration)
29
elapsed = elapsed / duration
30
return change * elapsed * elapsed + start
33
quadOut = function (elapsed, start, change, duration)
34
elapsed = elapsed / duration
35
return - change * elapsed * (elapsed - 2) + start
38
quadInOut = function (elapsed, start, change, duration)
39
elapsed = elapsed / (duration / 2)
42
return change / 2 * elapsed * elapsed + start
45
return - change / 2 * (elapsed * (elapsed - 2) - 1) + start
50
-- Method: reverseForever
51
-- A utility function; if set via <Promise.andAfter()> for an individual
52
-- tween, it reverses the tween that just happened. Use this to get a tween
53
-- to repeat back and forth indefinitely (e.g. to have something glow).
55
reverseForever = function (tween, tweener)
57
tweener:start(tween.target, tween.property, tween.to, tween.duration, tween.ease):andThen(Tween.reverseForever)
60
-- Method: reverseOnce
61
-- A utility function; if set via <Promise.andAfter()> for an individual
62
-- tween, it reverses the tween that just happened-- then stops the tween after that.
64
reverseOnce = function (tween, tweener)
66
tweener:start(tween.target, tween.property, tween.to, tween.duration, tween.ease)
70
-- Begins a tweened transition, overriding any existing tween.
73
-- target - target object
74
-- property - Usually, this is a string name of a property of the target object.
75
-- You may also specify a table of getter and setter methods instead,
76
-- i.e. { myGetter, mySetter }. In either case, the property or functions
77
-- must work with either number values, or tables of numbers.
78
-- to - destination value, either number or color table
79
-- duration - how long the tween should last in seconds, default 1
80
-- ease - function name (in Tween.easers) to use to control how the value changes, default 'linear'
83
-- A <Promise> that is fulfilled when the tween completes. If the object is already
84
-- in the state requested, the promise resolves immediately. The tween object returns two
85
-- things to the promise: a table of properties about the tween that match the arguments initially
86
-- passed, and a reference to the Tween that completing the tween.
88
start = function (self, target, property, to, duration, ease)
89
duration = duration or 1
90
ease = ease or 'linear'
91
local propType = type(property)
94
assert(type(target) == 'table' or type(target) == 'userdata', 'target must be a table or userdata')
95
assert(propType == 'string' or propType == 'number' or propType == 'table', 'property must be a key or table of getter/setter methods')
97
if propType == 'string' or propType == 'number' then
98
assert(target[property], 'no such property ' .. tostring(property) .. ' on target')
101
assert(type(duration) == 'number', 'duration must be a number')
102
assert(self.easers[ease], 'easer ' .. ease .. ' is not defined')
105
-- check for an existing tween for this target and property
107
for i, existing in ipairs(self.tweens) do
108
if target == existing.target and property == existing.property then
109
if to == existing.to then
110
return existing.promise
112
table.remove(self.tweens, i)
119
tween = { target = target, property = property, propType = propType, to = to, duration = duration, ease = ease }
120
tween.from = self:getTweenValue(tween)
121
tween.type = type(tween.from)
123
-- calculate change; if it's trivial, skip the tween
125
if tween.type == 'number' then
126
tween.change = tween.to - tween.from
127
if math.abs(tween.change) < NEARLY_ZERO then
128
return Promise:new{ state = 'fulfilled', _resolvedWith = { tween, self } }
130
elseif tween.type == 'table' then
135
for i, value in ipairs(tween.from) do
136
tween.change[i] = tween.to[i] - tween.from[i]
138
if math.abs(tween.change[i]) > NEARLY_ZERO then
144
return Promise:new{ state = 'fulfilled', _resolvedWith = { tween, self } }
147
error('tweened property must either be a number or a table of numbers, is ' .. tween.type)
151
tween.promise = Promise:new()
152
table.insert(self.tweens, tween)
158
-- Returns how much time is left for a particular tween to run.
161
-- target - target object
162
-- property - name of the property being tweened, or getter
163
-- (as set in the orignal <start()> call)
166
-- Either the time left in the tween, or nil if there is
167
-- no tween matching the arguments passed.
169
status = function (self, target, property)
170
for _, t in pairs(self.tweens) do
171
if t.target == target then
172
if t.property == property or (type(t.property) == 'table' and t.property[1] == property) then
173
return t.duration - t.elapsed
182
-- Stops a tween. The promise associated with it will be failed.
185
-- target - tween target
186
-- property - name of property being tweened, or getter (as set in the original <start()> call);
187
-- if omitted, stops all tweens on the target
192
stop = function (self, target, property)
195
for i, tween in ipairs(self.tweens) do
196
if tween.target == target and (tween.property == property or
197
(type(tween.property) == 'table' and tween.property[1] == property) or
200
tween.promise:fail('Tween stopped')
201
table.remove(self.tweens, i)
205
if STRICT and not found then
206
local info = debug.getinfo(2, 'Sl')
207
print('Warning: asked to stop a tween, but no active tweens match it (' ..
208
info.short_src .. ', line ' .. info.currentline .. ')')
212
update = function (self, elapsed)
213
for i, tween in ipairs(self.tweens) do
215
tween.elapsed = tween.elapsed + elapsed
217
if tween.elapsed >= tween.duration then
218
-- tween is completed
220
self:setTweenValue(tween, tween.to)
221
table.remove(self.tweens, i)
222
tween.promise:fulfill(tween, self)
224
-- move tween towards finished state
226
if tween.type == 'number' then
227
self:setTweenValue(tween, self.easers[tween.ease](tween.elapsed,
228
tween.from, tween.change, tween.duration))
229
elseif tween.type == 'table' then
232
for i, value in ipairs(tween.from) do
233
now[i] = self.easers[tween.ease](tween.elapsed, tween.from[i],
234
tween.change[i], tween.duration)
237
self:setTweenValue(tween, now)
242
self.active = (#self.tweens > 0)
245
getTweenValue = function (self, tween)
246
if tween.propType == 'string' or tween.propType == 'number' then
247
return tween.target[tween.property]
249
return tween.property[1](tween.target)
253
setTweenValue = function (self, tween, value)
254
if tween.propType == 'string' or tween.propType == 'number' then
255
tween.target[tween.property] = value
257
tween.property[2](tween.target, value)
261
__tostring = function (self)
262
local result = 'Tween ('
265
result = result .. 'active, '
266
result = result .. #self.tweens .. ' tweens running'
268
result = result .. 'inactive'