2
-- An app is where all the magic happens. :) It contains a
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
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>.
11
-- Once an app has begun running, it may be accessed globally via
18
-- Called once, when the app begins running.
20
-- Event: onEnterFullscreen
21
-- Called after entering fullscreen successfully.
23
-- Event: onExitFullscreen
24
-- Called after exiting fullscreen successfully.
29
-- This is shown in the window title bar.
33
-- A path to an image to use as the window icon (a 32x32 PNG is recommended).
34
-- This doesn't affect the actual executable's icon in the taskbar or dock.
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.
42
-- Property: timeScale
43
-- Multiplier for elapsed time; 1.0 is normal, 0 is completely frozen.
47
-- If false, nothing receives update-related events, including the meta view.
48
-- These events specifically are onStartFrame, onUpdate, and onEndFrame.
51
-- Property: deactivateOnBlur
52
-- Should the app automatically set its active property to false when its
53
-- window loses focus?
54
deactivateOnBlur = true,
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.
62
-- A <Group> that persists across all views during the app's lifespan.
65
-- A <Keys> object that listens to the keyboard. When the app is running, this
66
-- is also accessible globally via <the>.keys.
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.
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.
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.
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},
89
-- internal property: _sleepTime
90
-- The amount of time the app intentionally slept for, and should not
91
-- be counted against elapsed time.
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.
98
new = function (self, obj)
99
obj = self:extend(obj)
101
-- set icon if possible
104
love.graphics.setIcon(Cached:image(self.icon))
109
obj.meta = obj.meta or Group:new()
110
obj.view = obj.view or View:new()
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)
121
obj.mouse = obj.mouse or Mouse:new()
122
obj.meta:add(obj.mouse)
125
if love.joystick then
127
the.gamepads = obj.gamepads
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])
137
-- screen dimensions and state
139
obj.width, obj.height, obj.fullscreen = love.graphics.getMode()
144
if obj.onNew then obj:onNew() end
149
-- Starts the app running. Nothing will occur until this call.
157
run = function (self)
158
math.randomseed(os.time())
164
-- attach debug console
167
self.console = DebugConsole:new()
168
self.meta:add(self.console)
173
love.graphics.setCaption(self.name)
174
love.update = function (elapsed) self:update(elapsed) end
175
love.draw = function() self:draw() end
176
love.focus = function (value) self:onFocus(value) end
178
if self.onRun then self:onRun() end
179
self._nextFrameTime = love.timer.getMicroTime()
183
-- Quits the application immediately.
191
quit = function (self)
195
-- Method: useSysCursor
196
-- Shows or hides the system mouse cursor.
199
-- value - show the cursor?
204
useSysCursor = function (self, value)
206
assert(value == true or value == false,
207
'tried to set system cursor visibility to ' .. type(value))
210
love.mouse.setVisible(value)
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.
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.
224
-- boolean whether this succeeded
226
enterFullscreen = function (self, hint)
228
assert(not self.fullscreen, 'asked to enter fullscreen when already in fullscreen')
231
local modes = love.graphics.getModes()
234
if self.width * 9 == self.height * 16 then
236
elseif self.width * 3 == self.height * 4 then
241
-- find the mode with the highest screen area that
242
-- matches either width or height, according to our hint
244
local bestMode = { area = 0 }
246
for _, mode in pairs(modes) do
247
mode.area = mode.width * mode.height
249
if (mode.area > bestMode.area) and
250
((hint == 'letterbox' and mode.width == self.width) or
251
(hint == 'pillar' and mode.height == self.height)) then
256
-- if we found a match, switch to it
258
if bestMode.width then
259
love.graphics.setMode(bestMode.width, bestMode.height, true)
260
self.fullscreen = true
262
-- and adjust inset and scissor
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)
268
if self.onEnterFullscreen then self:onEnterFullscreen() end
271
return self.fullscreen
274
-- Method: exitFullscreen
275
-- Exits fullscreen mode. If the app is already windowed, this has no effect.
283
exitFullscreen = function (self)
285
assert(self.fullscreen, 'asked to exit fullscreen when already out of fullscreen')
288
love.graphics.setMode(self.width, self.height, false)
289
love.graphics.setScissor(0, 0, self.width, self.height)
290
self.fullscreen = false
293
if self.onExitFullscreen then self:onExitFullscreen() end
296
-- Method: toggleFullscreen
297
-- Toggles between windowed and fullscreen mode.
305
toggleFullscreen = function (self)
306
if self.fullscreen then
307
self:exitFullscreen()
309
self:enterFullscreen()
313
-- Method: saveScreenshot
314
-- Saves a snapshot of the current frame to disk.
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.
324
saveScreenshot = function (self, filename)
326
error('asked to save screenshot to a nil filename')
329
local screenshot = love.graphics.newScreenshot()
330
screenshot:encode(filename)
334
-- A shortcut for adding a sprite to the app's view.
337
-- sprite - sprite to add
342
add = function (self, sprite)
343
self.view:add(sprite)
347
-- A shortcut for removing a sprite from the app's view.
350
-- sprite - sprite to remove
354
remove = function (self, sprite)
355
self.view:remove(sprite)
358
update = function (self, elapsed)
359
-- set the next frame time right at the start
360
self._nextFrameTime = self._nextFrameTime + 1 / self.fps
362
elapsed = elapsed - (self._sleepTime or 0)
364
local realElapsed = elapsed
365
elapsed = elapsed * self.timeScale
367
-- sync the.view with our current view
369
local view = self.view
370
if the.view ~= view then the.view = view end
372
-- if we are not active at all, sleep for a half-second
374
if not self.active then
375
love.timer.sleep(0.5)
376
self._sleepTime = 0.5
383
-- all update events bubble up from child to parent
384
-- (we consider the meta view a little
386
view:startFrame(elapsed)
387
self.meta:startFrame(elapsed)
388
if self.onStartFrame then self:onStartFrame(elapsed) end
391
self.meta:update(elapsed)
392
if self.onUpdate then self:onUpdate(elapsed) end
394
view:endFrame(elapsed)
395
self.meta:endFrame(elapsed)
396
if self.onEndFrame then self:onEndFrame(elapsed) end
399
draw = function (self)
400
local inset = self.inset.x ~= 0 or self.inset.y ~= 0
402
if inset then love.graphics.translate(self.inset.x, self.inset.y) end
405
if inset then love.graphics.translate(0, 0) end
407
-- sleep off any unneeded time to keep up at our FPS
409
local now = love.timer.getMicroTime()
411
if self._nextFrameTime < now then
412
self._nextFrameTime = now
414
love.timer.sleep(self._nextFrameTime - now)
418
onFocus = function (self, value)
419
if self.deactivateOnBlur then
430
__tostring = function (self)
431
local result = 'App ('
434
result = result .. 'active'
436
result = result .. 'inactive'
439
return result .. ', ' .. self.fps .. ' fps, ' .. self.view:count(true) .. ' sprites)'