Template repo for tiny cross-platform apps that can be modified on phone, tablet or computer.
1# Building blocks for your Freewheeling Apps
2
3Freewheeling apps consist of 3 kinds of things:
4 - a small number of functions you can define that get automatically called
5 for you as appropriate,
6 - a wide variety of primitives that you can call but not modify,
7 - any other function you can define and call at will.
8
9The rest of this document will summarize what is available to you in the first
10two categories.
11
12## Variables you can set/read
13
14* `Disallow_error_recovery_on_key_release` -- set this when you detect
15 hard-to-recover-from errors. Right now I'm only aware of one such (see
16 driver.love): calls to `love.graphics.newCanvas` can fail at the SDL2 layer
17 if a computer doesn't have enough video RAM to perform them. In response,
18 set this variable before and unset after to prevent the system from
19 repeatedly retrying something that is unlikely to recover. In particular,
20 prevent settings from being saved to disk during an error state, thereby
21 perpetuating the error.
22
23 This variable is subject to removal if I can come up with a better approach,
24 but I'll document it here as long as I support it. Please
25 [let me know](http://akkartik.name/contact) if you find a new need to read
26 or write it.
27
28## Functions you can implement that will get automatically called
29
30* `on.initialize(arg)` -- called when app starts up. Provides in `arg` an
31 array of words typed in if you ran it from a terminal window.
32 (Based on [LÖVE](https://love2d.org/wiki/love.load).)
33
34* `on.draw()` -- called to draw on the window, around 30 times a second.
35 (Based on [LÖVE](https://love2d.org/wiki/love.draw).)
36
37* `on.update(dt)` -- called after every call to `on.draw`. Make changes to
38 your app's variables here rather than in `on.draw`. Provides in `dt` the
39 time since the previous call to `on.update`, which can be useful for things
40 like smooth animations.
41 (Based on [LÖVE](https://love2d.org/wiki/love.update).)
42
43* `on.mouse_press(x,y, mouse_button, is_touch, presses)` -- called when you
44 press down on a mouse button. Provides in `x` and `y` the point on the
45 screen at which the click occurred, and in `mouse_button` an integer id of
46 the mouse button pressed.
47 `1` is the primary mouse button (the left button on a right-handed mouse),
48 `2` is the secondary button (the right button on a right-handed mouse),
49 and `3` is the middle button. Further buttons are mouse-dependent.
50 (Based on [LÖVE](https://love2d.org/wiki/love.mousepressed).)
51
52* `on.mouse_release(x,y, mouse_button, is_touch, presses)` -- called when you
53 release a mouse button. Provides the same arguments as `on.mouse_press()`
54 above.
55 (Based on [LÖVE](https://love2d.org/wiki/love.mousereleased).)
56
57* `on.mouse_move(x,y, dx,dy, is_touch)` -- called when you move the mouse.
58 Provides in `x` and `y` the point on the screen at which the click occurred
59 and in `dx` and `dy` the amount moved since the previous call of
60 `mouse_move`.
61 (Based on [LÖVE](https://love2d.org/wiki/love.mousemoved).)
62
63* `on.mouse_wheel_move(dx,dy)` -- called when you use the scroll wheel on a
64 mouse that has it. Provides in `dx` and `dy` an indication of how fast the
65 wheel is being scrolled. Positive values for `dx` indicate movement to the
66 right. Positive values for `dy` indicate upward movement.
67 (Based on [LÖVE](https://love2d.org/wiki/love.wheelmoved).)
68
69* `on.touch_press(touch_id, x,y, dx,dy, pressure)` -- called when you touch a
70 multi-touch screen. Provides in `x` and `y` the point on the screen at which
71 the touch occurred.
72 (Based on [LÖVE](https://love2d.org/wiki/love.touchpressed).)
73
74* `on.touch_release(touch_id, x,y, dx,dy, pressure)` -- called when you
75 release a touch. Provides the same arguments as `on.touch_press()` above.
76 (Based on [LÖVE](https://love2d.org/wiki/love.touchreleased).)
77
78* `on.touch_move(touch_id, x,y, dx,dy, pressure)` -- called when you move any
79 finger on a multi-touch screen. Provides the same arguments as
80 `on.touch_press()` above, with `dx` and `dy` containing the amount moved
81 since the previous call to `touch_move`.
82 (Based on [LÖVE](https://love2d.org/wiki/love.touchmoved).)
83
84* `on.keychord_press(chord, key, scancode, is_repeat)` -- called when you
85 press a key-combination. Provides in `key` a string name for the key most
86 recently pressed ([valid values](https://love2d.org/wiki/KeyConstant)).
87 Provides in `chord` a string representation of the current key combination,
88 consisting of the key with the following prefixes:
89 * `C-` if one of the `ctrl` keys is pressed,
90 * `M-` if one of the `alt` keys is pressed,
91 * `S-` if one of the `shift` keys is pressed, and
92 * `s-` if the `windows`/`cmd`/`super` key is pressed.
93
94* `on.text_input(t)` -- called when you press a key combination that yields
95 (roughly) a printable character. For example, `shift` and `a` pressed
96 together will call `on.textinput` with `A`.
97 (Based on [LÖVE](https://love2d.org/wiki/love.textinput).)
98
99* `on.key_release(key, scancode)` -- called when you press a key on the
100 keyboard. Provides in `key` a string name for the key ([valid
101 values](https://love2d.org/wiki/KeyConstant)).
102 (Based on [LÖVE](https://love2d.org/wiki/love.keyreleased), including other
103 variants.)
104
105* `on.quit()` -- called before the app shuts down.
106 (Based on [LÖVE](https://love2d.org/wiki/love.quit).)
107
108* `on.save_settings()` -- called after on.quit and should return a table which
109 will be saved to disk.
110
111* `on.load_settings(settings)` -- called when app starts up, before
112 `on.initialize`. Provides in `settings` the table that was saved to disk the
113 last time the app shut down.
114
115* `on.focus(start?)` -- called when the app starts or stops receiving
116 keypresses. `start?` will be `true` when app starts receiving keypresses and
117 `false` when keypresses move to another window.
118 (Based on [LÖVE](https://love2d.org/wiki/love.focus).)
119
120* `on.resize(w,h)` -- called when you resize the app window. Provides new
121 window dimensions in `w` and `h`.
122 (Based on [LÖVE](https://love2d.org/wiki/love.resize))
123
124* `on.code_change()` -- called when you make changes to the app using
125 [driver.love](https://git.sr.ht/~akkartik/driver.love), any time you hit
126 `f4` inside driver.love, after a definition is created or modified.
127
128* `on.mouse_focus(in_focus)` -- called when the mouse pointer moves on or off
129 the app window. `in_focus` will be `true` when mouse comes within the window
130 area, and `false` when it goes off.
131 (Based on [LÖVE](https://love2d.org/wiki/love.mousefocus).)
132
133## Functions you can call
134
135Everything in the [LÖVE](https://love2d.org/wiki/Main_Page) and
136[Lua](https://www.lua.org/manual/5.1/manual.html) guides is available to you.
137
138### text editor primitives
139
140* `editor = edit.new(top, left, right, bottom, font, font_height)`
141 -- returns an object that can be used to render an interactive editor widget
142 for text within the given bounds. Wraps long lines at word boundaries where
143 possible, or in the middle of words (no hyphenation yet) when it must.
144
145* `edit.resize(editor, w, h, right, bottom)` -- adjusts screen dimensions and
146 editor bounds.
147
148* `edit.load_file(editor, filename)` -- loads the `editor` state with lines
149 from `filename`.
150
151Make sure to wire up the following functions from corresponding `on.*` functions:
152* `edit.draw(editor, fg_color, hide_cursor, show_line_numbers, cursor_color)`
153 - `fg_color` is either nil or a 3-array of rgb or a 4-array of rgba. If it's
154 `nil`, the text will be syntax highlighted.
155 - `hide_cursor` is a boolean
156 - `show_line_numbers` is a boolean
157 - `cursor_color` is an optional color
158* `edit.keychord_press(editor, chord, key, scancode, is_repeat, readonly)`
159 - `key` and `scancode` are for the final key pressed in the `chord`
160 - `readonly` should probably be in sync with `hide_cursor` in `edit.draw`
161* `edit.text_input(editor, t)`
162* `edit.key_release(editor, key, scancode)`
163* `edit.mouse_press(editor, x,y, mouse_button, is_touch, presses)`
164* `edit.mouse_move(editor, x,y, dx,dy, is_touch)`
165* `edit.mouse_release(editor, x,y, mouse_button, is_touch, presses)`
166* `edit.mouse_wheel_move(editor, dx,dy)`
167* `edit.touch_press(editor, touch_id, x,y, dx,dy, pressure)`
168* `edit.touch_move(editor, touch_id, x,y, dx,dy, pressure)`
169* `edit.touch_release(editor, touch_id, x,y, dx,dy, pressure)`
170
171The `touch_*` functions above implement inertial scroll. You'll also need to
172wire up two additional functions for inertial scroll:
173* `edit.update_inertial_scroll(editor, dt)` -- from `on.update`
174* `edit.maybe_stop_inertial_scroll(editor)` -- from `on.mouse_press`, at a
175 higher priority than `edit.touch_press` or any other UI elements on screen.
176 Will return `true` if any inertial scroll was in progress and stopped.
177
178[Basic example showing how to wire together these calls into a live-modifiable
179text editor.](https://git.sr.ht/~akkartik/live-editor-mobile2-example)
180
181Other optional configuration:
182
183* `edit.update_font_settings(editor, font_height)` -- updates all state
184 dependent on font height.
185
186* `edit.save_to_disk(editor)` -- save current state to `editor.filename`
187
188* `edit.is_this_love_version_supported()` -- might provide early warning if
189 the version of LÖVE is too old.
190
191* `editor.indent_wrapped_lines = true` -- now after wrapping the line
192 continues from the same x coordinate as its starting character, rather than
193 the left margin.
194
195### The freewheeling protocol with the driver
196
197Freewheeling apps currently respond to the following commands from the driver:
198 * `QUIT` -- tells the current app to quit
199 * `RESTART` -- tells the current app to reinitialize after saving any settings
200 * `MANIFEST` -- requests a list of definitions the app knows about. The
201 app returns only definitions that were created using the freewheeling
202 framework and so can be modified and errors recovered from.
203 * `DEFAULT_MAP` -- requests a default map of the code. Everyone is free to
204 create their own "memory palace", but this is usually a good default to
205 start with.
206 * `GET <name>` -- requests the source code for definition `<name>`.
207 * `GET* <name> ...` -- requests source code for multiple definitions.
208 * `DELETE <name>` -- requests deletion of the definition `<name>`. Only
209 permitted for definitions created using the freewheeling framework, not
210 lower-level definitions.
211 * anything else -- is considered a new definition to be loaded into the
212 app.
213
214Commands may cause an error response, which is sent back to the driver.
215
216In addition, any _run-time_ errors caused as the app executes are also sent
217back to the driver. These don't need an explicit command from the driver.
218
219Some primitives available for complying with the protocol:
220
221* `live.receive_from_driver()` -- looks for a message from the driver, and
222 returns nil if there's nothing.
223
224* `live.send_to_driver(msg)` -- sends a message to the driver.
225
226* `live.send_run_time_error_to_driver(msg)` -- sends an error to the driver.
227 Automatically invoked by the LÖVE error handler, so you shouldn't need to
228 call this.
229
230* `live.get_cmd_from_buffer(buf)` -- helper to extract the first word from a
231 command.
232
233* `live.get_binding(name)` -- look up the repo for the source code for a
234 `name`.