1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
|
-- Class: Tween
-- A tween transitions a property from one state to another
-- in in-game time. A tween instance is designed to manage
-- many of these transitions at once, in fact. In order for it
-- to work properly, it must receive update events, so it must
-- be added somewhere in the current view or app. If you are using
-- the <View> class, this is already done for you.
Tween = Sprite:extend{
tweens = {},
visible = false,
active = false,
solid = false,
-- Property: easers
-- These are different methods of easing a tween, and
-- can be set via the ease property of an individual tween.
-- They should be referred to by their key name, not the property
-- (e.g. 'linear', no Tweener.easers.linear).
-- See http://www.gizma.com/easing/ for details.
easers =
{
linear = function (elapsed, start, change, duration)
return change * elapsed / duration + start
end,
quadIn = function (elapsed, start, change, duration)
elapsed = elapsed / duration
return change * elapsed * elapsed + start
end,
quadOut = function (elapsed, start, change, duration)
elapsed = elapsed / duration
return - change * elapsed * (elapsed - 2) + start
end,
quadInOut = function (elapsed, start, change, duration)
elapsed = elapsed / (duration / 2)
if (elapsed < 1) then
return change / 2 * elapsed * elapsed + start
else
elapsed = elapsed - 1
return - change / 2 * (elapsed * (elapsed - 2) - 1) + start
end
end
},
-- Method: reverseForever
-- A utility function; if set via <Promise.andAfter()> for an individual
-- tween, it reverses the tween that just happened. Use this to get a tween
-- to repeat back and forth indefinitely (e.g. to have something glow).
reverseForever = function (tween, tweener)
tween.to = tween.from
tweener:start(tween.target, tween.property, tween.to, tween.duration, tween.ease):andThen(Tween.reverseForever)
end,
-- Method: reverseOnce
-- A utility function; if set via <Promise.andAfter()> for an individual
-- tween, it reverses the tween that just happened-- then stops the tween after that.
reverseOnce = function (tween, tweener)
tween.to = tween.from
tweener:start(tween.target, tween.property, tween.to, tween.duration, tween.ease)
end,
-- Method: start
-- Begins a tweened transition, overriding any existing tween.
--
-- Arguments:
-- target - target object
-- property - Usually, this is a string name of a property of the target object.
-- You may also specify a table of getter and setter methods instead,
-- i.e. { myGetter, mySetter }. In either case, the property or functions
-- must work with either number values, or tables of numbers.
-- to - destination value, either number or color table
-- duration - how long the tween should last in seconds, default 1
-- ease - function name (in Tween.easers) to use to control how the value changes, default 'linear'
--
-- Returns:
-- A <Promise> that is fulfilled when the tween completes. If the object is already
-- in the state requested, the promise resolves immediately. The tween object returns two
-- things to the promise: a table of properties about the tween that match the arguments initially
-- passed, and a reference to the Tween that completing the tween.
start = function (self, target, property, to, duration, ease)
duration = duration or 1
ease = ease or 'linear'
local propType = type(property)
if STRICT then
assert(type(target) == 'table' or type(target) == 'userdata', 'target must be a table or userdata')
assert(propType == 'string' or propType == 'number' or propType == 'table', 'property must be a key or table of getter/setter methods')
if propType == 'string' or propType == 'number' then
assert(target[property], 'no such property ' .. tostring(property) .. ' on target')
end
assert(type(duration) == 'number', 'duration must be a number')
assert(self.easers[ease], 'easer ' .. ease .. ' is not defined')
end
-- check for an existing tween for this target and property
for i, existing in ipairs(self.tweens) do
if target == existing.target and property == existing.property then
if to == existing.to then
return existing.promise
else
table.remove(self.tweens, i)
end
end
end
-- add it
tween = { target = target, property = property, propType = propType, to = to, duration = duration, ease = ease }
tween.from = self:getTweenValue(tween)
tween.type = type(tween.from)
-- calculate change; if it's trivial, skip the tween
if tween.type == 'number' then
tween.change = tween.to - tween.from
if math.abs(tween.change) < NEARLY_ZERO then
return Promise:new{ state = 'fulfilled', _resolvedWith = { tween, self } }
end
elseif tween.type == 'table' then
tween.change = {}
local skip = true
for i, value in ipairs(tween.from) do
tween.change[i] = tween.to[i] - tween.from[i]
if math.abs(tween.change[i]) > NEARLY_ZERO then
skip = false
end
end
if skip then
return Promise:new{ state = 'fulfilled', _resolvedWith = { tween, self } }
end
else
error('tweened property must either be a number or a table of numbers, is ' .. tween.type)
end
tween.elapsed = 0
tween.promise = Promise:new()
table.insert(self.tweens, tween)
self.active = true
return tween.promise
end,
-- Method: status
-- Returns how much time is left for a particular tween to run.
--
-- Arguments:
-- target - target object
-- property - name of the property being tweened, or getter
-- (as set in the orignal <start()> call)
--
-- Returns:
-- Either the time left in the tween, or nil if there is
-- no tween matching the arguments passed.
status = function (self, target, property)
for _, t in pairs(self.tweens) do
if t.target == target then
if t.property == property or (type(t.property) == 'table' and t.property[1] == property) then
return t.duration - t.elapsed
end
end
end
return nil
end,
-- Method: stop
-- Stops a tween. The promise associated with it will be failed.
--
-- Arguments:
-- target - tween target
-- property - name of property being tweened, or getter (as set in the original <start()> call);
-- if omitted, stops all tweens on the target
--
-- Returns:
-- nothing
stop = function (self, target, property)
local found = false
for i, tween in ipairs(self.tweens) do
if tween.target == target and (tween.property == property or
(type(tween.property) == 'table' and tween.property[1] == property) or
not property) then
found = true
tween.promise:fail('Tween stopped')
table.remove(self.tweens, i)
end
end
if STRICT and not found then
local info = debug.getinfo(2, 'Sl')
print('Warning: asked to stop a tween, but no active tweens match it (' ..
info.short_src .. ', line ' .. info.currentline .. ')')
end
end,
update = function (self, elapsed)
for i, tween in ipairs(self.tweens) do
self.active = true
tween.elapsed = tween.elapsed + elapsed
if tween.elapsed >= tween.duration then
-- tween is completed
self:setTweenValue(tween, tween.to)
table.remove(self.tweens, i)
tween.promise:fulfill(tween, self)
else
-- move tween towards finished state
if tween.type == 'number' then
self:setTweenValue(tween, self.easers[tween.ease](tween.elapsed,
tween.from, tween.change, tween.duration))
elseif tween.type == 'table' then
local now = {}
for i, value in ipairs(tween.from) do
now[i] = self.easers[tween.ease](tween.elapsed, tween.from[i],
tween.change[i], tween.duration)
end
self:setTweenValue(tween, now)
end
end
end
self.active = (#self.tweens > 0)
end,
getTweenValue = function (self, tween)
if tween.propType == 'string' or tween.propType == 'number' then
return tween.target[tween.property]
else
return tween.property[1](tween.target)
end
end,
setTweenValue = function (self, tween, value)
if tween.propType == 'string' or tween.propType == 'number' then
tween.target[tween.property] = value
else
tween.property[2](tween.target, value)
end
end,
__tostring = function (self)
local result = 'Tween ('
if self.active then
result = result .. 'active, '
result = result .. #self.tweens .. ' tweens running'
else
result = result .. 'inactive'
end
return result .. ')'
end
}
|