/ld27

To get this branch, use:
bzr branch http://9ix.org/bzr/ld27
35 by Josh C
cluke009 zoetrope + my spritebatch changes
1
-- Class: App
2
-- An app is where all the magic happens. :) It contains a
1 by Josh C
zoetrope 1.4
3
-- view, the group where all major action happens, as well as the
4
-- meta view, which persists across views. Only one app may run at
5
-- a time.
6
--
7
-- An app's job is to get things up and running -- most of its logic
8
-- lives in its onRun handler, but for a simple app, you can also
9
-- use the onUpdate handler instead of writing a custom <View>.
35 by Josh C
cluke009 zoetrope + my spritebatch changes
10
--
1 by Josh C
zoetrope 1.4
11
-- Once an app has begun running, it may be accessed globally via
12
-- <the>.app.
13
--
14
-- Extends:
15
--  	<Class>
16
--
17
-- Event: onRun
18
-- 		Called once, when the app begins running.
19
--
20
-- Event: onEnterFullscreen
21
--		Called after entering fullscreen successfully.
22
--
23
-- Event: onExitFullscreen
24
--		Called after exiting fullscreen successfully.
25
26
App = Class:extend
27
{
28
	-- Property: name
29
	-- This is shown in the window title bar.
30
	name = 'Zoetrope',
35 by Josh C
cluke009 zoetrope + my spritebatch changes
31
1 by Josh C
zoetrope 1.4
32
	-- Property: icon
33
	-- A path to an image to use as the window icon (a 32x32 PNG is recommended).
35 by Josh C
cluke009 zoetrope + my spritebatch changes
34
	-- This doesn't affect the actual executable's icon in the taskbar or dock.
1 by Josh C
zoetrope 1.4
35
36
	-- Property: fps
37
	-- Maximum frames per second requested. In practice, your
38
	-- FPS may vary from frame to frame. Every event handler (e.g. onUpdate)
39
	-- is passed the exact elapsed time in seconds.
40
	fps = 60,
35 by Josh C
cluke009 zoetrope + my spritebatch changes
41
1 by Josh C
zoetrope 1.4
42
	-- Property: timeScale
43
	-- Multiplier for elapsed time; 1.0 is normal, 0 is completely frozen.
44
	timeScale = 1,
35 by Josh C
cluke009 zoetrope + my spritebatch changes
45
1 by Josh C
zoetrope 1.4
46
	-- Property: active
47
	-- If false, nothing receives update-related events, including the meta view.
48
	-- These events specifically are onStartFrame, onUpdate, and onEndFrame.
49
	active = true,
50
51
	-- Property: deactivateOnBlur
52
	-- Should the app automatically set its active property to false when its
53
	-- window loses focus?
54
	deactivateOnBlur = true,
35 by Josh C
cluke009 zoetrope + my spritebatch changes
55
1 by Josh C
zoetrope 1.4
56
	-- Property: view
57
	-- The current <View>. When the app is running, this is also accessible
58
	-- globally via <the>.view. In order to switch views, you must set this
59
	-- property, *not* <the>.view.
60
61
	-- Property: meta
62
	-- A <Group> that persists across all views during the app's lifespan.
63
64
	-- Property: keys
65
	-- A <Keys> object that listens to the keyboard. When the app is running, this
66
	-- is also accessible globally via <the>.keys.
67
68
	-- Property: width
69
	-- The width of the app's canvas in pixels. Changing this value has no effect. To
70
	-- set this for real, edit conf.lua. This may *not* correspond to the overall
71
	-- resolution of the window when in fullscreen mode.
72
73
	-- Property: height
74
	-- The height of the window in pixels. Changing this value has no effect. To
75
	-- set this for real, edit conf.lua. This may *not* correspond to the overall
76
	-- resolution of the window when in fullscreen mode.
77
78
	-- Property: fullscreen
79
	-- Whether the app is currently running in fullscreen mode. Changing this value
80
	-- has no effect. To change this, use the enterFullscreen(), exitFullscreen(), or
81
	-- toggleFullscreen() methods.
82
83
	-- Property: inset
84
	-- The screen coordinates where the app's (0, 0) origin should lie. This is only
85
	-- used by Zoetrope to either letterbox or pillar fullscreen apps, but there may
86
	-- be other uses for it.
87
	inset = { x = 0, y = 0},
88
89
	-- internal property: _sleepTime
90
	-- The amount of time the app intentionally slept for, and should not
91
	-- be counted against elapsed time.
92
93
	-- internal property: _nextFrameTime
94
	-- Set at the start of a frame to be the next time a frame should be rendered,
95
	-- in timestamp format. This is used to properly maintain FPS -- see the example
96
	-- in https://love2d.org/wiki/love.timer.sleep for how this works.
97
98
	new = function (self, obj)
99
		obj = self:extend(obj)
100
101
		-- set icon if possible
35 by Josh C
cluke009 zoetrope + my spritebatch changes
102
1 by Josh C
zoetrope 1.4
103
		if self.icon then
35 by Josh C
cluke009 zoetrope + my spritebatch changes
104
			love.window.setIcon(Cached:image(self.icon))
1 by Josh C
zoetrope 1.4
105
		end
35 by Josh C
cluke009 zoetrope + my spritebatch changes
106
1 by Josh C
zoetrope 1.4
107
		-- view containers
108
35 by Josh C
cluke009 zoetrope + my spritebatch changes
109
		obj.meta = obj.meta or View:new()
110
		obj.view = obj.view or View:new()
111
1 by Josh C
zoetrope 1.4
112
		-- input
113
114
		if love.keyboard then
115
			obj.keys = obj.keys or Keys:new()
116
			love.keyboard.setKeyRepeat(0.4, 0.04)
117
			obj.meta:add(obj.keys)
118
		end
119
120
		if love.mouse then
121
			obj.mouse = obj.mouse or Mouse:new()
122
			obj.meta:add(obj.mouse)
123
		end
124
125
		if love.joystick then
126
			obj.gamepads = {}
127
			the.gamepads = obj.gamepads
128
		end
129
130
		if obj.numGamepads and obj.numGamepads > 0 then
131
			for i = 1, obj.numGamepads do
132
				obj.gamepads[i] = Gamepad:new{ number = i }
133
				obj.meta:add(obj.gamepads[i])
134
			end
135
		end
136
137
		-- screen dimensions and state
138
35 by Josh C
cluke009 zoetrope + my spritebatch changes
139
		obj.width, obj.height, obj.windowflags = love.window.getMode()
140
		obj.fullscreen = obj.windowflags.fullscreen
1 by Josh C
zoetrope 1.4
141
142
		-- housekeeping
35 by Josh C
cluke009 zoetrope + my spritebatch changes
143
1 by Josh C
zoetrope 1.4
144
		the.app = obj
145
		if obj.onNew then obj:onNew() end
146
		return obj
147
	end,
148
149
	-- Method: run
150
	-- Starts the app running. Nothing will occur until this call.
151
	--
152
	-- Arguments:
153
	-- 		none
35 by Josh C
cluke009 zoetrope + my spritebatch changes
154
	--
1 by Josh C
zoetrope 1.4
155
	-- Returns:
156
	--		nothing
157
158
	run = function (self)
159
		math.randomseed(os.time())
160
161
		-- sync the.view
162
163
		the.view = self.view
164
165
		-- attach debug console
166
167
		if DEBUG then
35 by Josh C
cluke009 zoetrope + my spritebatch changes
168
			debugger.init()
1 by Josh C
zoetrope 1.4
169
		end
170
171
		-- set up callbacks
35 by Josh C
cluke009 zoetrope + my spritebatch changes
172
173
		love.window.setTitle(self.name)
1 by Josh C
zoetrope 1.4
174
		love.update = function (elapsed) self:update(elapsed) end
175
		love.draw = function() self:draw() end
35 by Josh C
cluke009 zoetrope + my spritebatch changes
176
		love.focus = function (value) self:onFocus(value) end
1 by Josh C
zoetrope 1.4
177
178
		if self.onRun then self:onRun() end
35 by Josh C
cluke009 zoetrope + my spritebatch changes
179
		self._nextFrameTime = love.timer.getTime()
1 by Josh C
zoetrope 1.4
180
	end,
35 by Josh C
cluke009 zoetrope + my spritebatch changes
181
1 by Josh C
zoetrope 1.4
182
	-- Method: quit
183
	-- Quits the application immediately.
184
	--
185
	-- Arguments:
186
	--		none
187
	--
188
	-- Returns:
189
	--		nothing
190
191
	quit = function (self)
192
		love.event.quit()
193
	end,
194
195
	-- Method: useSysCursor
196
	-- Shows or hides the system mouse cursor.
197
	--
198
	-- Arguments:
199
	--		value - show the cursor?
200
	--
201
	-- Returns:
202
	--		nothing
35 by Josh C
cluke009 zoetrope + my spritebatch changes
203
1 by Josh C
zoetrope 1.4
204
	useSysCursor = function (self, value)
205
		if STRICT then
206
			assert(value == true or value == false,
207
				   'tried to set system cursor visibility to ' .. type(value))
208
		end
209
210
		love.mouse.setVisible(value)
211
	end,
212
213
	-- Method: enterFullscreen
214
	-- Enters fullscreen mode. If the app is already in fullscreen, this has no effect.
215
	-- This tries to use the highest resolution that will not result in distortion, and
216
	-- adjust the app's offset property to accomodate this.
217
	--
218
	-- Arguments:
219
	--		hint - whether to try to letterbox (vertical black bars) or pillar the app
220
	--			   (horizontal black bars). You don't need to specify this; the method
221
	--			   will try to infer based on the aspect ratio of your app.
222
	--
223
	-- Returns:
224
	--		boolean whether this succeeded
225
226
	enterFullscreen = function (self, hint)
227
		if STRICT then
228
			assert(not self.fullscreen, 'asked to enter fullscreen when already in fullscreen')
229
		end
230
35 by Josh C
cluke009 zoetrope + my spritebatch changes
231
		local modes = love.window.getFullscreenModes()
1 by Josh C
zoetrope 1.4
232
233
		if not hint then
234
			if self.width * 9 == self.height * 16 then
235
				hint = 'letterbox'
236
			elseif self.width * 3 == self.height * 4 then
237
				hint = 'pillar'
238
			end
239
		end
240
241
		-- find the mode with the highest screen area that
242
		-- matches either width or height, according to our hint
243
244
		local bestMode = { area = 0 }
245
246
		for _, mode in pairs(modes) do
247
			mode.area = mode.width * mode.height
248
35 by Josh C
cluke009 zoetrope + my spritebatch changes
249
			if (mode.area > bestMode.area) and
1 by Josh C
zoetrope 1.4
250
			   ((hint == 'letterbox' and mode.width == self.width) or
251
			    (hint == 'pillar' and mode.height == self.height)) then
252
					bestMode = mode
253
			end
254
		end
255
256
		-- if we found a match, switch to it
257
258
		if bestMode.width then
35 by Josh C
cluke009 zoetrope + my spritebatch changes
259
			love.window.setMode(bestMode.width, bestMode.height, {fullscreen = true})
1 by Josh C
zoetrope 1.4
260
			self.fullscreen = true
261
262
			-- and adjust inset and scissor
263
264
			self.inset.x = math.floor((bestMode.width - self.width) / 2)
265
			self.inset.y = math.floor((bestMode.height - self.height) / 2)
266
			love.graphics.setScissor(self.inset.x, self.inset.y, self.width, self.height)
267
268
			if self.onEnterFullscreen then self:onEnterFullscreen() end
269
		end
270
271
		return self.fullscreen
272
	end,
273
274
	-- Method: exitFullscreen
275
	-- Exits fullscreen mode. If the app is already windowed, this has no effect.
276
	--
277
	-- Arguments:
278
	--		none
279
	--
280
	-- Returns:
281
	--		nothing
282
283
	exitFullscreen = function (self)
284
		if STRICT then
285
			assert(self.fullscreen, 'asked to exit fullscreen when already out of fullscreen')
286
		end
35 by Josh C
cluke009 zoetrope + my spritebatch changes
287
288
		love.window.setMode(self.width, self.height, {fullscreen = false})
1 by Josh C
zoetrope 1.4
289
		love.graphics.setScissor(0, 0, self.width, self.height)
290
		self.fullscreen = false
291
		self.inset.x = 0
292
		self.inset.y = 0
293
		if self.onExitFullscreen then self:onExitFullscreen() end
294
	end,
295
296
	-- Method: toggleFullscreen
297
	-- Toggles between windowed and fullscreen mode.
298
	--
299
	-- Arguments:
300
	--		none
301
	--
302
	-- Returns:
303
	--		nothing
304
305
	toggleFullscreen = function (self)
306
		if self.fullscreen then
307
			self:exitFullscreen()
308
		else
309
			self:enterFullscreen()
310
		end
311
	end,
312
313
	-- Method: saveScreenshot
314
	-- Saves a snapshot of the current frame to disk.
315
	--
316
	-- Arguments:
317
	--		filename - filename to save as, image format is implied by suffix.
318
	--				   This is forced inside the app's data directory,
319
	--				   see https://love2d.org/wiki/love.filesystem for details.
320
	--
321
	-- Returns:
322
	--		nothing
323
324
	saveScreenshot = function (self, filename)
325
		if not filename then
326
			error('asked to save screenshot to a nil filename')
327
		end
328
329
		local screenshot = love.graphics.newScreenshot()
330
		screenshot:encode(filename)
331
	end,
332
333
	-- Method: add
334
	-- A shortcut for adding a sprite to the app's view.
335
	--
336
	-- Arguments:
337
	--		sprite - sprite to add
35 by Josh C
cluke009 zoetrope + my spritebatch changes
338
	--
1 by Josh C
zoetrope 1.4
339
	-- Returns:
35 by Josh C
cluke009 zoetrope + my spritebatch changes
340
	--		sprite added
1 by Josh C
zoetrope 1.4
341
342
	add = function (self, sprite)
35 by Josh C
cluke009 zoetrope + my spritebatch changes
343
		return self.view:add(sprite)
1 by Josh C
zoetrope 1.4
344
	end,
345
346
	-- Method: remove
347
	-- A shortcut for removing a sprite from the app's view.
348
	--
349
	-- Arguments:
350
	--		sprite - sprite to remove
351
	--
352
	-- Returns: nothing
353
354
	remove = function (self, sprite)
355
		self.view:remove(sprite)
356
	end,
357
358
	update = function (self, elapsed)
359
		-- set the next frame time right at the start
360
		self._nextFrameTime = self._nextFrameTime + 1 / self.fps
361
362
		elapsed = elapsed - (self._sleepTime or 0)
363
364
		local realElapsed = elapsed
365
		elapsed = elapsed * self.timeScale
366
367
		-- sync the.view with our current view
35 by Josh C
cluke009 zoetrope + my spritebatch changes
368
1 by Josh C
zoetrope 1.4
369
		local view = self.view
370
		if the.view ~= view then the.view = view end
371
372
		-- if we are not active at all, sleep for a half-second
373
374
		if not self.active then
375
			love.timer.sleep(0.5)
376
			self._sleepTime = 0.5
377
			return
378
		end
379
380
		self._sleepTime = 0
381
382
		-- update everyone
383
		-- all update events bubble up from child to parent
35 by Josh C
cluke009 zoetrope + my spritebatch changes
384
		-- (we consider the meta view a little
1 by Josh C
zoetrope 1.4
385
386
		view:startFrame(elapsed)
387
		self.meta:startFrame(elapsed)
388
		if self.onStartFrame then self:onStartFrame(elapsed) end
35 by Josh C
cluke009 zoetrope + my spritebatch changes
389
390
		view:update(elapsed)
1 by Josh C
zoetrope 1.4
391
		self.meta:update(elapsed)
392
		if self.onUpdate then self:onUpdate(elapsed) end
393
394
		view:endFrame(elapsed)
395
		self.meta:endFrame(elapsed)
396
		if self.onEndFrame then self:onEndFrame(elapsed) end
397
	end,
35 by Josh C
cluke009 zoetrope + my spritebatch changes
398
1 by Josh C
zoetrope 1.4
399
	draw = function (self)
35 by Josh C
cluke009 zoetrope + my spritebatch changes
400
drawStart = love.timer.getTime()
1 by Josh C
zoetrope 1.4
401
		local inset = self.inset.x ~= 0 or self.inset.y ~= 0
402
403
		if inset then love.graphics.translate(self.inset.x, self.inset.y) end
404
		self.view:draw()
405
		self.meta:draw()
406
		if inset then love.graphics.translate(0, 0) end
407
408
		-- sleep off any unneeded time to keep up at our FPS
409
35 by Josh C
cluke009 zoetrope + my spritebatch changes
410
		local now = love.timer.getTime()
16 by Josh C
improve performance with (sketchy, experimental) sprite batches
411
the.drawTook = now - drawStart
1 by Josh C
zoetrope 1.4
412
		if self._nextFrameTime < now then
413
			self._nextFrameTime = now
414
		else
415
			love.timer.sleep(self._nextFrameTime - now)
416
		end
417
	end,
418
419
	onFocus = function (self, value)
420
		if self.deactivateOnBlur then
421
			self.active = value
422
423
			if value then
424
				love.audio.resume()
425
			else
426
				love.audio.pause()
427
			end
428
		end
429
	end,
430
431
	__tostring = function (self)
432
		local result = 'App ('
433
434
		if self.active then
435
			result = result .. 'active'
436
		else
437
			result = result .. 'inactive'
438
		end
439
35 by Josh C
cluke009 zoetrope + my spritebatch changes
440
		return result .. ', ' .. tostring(self.fps) .. ' fps, ' .. self.view:count(true) .. ' sprites)'
1 by Josh C
zoetrope 1.4
441
	end
442
}