/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
37 by Josh C
zoetrope scaling patch
139
    local w, h, flags = love.window.getMode()
140
    if not obj.width then obj.width = w end
141
    if not obj.height then obj.height = h end
142
    obj.windowflags = flags
35 by Josh C
cluke009 zoetrope + my spritebatch changes
143
		obj.fullscreen = obj.windowflags.fullscreen
1 by Josh C
zoetrope 1.4
144
145
		-- housekeeping
35 by Josh C
cluke009 zoetrope + my spritebatch changes
146
1 by Josh C
zoetrope 1.4
147
		the.app = obj
148
		if obj.onNew then obj:onNew() end
149
		return obj
150
	end,
151
152
	-- Method: run
153
	-- Starts the app running. Nothing will occur until this call.
154
	--
155
	-- Arguments:
156
	-- 		none
35 by Josh C
cluke009 zoetrope + my spritebatch changes
157
	--
1 by Josh C
zoetrope 1.4
158
	-- Returns:
159
	--		nothing
160
161
	run = function (self)
162
		math.randomseed(os.time())
163
164
		-- sync the.view
165
166
		the.view = self.view
167
168
		-- attach debug console
169
170
		if DEBUG then
35 by Josh C
cluke009 zoetrope + my spritebatch changes
171
			debugger.init()
1 by Josh C
zoetrope 1.4
172
		end
173
174
		-- set up callbacks
35 by Josh C
cluke009 zoetrope + my spritebatch changes
175
176
		love.window.setTitle(self.name)
1 by Josh C
zoetrope 1.4
177
		love.update = function (elapsed) self:update(elapsed) end
178
		love.draw = function() self:draw() end
35 by Josh C
cluke009 zoetrope + my spritebatch changes
179
		love.focus = function (value) self:onFocus(value) end
1 by Josh C
zoetrope 1.4
180
181
		if self.onRun then self:onRun() end
35 by Josh C
cluke009 zoetrope + my spritebatch changes
182
		self._nextFrameTime = love.timer.getTime()
1 by Josh C
zoetrope 1.4
183
	end,
35 by Josh C
cluke009 zoetrope + my spritebatch changes
184
1 by Josh C
zoetrope 1.4
185
	-- Method: quit
186
	-- Quits the application immediately.
187
	--
188
	-- Arguments:
189
	--		none
190
	--
191
	-- Returns:
192
	--		nothing
193
194
	quit = function (self)
195
		love.event.quit()
196
	end,
197
198
	-- Method: useSysCursor
199
	-- Shows or hides the system mouse cursor.
200
	--
201
	-- Arguments:
202
	--		value - show the cursor?
203
	--
204
	-- Returns:
205
	--		nothing
35 by Josh C
cluke009 zoetrope + my spritebatch changes
206
1 by Josh C
zoetrope 1.4
207
	useSysCursor = function (self, value)
208
		if STRICT then
209
			assert(value == true or value == false,
210
				   'tried to set system cursor visibility to ' .. type(value))
211
		end
212
213
		love.mouse.setVisible(value)
214
	end,
215
216
	-- Method: enterFullscreen
217
	-- Enters fullscreen mode. If the app is already in fullscreen, this has no effect.
218
	-- This tries to use the highest resolution that will not result in distortion, and
219
	-- adjust the app's offset property to accomodate this.
220
	--
221
	-- Arguments:
222
	--		hint - whether to try to letterbox (vertical black bars) or pillar the app
223
	--			   (horizontal black bars). You don't need to specify this; the method
224
	--			   will try to infer based on the aspect ratio of your app.
225
	--
226
	-- Returns:
227
	--		boolean whether this succeeded
228
229
	enterFullscreen = function (self, hint)
230
		if STRICT then
231
			assert(not self.fullscreen, 'asked to enter fullscreen when already in fullscreen')
232
		end
233
35 by Josh C
cluke009 zoetrope + my spritebatch changes
234
		local modes = love.window.getFullscreenModes()
1 by Josh C
zoetrope 1.4
235
236
		if not hint then
