2023-04-10 05:03:12 +01:00
|
|
|
|
# Building blocks for your Freewheeling Apps
|
2023-04-10 03:01:33 +01:00
|
|
|
|
|
2023-04-10 05:03:12 +01:00
|
|
|
|
Freewheeling apps consist of 4 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,
|
|
|
|
|
- tests that start with `test_` and run on startup or after any change you
|
|
|
|
|
make to the app's code, and
|
|
|
|
|
- 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.
|
2023-04-10 03:01:33 +01:00
|
|
|
|
|
|
|
|
|
## Variables you can read
|
|
|
|
|
|
2023-04-21 02:42:29 +01:00
|
|
|
|
* `Current_time` -- seconds since some unspecified time. Useful for managing
|
|
|
|
|
durations of time (scheduling operations, etc.).
|
|
|
|
|
|
2023-04-10 03:01:33 +01:00
|
|
|
|
* `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`](https://love2d.org/wiki/love.window.getMode)
|
|
|
|
|
for details.
|
|
|
|
|
|
2023-04-10 05:03:12 +01:00
|
|
|
|
## Functions you can implement that will get automatically called
|
2023-04-10 03:01:33 +01:00
|
|
|
|
|
2023-04-10 05:03:12 +01:00
|
|
|
|
* `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.
|
2023-04-10 03:01:33 +01:00
|
|
|
|
(Based on [LÖVE](https://love2d.org/wiki/love.load).)
|
|
|
|
|
|
2023-04-10 05:03:12 +01:00
|
|
|
|
* `on.quit()` -- called before the app shuts down.
|
2023-04-10 03:01:33 +01:00
|
|
|
|
(Based on [LÖVE](https://love2d.org/wiki/love.quit).)
|
|
|
|
|
|
2023-04-10 05:03:12 +01:00
|
|
|
|
* `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
|
2023-04-10 03:01:33 +01:00
|
|
|
|
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).)
|
|
|
|
|
|
2023-04-10 05:03:12 +01:00
|
|
|
|
* `on.resize(w,h)` -- called when you resize the app window. Provides new
|
2023-04-10 03:01:33 +01:00
|
|
|
|
window dimensions in `w` and `h`. Don't bother updating `App.screen.width`
|
|
|
|
|
and `App.screen.height`, that will happen automatically before calling
|
2023-08-26 21:15:32 +01:00
|
|
|
|
`App.resize`.
|
2023-04-10 03:01:33 +01:00
|
|
|
|
(Based on [LÖVE](https://love2d.org/wiki/love.resize))
|
|
|
|
|
|
2023-04-10 05:03:12 +01:00
|
|
|
|
* `on.file_drop(file)` -- called when a file icon is dragged and dropped on
|
2023-04-10 03:01:33 +01:00
|
|
|
|
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](https://love2d.org/wiki/love.filedropped).)
|
|
|
|
|
|
2023-04-10 05:03:12 +01:00
|
|
|
|
* `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.draw()` -- called to draw on the window, around 30 times a second.
|
2023-04-10 03:01:33 +01:00
|
|
|
|
(Based on [LÖVE](https://love2d.org/wiki/love.draw).)
|
|
|
|
|
|
2023-04-10 05:03:12 +01:00
|
|
|
|
* `on.update(dt)` -- called after every call to `on.draw`. Make changes to
|
2023-04-10 03:01:33 +01:00
|
|
|
|
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).)
|
|
|
|
|
|
2023-04-10 05:03:12 +01:00
|
|
|
|
* `on.mouse_press(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.
|
2023-04-10 03:01:33 +01:00
|
|
|
|
`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).)
|
|
|
|
|
|
2023-04-10 05:03:12 +01:00
|
|
|
|
* `on.mouse_release(x,y, mouse_button)` -- called when you release a mouse
|
2023-04-10 03:01:33 +01:00
|
|
|
|
button. Provides the same arguments as `on.mouse_press()` above.
|
|
|
|
|
(Based on [LÖVE](https://love2d.org/wiki/love.mousereleased).)
|
|
|
|
|
|
2023-04-10 05:03:12 +01:00
|
|
|
|
* `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.
|
2023-04-10 03:01:33 +01:00
|
|
|
|
(Based on [LÖVE](https://love2d.org/wiki/love.wheelmoved).)
|
|
|
|
|
|
2023-04-10 05:03:12 +01:00
|
|
|
|
* `on.keychord_press(chord, key)` -- called when you press a key-combination.
|
2023-04-10 03:01:33 +01:00
|
|
|
|
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.
|
|
|
|
|
|
2023-04-10 05:03:12 +01:00
|
|
|
|
* `on.text_input(t)` -- called when you press a key combination that yields
|
2023-04-10 03:01:33 +01:00
|
|
|
|
(roughly) a printable character. For example, `shift` and `a` pressed
|
2023-08-26 21:15:32 +01:00
|
|
|
|
together will call `App.textinput` with `A`.
|
2023-04-10 03:01:33 +01:00
|
|
|
|
(Based on [LÖVE](https://love2d.org/wiki/love.textinput).)
|
|
|
|
|
|
2023-04-10 05:03:12 +01:00
|
|
|
|
* `on.key_release(key)` -- called when you press a key on the keyboard.
|
2023-04-10 03:01:33 +01:00
|
|
|
|
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.)
|
|
|
|
|
|
|
|
|
|
## 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,
|
|
|
|
|
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](https://love2d.org/wiki/love.window.getMode).)
|
|
|
|
|
|
|
|
|
|
* `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](https://love2d.org/wiki/love.window.setMode).)
|
|
|
|
|
|
|
|
|
|
* `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](https://love2d.org/wiki/love.window.getPosition).)
|
|
|
|
|
|
|
|
|
|
* `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](https://love2d.org/wiki/love.window.setPosition).)
|
|
|
|
|
|
|
|
|
|
### 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](https://love2d.org/wiki/love.graphics.print).)
|
|
|
|
|
|
|
|
|
|
* `love.graphics.getFont()` -- returns a representation of the current font.
|
|
|
|
|
(From [LÖVE](https://love2d.org/wiki/love.graphics.getFont).)
|
|
|
|
|
|
|
|
|
|
* `love.graphics.setFont(font)` -- switches the current font to `font`.
|
|
|
|
|
(From [LÖVE](https://love2d.org/wiki/love.graphics.setFont).)
|
|
|
|
|
|
|
|
|
|
* `love.graphics.newFont(filename)` -- creates a font from the given font
|
|
|
|
|
file.
|
|
|
|
|
(From [LÖVE](https://love2d.org/wiki/love.graphics.newFont), including other
|
|
|
|
|
variants.)
|
|
|
|
|
|
|
|
|
|
* `App.width(text)` returns the width of `text` in pixels when rendered using
|
|
|
|
|
the current font.
|
|
|
|
|
(Based on [LÖVE](https://love2d.org/wiki/Font:getWidth).)
|
|
|
|
|
|
|
|
|
|
* `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](https://love2d.org/wiki/love.graphics.setColor).)
|
|
|
|
|
|
|
|
|
|
* `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](https://love2d.org/wiki/love.graphics.line), 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](https://love2d.org/wiki/love.graphics.circle), 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](https://love2d.org/wiki/love.graphics.circle), 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](https://en.wikipedia.org/wiki/Radian).
|
|
|
|
|
(From [LÖVE](https://love2d.org/wiki/love.graphics.circle), including other
|
|
|
|
|
variants.)
|
|
|
|
|
|
|
|
|
|
There's much more I could include here; check out [the LÖVE manual](https://love2d.org/wiki/love.graphics).
|
|
|
|
|
|
2023-04-10 08:42:56 +01:00
|
|
|
|
### 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_height, line_height)` --
|
2023-04-11 23:15:58 +01:00
|
|
|
|
returns an object that can be used to render an interactive editor widget
|
2023-04-10 08:47:12 +01:00
|
|
|
|
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.
|
2023-04-10 08:42:56 +01:00
|
|
|
|
|
2023-04-21 07:41:25 +01:00
|
|
|
|
* `state.lines = load_array(lines)` -- loads the editor state with the array
|
|
|
|
|
of `lines`
|
|
|
|
|
|
2023-04-10 08:42:56 +01:00
|
|
|
|
* `edit.quit()` -- calling this ensures any final edits are flushed to disk
|
|
|
|
|
before the app exits.
|
|
|
|
|
|
2023-06-08 06:10:30 +01:00
|
|
|
|
* `edit.draw(state, fg, hide_cursor)` -- call this from `on.draw` to display
|
|
|
|
|
the current editor state on the app window as requested in the call to
|
|
|
|
|
`edit.initialize_state` that created `state`. `fg` is the color for
|
|
|
|
|
foreground text (`Text_color` is a good default). Set `hide_cursor` to stop
|
|
|
|
|
showing a blinking cursor (usually because you want a `readonly` editor; see
|
|
|
|
|
below).
|
2023-04-10 08:42:56 +01:00
|
|
|
|
|
2023-04-12 05:45:13 +01:00
|
|
|
|
* `edit.update(state, dt)` -- call this from `on.update` to periodically
|
2023-04-11 23:15:58 +01:00
|
|
|
|
auto-save editor contents to disk.
|
2023-04-10 08:42:56 +01:00
|
|
|
|
|
|
|
|
|
* `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.
|
|
|
|
|
|
2023-06-08 06:16:25 +01:00
|
|
|
|
* `edit.keychord_press(state, chord, key, readonly)` and `edit.key_release(state, key)`
|
2023-04-10 08:42:56 +01:00
|
|
|
|
-- call these to perform some standard shortcuts: insert new lines,
|
|
|
|
|
backspace/delete, zoom in/out font size, cut/copy/paste to and from the
|
2023-06-08 06:16:25 +01:00
|
|
|
|
clipboard, undo/redo. Setting `readonly` will disable mutating keystrokes.
|
2023-04-10 08:42:56 +01:00
|
|
|
|
|
|
|
|
|
* `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
|
2023-04-21 07:41:25 +01:00
|
|
|
|
state as the cursor moves and the buffer scrolls. You shouldn't need to do
|
|
|
|
|
this for the most part, just run it after loading or modifying
|
|
|
|
|
`state.lines`.
|
2023-04-10 08:42:56 +01:00
|
|
|
|
|
2023-06-06 06:17:01 +01:00
|
|
|
|
* `edit.update_font_height(state, font_height)` -- updates all state dependent
|
|
|
|
|
on font height.
|
|
|
|
|
|
2023-04-10 08:42:56 +01:00
|
|
|
|
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.
|
|
|
|
|
|
2023-06-08 05:30:17 +01:00
|
|
|
|
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.
|
|
|
|
|
|
2023-06-06 19:18:20 +01:00
|
|
|
|
### 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=..., color={r,g,b},
|
|
|
|
|
icon = function({x=..., y=..., w=..., h=...}) ... end,
|
|
|
|
|
onpress1 = ...
|
|
|
|
|
})
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
Call this either directly or indirectly from `App.draw`. It will paint a
|
|
|
|
|
rectangle to the screen with top-left at (x,y), dimensions w×h pixels in the
|
|
|
|
|
specified `color`. It will then overlay any drawing instructions within
|
|
|
|
|
`icon` atop it. The `icon` callback will receive a table containing the same
|
|
|
|
|
x/y/w/h.
|
|
|
|
|
|
|
|
|
|
The rectangle also registers within `state` the `onpress1` callback (without
|
|
|
|
|
any arguments) when mouse button 1 is clicked on it. 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_handler(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_handler(state, x,y, mouse_button) then
|
|
|
|
|
return
|
|
|
|
|
end
|
|
|
|
|
```
|
|
|
|
|
|
2023-04-10 03:01:33 +01:00
|
|
|
|
### mouse primitives
|
|
|
|
|
|
|
|
|
|
* `App.mouse_move(x, y)` -- sets the current position of the mouse to (`x`,
|
|
|
|
|
`y`).
|
|
|
|
|
(Based on [LÖVE](https://love2d.org/wiki/love.mouse.setPosition).)
|
|
|
|
|
|
|
|
|
|
* `App.mouse_down(mouse_button)` -- returns `true` if the button
|
2023-08-26 21:15:32 +01:00
|
|
|
|
`mouse_button` is pressed. See `App.mousepressed` for `mouse_button` codes.
|
2023-04-10 03:01:33 +01:00
|
|
|
|
(Based on [LÖVE](https://love2d.org/wiki/love.mouse.isDown).)
|
|
|
|
|
|
|
|
|
|
* `App.mouse_x()` -- returns the x coordinate of the current position of the
|
|
|
|
|
mouse.
|
|
|
|
|
(Based on [LÖVE](https://love2d.org/wiki/love.mouse.getX).)
|
|
|
|
|
|
|
|
|
|
* `App.mouse_y()` -- returns the x coordinate of the current position of the
|
|
|
|
|
mouse.
|
|
|
|
|
(Based on [LÖVE](https://love2d.org/wiki/love.mouse.getY).)
|
|
|
|
|
|
|
|
|
|
### keyboard primitives
|
|
|
|
|
|
2023-06-06 06:22:53 +01:00
|
|
|
|
* `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.
|
|
|
|
|
|
2023-06-06 06:21:04 +01:00
|
|
|
|
* `App.key_down(key)` -- returns `true` if the given key is currently pressed.
|
2023-04-10 03:01:33 +01:00
|
|
|
|
(Based on [LÖVE](https://love2d.org/wiki/love.keyboard.isDown).)
|
|
|
|
|
|
|
|
|
|
### interacting with files
|
|
|
|
|
|
|
|
|
|
* `App.open_for_reading(filename)` -- returns a file handle that you can
|
|
|
|
|
[`read()`](https://www.lua.org/manual/5.1/manual.html#pdf-file:read) from.
|
|
|
|
|
Make sure `filename` is an absolute path so that your app can work reliably
|
|
|
|
|
by double-clicking on it.
|
|
|
|
|
(Based on [Lua](https://www.lua.org/manual/5.1/manual.html#pdf-io.open).)
|
|
|
|
|
|
|
|
|
|
* `App.open_for_writing(filename)` -- returns a file handle that you can
|
|
|
|
|
[`write()`](https://www.lua.org/manual/5.1/manual.html#pdf-file:write) to.
|
|
|
|
|
Make sure `filename` is an absolute path so that your app can work reliably
|
|
|
|
|
by double-clicking on it.
|
|
|
|
|
(Based on [Lua](https://www.lua.org/manual/5.1/manual.html#pdf-io.open).)
|
|
|
|
|
|
|
|
|
|
* `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](https://github.com/rxi/json.lua).)
|
|
|
|
|
|
|
|
|
|
* `json.decode(obj)` -- turns a JSON string into a Lua object.
|
|
|
|
|
(From [json.lua](https://github.com/rxi/json.lua).)
|
|
|
|
|
|
2023-08-31 03:04:06 +01:00
|
|
|
|
* `App.files(dir)` -- returns an unsorted array of the files and directories
|
|
|
|
|
available under `dir`.
|
2023-04-10 03:01:33 +01:00
|
|
|
|
(From [LÖVE](https://love2d.org/wiki/love.filesystem.getDirectoryItems).]
|
|
|
|
|
|
|
|
|
|
* `love.filesystem.getInfo(filename)` -- returns some information about
|
|
|
|
|
`filename`, particularly whether it exists (non-`nil` return value) or not.
|
|
|
|
|
(From [LÖVE](https://love2d.org/wiki/love.filesystem.getInfo).]
|
|
|
|
|
|
|
|
|
|
* `os.remove(filename)` -- removes a file or empty directory. Definitely make
|
|
|
|
|
sure `filename` is an absolute path.
|
|
|
|
|
(From [Lua](https://www.lua.org/manual/5.1/manual.html#pdf-os.remove).)
|
|
|
|
|
|
|
|
|
|
There's much more I could include here; check out [the LÖVE manual](https://love2d.org/wiki/love.filesystem)
|
|
|
|
|
and [the Lua manual](https://www.lua.org/manual/5.1/manual.html#5.7).
|
|
|
|
|
|
|
|
|
|
### desiderata
|
|
|
|
|
|
2023-08-30 14:43:01 +01:00
|
|
|
|
* `App.get_time()` -- returns the number of seconds elapsed since some
|
2023-04-10 03:01:33 +01:00
|
|
|
|
unspecified start time.
|
|
|
|
|
(Based on [LÖVE](https://love2d.org/wiki/love.timer.getTime).)
|
|
|
|
|
|
2023-08-30 14:43:01 +01:00
|
|
|
|
* `App.get_clipboard()` -- returns a string with the current clipboard
|
2023-04-10 03:01:33 +01:00
|
|
|
|
contents.
|
|
|
|
|
(Based on [LÖVE](https://love2d.org/wiki/love.system.getClipboardText).)
|
|
|
|
|
|
2023-08-30 14:43:01 +01:00
|
|
|
|
* `App.set_clipboard(text)` -- stores the string `text` in the clipboard.
|
2023-04-10 03:01:33 +01:00
|
|
|
|
(Based on [LÖVE](https://love2d.org/wiki/love.system.setClipboardText).)
|
|
|
|
|
|
2023-06-06 06:23:25 +01:00
|
|
|
|
* `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.
|
|
|
|
|
|
2023-04-10 03:01:33 +01:00
|
|
|
|
There's much more I could include here; check out [the LÖVE manual](https://love2d.org/wiki)
|
|
|
|
|
and [the Lua manual](https://www.lua.org/manual/5.1/manual.html).
|
2023-04-12 05:33:33 +01:00
|
|
|
|
|
|
|
|
|
### 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)` -- mimics keystrokes 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()`.
|
2023-05-01 08:07:56 +01:00
|
|
|
|
|
|
|
|
|
### 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 <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 a
|
|
|
|
|
`name`.
|