# 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 to `love.graphics.newCanvas` can 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](http://akkartik.name/contact) 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 in `arg` an array of words typed in if you ran it from a terminal window. (Based on [LÖVE](https://love2d.org/wiki/love.load).) * `on.draw()` -- called to draw on the window, around 30 times a second. (Based on [LÖVE](https://love2d.org/wiki/love.draw).) * `on.update(dt)` -- called after every call to `on.draw`. Make changes to your app's variables here rather than in `on.draw`. Provides in `dt` the time since the previous call to `on.update`, which can be useful for things like smooth animations. (Based on [LÖVE](https://love2d.org/wiki/love.update).) * `on.mouse_press(x,y, mouse_button, is_touch, presses)` -- 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](https://love2d.org/wiki/love.mousepressed).) * `on.mouse_release(x,y, mouse_button, is_touch, presses)` -- called when you release a mouse button. Provides the same arguments as `on.mouse_press()` above. (Based on [LÖVE](https://love2d.org/wiki/love.mousereleased).) * `on.mouse_move(x,y, dx,dy, is_touch)` -- called when you move the mouse. Provides in `x` and `y` the point on the screen at which the click occurred and in `dx` and `dy` the amount moved since the previous call of `mouse_move`. (Based on [LÖVE](https://love2d.org/wiki/love.mousemoved).) * `on.mouse_wheel_move(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](https://love2d.org/wiki/love.wheelmoved).) * `on.touch_press(touch_id, x,y, dx,dy, pressure)` -- called when you touch a multi-touch screen. Provides in `x` and `y` the point on the screen at which the touch occurred. (Based on [LÖVE](https://love2d.org/wiki/love.touchpressed).) * `on.touch_release(touch_id, x,y, dx,dy, pressure)` -- called when you release a touch. Provides the same arguments as `on.touch_press()` above. (Based on [LÖVE](https://love2d.org/wiki/love.touchreleased).) * `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 as `on.touch_press()` above, with `dx` and `dy` containing the amount moved since the previous call to `touch_move`. (Based on [LÖVE](https://love2d.org/wiki/love.touchmoved).) * `on.keychord_press(chord, key, scancode, is_repeat)` -- called when you press a key-combination. Provides in `key` a string name for the key most recently pressed ([valid values](https://love2d.org/wiki/KeyConstant)). 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. * `on.text_input(t)` -- called when you press a key combination that yields (roughly) a printable character. For example, `shift` and `a` pressed together will call `on.textinput` with `A`. (Based on [LÖVE](https://love2d.org/wiki/love.textinput).) * `on.key_release(key, scancode)` -- called when you press a key on the keyboard. Provides in `key` a string name for the key ([valid values](https://love2d.org/wiki/KeyConstant)). (Based on [LÖVE](https://love2d.org/wiki/love.keyreleased), including other variants.) * `on.quit()` -- called before the app shuts down. (Based on [LÖVE](https://love2d.org/wiki/love.quit).) * `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, before `on.initialize`. Provides in `settings` the 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 be `true` when app starts receiving keypresses and `false` when keypresses move to another window. (Based on [LÖVE](https://love2d.org/wiki/love.focus).) * `on.resize(w,h)` -- called when you resize the app window. Provides new window dimensions in `w` and `h`. (Based on [LÖVE](https://love2d.org/wiki/love.resize)) * `on.code_change()` -- called when you make changes to the app using [driver.love](https://git.sr.ht/~akkartik/driver.love), any time you hit `f4` inside 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_focus` will be `true` when mouse comes within the window area, and `false` when it goes off. (Based on [LÖVE](https://love2d.org/wiki/love.mousefocus).) ## Functions you can call Everything in the [LÖVE](https://love2d.org/wiki/Main_Page) and [Lua](https://www.lua.org/manual/5.1/manual.html) 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 the `editor` state with lines from `filename`. 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_color` is either nil or a 3-array of rgb or a 4-array of rgba. If it's `nil`, the text will be syntax highlighted. - `hide_cursor` is a boolean - `show_line_numbers` is a boolean - `cursor_color` is an optional color * `edit.keychord_press(editor, chord, key, scancode, is_repeat, readonly)` - `key` and `scancode` are for the final key pressed in the `chord` - `readonly` should probably be in sync with `hide_cursor` in `edit.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)` -- from `on.update` * `edit.maybe_stop_inertial_scroll(editor)` -- from `on.mouse_press`, at a higher priority than `edit.touch_press` or any other UI elements on screen. Will return `true` if any inertial scroll was in progress and stopped. [Basic example showing how to wire together these calls into a live-modifiable text editor.](https://git.sr.ht/~akkartik/live-editor-mobile2-example) 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 to `editor.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 quit * `RESTART` -- tells the current app to reinitialize after saving any settings * `MANIFEST` -- 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 ` -- requests the source code for definition ``. * `GET* ...` -- requests source code for multiple definitions. * `DELETE ` -- requests deletion of the definition ``. 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 a `name`.