237
			if self.width * 9 == self.height * 16 then
238
				hint = 'letterbox'
239
			elseif self.width * 3 == self.height * 4 then
240
				hint = 'pillar'
241
			end
242
		end
243
244
		-- find the mode with the highest screen area that
245
		-- matches either width or height, according to our hint
246
247
		local bestMode = { area = 0 }
248
249
		for _, mode in pairs(modes) do
250
			mode.area = mode.width * mode.height
251
35 by Josh C
cluke009 zoetrope + my spritebatch changes
252
			if (mode.area > bestMode.area) and
1 by Josh C
zoetrope 1.4
253
			   ((hint == 'letterbox' and mode.width == self.width) or
254
			    (hint == 'pillar' and mode.height == self.height)) then
255
					bestMode = mode
256
			end
257
		end
258
259
		-- if we found a match, switch to it
260
261
		if bestMode.width then
35 by Josh C
cluke009 zoetrope + my spritebatch changes
262
			love.window.setMode(bestMode.width, bestMode.height, {fullscreen = true})
1 by Josh C
zoetrope 1.4
263
			self.fullscreen = true
264
265
			-- and adjust inset and scissor
266
267
			self.inset.x = math.floor((bestMode.width - self.width) / 2)
268
			self.inset.y = math.floor((bestMode.height - self.height) / 2)
269
			love.graphics.setScissor(self.inset.x, self.inset.y, self.width, self.height)
270
271
			if self.onEnterFullscreen then self:onEnterFullscreen() end
272
		end
273
274
		return self.fullscreen
275
	end,
276
277
	-- Method: exitFullscreen
278
	-- Exits fullscreen mode. If the app is already windowed, this has no effect.
279
	--
280
	-- Arguments:
281
	--		none
282
	--
283
	-- Returns:
284
	--		nothing
285
286
	exitFullscreen = function (self)
287
		if STRICT then
288
			assert(self.fullscreen, 'asked to exit fullscreen when already out of fullscreen')
289
		end
35 by Josh C
cluke009 zoetrope + my spritebatch changes
290
291
		love.window.setMode(self.width, self.height, {fullscreen = false})
1 by Josh C
zoetrope 1.4
292
		love.graphics.setScissor(0, 0, self.width, self.height)
293
		self.fullscreen = false
294
		self.inset.x = 0
295
		self.inset.y = 0
296
		if self.onExitFullscreen then self:onExitFullscreen() end
297
	end,
298
299
	-- Method: toggleFullscreen
300
	-- Toggles between windowed and fullscreen mode.
301
	--
302
	-- Arguments:
303
	--		none
304
	--
305
	-- Returns:
306
	--		nothing
307
308
	toggleFullscreen = function (self)
309
		if self.fullscreen then
310
			self:exitFullscreen()
311
		else
312
			self:enterFullscreen()
313
		end
314
	end,
315
316
	-- Method: saveScreenshot
317
	-- Saves a snapshot of the current frame to disk.
318
	--
319
	-- Arguments:
320
	--		filename - filename to save as, image format is implied by suffix.
321
	--				   This is forced inside the app's data directory,
322
	--				   see https://love2d.org/wiki/love.filesystem for details.
323
	--
324
	-- Returns:
325
	--		nothing
326
327
	saveScreenshot = function (self, filename)
328
		if not filename then
329
			error('asked to save screenshot to a nil filename')
330
		end
331
332
		local screenshot = love.graphics.newScreenshot()
333
		screenshot:encode(filename)
334
	end,
335
336
	-- Method: add
337
	-- A shortcut for adding a sprite to the app's view.
338
	--
339
	-- Arguments:
340
	--		sprite - sprite to add
35 by Josh C
cluke009 zoetrope + my spritebatch changes
341
	--
1 by Josh C
zoetrope 1.4
342
	-- Returns:
35 by Josh C
cluke009 zoetrope + my spritebatch changes
343
	--		sprite added
