clean up test mocks before aborting
[view.love.git] / reference.md
blobdf08f282a6cea8b5cbfcc02425ebcc150f99055b
1 # Some useful building blocks
3 Apps can be composed of a wide variety of building blocks that you
4 can use in your functions, including a small number of functions that get
5 automatically called for you as appropriate.
7 ## Variables you can read
9 * `App.screen`
10   * `width` and `height` -- integer dimensions for the app window in pixels.
11   * `flags` -- some properties of the app window. See [`flags` in `love.graphics.getMode`](https://love2d.org/wiki/love.window.getMode)
12     for details.
14 * `Version` -- the running version of LÖVE as a string, e.g. '11.4'.
15 * `Major_version` -- just the part before the period as an int, e.g. 11.
17 ## Functions that get automatically called
19 * `App.initialize_globals()` -- called before running each test and also
20   before the app starts up. As the name suggests, use this to initialize all
21   your global variables to something consistent. I also find it useful to be
22   able to see all my global variables in one place, and avoid defining
23   top-level variables anywhere else (unless they're constants and never going
24   to be modified).
26 * `App.initialize(arg)` -- called when app starts up after
27   `App.initialize_globals`. Provides in `arg` an array of words typed in if
28   you ran it from a terminal window.
29   (Based on [LÖVE](https://love2d.org/wiki/love.load).)
31 * `App.quit()` -- called before the app shuts down.
32   (Based on [LÖVE](https://love2d.org/wiki/love.quit).)
34 * `App.focus(start?)` -- called when the app starts or stops receiving
35   keypresses. `start?` will be `true` when app starts receiving keypresses and
36   `false` when keypresses move to another window.
37   (Based on [LÖVE](https://love2d.org/wiki/love.focus).)
39 * `App.resize(w,h)` -- called when you resize the app window. Provides new
40   window dimensions in `w` and `h`. Don't bother updating `App.screen.width`
41   and `App.screen.height`, that will happen automatically before calling
42   `App.resize`.
43   (Based on [LÖVE](https://love2d.org/wiki/love.resize))
45 * `App.filedropped(file)` -- called when a file icon is dragged and dropped on
46   the app window. Provides in `file` an object representing the file that was
47   dropped, that will respond to the following messages:
49     * `file:getFilename()` returning a string name
50     * `file:read()` returning the entire file contents in a single string
52   (Based on [LÖVE](https://love2d.org/wiki/love.filedropped).)
54 * `App.draw()` -- called to draw on the window, around 30 times a second.
55   (Based on [LÖVE](https://love2d.org/wiki/love.draw).)
57 * `App.update(dt)` -- called after every call to `App.draw`. Make changes to
58   your app's variables here rather than in `App.draw`. Provides in `dt` the
59   time since the previous call to `App.update`, which can be useful for things
60   like smooth animations.
61   (Based on [LÖVE](https://love2d.org/wiki/love.update).)
63 * `App.mousepressed(x,y, mouse_button)` -- called when you press down on a
64   mouse button. Provides in `x` and `y` the point on the screen at which the
65   click occurred, and in `mouse_button` an integer id of the mouse button
66   pressed.
67   `1` is the primary mouse button (the left button on a right-handed mouse),
68   `2` is the secondary button (the right button on a right-handed mouse),
69   and `3` is the middle button. Further buttons are mouse-dependent.
70   (Based on [LÖVE](https://love2d.org/wiki/love.mousepressed).)
72 * `App.mousereleased(x,y, mouse_button)` -- called when you release a mouse
73   button. Provides the same arguments as `App.mousepressed()` above.
74   (Based on [LÖVE](https://love2d.org/wiki/love.mousereleased).)
76 * `App.wheelmoved(dx,dy)` -- called when you use the scroll wheel on a mouse
77   that has it. Provides in `dx` and `dy` an indication of how fast the wheel
78   is being scrolled. Positive values for `dx` indicate movement to the right.
79   Positive values for `dy` indicate upward movement.
80   (Based on [LÖVE](https://love2d.org/wiki/love.wheelmoved).)
82 * `App.keychord_press(chord, key)` -- called when you press a key-combination.
83   Provides in `key` a string name for the key most recently pressed ([valid
84   values](https://love2d.org/wiki/KeyConstant)). Provides in `chord` a
85   string representation of the current key combination, consisting of the key
86   with the following prefixes:
87     * `C-` if one of the `ctrl` keys is pressed,
88     * `M-` if one of the `alt` keys is pressed,
89     * `S-` if one of the `shift` keys is pressed, and
90     * `s-` if the `windows`/`cmd`/`super` key is pressed.
92 * `App.textinput(t)` -- called when you press a key combination that yields
93   (roughly) a printable character. For example, `shift` and `a` pressed
94   together will call `App.textinput` with `A`.
95   (Based on [LÖVE](https://love2d.org/wiki/love.textinput).)
97 * `App.keyreleased(key)` -- called when you press a key on the keyboard.
98   Provides in `key` a string name for the key ([valid values](https://love2d.org/wiki/KeyConstant)).
99   (Based on [LÖVE](https://love2d.org/wiki/love.keyreleased), including other
100   variants.)
102 ## Functions you can call
104 Everything in the [LÖVE](https://love2d.org/wiki/Main_Page) and
105 [Lua](https://www.lua.org/manual/5.1/manual.html) guides is available to you,
106 but here's a brief summary of the most useful primitives. Some primitives have
107 new, preferred names under the `App` namespace, often because these variants
108 are more testable. If you run them within a test you'll be able to make
109 assertions on their side-effects.
111 ### regarding the app window
113 * `width, height, flags = App.screen.size()` -- returns the dimensions and
114   some properties of the app window.
115   (Based on [LÖVE](https://love2d.org/wiki/love.window.getMode).)
117 * `App.screen.resize(width, height, flags)` -- modify the size and properties
118   of the app window. The OS may or may not act on the request.
119   (Based on [LÖVE](https://love2d.org/wiki/love.window.setMode).)
121 * `x, y, displayindex = App.screen.position()` -- returns the coordinates and
122   monitor index (if you have more than one monitor) for the top-left corner of
123   the app window.
124   (Based on [LÖVE](https://love2d.org/wiki/love.window.getPosition).)
126 * `App.screen.move(x, y, displayindex)` -- moves the app window so its
127   top-left corner is at the specified coordinates of the specified monitor.
128   The OS may or may not act on the request.
129   (Based on [LÖVE](https://love2d.org/wiki/love.window.setPosition).)
131 ### drawing to the app window
133 * `App.screen.print(text, x,y)` -- print the given `text` in the current font
134   using the current color so its top-left corner is at the specified
135   coordinates of the app window.
136   (Based on [LÖVE](https://love2d.org/wiki/love.graphics.print).)
138 * `love.graphics.getFont()` -- returns a representation of the current font.
139   (From [LÖVE](https://love2d.org/wiki/love.graphics.getFont).)
141 * `love.graphics.setFont(font)` -- switches the current font to `font`.
142   (From [LÖVE](https://love2d.org/wiki/love.graphics.setFont).)
144 * `love.graphics.newFont(filename)` -- creates a font from the given font
145   file.
146   (From [LÖVE](https://love2d.org/wiki/love.graphics.newFont), including other
147   variants.)
149 * `App.width(text)` returns the width of `text` in pixels when rendered using
150   the current font.
151   (Based on [LÖVE](https://love2d.org/wiki/Font:getWidth).)
153 * `App.color(color)` -- sets the current color based on the fields `r`, `g`,
154   `b` and `a` (for opacity) of the table `color`.
155   (Based on [LÖVE](https://love2d.org/wiki/love.graphics.setColor).)
157 * `love.graphics.line(x1,y1, x2,y2)` -- draws a line from (`x1`,`y1`) to
158   (`x2`, `y2`) in the app window using the current color, clipping data for
159   negative coordinates and coordinates outside (`App.screen.width`,
160   `App.screen.height`)
161   (From [LÖVE](https://love2d.org/wiki/love.graphics.line), including other
162   variants.)
164 * `love.graphics.rectangle(mode, x, y, w, h)` -- draws a rectangle using the
165   current color, with a top-left corner at (`x`, `y`), with dimensions `width`
166   along the x axis and `height` along the y axis
167   (though check out https://love2d.org/wiki/love.graphics for ways to scale
168   and rotate shapes).
169   `mode` is a string, either `'line'` (to draw just the outline) and `'fill'`.
170   (From [LÖVE](https://love2d.org/wiki/love.graphics.circle), including other
171   variants.)
173 * `love.graphics.circle(mode, x, y, r)` -- draws a circle using the current
174   color, centered at (`x`, `y`) and with radius `r`.
175   `mode` is a string, either `'line'` and `'fill'`.
176   (From [LÖVE](https://love2d.org/wiki/love.graphics.circle), including other
177   variants.)
179 * `love.graphics.arc(mode, x, y, r, angle1, angle2)` -- draws an arc of a
180   circle using the current color, centered at (`x`, `y`) and with radius `r`.
181   `mode` is a string, either `'line'` and `'fill'`.
182   `angle1` and `angle2` are in [radians](https://en.wikipedia.org/wiki/Radian).
183   (From [LÖVE](https://love2d.org/wiki/love.graphics.circle), including other
184   variants.)
186 There's much more I could include here; check out [the LÖVE manual](https://love2d.org/wiki/love.graphics).
188 ### text editor primitives
190 The text-editor widget includes extremely thorough automated tests to give you
191 early warning if you break something.
193 * `state = edit.initialize_state(top, left, right, font, font_height, line_height)`
194   -- returns an object that can be used to render an interactive editor widget
195   for text and line drawings starting at `y=top` on the app window, between
196   `x=left` and `x=right`. Wraps long lines at word boundaries where possible,
197   or in the middle of words (no hyphenation yet) when it must.
199 * `edit.quit()` -- calling this ensures any final edits are flushed to disk
200   before the app exits.
202 * `edit.draw(state)` -- call this from `App.draw` to display the current
203   editor state on the app window as requested in the call to
204   `edit.initialize_state` that created `state`.
206 * `edit.mouse_press(state, x,y, mouse_button)` and `edit.mouse_release(x,y,
207   mouse_button)` -- call these to position the cursor or select some text.
209 * `edit.mouse_wheel_move(state, dx,dy)` -- call this to scroll the editor in
210   response to a mouse wheel.
212 * `edit.keychord_press(state, chord, key)` and `edit.key_release(state, key)`
213   -- call these to perform some standard shortcuts: insert new lines,
214   backspace/delete, zoom in/out font size, cut/copy/paste to and from the
215   clipboard, undo/redo.
217 * `edit.text_input(state, t)` -- call this to insert keystrokes into the
218   buffer.
220 * `Text.redraw_all(state)` -- call this to clear and recompute any cached
221   state as the cursor moves and the buffer scrolls.
223 * `edit.update(state, dt)` -- call this from `App.update` to periodically
224   auto-save editor contents to disk.
226 * `edit.quit(state)` -- call this from `App.quit` to ensure any final edits
227   get saved before quitting.
229 If you need more precise control, look at the comment at the top of
230 `edit.initialize_state` in edit.lua. In brief, the widget contains an array of
231 `lines`. Positions in the buffer are described in _schema-1_ locations
232 consisting of a `line` index and a code-point `pos`. We may also convert them
233 at times to _schema-2_ locations consisting of a `line`, `screen_line` and
234 `pos` that better indicates how long lines wrap. Schema-2 locations are never
235 persisted, just generated as needed from schema-1. Important schema-1
236 locations in the widget are `cursor1` describing where text is inserted or
237 deleted and `screen_top1` which specifies how far down the lines is currently
238 visible on screen.
240 Some constants that affect editor behavior:
241 * `Margin_top`, `Margin_left`, `Margin_right` are integers in pixel units that
242   affect where the editor is drawn on window (it always extends to bottom of
243   window as needed)
244 * `Drawing_padding_top` and `Drawing_padding_bottom` affect spacing around
245   drawings.
247 * Various color constants are represented as tables with r/g/b keys:
248   * `Text_color`, `Cursor_color`, `Highlight_color` for drawing text.
249   * `Stroke_color`, `Current_stroke_color` for line drawings.
250   * `Icon_color` affects the color of the little mode icon on the top right of
251     a drawing.
252   * `Current_name_background_color` manages the color when naming points using
253     `ctrl+n`.
254   * `Focus_stroke_color` affects the color of a point or line when you hover
255     over it.
256   * `Help_color` and `Help_background_color` affect the color of online help
257     within line drawings.
259 ### clickable buttons
261 There's a facility for rendering buttons and responding to events when they're
262 clicked. It requires setting up 3 things:
263   - a `state` table housing all buttons. Can be the same `state` variable the
264     text-editor widget uses, but doesn't have to be.
265   - specifying buttons to create in `state`. This must happen either directly
266     or indirectly within `App.draw`.
267   - responding to clicks on buttons in `state`. This must happen either
268     directly or indirectly within `App.mousepressed`.
270 The following facilities help set these things up:
272 * Clear `state` at the start of each frame:
274     ```
275     state.button_handlers = {}
276     ```
278   Don't forget to do this, or your app will get slower over time.
280 * `button` creates a single button. The syntax is:
282     ```
283     button(state, name, {x=..., y=..., w=..., h=..., bg={r,g,b},
284       icon = function({x=..., y=..., w=..., h=...}) ... end,
285       onpress1 = ...
286     })
287     ```
289   Call this either directly or indirectly from `App.draw`. It will assign a
290   rectangle with the given dimensions and trigger the provided (zero-arg)
291   `onpress1` callback when the primary mouse button is clicked within.
292   It will also optionally paint the rectangle with the specified background
293   color `bg` and a foreground described by the `icon` callback (which will
294   receive the same dimensions).
296   This way you can see everything about a button in one place. Create as many
297   buttons as you like within a single shared `state`.
299 * `mouse_press_consumed_by_any_button(state, x,y, mouse_button)`
301   Call this either directly or indirectly from `App.mousepressed`. It will
302   pass on a click to any button registered in `state`. It's also helpful to
303   ensure clicks on a button don't have other effects, so I prefer the
304   following boilerplate early in `mousepressed`:
306     ```
307     if mouse_press_consumed_by_any_button(state, x,y, mouse_button) then
308       return
309     end
310     ```
312 ### mouse primitives
314 * `App.mouse_move(x, y)` -- sets the current position of the mouse to (`x`,
315   `y`).
316   (Based on [LÖVE](https://love2d.org/wiki/love.mouse.setPosition).)
318 * `App.mouse_down(mouse_button)` -- returns `true` if the button
319   `mouse_button` is pressed. See `App.mousepressed` for `mouse_button` codes.
320   (Based on [LÖVE](https://love2d.org/wiki/love.mouse.isDown).)
322 * `App.mouse_x()` -- returns the x coordinate of the current position of the
323   mouse.
324   (Based on [LÖVE](https://love2d.org/wiki/love.mouse.getX).)
326 * `App.mouse_y()` -- returns the x coordinate of the current position of the
327   mouse.
328   (Based on [LÖVE](https://love2d.org/wiki/love.mouse.getY).)
330 ### keyboard primitives
332 * `App.is_cursor_movement(key)` -- return `true` if `key` is a cursor movement
333   key (arrow keys, page-up/down, home/end)
335 * `App.cmd_down()`, `App.ctrl_down`, `App.alt_down()`, `App.shift_down()` --
336   predicates for different modifier keys.
338 * `App.any_modifier_down()` -- returns `true` if any of the modifier keys is
339   currently pressed.
341 * `App.key_down(key)` -- returns `true` if the given key is currently pressed.
342   (Based on [LÖVE](https://love2d.org/wiki/love.keyboard.isDown).)
344 ### interacting with files
346 * `App.open_for_reading(filename)` -- returns a file handle that you can
347   [`read()`](https://www.lua.org/manual/5.1/manual.html#pdf-file:read) from.
348   Make sure `filename` is an absolute path so that your app can work reliably
349   by double-clicking on it.
350   (Based on [Lua](https://www.lua.org/manual/5.1/manual.html#pdf-io.open).)
352 * `App.open_for_writing(filename)` -- returns a file handle that you can
353   [`write()`](https://www.lua.org/manual/5.1/manual.html#pdf-file:write) to.
354   Make sure `filename` is an absolute path so that your app can work reliably
355   by double-clicking on it.
356   (Based on [Lua](https://www.lua.org/manual/5.1/manual.html#pdf-io.open).)
358 * `json.encode(obj)` -- returns a JSON string for an object `obj` that will
359   recreate `obj` when passed to `json.decode`. `obj` can be of most types but
360   has some exceptions.
361   (From [json.lua](https://github.com/rxi/json.lua).)
363 * `json.decode(obj)` -- turns a JSON string into a Lua object.
364   (From [json.lua](https://github.com/rxi/json.lua).)
366 * `App.files(dir)` -- returns an unsorted array of the files and directories
367   available under `dir`.
368   (From [LÖVE](https://love2d.org/wiki/love.filesystem.getDirectoryItems).]
370 * `App.file_info(filename)` -- returns some information about
371   `filename`, particularly whether it exists (non-`nil` return value) or not.
372   (From [LÖVE](https://love2d.org/wiki/love.filesystem.getInfo).]
374 * `App.mkdir(path)` -- creates a directory. Make sure `path` is absolute.
375   (From [LÖVE](https://love2d.org/wiki/love.filesystem.remove).]
377 * `App.remove(filename)` -- removes a file or empty directory. Definitely make
378   sure `filename` is an absolute path.
379   (From [LÖVE](https://love2d.org/wiki/love.filesystem.remove).]
381 There's much more I could include here; check out [the LÖVE manual](https://love2d.org/wiki/love.filesystem)
382 and [the Lua manual](https://www.lua.org/manual/5.1/manual.html#5.7).
384 ### desiderata
386 * `App.get_time()` -- returns the number of seconds elapsed since some
387   unspecified start time.
388   (Based on [LÖVE](https://love2d.org/wiki/love.timer.getTime).)
390 * `App.get_clipboard()` -- returns a string with the current clipboard
391   contents.
392   (Based on [LÖVE](https://love2d.org/wiki/love.system.getClipboardText).)
394 * `App.set_clipboard(text)` -- stores the string `text` in the clipboard.
395   (Based on [LÖVE](https://love2d.org/wiki/love.system.setClipboardText).)
397 * `array.find(arr, elem)` -- scan table `arr` for `elem` assuming it's
398   organized as an array (just numeric indices).
400 * `array.any(arr, f)` -- scan table `arr` for any elements satisfying
401   predicate `f`. Return first such element or `false` if none.
403 There's much more I could include here; check out [the LÖVE manual](https://love2d.org/wiki)
404 and [the Lua manual](https://www.lua.org/manual/5.1/manual.html).
406 ### writing tests
408 * `App.screen.init{width=.., height=..}` -- creates a fake screen for a test
410 * `App.screen.check(y, expected_contents, msg)` -- verifies text written to
411   the fake screen at `y`. This isn't very realistic; `y` must exactly match
412   what was displayed, and the expected contents show everything printed to
413   that `y` in chronological order, regardless of `x` coordinate. In spite of
414   these limitations, you can write lots of useful tests with this.
416 * `App.run_after_textinput(t)` -- mimics keystrokes resulting in `t` and then
417   draws one frame.
419 * `App.run_after_keychord(chord)` -- mimics keystrokes resulting in `chord`
420   and then draws one frame.
422 * `App.run_after_mouse_press(x,y, mouse_button)` -- mimics a mouse press down
423   followed by drawing a frame.
425 * `App.run_after_mouse_release(x,y, mouse_button)` -- mimics a mouse release
426   up followed by drawing a frame.
428 * `App.run_after_mouse_click(x,y, mouse_button)` -- mimics a mouse press down
429   and mouse release up followed by drawing a frame.
431 * `App.wait_fake_time(t)` -- simulates the passage of time for `App.getTime()`.