text.love/reference.md

18 KiB

Some useful building blocks

Apps can be composed of a wide variety of building blocks that you can use in your functions, including a small number of functions that get automatically called for you as appropriate.

Variables you can read

  • App.screen

    • width and height -- integer dimensions for the app window in pixels.
    • flags -- some properties of the app window. See flags in love.graphics.getMode for details.
  • Version -- the running version of LÖVE as a string, e.g. '11.4'.

  • Major_version -- just the part before the period as an int, e.g. 11.

Functions that get automatically called

  • App.initialize_globals() -- called before running each test and also before the app starts up. As the name suggests, use this to initialize all your global variables to something consistent. I also find it useful to be able to see all my global variables in one place, and avoid defining top-level variables anywhere else (unless they're constants and never going to be modified).

  • App.initialize(arg) -- called when app starts up after App.initialize_globals. Provides in arg an array of words typed in if you ran it from a terminal window. (Based on LÖVE.)

  • App.quit() -- called before the app shuts down. (Based on LÖVE.)

  • App.focus(start?) -- called when the app starts or stops receiving keypresses. start? will be true when app starts receiving keypresses and false when keypresses move to another window. (Based on LÖVE.)

  • App.resize(w,h) -- called when you resize the app window. Provides new window dimensions in w and h. Don't bother updating App.screen.width and App.screen.height, that will happen automatically before calling App.resize. (Based on LÖVE)

  • App.filedropped(file) -- called when a file icon is dragged and dropped on the app window. Provides in file an object representing the file that was dropped, that will respond to the following messages:

    • file:getFilename() returning a string name
    • file:read() returning the entire file contents in a single string

    (Based on LÖVE.)

  • App.draw() -- called to draw on the window, around 30 times a second. (Based on LÖVE.)

  • App.update(dt) -- called after every call to App.draw. Make changes to your app's variables here rather than in App.draw. Provides in dt the time since the previous call to App.update, which can be useful for things like smooth animations. (Based on LÖVE.)

  • App.mousepressed(x,y, mouse_button) -- called when you press down on a mouse button. Provides in x and y the point on the screen at which the click occurred, and in mouse_button an integer id of the mouse button pressed. 1 is the primary mouse button (the left button on a right-handed mouse), 2 is the secondary button (the right button on a right-handed mouse), and 3 is the middle button. Further buttons are mouse-dependent. (Based on LÖVE.)

  • App.mousereleased(x,y, mouse_button) -- called when you release a mouse button. Provides the same arguments as App.mousepressed() above. (Based on LÖVE.)

  • App.wheelmoved(dx,dy) -- called when you use the scroll wheel on a mouse that has it. Provides in dx and dy an indication of how fast the wheel is being scrolled. Positive values for dx indicate movement to the right. Positive values for dy indicate upward movement. (Based on LÖVE.)

  • App.keychord_press(chord, key) -- called when you press a key-combination. Provides in key a string name for the key most recently pressed (valid values). Provides in chord a string representation of the current key combination, consisting of the key with the following prefixes:

    • C- if one of the ctrl keys is pressed,
    • M- if one of the alt keys is pressed,
    • S- if one of the shift keys is pressed, and
    • s- if the windows/cmd/super key is pressed.
  • App.textinput(t) -- called when you press a key combination that yields (roughly) a printable character. For example, shift and a pressed together will call App.textinput with A. (Based on LÖVE.)

  • App.keyreleased(key) -- called when you press a key on the keyboard. Provides in key a string name for the key (valid values). (Based on LÖVE, including other variants.)

Functions you can call

Everything in the LÖVE and Lua guides is available to you, but here's a brief summary of the most useful primitives. Some primitives have new, preferred names under the App namespace, often because these variants are more testable. If you run them within a test you'll be able to make assertions on their side-effects.

regarding the app window

  • width, height, flags = App.screen.size() -- returns the dimensions and some properties of the app window. (Based on LÖVE.)

  • App.screen.resize(width, height, flags) -- modify the size and properties of the app window. The OS may or may not act on the request. (Based on LÖVE.)

  • x, y, displayindex = App.screen.position() -- returns the coordinates and monitor index (if you have more than one monitor) for the top-left corner of the app window. (Based on LÖVE.)

  • App.screen.move(x, y, displayindex) -- moves the app window so its top-left corner is at the specified coordinates of the specified monitor. The OS may or may not act on the request. (Based on LÖVE.)

drawing to the app window

  • App.screen.print(text, x,y) -- print the given text in the current font using the current color so its top-left corner is at the specified coordinates of the app window. (Based on LÖVE.)

  • love.graphics.getFont() -- returns a representation of the current font. (From LÖVE.)

  • love.graphics.setFont(font) -- switches the current font to font. (From LÖVE.)

  • love.graphics.newFont(filename) -- creates a font from the given font file. (From LÖVE, including other variants.)

  • App.width(text) returns the width of text in pixels when rendered using the current font. (Based on LÖVE.)

  • App.color(color) -- sets the current color based on the fields r, g, b and a (for opacity) of the table color. (Based on LÖVE.)

  • love.graphics.line(x1,y1, x2,y2) -- draws a line from (x1,y1) to (x2, y2) in the app window using the current color, clipping data for negative coordinates and coordinates outside (App.screen.width, App.screen.height) (From LÖVE, including other variants.)

  • love.graphics.rectangle(mode, x, y, w, h) -- draws a rectangle using the current color, with a top-left corner at (x, y), with dimensions width along the x axis and height along the y axis (though check out https://love2d.org/wiki/love.graphics for ways to scale and rotate shapes). mode is a string, either 'line' (to draw just the outline) and 'fill'. (From LÖVE, including other variants.)

  • love.graphics.circle(mode, x, y, r) -- draws a circle using the current color, centered at (x, y) and with radius r. mode is a string, either 'line' and 'fill'. (From LÖVE, including other variants.)

  • love.graphics.arc(mode, x, y, r, angle1, angle2) -- draws an arc of a circle using the current color, centered at (x, y) and with radius r. mode is a string, either 'line' and 'fill'. angle1 and angle2 are in radians. (From LÖVE, including other variants.)

There's much more I could include here; check out the LÖVE manual.

text editor primitives

The text-editor widget includes extremely thorough automated tests to give you early warning if you break something.

  • state = edit.initialize_state(top, left, right, font, font_height, line_height) -- returns an object that can be used to render an interactive editor widget for text starting at y=top on the app window, between x=left and x=right. Wraps long lines at word boundaries where possible, or in the middle of words (no hyphenation yet) when it must.

  • edit.quit() -- calling this ensures any final edits are flushed to disk before the app exits.

  • edit.draw(state) -- call this from App.draw to display the current editor state on the app window as requested in the call to edit.initialize_state that created state.

  • edit.mouse_press(state, x,y, mouse_button) and edit.mouse_release(x,y, mouse_button) -- call these to position the cursor or select some text.

  • edit.mouse_wheel_move(state, dx,dy) -- call this to scroll the editor in response to a mouse wheel.

  • edit.keychord_press(state, chord, key) and edit.key_release(state, key) -- call these to perform some standard shortcuts: insert new lines, backspace/delete, zoom in/out font size, cut/copy/paste to and from the clipboard, undo/redo.

  • edit.text_input(state, t) -- call this to insert keystrokes into the buffer.

  • Text.redraw_all(state) -- call this to clear and recompute any cached state as the cursor moves and the buffer scrolls.

  • edit.update(state, dt) -- call this from App.update to periodically auto-save editor contents to disk.

  • edit.quit(state) -- call this from App.quit to ensure any final edits get saved before quitting.

If you need more precise control, look at the comment at the top of edit.initialize_state in edit.lua. In brief, the widget contains an array of lines. Positions in the buffer are described in schema-1 locations consisting of a line index and a code-point pos. We may also convert them at times to schema-2 locations consisting of a line, screen_line and pos that better indicates how long lines wrap. Schema-2 locations are never persisted, just generated as needed from schema-1. Important schema-1 locations in the widget are cursor1 describing where text is inserted or deleted and screen_top1 which specifies how far down the lines is currently visible on screen.

Some constants that affect editor behavior:

  • Margin_top, Margin_left, Margin_right are integers in pixel units that affect where the editor is drawn on window (it always extends to bottom of window as needed)

  • Various color constants are represented as tables with r/g/b keys:

    • Text_color, Cursor_color, Highlight_color for drawing text.

clickable buttons

There's a facility for rendering buttons and responding to events when they're clicked. It requires setting up 3 things:

  • a state table housing all buttons. Can be the same state variable the text-editor widget uses, but doesn't have to be.
  • specifying buttons to create in state. This must happen either directly or indirectly within App.draw.
  • responding to clicks on buttons in state. This must happen either directly or indirectly within App.mousepressed.

The following facilities help set these things up:

  • Clear state at the start of each frame:

    state.button_handlers = {}
    

    Don't forget to do this, or your app will get slower over time.

  • button creates a single button. The syntax is:

    button(state, name, {x=..., y=..., w=..., h=..., bg={r,g,b},
      icon = function({x=..., y=..., w=..., h=...}) ... end,
      onpress1 = ...
    })
    

    Call this either directly or indirectly from App.draw. It will assign a rectangle with the given dimensions and trigger the provided (zero-arg) onpress1 callback when the primary mouse button is clicked within. It will also optionally paint the rectangle with the specified background color bg and a foreground described by the icon callback (which will receive the same dimensions).

    This way you can see everything about a button in one place. Create as many buttons as you like within a single shared state.

  • mouse_press_consumed_by_any_button(state, x,y, mouse_button)

    Call this either directly or indirectly from App.mousepressed. It will pass on a click to any button registered in state. It's also helpful to ensure clicks on a button don't have other effects, so I prefer the following boilerplate early in mousepressed:

    if mouse_press_consumed_by_any_button(state, x,y, mouse_button) then
      return
    end
    

mouse primitives

  • App.mouse_move(x, y) -- sets the current position of the mouse to (x, y). (Based on LÖVE.)

  • App.mouse_down(mouse_button) -- returns true if the button mouse_button is pressed. See App.mousepressed for mouse_button codes. (Based on LÖVE.)

  • App.mouse_x() -- returns the x coordinate of the current position of the mouse. (Based on LÖVE.)

  • App.mouse_y() -- returns the x coordinate of the current position of the mouse. (Based on LÖVE.)

keyboard primitives

  • App.is_cursor_movement(key) -- return true if key is a cursor movement key (arrow keys, page-up/down, home/end)

  • App.cmd_down(), App.ctrl_down, App.alt_down(), App.shift_down() -- predicates for different modifier keys.

  • App.any_modifier_down() -- returns true if any of the modifier keys is currently pressed.

  • App.key_down(key) -- returns true if the given key is currently pressed. (Based on LÖVE.)

interacting with files

  • App.open_for_reading(filename) -- returns a file handle that you can read() from. Make sure filename is an absolute path so that your app can work reliably by double-clicking on it. (Based on Lua.)

  • App.open_for_writing(filename) -- returns a file handle that you can write() to. Make sure filename is an absolute path so that your app can work reliably by double-clicking on it. (Based on Lua.)

  • json.encode(obj) -- returns a JSON string for an object obj that will recreate obj when passed to json.decode. obj can be of most types but has some exceptions. (From json.lua.)

  • json.decode(obj) -- turns a JSON string into a Lua object. (From json.lua.)

  • App.files(dir) -- returns an unsorted array of the files and directories available under dir. (From LÖVE.]

  • App.file_info(filename) -- returns some information about filename, particularly whether it exists (non-nil return value) or not. (From LÖVE.]

  • App.mkdir(path) -- creates a directory. Make sure path is absolute. (From LÖVE.]

  • App.remove(filename) -- removes a file or empty directory. Definitely make sure filename is an absolute path. (From LÖVE.]

There's much more I could include here; check out the LÖVE manual and the Lua manual.

desiderata

  • App.get_time() -- returns the number of seconds elapsed since some unspecified start time. (Based on LÖVE.)

  • App.get_clipboard() -- returns a string with the current clipboard contents. (Based on LÖVE.)

  • App.set_clipboard(text) -- stores the string text in the clipboard. (Based on LÖVE.)

  • array.find(arr, elem) -- scan table arr for elem assuming it's organized as an array (just numeric indices).

  • array.any(arr, f) -- scan table arr for any elements satisfying predicate f. Return first such element or false if none.

There's much more I could include here; check out the LÖVE manual and the Lua manual.

writing tests

  • App.screen.init{width=.., height=..} -- creates a fake screen for a test

  • App.screen.check(y, expected_contents, msg) -- verifies text written to the fake screen at y. This isn't very realistic; y must exactly match what was displayed, and the expected contents show everything printed to that y in chronological order, regardless of x coordinate. In spite of these limitations, you can write lots of useful tests with this.

  • App.run_after_textinput(t) -- mimics keystrokes resulting in t and then draws one frame.

  • App.run_after_keychord(chord, key) -- mimics the final key press resulting in chord and then draws one frame.

  • App.run_after_mouse_press(x,y, mouse_button) -- mimics a mouse press down followed by drawing a frame.

  • App.run_after_mouse_release(x,y, mouse_button) -- mimics a mouse release up followed by drawing a frame.

  • App.run_after_mouse_click(x,y, mouse_button) -- mimics a mouse press down and mouse release up followed by drawing a frame.

  • App.wait_fake_time(t) -- simulates the passage of time for App.getTime().