/spacey

To get this branch, use:
bzr branch /bzr/spacey
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
}