bzr branch
http://9ix.org/bzr/traderous
1
by Josh C
zoetrope 1.4 |
1 |
-- Class: View |
2 |
-- A view is a group that packages several useful objects with it. |
|
3 |
-- It's helpful to use, but not required. When a view is created, it |
|
4 |
-- automatically sets the.view for itself. the.view should be considered |
|
5 |
-- a read-only reference. If you want to switch views, you *must* set |
|
6 |
-- the app's view property instead. |
|
7 |
-- |
|
8 |
-- Extends: |
|
9 |
-- <Group> |
|
10 |
||
11 |
View = Group:extend |
|
12 |
{ |
|
13 |
-- Property: timer |
|
14 |
-- A built-in <Timer> object for use as needed. |
|
15 |
||
16 |
-- Property: tween |
|
17 |
-- A built-in <Tween> object for use as needed. |
|
18 |
||
19 |
-- Property: factory |
|
20 |
-- A built-in <Factory> object for use as needed. |
|
21 |
||
22 |
-- Property: focus |
|
23 |
-- A <Sprite> to keep centered onscreen. |
|
24 |
||
25 |
-- Property: focusOffset |
|
26 |
-- This shifts the view of the focus, if one is set. If both |
|
27 |
-- x and y properties are set to 0, then the view keeps the focus |
|
28 |
-- centered onscreen. |
|
29 |
focusOffset = { x = 0, y = 0 }, |
|
30 |
||
31 |
-- Property: minVisible |
|
32 |
-- The view clamps its scrolling so that nothing above or to the left |
|
33 |
-- of these x and y coordinates is visible. |
|
34 |
minVisible = { x = -math.huge, y = -math.huge }, |
|
35 |
||
36 |
-- Property: maxVisible |
|
37 |
-- This view clamps its scrolling so that nothing below or to the right |
|
38 |
-- of these x and y coordinates is visible. |
|
39 |
maxVisible = { x = math.huge, y = math.huge }, |
|
40 |
||
41 |
-- private property: _tint |
|
42 |
-- used to implement tints. |
|
43 |
||
44 |
-- private property: _fx |
|
45 |
-- used to perform fades and flashes. |
|
46 |
||
47 |
new = function (self, obj) |
|
48 |
obj = self:extend(obj) |
|
49 |
||
50 |
obj.timer = Timer:new() |
|
51 |
obj:add(obj.timer) |
|
52 |
obj.tween = Tween:new() |
|
53 |
obj:add(obj.tween) |
|
54 |
obj.factory = Factory:new() |
|
55 |
||
56 |
-- set the.view briefly, so that during the onNew() handler |
|
57 |
-- we appear to be the current view |
|
58 |
||
59 |
local oldView = the.view |
|
60 |
||
61 |
the.view = obj |
|
62 |
if obj.onNew then obj:onNew() end |
|
63 |
||
64 |
-- then reset it so that nothing breaks for the remainder |
|
65 |
-- of the frame for the old, outgoing view members. |
|
66 |
-- our parent app will restore us into the.view at the top of the next frame |
|
67 |
-- exception: there was no old view. |
|
68 |
||
69 |
if oldView then the.view = oldView end |
|
70 |
return obj |
|
71 |
end, |
|
72 |
||
73 |
-- Method: clampTo |
|
74 |
-- Clamps the view so that it never scrolls past a sprite's boundaries. |
|
75 |
-- This only looks at the sprite's position at this instant in time, |
|
76 |
-- not afterwards. |
|
77 |
-- |
|
78 |
-- Arguments: |
|
79 |
-- sprite - sprite to clamp to |
|
80 |
-- |
|
81 |
-- Returns: |
|
82 |
-- nothing |
|
83 |
||
84 |
clampTo = function (self, sprite) |
|
85 |
self.minVisible.x = sprite.x |
|
86 |
||
87 |
if sprite.x + sprite.width > the.app.width then |
|
88 |
self.maxVisible.x = sprite.x + sprite.width |
|
89 |
else |
|
90 |
self.maxVisible.x = the.app.width |
|
91 |
end |
|
92 |
||
93 |
self.minVisible.y = sprite.y |
|
94 |
||
95 |
if sprite.y + sprite.height > the.app.height then |
|
96 |
self.maxVisible.y = sprite.y + sprite.height |
|
97 |
else |
|
98 |
self.maxVisible.y = the.app.height |
|
99 |
end |
|
100 |
end, |
|
101 |
||
102 |
-- Method: panTo |
|
103 |
-- Pans the view so that the target sprite or position is centered |
|
104 |
-- onscreen. This sets the view's focus to nil. |
|
105 |
-- |
|
106 |
-- Arguments: |
|
107 |
-- target - sprite or coordinate pair to pan to |
|
108 |
-- duration - how long the pan will take, in seconds |
|
109 |
-- ease - what easing to apply, see <Tween> for details, defaults to 'quadInOut' |
|
110 |
-- |
|
111 |
-- Returns: |
|
112 |
-- A <Promise> that is fulfilled when the pan completes. |
|
113 |
||
114 |
panTo = function (self, target, duration, ease) |
|
115 |
ease = ease or 'quadInOut' |
|
116 |
local targetX, targetY |
|
117 |
||
118 |
if STRICT then |
|
119 |
assert((target.x and target.y and target.width and target.height) or (#target == 2), |
|
120 |
'pan target does not appear to be a sprite or coordinate pair') |
|
121 |
assert(type(duration) == 'number', 'pan duration is not a number') |
|
122 |
assert(self.tween.easers[ease], 'pan easing method ' .. ease .. ' is not defined') |
|
123 |
end |
|
124 |
||
125 |
if target.x and target.y and target.width and target.height then |
|
126 |
targetX = target.x + target.width / 2 |
|
127 |
targetY = target.y + target.height / 2 |
|
128 |
else |
|
129 |
targetX = target[1] |
|
130 |
targetY = target[2] |
|
131 |
end |
|
132 |
||
133 |
-- calculate translation to center these coordinates |
|
134 |
||
135 |
local tranX = math.floor(-targetX + the.app.width / 2) |
|
136 |
local tranY = math.floor(-targetY + the.app.height / 2) |
|
137 |
||
138 |
-- clamp translation to min and max visible |
|
139 |
||
140 |
if tranX > - self.minVisible.x then tranX = - self.minVisible.x end |
|
141 |
if tranY > - self.minVisible.y then tranY = - self.minVisible.y end |
|
142 |
||
143 |
if tranX < the.app.width - self.maxVisible.x then |
|
144 |
tranX = the.app.width - self.maxVisible.x |
|
145 |
end |
|
146 |
||
147 |
if tranY < the.app.height - self.maxVisible.y then |
|
148 |
tranY = the.app.height - self.maxVisible.y |
|
149 |
end |
|
150 |
||
151 |
-- tween the appropriate properties |
|
152 |
-- some care has to be taken to avoid fulfilling the promise twice |
|
153 |
||
154 |
self.focus = nil |
|
155 |
local promise = Promise:new() |
|
156 |
||
157 |
if tranX ~= self.translate.x then |
|
158 |
self.tween:start(self.translate, 'x', tranX, duration, ease) |
|
159 |
:andThen(function() promise:fulfill() end) |
|
160 |
||
161 |
if tranY ~= self.translate.y then |
|
162 |
self.tween:start(self.translate, 'y', tranY, duration, ease) |
|
163 |
end |
|
164 |
elseif tranY ~= self.translate.y then |
|
165 |
self.tween:start(self.translate, 'y', tranY, duration, ease) |
|
166 |
:andThen(function() promise:fulfill() end) |
|
167 |
else |
|
168 |
promise:fulfill() |
|
169 |
end |
|
170 |
||
171 |
return promise |
|
172 |
end, |
|
173 |
||
174 |
-- Method: fade |
|
175 |
-- Fades out to a specified color over a period of time. |
|
176 |
-- |
|
177 |
-- Arguments: |
|
178 |
-- color - color table to fade to, e.g. { 0, 0, 0 } |
|
179 |
-- duration - how long to fade out in seconds, default 1 |
|
180 |
-- |
|
181 |
-- Returns: |
|
182 |
-- A <Promise> that is fulfilled when the effect completes. |
|
183 |
||
184 |
fade = function (self, color, duration) |
|
185 |
assert(type(color) == 'table', 'color to fade to is ' .. type(color) .. ', not a table') |
|
186 |
local alpha = color[4] or 255 |
|
187 |
self._fx = color |
|
188 |
self._fx[4] = 0 |
|
189 |
return self.tween:start(self._fx, 4, alpha, duration or 1, 'quadOut') |
|
190 |
end, |
|
191 |
||
192 |
-- Method: flash |
|
193 |
-- Immediately flashes the screen to a specific color, then fades out. |
|
194 |
-- |
|
195 |
-- Arguments: |
|
196 |
-- color - color table to flash, e.g. { 0, 0, 0 } |
|
197 |
-- duration - how long to restore normal view in seconds, default 1 |
|
198 |
-- |
|
199 |
-- Returns: |
|
200 |
-- A <Promise> that is fulfilled when the effect completes. |
|
201 |
||
202 |
flash = function (self, color, duration) |
|
203 |
assert(type(color) == 'table', 'color to flash is ' .. type(color) .. ', not a table') |
|
204 |
color[4] = color[4] or 255 |
|
205 |
self._fx = color |
|
206 |
return self.tween:start(self._fx, 4, 0, duration or 1, 'quadOut') |
|
207 |
end, |
|
208 |
||
209 |
-- Method: tint |
|
210 |
-- Immediately tints the screen a color. To restore normal viewing, |
|
211 |
-- call this method again with no arguments. |
|
212 |
-- |
|
213 |
-- Arguments: |
|
214 |
-- red - red component, 0-255 |
|
215 |
-- green - green component, 0-255 |
|
216 |
-- blue - blue component, 0-255 |
|
217 |
-- alpha - alpha, 0-255, default 255 |
|
218 |
-- |
|
219 |
-- Returns: |
|
220 |
-- nothing |
|
221 |
||
222 |
tint = function (self, red, green, blue, alpha) |
|
223 |
alpha = alpha or 255 |
|
224 |
||
225 |
if red and green and blue and alpha > 0 then |
|
226 |
self._tint = { red, green, blue, alpha } |
|
227 |
else |
|
228 |
self._tint = nil |
|
229 |
end |
|
230 |
end, |
|
231 |
||
232 |
update = function (self, elapsed) |
|
233 |
Group.update(self, elapsed) |
|
234 |
||
235 |
-- follow the focused sprite |
|
236 |
||
237 |
local screenWidth = the.app.width |
|
238 |
local screenHeight = the.app.height |
|
239 |
||
240 |
if self.focus and self.focus.width < screenWidth |
|
241 |
and self.focus.height < screenHeight then |
|
242 |
self.translate.x = math.floor(- (self.focus.x + self.focusOffset.x) + |
|
243 |
(screenWidth - self.focus.width) / 2) |
|
244 |
self.translate.y = math.floor(- (self.focus.y + self.focusOffset.y) + |
|
245 |
(screenHeight - self.focus.height) / 2) |
|
246 |
end |
|
247 |
||
248 |
-- clamp translation to min and max visible |
|
249 |
||
250 |
if self.translate.x > - self.minVisible.x then |
|
251 |
self.translate.x = - self.minVisible.x |
|
252 |
end |
|
253 |
||
254 |
if self.translate.y > - self.minVisible.y then |
|
255 |
self.translate.y = - self.minVisible.y |
|
256 |
end |
|
257 |
||
258 |
if self.translate.x < screenWidth - self.maxVisible.x then |
|
259 |
self.translate.x = screenWidth - self.maxVisible.x |
|
260 |
end |
|
261 |
||
262 |
if self.translate.y < screenHeight - self.maxVisible.y then |
|
263 |
self.translate.y = screenHeight - self.maxVisible.y |
|
264 |
end |
|
265 |
end, |
|
266 |
||
267 |
draw = function (self, x, y) |
|
268 |
Group.draw(self, x, y) |
|
269 |
||
270 |
-- draw our fx and tint on top of everything |
|
271 |
||
272 |
if self._tint then |
|
273 |
love.graphics.setColor(self._tint) |
|
274 |
love.graphics.rectangle('fill', 0, 0, the.app.width, the.app.height) |
|
275 |
love.graphics.setColor(255, 255, 255, 255) |
|
276 |
end |
|
277 |
||
278 |
if self._fx then |
|
279 |
love.graphics.setColor(self._fx) |
|
280 |
love.graphics.rectangle('fill', 0, 0, the.app.width, the.app.height) |
|
281 |
love.graphics.setColor(255, 255, 255, 255) |
|
282 |
end |
|
283 |
end, |
|
284 |
||
285 |
__tostring = function (self) |
|
286 |
local result = 'View (' |
|
287 |
||
288 |
if self.active then |
|
289 |
result = result .. 'active' |
|
290 |
else |
|
291 |
result = result .. 'inactive' |
|
292 |
end |
|
293 |
||
294 |
if self.visible then |
|
295 |
result = result .. ', visible' |
|
296 |
else |
|
297 |
result = result .. ', invisible' |
|
298 |
end |
|
299 |
||
300 |
if self.solid then |
|
301 |
result = result .. ', solid' |
|
302 |
else |
|
303 |
result = result .. ', not solid' |
|
304 |
end |
|
305 |
||
306 |
return result .. ', ' .. self:count(true) .. ' sprites)' |
|
307 |
end |
|
308 |
} |