Building blocks for your Freewheeling Apps#
Freewheeling apps consist of 3 kinds of things:
- a small number of functions you can define that get automatically called for you as appropriate,
- a wide variety of primitives that you can call but not modify,
- any other function you can define and call at will.
The rest of this document will summarize what is available to you in the first two categories.
Variables you can set/read#
-
Disallow_error_recovery_on_key_release-- set this when you detect hard-to-recover-from errors. Right now I'm only aware of one such (see driver.love): calls tolove.graphics.newCanvascan fail at the SDL2 layer if a computer doesn't have enough video RAM to perform them. In response, set this variable before and unset after to prevent the system from repeatedly retrying something that is unlikely to recover. In particular, prevent settings from being saved to disk during an error state, thereby perpetuating the error.This variable is subject to removal if I can come up with a better approach, but I'll document it here as long as I support it. Please let me know if you find a new need to read or write it.
Functions you can implement that will get automatically called#
-
on.initialize(arg)-- called when app starts up. Provides inargan array of words typed in if you ran it from a terminal window. (Based on LÖVE.) -
on.draw()-- called to draw on the window, around 30 times a second. (Based on LÖVE.) -
on.update(dt)-- called after every call toon.draw. Make changes to your app's variables here rather than inon.draw. Provides indtthe time since the previous call toon.update, which can be useful for things like smooth animations. (Based on LÖVE.) -
on.mouse_press(x,y, mouse_button, is_touch, presses)-- called when you press down on a mouse button. Provides inxandythe point on the screen at which the click occurred, and inmouse_buttonan integer id of the mouse button pressed.1is the primary mouse button (the left button on a right-handed mouse),2is the secondary button (the right button on a right-handed mouse), and3is the middle button. Further buttons are mouse-dependent. (Based on LÖVE.) -
on.mouse_release(x,y, mouse_button, is_touch, presses)-- called when you release a mouse button. Provides the same arguments ason.mouse_press()above. (Based on LÖVE.) -
on.mouse_move(x,y, dx,dy, is_touch)-- called when you move the mouse. Provides inxandythe point on the screen at which the click occurred and indxanddythe amount moved since the previous call ofmouse_move. (Based on LÖVE.) -
on.mouse_wheel_move(dx,dy)-- called when you use the scroll wheel on a mouse that has it. Provides indxanddyan indication of how fast the wheel is being scrolled. Positive values fordxindicate movement to the right. Positive values fordyindicate upward movement. (Based on LÖVE.) -
on.touch_press(touch_id, x,y, dx,dy, pressure)-- called when you touch a multi-touch screen. Provides inxandythe point on the screen at which the touch occurred. (Based on LÖVE.) -
on.touch_release(touch_id, x,y, dx,dy, pressure)-- called when you release a touch. Provides the same arguments ason.touch_press()above. (Based on LÖVE.) -
on.touch_move(touch_id, x,y, dx,dy, pressure)-- called when you move any finger on a multi-touch screen. Provides the same arguments ason.touch_press()above, withdxanddycontaining the amount moved since the previous call totouch_move. (Based on LÖVE.) -
on.keychord_press(chord, key, scancode, is_repeat)-- called when you press a key-combination. Provides inkeya string name for the key most recently pressed (valid values). Provides inchorda string representation of the current key combination, consisting of the key with the following prefixes:C-if one of thectrlkeys is pressed,M-if one of thealtkeys is pressed,S-if one of theshiftkeys is pressed, ands-if thewindows/cmd/superkey is pressed.
-
on.text_input(t)-- called when you press a key combination that yields (roughly) a printable character. For example,shiftandapressed together will callon.textinputwithA. (Based on LÖVE.) -
on.key_release(key, scancode)-- called when you press a key on the keyboard. Provides inkeya string name for the key (valid values). (Based on LÖVE, including other variants.) -
on.quit()-- called before the app shuts down. (Based on LÖVE.) -
on.save_settings()-- called after on.quit and should return a table which will be saved to disk. -
on.load_settings(settings)-- called when app starts up, beforeon.initialize. Provides insettingsthe table that was saved to disk the last time the app shut down. -
on.focus(start?)-- called when the app starts or stops receiving keypresses.start?will betruewhen app starts receiving keypresses andfalsewhen keypresses move to another window. (Based on LÖVE.) -
on.resize(w,h)-- called when you resize the app window. Provides new window dimensions inwandh. (Based on LÖVE) -
on.code_change()-- called when you make changes to the app using driver.love, any time you hitf4inside driver.love, after a definition is created or modified. -
on.mouse_focus(in_focus)-- called when the mouse pointer moves on or off the app window.in_focuswill betruewhen mouse comes within the window area, andfalsewhen it goes off. (Based on LÖVE.)
Functions you can call#
Everything in the LÖVE and Lua guides is available to you.
text editor primitives#
-
editor = edit.new(top, left, right, bottom, font, font_height)-- returns an object that can be used to render an interactive editor widget for text within the given bounds. Wraps long lines at word boundaries where possible, or in the middle of words (no hyphenation yet) when it must. -
edit.resize(editor, w, h, right, bottom)-- adjusts screen dimensions and editor bounds. -
edit.load_file(editor, filename)-- loads theeditorstate with lines fromfilename.
Make sure to wire up the following functions from corresponding on.* functions:
edit.draw(editor, fg_color, hide_cursor, show_line_numbers, cursor_color)fg_coloris either nil or a 3-array of rgb or a 4-array of rgba. If it'snil, the text will be syntax highlighted.hide_cursoris a booleanshow_line_numbersis a booleancursor_coloris an optional color
edit.keychord_press(editor, chord, key, scancode, is_repeat, readonly)keyandscancodeare for the final key pressed in thechordreadonlyshould probably be in sync withhide_cursorinedit.draw
edit.text_input(editor, t)edit.key_release(editor, key, scancode)edit.mouse_press(editor, x,y, mouse_button, is_touch, presses)edit.mouse_move(editor, x,y, dx,dy, is_touch)edit.mouse_release(editor, x,y, mouse_button, is_touch, presses)edit.mouse_wheel_move(editor, dx,dy)edit.touch_press(editor, touch_id, x,y, dx,dy, pressure)edit.touch_move(editor, touch_id, x,y, dx,dy, pressure)edit.touch_release(editor, touch_id, x,y, dx,dy, pressure)
The touch_* functions above implement inertial scroll. You'll also need to
wire up two additional functions for inertial scroll:
edit.update_inertial_scroll(editor, dt)-- fromon.updateedit.maybe_stop_inertial_scroll(editor)-- fromon.mouse_press, at a higher priority thanedit.touch_pressor any other UI elements on screen. Will returntrueif any inertial scroll was in progress and stopped.
Basic example showing how to wire together these calls into a live-modifiable text editor.
Other optional configuration:
-
edit.update_font_settings(editor, font_height)-- updates all state dependent on font height. -
edit.save_to_disk(editor)-- save current state toeditor.filename -
edit.is_this_love_version_supported()-- might provide early warning if the version of LÖVE is too old. -
editor.indent_wrapped_lines = true-- now after wrapping the line continues from the same x coordinate as its starting character, rather than the left margin.
The freewheeling protocol with the driver#
Freewheeling apps currently respond to the following commands from the driver:
QUIT-- tells the current app to quitRESTART-- tells the current app to reinitialize after saving any settingsMANIFEST-- requests a list of definitions the app knows about. The app returns only definitions that were created using the freewheeling framework and so can be modified and errors recovered from.DEFAULT_MAP-- requests a default map of the code. Everyone is free to create their own "memory palace", but this is usually a good default to start with.GET <name>-- requests the source code for definition<name>.GET* <name> ...-- requests source code for multiple definitions.DELETE <name>-- requests deletion of the definition<name>. Only permitted for definitions created using the freewheeling framework, not lower-level definitions.- anything else -- is considered a new definition to be loaded into the app.
Commands may cause an error response, which is sent back to the driver.
In addition, any run-time errors caused as the app executes are also sent back to the driver. These don't need an explicit command from the driver.
Some primitives available for complying with the protocol:
-
live.receive_from_driver()-- looks for a message from the driver, and returns nil if there's nothing. -
live.send_to_driver(msg)-- sends a message to the driver. -
live.send_run_time_error_to_driver(msg)-- sends an error to the driver. Automatically invoked by the LÖVE error handler, so you shouldn't need to call this. -
live.get_cmd_from_buffer(buf)-- helper to extract the first word from a command. -
live.get_binding(name)-- look up the repo for the source code for aname.