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.
14
-- A built-in <Timer> object for use as needed.
17
-- A built-in <Tween> object for use as needed.
20
-- A built-in <Factory> object for use as needed.
23
-- A <Sprite> to keep centered onscreen.
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
29
focusOffset = { x = 0, y = 0 },
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 },
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 },
41
-- private property: _tint
42
-- used to implement tints.
44
-- private property: _fx
45
-- used to perform fades and flashes.
47
new = function (self, obj)
48
obj = self:extend(obj)
50
obj.timer = Timer:new()
52
obj.tween = Tween:new()
54
obj.factory = Factory:new()
56
-- set the.view briefly, so that during the onNew() handler
57
-- we appear to be the current view
59
local oldView = the.view
62
if obj.onNew then obj:onNew() end
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.
69
if oldView then the.view = oldView end
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,
79
-- sprite - sprite to clamp to
84
clampTo = function (self, sprite)
85
self.minVisible.x = sprite.x
87
if sprite.x + sprite.width > the.app.width then
88
self.maxVisible.x = sprite.x + sprite.width
90
self.maxVisible.x = the.app.width
93
self.minVisible.y = sprite.y
95
if sprite.y + sprite.height > the.app.height then
96
self.maxVisible.y = sprite.y + sprite.height
98
self.maxVisible.y = the.app.height
103
-- Pans the view so that the target sprite or position is centered
104
-- onscreen. This sets the view's focus to nil.
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'
112
-- A <Promise> that is fulfilled when the pan completes.
114
panTo = function (self, target, duration, ease)
115
ease = ease or 'quadInOut'
116
local targetX, targetY
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')
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
133
-- calculate translation to center these coordinates
135
local tranX = math.floor(-targetX + the.app.width / 2)
136
local tranY = math.floor(-targetY + the.app.height / 2)
138
-- clamp translation to min and max visible
140
if tranX > - self.minVisible.x then tranX = - self.minVisible.x end
141
if tranY > - self.minVisible.y then tranY = - self.minVisible.y end
143
if tranX < the.app.width - self.maxVisible.x then
144
tranX = the.app.width - self.maxVisible.x
147
if tranY < the.app.height - self.maxVisible.y then
148
tranY = the.app.height - self.maxVisible.y
151
-- tween the appropriate properties
152
-- some care has to be taken to avoid fulfilling the promise twice
155
local promise = Promise:new()
157
if tranX ~= self.translate.x then
158
self.tween:start(self.translate, 'x', tranX, duration, ease)
159
:andThen(function() promise:fulfill() end)
161
if tranY ~= self.translate.y then
162
self.tween:start(self.translate, 'y', tranY, duration, ease)
164
elseif tranY ~= self.translate.y then
165
self.tween:start(self.translate, 'y', tranY, duration, ease)
166
:andThen(function() promise:fulfill() end)
175
-- Fades out to a specified color over a period of time.
178
-- color - color table to fade to, e.g. { 0, 0, 0 }
179
-- duration - how long to fade out in seconds, default 1
182
-- A <Promise> that is fulfilled when the effect completes.
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
189
return self.tween:start(self._fx, 4, alpha, duration or 1, 'quadOut')
193
-- Immediately flashes the screen to a specific color, then fades out.
196
-- color - color table to flash, e.g. { 0, 0, 0 }
197
-- duration - how long to restore normal view in seconds, default 1
200
-- A <Promise> that is fulfilled when the effect completes.
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
206
return self.tween:start(self._fx, 4, 0, duration or 1, 'quadOut')
210
-- Immediately tints the screen a color. To restore normal viewing,
211
-- call this method again with no 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
222
tint = function (self, red, green, blue, alpha)
225
if red and green and blue and alpha > 0 then
226
self._tint = { red, green, blue, alpha }
232
update = function (self, elapsed)
233
Group.update(self, elapsed)
235
-- follow the focused sprite
237
local screenWidth = the.app.width
238
local screenHeight = the.app.height
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)
248
-- clamp translation to min and max visible
250
if self.translate.x > - self.minVisible.x then
251
self.translate.x = - self.minVisible.x
254
if self.translate.y > - self.minVisible.y then
255
self.translate.y = - self.minVisible.y
258
if self.translate.x < screenWidth - self.maxVisible.x then
259
self.translate.x = screenWidth - self.maxVisible.x
262
if self.translate.y < screenHeight - self.maxVisible.y then
263
self.translate.y = screenHeight - self.maxVisible.y
267
draw = function (self, x, y)
268
Group.draw(self, x, y)
270
-- draw our fx and tint on top of everything
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)
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)
285
__tostring = function (self)
286
local result = 'View ('
289
result = result .. 'active'
291
result = result .. 'inactive'
295
result = result .. ', visible'
297
result = result .. ', invisible'
301
result = result .. ', solid'
303
result = result .. ', not solid'
306
return result .. ', ' .. self:count(true) .. ' sprites)'