16 KiB
Building blocks for your Freewheeling Apps
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.
Variables you can read
App.screen
width
andheight
-- integer dimensions for the app window in pixels.flags
-- some properties of the app window. Seeflags
inlove.graphics.getMode
for details.
Functions you can implement that will get automatically called
-
on.initialize(arg)
-- called when app starts up. Provides inarg
an array of words typed in if you ran it from a terminal window. (Based on LÖVE.) -
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 insettings
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 betrue
when app starts receiving keypresses andfalse
when keypresses move to another window. (Based on LÖVE.) -
on.resize(w,h)
-- called when you resize the app window. Provides new window dimensions inw
andh
. Don't bother updatingApp.screen.width
andApp.screen.height
, that will happen automatically before callingon.resize
. (Based on LÖVE) -
on.file_drop(file)
-- called when a file icon is dragged and dropped on the app window. Provides infile
an object representing the file that was dropped, that will respond to the following messages:file:getFilename()
returning a string namefile:read()
returning the entire file contents in a single string
(Based on LÖVE.)
-
on.code_change()
-- called when you make changes to the app using driver.love, any time you hitf4
inside driver.love, after a definition is created or modified. -
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 indt
the 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)
-- called when you press down on a mouse button. Provides inx
andy
the point on the screen at which the click occurred, and inmouse_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), and3
is the middle button. Further buttons are mouse-dependent. (Based on LÖVE.) -
on.mouse_release(x,y, mouse_button)
-- called when you release a mouse button. Provides the same arguments ason.mouse_press()
above. (Based on LÖVE.) -
on.mouse_wheel_move(dx,dy)
-- called when you use the scroll wheel on a mouse that has it. Provides indx
anddy
an indication of how fast the wheel is being scrolled. Positive values fordx
indicate movement to the right. Positive values fordy
indicate upward movement. (Based on LÖVE.) -
on.keychord_press(chord, key)
-- called when you press a key-combination. Provides inkey
a string name for the key most recently pressed (valid values). Provides inchord
a string representation of the current key combination, consisting of the key with the following prefixes:C-
if one of thectrl
keys is pressed,M-
if one of thealt
keys is pressed,S-
if one of theshift
keys is pressed, ands-
if thewindows
/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
anda
pressed together will callon.textinput
withA
. (Based on LÖVE.) -
on.key_release(key)
-- called when you press a key on the keyboard. Provides inkey
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 giventext
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 tofont
. (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 oftext
in pixels when rendered using the current font. (Based on LÖVE.) -
App.color(color)
-- sets the current color based on the fieldsr
,g
,b
anda
(for opacity) of the tablecolor
. (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 dimensionswidth
along the x axis andheight
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 radiusr
.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 radiusr
.mode
is a string, either'line'
and'fill'
.angle1
andangle2
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_height, line_height)
-- returns an object that can be used to render an interactive editor widget for text starting aty=top
on the app window, betweenx=left
andx=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 fromon.draw
to display the current editor state on the app window as requested in the call toedit.initialize_state
that createdstate
. -
edit.update(state, dt)
-- call this fromon.update
to periodically auto-save editor contents to disk. -
edit.mouse_press(state, x,y, mouse_button)
andedit.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)
andedit.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.
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.
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)
-- returnstrue
if the buttonmouse_button
is pressed. Seeon.mouse_press
formouse_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.modifier_down(key)
-- returnstrue
if the given key (doesn't actually have to be just a modifier) is currently pressed. (Based on LÖVE.)
interacting with files
-
App.open_for_reading(filename)
-- returns a file handle that you canread()
from. Make surefilename
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 canwrite()
to. Make surefilename
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 objectobj
that will recreateobj
when passed tojson.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.) -
love.filesystem.getDirectoryItems(dir)
-- returns an unsorted array of the files and directories available underdir
.dir
must be relative to LÖVE's save directory. There is no easy, portable way in Lua/LÖVE to list directories outside the save dir. (From LÖVE.] -
love.filesystem.getInfo(filename)
-- returns some information aboutfilename
, particularly whether it exists (non-nil
return value) or not.filename
must be relative to LÖVE's save directory. (From LÖVE.] -
os.remove(filename)
-- removes a file or empty directory. Definitely make surefilename
is an absolute path. (From Lua.)
There's much more I could include here; check out the LÖVE manual and the Lua manual.
desiderata
-
App.getTime()
-- returns the number of seconds elapsed since some unspecified start time. (Based on LÖVE.) -
App.getClipboardText()
-- returns a string with the current clipboard contents. (Based on LÖVE.) -
App.setClipboardText(text)
-- stores the stringtext
in the clipboard. (Based on LÖVE.)
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 aty
. This isn't very realistic;y
must exactly match what was displayed, and the expected contents show everything printed to thaty
in chronological order, regardless ofx
coordinate. In spite of these limitations, you can write lots of useful tests with this. -
App.run_after_textinput(t)
-- mimics keystrokes resulting int
and then draws one frame. -
App.run_after_keychord(chord)
-- mimics keystrokes resulting inchord
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 forApp.getTime()
.