1 by Josh C
zoetrope 1.4
344
345
	add = function (self, sprite)
35 by Josh C
cluke009 zoetrope + my spritebatch changes
346
		return self.view:add(sprite)
1 by Josh C
zoetrope 1.4
347
	end,
348
349
	-- Method: remove
350
	-- A shortcut for removing a sprite from the app's view.
351
	--
352
	-- Arguments:
353
	--		sprite - sprite to remove
354
	--
355
	-- Returns: nothing
356
357
	remove = function (self, sprite)
358
		self.view:remove(sprite)
359
	end,
360
361
	update = function (self, elapsed)
362
		-- set the next frame time right at the start
363
		self._nextFrameTime = self._nextFrameTime + 1 / self.fps
364
365
		elapsed = elapsed - (self._sleepTime or 0)
366
367
		local realElapsed = elapsed
368
		elapsed = elapsed * self.timeScale
369
370
		-- sync the.view with our current view
35 by Josh C
cluke009 zoetrope + my spritebatch changes
371
1 by Josh C
zoetrope 1.4
372
		local view = self.view
373
		if the.view ~= view then the.view = view end
374
375
		-- if we are not active at all, sleep for a half-second
376
377
		if not self.active then
378
			love.timer.sleep(0.5)
379
			self._sleepTime = 0.5
380
			return
381
		end
382
383
		self._sleepTime = 0
384
385
		-- update everyone
386
		-- all update events bubble up from child to parent
35 by Josh C
cluke009 zoetrope + my spritebatch changes
387
		-- (we consider the meta view a little
1 by Josh C
zoetrope 1.4
388
389
		view:startFrame(elapsed)
390
		self.meta:startFrame(elapsed)
391
		if self.onStartFrame then self:onStartFrame(elapsed) end
35 by Josh C
cluke009 zoetrope + my spritebatch changes
392
393
		view:update(elapsed)
1 by Josh C
zoetrope 1.4
394
		self.meta:update(elapsed)
395
		if self.onUpdate then self:onUpdate(elapsed) end
396
397
		view:endFrame(elapsed)
398
		self.meta:endFrame(elapsed)
399
		if self.onEndFrame then self:onEndFrame(elapsed) end
400
	end,
35 by Josh C
cluke009 zoetrope + my spritebatch changes
401
1 by Josh C
zoetrope 1.4
402
	draw = function (self)
35 by Josh C
cluke009 zoetrope + my spritebatch changes
403
drawStart = love.timer.getTime()
1 by Josh C
zoetrope 1.4
404
		local inset = self.inset.x ~= 0 or self.inset.y ~= 0
405
406
		if inset then love.graphics.translate(self.inset.x, self.inset.y) end
407
		self.view:draw()
408
		self.meta:draw()
409
		if inset then love.graphics.translate(0, 0) end
410
411
		-- sleep off any unneeded time to keep up at our FPS
412
35 by Josh C
cluke009 zoetrope + my spritebatch changes
413
		local now = love.timer.getTime()
16 by Josh C
improve performance with (sketchy, experimental) sprite batches
414
the.drawTook = now - drawStart
1 by Josh C
zoetrope 1.4
415
		if self._nextFrameTime < now then
416
			self._nextFrameTime = now
417
		else
418
			love.timer.sleep(self._nextFrameTime - now)
419
		end
420
	end,
421
422
	onFocus = function (self, value)
423
		if self.deactivateOnBlur then
424
			self.active = value
425
426
			if value then
427
				love.audio.resume()
428
			else
429
				love.audio.pause()
430
			end
431
		end
432
	end,
433
434
	__tostring = function (self)
435
		local result = 'App ('
436
437
		if self.active then
438
			result = result .. 'active'
439
		else
440
			result = result .. 'inactive'
441
		end
442
35 by Josh C
cluke009 zoetrope + my spritebatch changes
443
		return result .. ', ' .. tostring(self.fps) .. ' fps, ' .. self.view:count(true) .. ' sprites)'
1 by Josh C
zoetrope 1.4
444
	end
445
}