/traderous

To get this branch, use:
bzr branch http://9ix.org/bzr/traderous

« back to all changes in this revision

Viewing changes to zoetrope/core/app.lua

  • Committer: Josh C
  • Date: 2013-05-04 20:45:17 UTC
  • Revision ID: josh@9ix.org-20130504204517-1rfp92svud12kg42
zoetrope 1.4

Show diffs side-by-side

added added

removed removed

Lines of Context:
 
1
-- Class: App 
 
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
 
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>.
 
10
-- 
 
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',
 
31
        
 
32
        -- Property: icon
 
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. 
 
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,
 
41
        
 
42
        -- Property: timeScale
 
43
        -- Multiplier for elapsed time; 1.0 is normal, 0 is completely frozen.
 
44
        timeScale = 1,
 
45
        
 
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,
 
55
        
 
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
 
102
                
 
103
                if self.icon then
 
104
                        love.graphics.setIcon(Cached:image(self.icon))
 
105
                end
 
106
        
 
107
                -- view containers
 
108
 
 
109
                obj.meta = obj.meta or Group:new()
 
110
                obj.view = obj.view or View:new()               
 
111
                
 
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
 
 
139
                obj.width, obj.height, obj.fullscreen = love.graphics.getMode()
 
140
 
 
141
                -- housekeeping
 
142
                
 
143
                the.app = obj
 
144
                if obj.onNew then obj:onNew() end
 
145
                return obj
 
146
        end,
 
147
 
 
148
        -- Method: run
 
149
        -- Starts the app running. Nothing will occur until this call.
 
150
        --
 
151
        -- Arguments:
 
152
        --              none
 
153
        -- 
 
154
        -- Returns:
 
155
        --              nothing
 
156
 
 
157
        run = function (self)
 
158
                math.randomseed(os.time())
 
159
 
 
160
                -- sync the.view
 
161
 
 
162
                the.view = self.view
 
163
 
 
164
                -- attach debug console
 
165
 
 
166
                if DEBUG then
 
167
                        self.console = DebugConsole:new()
 
168
                        self.meta:add(self.console)
 
169
                end
 
170
 
 
171
                -- set up callbacks
 
172
                
 
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   
 
177
 
 
178
                if self.onRun then self:onRun() end
 
179
                self._nextFrameTime = love.timer.getMicroTime()
 
180
        end,
 
181
        
 
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
 
203
        
 
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
 
 
231
                local modes = love.graphics.getModes()
 
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
 
 
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
 
252
                                        bestMode = mode
 
253
                        end
 
254
                end
 
255
 
 
256
                -- if we found a match, switch to it
 
257
 
 
258
                if bestMode.width then
 
259
                        love.graphics.setMode(bestMode.width, bestMode.height, true)
 
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
 
287
        
 
288
                love.graphics.setMode(self.width, self.height, false)
 
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
 
338
        -- 
 
339
        -- Returns:
 
340
        --              nothing
 
341
 
 
342
        add = function (self, sprite)
 
343
                self.view:add(sprite)
 
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
 
368
                
 
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
 
384
                -- (we consider the meta view a little 
 
385
 
 
386
                view:startFrame(elapsed)
 
387
                self.meta:startFrame(elapsed)
 
388
                if self.onStartFrame then self:onStartFrame(elapsed) end
 
389
                
 
390
                view:update(elapsed)    
 
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,
 
398
        
 
399
        draw = function (self)
 
400
                local inset = self.inset.x ~= 0 or self.inset.y ~= 0
 
401
 
 
402
                if inset then love.graphics.translate(self.inset.x, self.inset.y) end
 
403
                self.view:draw()
 
404
                self.meta:draw()
 
405
                if inset then love.graphics.translate(0, 0) end
 
406
 
 
407
                -- sleep off any unneeded time to keep up at our FPS
 
408
 
 
409
                local now = love.timer.getMicroTime()
 
410
 
 
411
                if self._nextFrameTime < now then
 
412
                        self._nextFrameTime = now
 
413
                else
 
414
                        love.timer.sleep(self._nextFrameTime - now)
 
415
                end
 
416
        end,
 
417
 
 
418
        onFocus = function (self, value)
 
419
                if self.deactivateOnBlur then
 
420
                        self.active = value
 
421
 
 
422
                        if value then
 
423
                                love.audio.resume()
 
424
                        else
 
425
                                love.audio.pause()
 
426
                        end
 
427
                end
 
428
        end,
 
429
 
 
430
        __tostring = function (self)
 
431
                local result = 'App ('
 
432
 
 
433
                if self.active then
 
434
                        result = result .. 'active'
 
435
                else
 
436
                        result = result .. 'inactive'
 
437
                end
 
438
 
 
439
                return result .. ', ' .. self.fps .. ' fps, ' .. self.view:count(true) .. ' sprites)'
 
440
        end
 
441
}