Sloppy standard library reference

Generated by tools/apidoc.py from the library sources. Everything listed is available without imports. Functions can be called as f(x, y) or x.f(y).

Core (always available)

array.jo

Generic array utilities. Arrays are values with copy-on-write storage.

is_empty[T] = (xs: T[]) -> bool
first[T] = (xs: T[]) -> T
last[T] = (xs: T[]) -> T
contains[T] = (xs: T[], x: T) -> bool
index_of[T] = (xs: T[], x: T) -> int
map[T, U] = (xs: T[], f: (T) -> U) -> U[]
filter[T] = (xs: T[], f: (T) -> bool) -> T[]
each[T] = (xs: T[], f: (T) -> void)
reduce[T, A] = (xs: T[], init: A, f: (A, T) -> A) -> A
any[T] = (xs: T[], f: (T) -> bool) -> bool
all[T] = (xs: T[], f: (T) -> bool) -> bool
find[T] = (xs: T[], f: (T) -> bool) -> T?
count[T] = (xs: T[], f: (T) -> bool) -> int
sum[T] = (xs: T[]) -> T
min[T] = (xs: T[]) -> T
max[T] = (xs: T[]) -> T
reverse[T] = (xs: mut T[])
reversed[T] = (xs: T[]) -> T[]
range = (a: int, b: int) -> int[]
enumerate[T] = (xs: T[]) -> (int, T)[]
zip[A, B] = (xs: A[], ys: B[]) -> (A, B)[]
flatten[T] = (xss: T[][]) -> T[]
swap[T] = (xs: mut T[], i: int, j: int)
sort[T] = (xs: mut T[])    # sort in place (ascending, using <)
sort_with[T] = (xs: mut T[], less: (T, T) -> bool)    # sort in place with a custom "less than" function
sort_by[T, K] = (xs: mut T[], key: (T) -> K)    # sort in place by a key
sorted[T] = (xs: T[]) -> T[]

map.jo

Hash map {K: V}: insertion ordered, open addressing over a dense entry list.
The first two fields must stay keys and vals (the compiler iterates them in for loops).

struct Map[K, V]
hash = (x: int) -> u64    # full 64-bit avalanche (murmur3 finalizer): every key bit affects the low bits used for slots, so keys like multiples of 4096 or float bit patterns spread out
hash = (x: u64) -> u64
hash = (x: bool) -> u64
hash = (x)
([]=)[K, V] = (m: mut Map[K, V], k: K, v: V)
([])[K, V] = (m: Map[K, V], k: K) -> V
get[K, V] = (m: Map[K, V], k: K) -> V?
get[K, V] = (m: Map[K, V], k: K, default: V) -> V
contains[K, V] = (m: Map[K, V], k: K) -> bool
has[K, V] = (m: Map[K, V], k: K) -> bool
len[K, V] = (m: Map[K, V]) -> int
is_empty[K, V] = (m: Map[K, V]) -> bool
keys[K, V] = (m: Map[K, V]) -> K[]
values[K, V] = (m: Map[K, V]) -> V[]
clear[K, V] = (m: mut Map[K, V])
remove[K, V] = (m: mut Map[K, V], k: K) -> bool    # remove a key; returns true if it was present. The last entry moves into the hole.

math.jo

Math basics.

const PI = 3.141592653589793
const TAU = 6.283185307179586
const E = 2.718281828459045
const INF = 1e308 * 10.0
const MAX_INT = 9223372036854775807
const MIN_INT = -9223372036854775807 - 1
abs = (x: int) -> int
abs = (x: f64) -> f64
abs = (x: f32) -> f32
min = (a: int, b: int) -> int
max = (a: int, b: int) -> int
min = (a: f64, b: f64) -> f64
max = (a: f64, b: f64) -> f64
min = (a: f32, b: f32) -> f32
max = (a: f32, b: f32) -> f32
clamp = (x: int, lo: int, hi: int) -> int
clamp = (x: f64, lo: f64, hi: f64) -> f64
clamp = (x: f32, lo: f32, hi: f32) -> f32
sign = (x: f64) -> f64
sign = (x: int) -> int
floor = (x: f64) -> f64
ceil = (x: f64) -> f64
round = (x: f64) -> f64
trunc = (x: f64) -> f64
fract = (x: f64) -> f64
mod = (x: f64, y: f64) -> f64
mod = (x: int, y: int) -> int
is_nan = (x: f64) -> bool
lerp = (a: f64, b: f64, t: f64) -> f64
mix = (a: f64, b: f64, t: f64) -> f64
smoothstep = (e0: f64, e1: f64, x: f64) -> f64
sin = (x: f64) -> f64
cos = (x: f64) -> f64
tan = (x: f64) -> f64
atan = (x0: f64) -> f64
atan2 = (y: f64, x: f64) -> f64
asin = (x: f64) -> f64
acos = (x: f64) -> f64
exp = (x: f64) -> f64
log = (x: f64) -> f64
log2 = (x: f64) -> f64
log10 = (x: f64) -> f64
pow = (x: f64, y: f64) -> f64
exp = (x: f32) -> f32    # f32 versions (shader code, games): computed in double with shorter polynomials, so they are within an ulp of the f32 result at a fraction of the cost of the full-precision ones
log = (x: f32) -> f32
pow = (x: f32, y: f32) -> f32
pow = (x: int, n: int) -> int
hypot = (x: f64, y: f64) -> f64
radians = (deg: f64) -> f64
degrees = (rad: f64) -> f64
struct Rng
rng = (seed: int) -> Rng
next_u64 = (r: mut Rng) -> u64
next_float = (r: mut Rng) -> f64    # uniform f64 in [0, 1)
next_int = (r: mut Rng, lo: int, hi: int) -> int    # uniform int in [lo, hi)
seed_random = (seed: int)
random = () -> f64    # random f64 in [0, 1)
random = (lo: f64, hi: f64) -> f64    # random f64 in [lo, hi)
random_int = (lo: int, hi: int) -> int    # random int in [lo, hi)

os.jo

Operating system access: files, arguments, environment, time.

exit = (code: int)
args = () -> str[]    # command line arguments (args()[0] is the program path)
env = (name: str) -> str?    # an environment variable (in a web page: the page's URL parameter of that name, where `SLOPPY_X` may also be written `x`, and a bare `?x` is "1")
read_file = (path: str) -> str?    # read a whole file; none if it cannot be read
read_bytes = (path: str) -> u8[]?
write_file = (path: str, contents: str) -> bool
write_bytes = (path: str, b: u8[]) -> bool
file_exists = (path: str) -> bool
make_executable = (path: str) -> bool
delete_file = (path: str) -> bool
time = () -> f64    # seconds since an arbitrary point (monotonic clock)
time_ns = () -> int    # nanoseconds since an arbitrary point (monotonic clock)
sleep = (seconds: f64)
write = (s: str)    # print without a trailing newline
eprint = (s: str)
list_dir = (path: str) -> str[]    # names of the entries in a directory (excluding . and ..)
exe_path = () -> str    # absolute path of the running executable
exec = (path: str, argv: str[]) -> bool    # replace the current process with another program (returns only on failure)
spawn_detached = (path: str, argv: str[]) -> bool    # start a program in the background, detached from this one: its own session, no terminal (input and output go to /dev/null); returns whether it was started
chmod = (path: str, mode: int) -> bool
mkdir = (path: str) -> bool

rt.jo

Sloppy core runtime: memory, refcounted objects, arrays, strings.
Everything here is called by compiler-generated code. Heap objects (arrays, strings)
share one layout: [rc: int][len: int][cap: int][data...]. rc < 0 means immortal.

alloc_count = () -> int    # number of live heap allocations (useful for leak tests)

str.jo

String utilities. Strings are immutable UTF-8 byte sequences; indexing gives bytes.

is_digit = (c: u8) -> bool
is_alpha = (c: u8) -> bool
is_alnum = (c: u8) -> bool
is_space = (c: u8) -> bool
is_upper = (c: u8) -> bool
is_lower = (c: u8) -> bool
chr = (c: int) -> str    # string made of a single byte
find = (s: str, sub: str, start: int = 0) -> int
rfind = (s: str, sub: str) -> int
find_byte = (s: str, c: u8, start: int = 0) -> int
contains = (s: str, sub: str) -> bool
starts_with = (s: str, prefix: str) -> bool
ends_with = (s: str, suffix: str) -> bool
trim = (s: str) -> str
trim = (s: str, chars: str) -> str    # remove any of the characters in `chars` from both ends
trim_start = (s: str) -> str
trim_end = (s: str) -> str
split = (s: str, sep: str) -> str[]
words = (s: str) -> str[]    # split on runs of whitespace
lines = (s: str) -> str[]
join = (parts: str[], sep: str = "") -> str
join = (sep: str, parts: str[]) -> str    # sep.join(parts), as in Python
replace = (s: str, old: str, new: str) -> str
repeat = (s: str, n: int) -> str
(*) = (s: str, n: int) -> str
upper = (s: str) -> str
lower = (s: str) -> str
to_int = (s: str) -> int?
to_float = (s: str) -> f64?    # Parse a decimal number. The result is the double nearest to the exact decimal value (ties to even), like C's strtod: an estimate, then exact big-integer comparisons.
reverse = (s: str) -> str
char_count = (s: str) -> int    # number of unicode code points
chars = (s: str) -> int[]    # unicode code points of s
hash = (s: str) -> u64

Parallelism and numeric arrays

nd.jo

Arrays for numeric work, in the spirit of numpy and MATLAB.

T[,] is an n-dimensional array (NDArray[T]): its elements in row-major order, and its shape.
How many dimensions it has is part of the value, as in numpy (T[,,] is the same type; the
commas are only for the reader). Write a 2-D one as [1 2; 3 4] (rows separated by ';' or line
breaks), stack arrays with [a; b], or make one with zeros(2, 3, 4), ones, eye, rand, reshape.
m[i, j] (with as many indexes as dimensions) reads and writes an element; m.shape is its shape.
Ranges select parts: m[i, ..] is row i, m[.., j] column j, m[1..2, 0..] a block (ranges include
both ends; missing indexes at the end mean whole axes). A part is an array of its own; assigning
to one (m[.., 0] = xs, m[0, ..] = 0.0) writes into m.

The dotted operators work element by element on arrays of any shape, and broadcast like numpy
(a dimension of length 1, or a missing leading one, stretches to fit): a .* b, m .+ 1.0, v .^ 2,
xs .< 0.5, and f.(xs) applies f to each element. A whole dotted expression runs as one loop.
On 2-D arrays the plain operators follow linear algebra: a * b is the matrix product.

type Array[T] = T[]    # another spelling of T[]
struct NDArray[T]
ndarray[T] = (data: T[], shape: int[]) -> NDArray[T]
ndim[T] = (m: NDArray[T]) -> int
size[T] = (m: NDArray[T]) -> int
len[T] = (m: NDArray[T]) -> int
rows[T] = (m: NDArray[T]) -> int
cols[T] = (m: NDArray[T]) -> int
flatten[T] = (m: NDArray[T]) -> T[]
([])[T] = (m: NDArray[T], i: int) -> T
([])[T] = (m: NDArray[T], i: int, j: int) -> T
([])[T] = (m: NDArray[T], i: int, j: int, k: int) -> T
([])[T] = (m: NDArray[T], i: int, j: int, k: int, l: int) -> T
([]=)[T] = (m: mut NDArray[T], i: int, v: T)
([]=)[T] = (m: mut NDArray[T], i: int, j: int, v: T)
([]=)[T] = (m: mut NDArray[T], i: int, j: int, k: int, v: T)
([]=)[T] = (m: mut NDArray[T], i: int, j: int, k: int, l: int, v: T)
zeros = (n: int) -> f64[]
zeros = (a: int, b: int) -> NDArray[f64]
zeros = (a: int, b: int, c: int) -> NDArray[f64]
zeros = (a: int, b: int, c: int, d: int) -> NDArray[f64]
ones = (n: int) -> f64[]
ones = (a: int, b: int) -> NDArray[f64]
ones = (a: int, b: int, c: int) -> NDArray[f64]
ones = (a: int, b: int, c: int, d: int) -> NDArray[f64]
eye = (n: int) -> NDArray[f64]    # the identity matrix
rand = (n: int) -> f64[]    # uniformly random in [0, 1)
rand = (a: int, b: int) -> NDArray[f64]
rand = (a: int, b: int, c: int) -> NDArray[f64]
randn = () -> f64    # normally distributed (mean 0, standard deviation 1)
randn = (n: int) -> f64[]
randn = (a: int, b: int) -> NDArray[f64]
randn = (a: int, b: int, c: int) -> NDArray[f64]
linspace = (a: f64, b: f64, n: int) -> f64[]    # n evenly spaced values from a to b (both included)
reshape[T] = (xs: T[], shape: int[]) -> NDArray[T]
reshape[T] = (xs: T[], a: int, b: int) -> NDArray[T]
reshape[T] = (xs: T[], a: int, b: int, c: int) -> NDArray[T]
reshape[T] = (m: NDArray[T], shape: int[]) -> NDArray[T]
reshape[T] = (m: NDArray[T], a: int, b: int) -> NDArray[T]
reshape[T] = (m: NDArray[T], a: int, b: int, c: int) -> NDArray[T]
transpose[T] = (m: NDArray[T]) -> NDArray[T]    # the axes in reverse order (rows and columns swapped, for a 2-D array)
row[T] = (m: NDArray[T], i: int) -> T[]
col[T] = (m: NDArray[T], j: int) -> T[]
sum[T] = (m: NDArray[T]) -> T
min[T] = (m: NDArray[T]) -> T
max[T] = (m: NDArray[T]) -> T
mean = (xs: f64[]) -> f64
mean = (m: NDArray[f64]) -> f64
sum[T] = (m: NDArray[T], axis: int) -> NDArray[T]    # along one axis: the array without that axis, each element combining the ones along it (sum(m, 0) adds up the rows of a 2-D array: one sum per column)
min[T] = (m: NDArray[T], axis: int) -> NDArray[T]
max[T] = (m: NDArray[T], axis: int) -> NDArray[T]
mean = (m: NDArray[f64], axis: int) -> NDArray[f64]
dot = (a: f64[], b: f64[]) -> f64
norm = (a: f64[]) -> f64
(*)[T] = (a: NDArray[T], b: NDArray[T]) -> NDArray[T]    # matrix product
(*)[T] = (a: NDArray[T], v: T[]) -> T[]    # matrix times column vector
(*)[T] = (k: T, m: NDArray[T]) -> NDArray[T]
(*)[T] = (m: NDArray[T], k: T) -> NDArray[T]
(/)[T] = (m: NDArray[T], k: T) -> NDArray[T]
(+)[T] = (a: NDArray[T], b: NDArray[T]) -> NDArray[T]
(-)[T] = (a: NDArray[T], b: NDArray[T]) -> NDArray[T]

thread.jo

Data parallelism on a pool of worker threads (fork-join).

squares = parallel_map(nums, (x): x * x)
parallel_update(particles, (p): step(p, dt)) # xs[i] = f(xs[i]), in place
rows = parallel_range(height, (y): render_row(y))

The function runs on several threads at once: it may read anything, but it must not change
globals or the variables it captured (that would be a data race); return results instead.
On the web target everything runs on the calling thread.

cpu_count = () -> int    # number of hardware threads available to this process
parallel_map[T, U] = (xs: T[], f: (T) -> U) -> U[]
parallel_update[T] = (xs: mut T[], f: (T) -> T)
parallel_range[U] = (n: int, f: (int) -> U) -> U[]

Games: windows, input, 2D/3D drawing, GPU programs, audio

audio.jo

Audio: a software mixer written in Sloppy. Sounds are float sample buffers (stereo,
interleaved, at the mixer rate). Each frame the mixer renders just enough samples to
keep the output device fed. Native: ALSA (loaded at runtime; silent if unavailable).
Web: WebAudio.

struct Sound
struct Voice
enum Wave: sine, square, triangle, saw, noise
const MIX_RATE = 48000
frames = (s: Sound) -> int
duration = (s: Sound) -> f64
play = (s: Sound, volume: f64 = 1.0, pan: f64 = 0.0, pitch: f64 = 1.0, looping: bool = false) -> int    # play a sound; returns a voice id for stop()/set_volume()
stop = (id: int)
stop_all = ()
is_playing = (id: int) -> bool
set_volume = (id: int, volume: f64)
set_pitch = (id: int, pitch: f64)
master_volume = (v: f64)
tanh = (x: f64) -> f64
tone = (freq: f64, seconds: f64, wave: Wave = .sine, volume: f64 = 0.5, attack: f64 = 0.005, release: f64 = 0.1, slide: f64 = 1.0, vibrato: f64 = 0.0) -> Sound    # a single synthesized note: frequency slides from freq to freq * slide over the duration
mix = (sounds: Sound[]) -> Sound    # mix several sounds into one (useful for chords and layered effects)
sfx_jump = () -> Sound    # sound effect presets
sfx_coin = () -> Sound
sfx_hit = () -> Sound
sfx_explosion = () -> Sound
sfx_laser = () -> Sound
sfx_powerup = () -> Sound
sfx_click = () -> Sound
sfx_whoosh = () -> Sound
sfx_chime = () -> Sound
note_freq = (name: str) -> f64    # note name ("C4", "F#3", "Bb5") to frequency in Hz
melody = (notes: str, bpm: f64 = 120.0, wave: Wave = .triangle, volume: f64 = 0.3, step: f64 = 0.5) -> Sound    # render a melody: notes separated by spaces, "-" holds the previous note, "." is a rest
load_wav = (file: u8[]) -> Sound

dl.jo

Loading system shared libraries (GPU drivers) from a fully static executable.

The program has no dynamic loader of its own. To call into libEGL/libGL we start the
system's ld.so inside our process: we map it, hand it a tiny in-memory "main program"
that depends on libc and imports dlopen/dlsym, let it initialize libc, and when it jumps
to the helper's entry point the helper calls back into our code, which jumps back to
where we started. From then on dlopen/dlsym are ordinary C function pointers.
If anything is missing (no ld.so, unusual system) dl_init() returns false and the
caller falls back to software rendering.

dl_init = () -> bool
dl_open = (name: str) -> *u8    # open a shared library (RTLD_NOW | RTLD_GLOBAL); null if unavailable
dl_sym = (lib: *u8, name: str) -> *u8
dl_error = () -> str
lib_available = (lib: str) -> bool    # true if the library can be loaded on this system

draw2d.jo

2D drawing: batched shapes, textured sprites, SDF text, transforms, blend modes and
render targets. Coordinates are in pixels with (0, 0) at the top-left.

struct Texture
struct RenderTarget
enum Blend: alpha, add, multiply, premultiplied
texture = (img: Image, smooth: bool = true) -> Texture    # upload an image to the GPU
set_repeat = (t: Texture, on: bool)
update_texture = (t: Texture, img: Image)
load_texture = (file: u8[], smooth: bool = true) -> Texture
render_target = (w: int, h: int, smooth: bool = true) -> RenderTarget
free_render_target = (rt: RenderTarget)    # give back a render target's memory (it may not be used after)
draw_to = (rt: RenderTarget)    # draw into a render target until draw_to_screen() is called
draw_to_screen = ()
clear = (c: vec4 = vec4(0, 0, 0, 1))
blend = (b: Blend)
push_transform = ()
pop_transform = ()
translate = (x: f64, y: f64)
rotate = (angle: f64)
scale = (sx: f64, sy: f64)
reset_transform = ()
camera2d = (target: vec2, zoom: f64 = 1.0, angle: f64 = 0.0)    # 2D camera: `target` appears at the screen center, scaled by zoom
screen_to_world = (p: vec2) -> vec2    # convert a screen position to world coordinates under the current transform
flush = ()
rect = (x: f64, y: f64, w: f64, h: f64, c: vec4)
rect = (pos: vec2, size: vec2, c: vec4)
rect_gradient = (x: f64, y: f64, w: f64, h: f64, top: vec4, bottom: vec4)    # rectangle with a vertical color gradient
rect_outline = (x: f64, y: f64, w: f64, h: f64, thickness: f64, c: vec4)
triangle = (a: vec2, b: vec2, c: vec2, col: vec4)
triangle_gradient = (a: vec2, b: vec2, c: vec2, ca: vec4, cb: vec4, cc: vec4)    # triangle with a color per corner (colors blend across it)
quad_gradient = (a: vec2, b: vec2, c: vec2, d: vec2, ca: vec4, cb: vec4, cc: vec4, cd: vec4)    # quad (a, b, c, d in order around it) with a color per corner
circle = (center: vec2, radius: f64, c: vec4, segments: int = 0)
circle_gradient = (center: vec2, radius: f64, inner: vec4, outer: vec4, segments: int = 48)    # circle that fades from `inner` color at the center to `outer` at the edge (glows, soft lights)
ring = (center: vec2, radius: f64, thickness: f64, c: vec4, segments: int = 48)
line = (a: vec2, b: vec2, thickness: f64, c: vec4)
polyline = (points: vec2[], thickness: f64, c: vec4, closed: bool = false)
polygon = (points: vec2[], c: vec4)    # convex polygon (triangle fan)
sprite = (t: Texture, pos: vec2, size: vec2 = vec2(0, 0), tint: vec4 = vec4(1, 1, 1, 1), angle: f64 = 0.0)    # draw a texture at `pos` (top-left), optionally scaled, rotated around its center and tinted
sprite_region = (t: Texture, src: vec4, center: vec2, size: vec2, tint: vec4 = vec4(1, 1, 1, 1), angle: f64 = 0.0)    # draw part of a texture (src = x, y, w, h in pixels) centered at `center`
draw_target = (rt: RenderTarget, pos: vec2 = vec2(0, 0), size: vec2 = vec2(0, 0), tint: vec4 = vec4(1, 1, 1, 1))    # draw a render target's texture (flipped: render targets are stored bottom-up)
text_width = (s: str, size: f64 = 20.0) -> f64
text = (s: str, pos: vec2, size: f64 = 20.0, color: vec4 = vec4(1, 1, 1, 1))    # draw text with its top-left corner at `pos`
text_centered = (s: str, center: vec2, size: f64 = 20.0, color: vec4 = vec4(1, 1, 1, 1))    # text centered horizontally at `center`

draw3d.jo

3D rendering: camera, sun + sky lighting, fog, shadows, instancing and post effects.
Draw calls made during draw() are recorded and rendered (shadow pass, then the lit
pass) before any 2D drawing that follows them, so a HUD can be drawn on top.

struct Vertex3D
struct Instance3D
struct LitParams
struct LitOut
lit_vs = (v: Vertex3D, u: LitParams) -> LitOut
lit_vs_inst = (v: Vertex3D, inst: Instance3D, u: LitParams) -> LitOut
shadow_factor = (u: LitParams, sp: vec4, ndl: f32) -> f32
aces = (c: vec3) -> vec3
lit_fs = (i: LitOut, u: LitParams) -> vec4
struct ShadowOut
shadow_vs = (v: Vertex3D, u: LitParams) -> ShadowOut
shadow_vs_inst = (v: Vertex3D, inst: Instance3D, u: LitParams) -> ShadowOut
shadow_fs = (i: ShadowOut, u: LitParams) -> vec4
struct SkyVertex
struct SkyParams
struct SkyOut
sky_vs = (v: SkyVertex, u: SkyParams) -> SkyOut
hash21 = (p: vec2) -> f32
sky_fs = (i: SkyOut, u: SkyParams) -> vec4
struct PostParams
struct PostOut
post_vs = (v: SkyVertex, u: PostParams) -> PostOut
bright_fs = (i: PostOut, u: PostParams) -> vec4
blur_fs = (i: PostOut, u: PostParams) -> vec4
composite_fs = (i: PostOut, u: PostParams) -> vec4
struct Draw3D
camera3d = (pos: vec3, target: vec3, fov_degrees: f64 = 60.0, near: f64 = 0.1, far: f64 = 500.0)    # place the camera; call once per frame before drawing 3D objects
sun = (direction: vec3, color: vec3 = vec3(1, 0.95, 0.85))
sky = (top: vec3, horizon: vec3, ground: vec3 = vec3(0.3, 0.27, 0.24))
hide_sky = ()
night_stars = (amount: f64)
fog = (color: vec3, density: f64 = 0.01, height_falloff: f64 = 0.05)
shadows = (on: bool, strength: f64 = 0.8, area: f64 = 30.0)
post_effects = (bloom: f64 = 0.6, threshold: f64 = 0.7, vignette: f64 = 0.35, saturation: f64 = 1.0, tint: vec3 = vec3(1, 1, 1))    # bloom + color grading for the whole frame (2D and 3D)
no_post_effects = ()
draw_mesh = (m: Mesh, transform: mat4 = mat4(), color: vec4 = vec4(1, 1, 1, 1), texture: Texture = Texture(0, 0, 0), emissive: f64 = 0.0, specular: f64 = 0.3, cast_shadow: bool = true)
draw_mesh_instanced = (m: Mesh, instances: Instance3D[], color: vec4 = vec4(1, 1, 1, 1), emissive: f64 = 0.0, specular: f64 = 0.2, cast_shadow: bool = true, wind: f64 = 0.0)    # many copies of a mesh in one draw call (crowds, forests, particles...)
draw_custom = (render: () -> void, transparent: bool = false, additive: bool = false)    # draw with your own shader inside the 3D pass; `render` runs with depth testing on. Use scene_view_proj(), scene_camera(), scene_sun(), scene_shadow_vp(), scene_shadow_map()... transparent = true: run after the sky with alpha blending on and depth writes off; additive = true: like transparent, but colors add up (glows, beams, light shafts)
scene_view_proj = () -> mat4
scene_camera = () -> vec3
scene_sun = () -> vec3
scene_sun_color = () -> vec3
scene_sky_color = () -> vec3
scene_ground_color = () -> vec3
scene_fog_color = () -> vec3
scene_fog = () -> vec2
scene_shadow_vp = () -> mat4
scene_shadow_map = () -> Texture
scene_shadow_strength = () -> f32
instance = (transform: mat4, color: vec4 = vec4(1, 1, 1, 1)) -> Instance3D
transform = (pos: vec3, rot_y: f64 = 0.0, scale: f64 = 1.0) -> mat4    # transform helpers
cube_mesh = (size: f64 = 1.0) -> Mesh
sphere_mesh = (radius: f64 = 0.5, segments: int = 32, rings: int = 16) -> Mesh
terrain_mesh = (size: f64, divisions: int, height: (f64, f64) -> f64, color: (f64, f64, f64) -> vec4) -> Mesh    # a flat grid in the XZ plane; `height` gives the terrain elevation at (x, z)
plane_mesh = (size: f64 = 10.0) -> Mesh
cylinder_mesh = (radius: f64 = 0.5, height: f64 = 1.0, segments: int = 24, top_radius: f64 = -1.0) -> Mesh
cone_mesh = (radius: f64 = 0.5, height: f64 = 1.0, segments: int = 24) -> Mesh
custom_mesh = (vertices: Vertex3D[], indices: u32[] = []) -> Mesh    # custom mesh from vertex/index arrays

gamepad.jo

Gamepads: the first connected controller, with a standard layout (Xbox-style names).

if gamepad_pressed(.a): jump()
move = left_stick() # vec2 in -1..1, y down, with a dead zone

input_axis() also follows the left stick and the d-pad, so keyboard games work with a pad.
Native builds read the Linux joystick device (/dev/input/js0); web builds use the browser's
Gamepad API.

enum PadButton: a, b, x, y, lb, rb, back, start, home, lstick, rstick, up, down, left, right
gamepad_connected = () -> bool
gamepad_axis = (i: int) -> f64    # axis value in -1..1 (triggers 0..1): 0 left x, 1 left y, 2 right x, 3 right y, 4 left trigger, 5 right trigger
gamepad_down = (b: PadButton) -> bool
gamepad_pressed = (b: PadButton) -> bool
gamepad_released = (b: PadButton) -> bool
left_stick = () -> vec2
right_stick = () -> vec2

gameutil.jo

Common game helpers: noise, collision shapes, easing, timers and 2D particles.

noise = (x: f64, y: f64) -> f64    # smooth value noise in [0, 1]
fbm = (x: f64, y: f64, octaves: int = 4) -> f64    # fractal noise (several octaves) in roughly [0, 1]
ease_in_out = (t: f64) -> f64
ease_out = (t: f64) -> f64
ease_out_back = (t: f64) -> f64
approach = (current: f64, target: f64, speed: f64, dt: f64) -> f64    # frame-rate independent smoothing toward a target (higher speed = snappier)
approach = (current: vec2, target: vec2, speed: f64, dt: f64) -> vec2
approach = (current: vec3, target: vec3, speed: f64, dt: f64) -> vec3
struct Rect
overlaps = (a: Rect, b: Rect) -> bool
contains = (r: Rect, p: vec2) -> bool
center = (r: Rect) -> vec2
circles_overlap = (a: vec2, ra: f64, b: vec2, rb: f64) -> bool
circle_rect = (c: vec2, r: f64, rect: Rect) -> bool
struct Particle
struct Particles
emit = (ps: mut Particles, pos: vec2, count: int, speed: f64, color: vec4, life: f64 = 1.0, size: f64 = 4.0, spread: f64 = TAU, direction: f64 = 0.0)
update = (ps: mut Particles, dt: f64)
draw = (ps: Particles, glow: bool = true)    # draw particles as soft glowing dots (additive blending)
every = (id: int, period: f64) -> bool

gl.jo

Portable OpenGL ES 3.0 / WebGL2 layer. Native builds load the system driver
(libGLESv2) at runtime; web builds call WebGL2 through the page's JavaScript.

const GL_DEPTH_BUFFER_BIT = 0x100
const GL_COLOR_BUFFER_BIT = 0x4000
const GL_POINTS = 0
const GL_LINES = 1
const GL_LINE_STRIP = 3
const GL_TRIANGLES = 4
const GL_TRIANGLE_STRIP = 5
const GL_TRIANGLE_FAN = 6
const GL_BLEND = 0xBE2
const GL_DEPTH_TEST = 0xB71
const GL_CULL_FACE = 0xB44
const GL_SCISSOR_TEST = 0xC11
const GL_FRONT = 0x404
const GL_BACK = 0x405
const GL_CW = 0x900
const GL_CCW = 0x901
const GL_ZERO = 0
const GL_ONE = 1
const GL_SRC_ALPHA = 0x302
const GL_ONE_MINUS_SRC_ALPHA = 0x303
const GL_DST_COLOR = 0x306
const GL_LESS = 0x201
const GL_LEQUAL = 0x203
const GL_ARRAY_BUFFER = 0x8892
const GL_ELEMENT_ARRAY_BUFFER = 0x8893
const GL_STATIC_DRAW = 0x88E4
const GL_DYNAMIC_DRAW = 0x88E8
const GL_STREAM_DRAW = 0x88E0
const GL_BYTE = 0x1400
const GL_UNSIGNED_BYTE = 0x1401
const GL_UNSIGNED_SHORT = 0x1403
const GL_UNSIGNED_INT = 0x1405
const GL_FLOAT = 0x1406
const GL_HALF_FLOAT = 0x140B
const GL_FRAGMENT_SHADER = 0x8B30
const GL_VERTEX_SHADER = 0x8B31
const GL_TEXTURE_2D = 0xDE1
const GL_TEXTURE0 = 0x84C0
const GL_TEXTURE_MAG_FILTER = 0x2800
const GL_TEXTURE_MIN_FILTER = 0x2801
const GL_TEXTURE_WRAP_S = 0x2802
const GL_TEXTURE_WRAP_T = 0x2803
const GL_NEAREST = 0x2600
const GL_LINEAR = 0x2601
const GL_LINEAR_MIPMAP_LINEAR = 0x2703
const GL_REPEAT = 0x2901
const GL_CLAMP_TO_EDGE = 0x812F
const GL_MIRRORED_REPEAT = 0x8370
const GL_RGBA = 0x1908
const GL_RGB = 0x1907
const GL_RED = 0x1903
const GL_RGBA8 = 0x8058
const GL_R8 = 0x8229
const GL_RGBA16F = 0x881A
const GL_DEPTH_COMPONENT = 0x1902
const GL_DEPTH_COMPONENT24 = 0x81A6
const GL_FRAMEBUFFER = 0x8D40
const GL_RENDERBUFFER = 0x8D41
const GL_COLOR_ATTACHMENT0 = 0x8CE0
const GL_DEPTH_ATTACHMENT = 0x8D00
const GL_FRAMEBUFFER_COMPLETE = 0x8CD5
const GL_UNPACK_ALIGNMENT = 0xCF5
const GL_COMPILE_STATUS = 0x8B81
const GL_LINK_STATUS = 0x8B82
const GL_INFO_LOG_LENGTH = 0x8B84

gpu.jo

GPU programs written in Sloppy: meshes, shaders and uniform binding.

struct Vertex: pos: vec3; color: vec4 -> vertex attributes
struct Params: mvp: mat4 -> uniforms
struct Out: pos: vec4; color: vec4 -> first field is the clip position
vs = (v: Vertex, u: Params) -> Out: Out(u.mvp * vec4(v.pos, 1), v.color)
fs = (i: Out, u: Params) -> vec4: i.color
prog = make_shader(shader(vs, fs))
m = mesh(vertices)
draw(prog, m, Params(mvp))

struct ShaderSource    # produced by the compiler from shader(vs, fs)
struct ShaderAttrib
struct ShaderUniform
struct Shader
struct Mesh
make_shader = (src: ShaderSource) -> Shader
mesh[V] = (vertices: V[], indices: u32[] = [], mode: int = GL_TRIANGLES) -> Mesh    # upload vertices (any struct type matching the shader's vertex input) and optional indices
update_mesh[V] = (m: mut Mesh, vertices: V[])
draw[U] = (sh: Shader, m: Mesh, params: U)    # draw a mesh with a shader; `params` is the uniforms struct the shader functions take
draw_instanced[U, I] = (sh: Shader, m: mut Mesh, instances: I[], params: U)    # draw many copies of a mesh in one call; `instances` holds the per-instance shader input
sample = (t: Texture, uv: vec2) -> vec4    # texture lookup inside shader functions (maps to GLSL texture()); outside the software renderer, calling it on the CPU returns white
sample_lod = (t: Texture, uv: vec2, lod: f32) -> vec4    # texture lookup at a given mipmap level (maps to GLSL textureLod; the software renderer has no mipmaps and samples the full-size image)
texel = (t: Texture, p: ivec2) -> vec4    # the texel at integer coordinates p (0 to size - 1; y = 0 is the first row of the image data): no filtering or wrapping (maps to GLSL texelFetch)
texture_size = (t: Texture) -> ivec2    # a texture's size in texels (maps to GLSL textureSize)
discard = ()    # in a fragment shader: drop this fragment (no color, no depth), e.g. for alpha-tested leaves: `if c.a < 0.5: discard()`. The rest of the function still runs on the CPU; its result is unused.
vertex_id = () -> int    # in a vertex shader: the index of the vertex being shaded (GLSL gl_VertexID) and of the instance (gl_InstanceID: 0 for draw, the index into the instances for draw_instanced)
instance_id = () -> int
fract = (v: vec2) -> vec2
step = (edge: f32, x: f32) -> f32

gpuarray.jo

GPU arrays: numbers kept in GPU memory and computed on by GPU programs.

g = gpu(xs) # upload (up to 4 dimensions)
h = g .* 2.0 .+ sin.(g) # one GPU program, made from the expression at compile time
ys = cpu(h) # download: an array of the same shape
total = sum(h) # reductions run on the GPU too

Values are f32, or i32 for arrays made from integers (gpu(int_array)): integer arithmetic
keeps integer semantics (7 / 2 is 3), and mixing with floats gives floats. A dotted expression
whose arrays are GPU arrays runs on the GPU as one fragment program (the functions applied with
f.(g) are translated to GLSL, like shader functions; numbers in the expression become
uniforms). Shapes broadcast as on the CPU. Native programs use OpenGL ES (without a window,
through a windowless EGL context), web builds WebGL 2. Where neither can render to float
textures (no driver, SLOPPY_SOFTWARE=1, an old browser, node) the same expressions run on the CPU,
so programs work everywhere; gpu_available() tells which.

Textures hold four values per texel (RGBA), as wide as the GPU allows: up to about a billion
values on a typical desktop GPU (64 million where textures are at most 4096 wide).

struct GpuArray[T]
gpu_available = () -> bool
gpu = (xs: f32[]) -> GpuArray[f32]
gpu = (xs: f64[]) -> GpuArray[f32]
gpu = (xs: i32[]) -> GpuArray[i32]
gpu = (xs: int[]) -> GpuArray[i32]
gpu = (xs: bool[]) -> GpuArray[f32]
gpu = (m: NDArray[f32]) -> GpuArray[f32]
gpu = (m: NDArray[f64]) -> GpuArray[f32]
gpu = (m: NDArray[i32]) -> GpuArray[i32]
gpu = (m: NDArray[int]) -> GpuArray[i32]
cpu[T] = (g: GpuArray[T]) -> NDArray[T]    # the values (an array of the GPU array's shape)
len[T] = (g: GpuArray[T]) -> int
sum = (g: GpuArray[f32]) -> f64    # Floats are reduced on the GPU (64 values at a time, until few are left), then added up in f64; integers are added up exactly, in 64 bits, on the CPU.
sum = (g: GpuArray[i32]) -> int
mean = (g: GpuArray[f32]) -> f64
mean = (g: GpuArray[i32]) -> f64
min = (g: GpuArray[f32]) -> f32
max = (g: GpuArray[f32]) -> f32
min = (g: GpuArray[i32]) -> i32
max = (g: GpuArray[i32]) -> i32

image.jo

Images: RGBA8 pixel buffers, PNG decoding (with a built-in inflate), procedural helpers.

struct Image
image = (w: int, h: int, fill: vec4 = vec4(0, 0, 0, 0)) -> Image
set = (img: mut Image, x: int, y: int, c: vec4)
get = (img: Image, x: int, y: int) -> vec4
struct Inflate
struct Huff
inflate = (src: u8[], start: int = 0) -> u8[]?
load_png = (file: u8[]) -> Image?    # decode a PNG file; none if the data is not a supported PNG
load_image = (file: u8[]) -> Image    # load an image from an embedded file: load_image(embed("hero.png"))
encode_png = (img: Image) -> u8[]
save_png = (img: Image, path: str) -> bool
screenshot = () -> Image    # read the current framebuffer into an image

screen.jo

How a game's picture fits windows of any size and shape.

A game is drawn in 2D units: the size given to window() (its design size), so the same
coordinates work in any window. Two settings decide how that maps to the window's pixels:

screen_fit(mode): what a window of another shape shows
.expand the design area, scaled to fit, and more around it (the default: no bars,
nothing cut; visible_rect() says what is shown)
.letterbox just the design area, scaled to fit, with bars (letterbox_color) around it
.crop the design area scaled to fill the window: what does not fit is cut off
.stretch the design area stretched to the window (shapes distort)
.native no scaling: one 2D unit is one pixel of the window (screen_size() is its size)

render_resolution(w, h): draw at a fixed resolution, scaled up to the window, by default
with sharp pixels (nearest) and in whole steps (2x, 3x...); 2D units stay the design size
(give both the same shape). The picture letterboxes (or crops/stretches with those
fits). render_resolution(0, 0) goes back to the window's resolution.
pixel_art(): render_resolution at the design size: for a game designed at 320 x 180, one
2D unit is one sharp pixel. (A window for a design that small opens a whole number of
times larger.)
render_scale(s): draw at a fraction of the window's pixels (0.5: a quarter of them), scaled
up smoothly: faster where pixels cost (3D, the software renderer).

Drawing that needs it goes to an offscreen canvas, put on the window when the frame ends; the
mouse is mapped back the same way (mouse_pos() is in 2D units).

enum Fit: expand, letterbox, crop, stretch, native
screen_fit = (mode: Fit)
letterbox_color = (c: vec4)
render_resolution = (w: int, h: int, smooth: bool = false, whole: bool = true)
pixel_art = (smooth: bool = false)
render_scale = (s: f64)

softgl.jo

Software rendering: a CPU implementation of the part of OpenGL ES the game library uses,
for machines without a GPU driver (or with SLOPPY_SOFTWARE=1). Shaders written in Sloppy run as
the compiled Sloppy functions they are; triangles are rasterized in horizontal bands on all
cores. Frames are rendered at a reduced internal resolution and scaled up on screen.

soft_rendering = () -> bool
sgl_clear_color = (r: f32, g: f32, b: f32, a: f32)
sgl_clear = (mask: u32)
sgl_viewport = (x: i32, y: i32, w: i32, h: i32)
sgl_scissor = (x: i32, y: i32, w: i32, h: i32)
sgl_enable = (cap: u32, on: bool)
sgl_blend_func = (s: u32, d: u32, sa: u32, da: u32)
sgl_depth_func = (f: u32)
sgl_depth_mask = (on: u32)
sgl_color_mask = (r: u32, g: u32, b: u32, a: u32)
sgl_cull_face = (mode: u32)
sgl_create_buffer = () -> int
sgl_bind_buffer = (target: u32, b: u32)
sgl_buffer_data = (target: u32, size: int, p: *u8)
sgl_buffer_sub_data = (target: u32, off: int, size: int, p: *u8)
sgl_create_texture = () -> int
sgl_active_texture = (unit: u32)
sgl_bind_texture = (t: u32)
sgl_tex_image = (w: int, h: int, format: int, ty: int, pixels: *u8)
sgl_tex_sub_image = (x: int, y: int, w: int, h: int, format: int, ty: int, pixels: *u8)
sgl_tex_parameter = (pname: u32, v: i32)
sgl_create_framebuffer = () -> int
sgl_bind_framebuffer = (f: u32)
sgl_framebuffer_texture = (attachment: u32, tex: u32)
sgl_create_renderbuffer = () -> int
sgl_bind_renderbuffer = (r: u32)
sgl_renderbuffer_storage = (w: i32, h: i32)
sgl_framebuffer_renderbuffer = (attachment: u32, r: u32)
sgl_create_program = () -> int
sgl_read_pixels = (x: int, y: int, w: int, h: int, out: *u8)    # read the window (or bound target) as RGBA bytes, bottom row first, scaled to w x h

stats.jo

A frame statistics overlay: frame times as a graph, CPU time, time handing frames to the
screen, and frames that took much longer than usual (stutter). show_stats() turns it on, as do
SLOPPY_STATS=1 and, in a web page, ?stats in the URL. SLOPPY_STATS=log (?stats=log) prints the same
numbers every two seconds instead, and each long frame as it happens.

show_stats = (on: bool = true)

vmath.jo

Vector and matrix math for the built-in vec2/vec3/vec4/mat4 types (f32 components).
mat4 is column-major: m[i] is column i.

dot = (a: vec2, b: vec2) -> f32
dot = (a: vec3, b: vec3) -> f32
dot = (a: vec4, b: vec4) -> f32
cross = (a: vec3, b: vec3) -> vec3
cross = (a: vec2, b: vec2) -> f32
length = (v: vec2) -> f32
length = (v: vec3) -> f32
length = (v: vec4) -> f32
length_sq = (v: vec2) -> f32
length_sq = (v: vec3) -> f32
distance = (a: vec2, b: vec2) -> f32
distance = (a: vec3, b: vec3) -> f32
normalize = (v: vec2) -> vec2
normalize = (v: vec3) -> vec3
normalize = (v: vec4) -> vec4
lerp = (a: f32, b: f32, t: f32) -> f32
lerp = (a: vec2, b: vec2, t: f32) -> vec2
lerp = (a: vec3, b: vec3, t: f32) -> vec3
lerp = (a: vec4, b: vec4, t: f32) -> vec4
mix = (a: f32, b: f32, t: f32) -> f32
mix = (a: vec2, b: vec2, t: f32) -> vec2
mix = (a: vec3, b: vec3, t: f32) -> vec3
mix = (a: vec4, b: vec4, t: f32) -> vec4
abs = (v: vec2) -> vec2
abs = (v: vec3) -> vec3
min = (a: vec2, b: vec2) -> vec2
max = (a: vec2, b: vec2) -> vec2
min = (a: vec3, b: vec3) -> vec3
max = (a: vec3, b: vec3) -> vec3
clamp = (v: vec2, lo: vec2, hi: vec2) -> vec2
clamp = (v: vec3, lo: f32, hi: f32) -> vec3
floor = (v: vec2) -> vec2
reflect = (d: vec3, n: vec3) -> vec3
reflect = (d: vec2, n: vec2) -> vec2
perp = (v: vec2) -> vec2
angle = (v: vec2) -> f64
from_angle = (a: f64) -> vec2
rotate = (v: vec2, a: f64) -> vec2
move_toward = (a: vec2, b: vec2, step: f32) -> vec2
sinf = (x: f32) -> f32
cosf = (x: f32) -> f32
rgb = (r: f32, g: f32, b: f32) -> vec4
rgba = (r: f32, g: f32, b: f32, a: f32) -> vec4
gray = (v: f32) -> vec4
hex = (c: int) -> vec4
hsv = (h: f64, s: f64, v: f64) -> vec4
with_alpha = (c: vec4, a: f32) -> vec4
lighten = (c: vec4, k: f32) -> vec4
darken = (c: vec4, k: f32) -> vec4
const WHITE = vec4(1, 1, 1, 1)
const BLACK = vec4(0, 0, 0, 1)
const RED = vec4(0.9, 0.2, 0.2, 1)
const GREEN = vec4(0.2, 0.8, 0.3, 1)
const BLUE = vec4(0.2, 0.4, 0.9, 1)
const YELLOW = vec4(1, 0.85, 0.2, 1)
const ORANGE = vec4(1, 0.55, 0.15, 1)
const PURPLE = vec4(0.6, 0.3, 0.8, 1)
const CYAN = vec4(0.2, 0.85, 0.9, 1)
const MAGENTA = vec4(0.9, 0.25, 0.75, 1)
const GRAY = vec4(0.5, 0.5, 0.5, 1)
const TRANSPARENT = vec4(0, 0, 0, 0)
mat4_identity = () -> mat4
translation = (t: vec3) -> mat4
scaling = (s: vec3) -> mat4
rotation = (axis0: vec3, a: f64) -> mat4
rotation_x = (a: f64) -> mat4
rotation_y = (a: f64) -> mat4
rotation_z = (a: f64) -> mat4
perspective = (fov_y: f64, aspect: f64, near: f64, far: f64) -> mat4
orthographic = (left: f64, right: f64, bottom: f64, top: f64, near: f64, far: f64) -> mat4
look_at = (eye: vec3, target: vec3, up: vec3) -> mat4
transpose = (m: mat4) -> mat4
transform_point = (m: mat4, p: vec3) -> vec3
transform_dir = (m: mat4, d: vec3) -> vec3
inverse = (m: mat4) -> mat4
quat_axis_angle = (axis: vec3, a: f64) -> vec4
quat_mul = (a: vec4, b: vec4) -> vec4
quat_rotate = (q: vec4, v: vec3) -> vec3
quat_to_mat4 = (q: vec4) -> mat4
slerp = (a: vec4, b0: vec4, t: f32) -> vec4

wayland.jo

A Wayland client: a window from xdg-shell, GPU frames handed over as dma-bufs
(zwp_linux_dmabuf_v1, see dmabuf.jo) or else presented through shared memory (wl_shm),
keyboard and pointer input from wl_seat. Decorations: libdecor's (as the desktop draws them,
see wlclient.jo), or the compositor's (xdg-decoration), or else a small title bar drawn here.
Requests are built and events read in the wire format, then go through libwayland-client
(wlclient.jo) or, without it, straight over the socket. Windows render at the display's real
resolution: the window's size is in the compositor's logical units, scaled by the output's
(possibly fractional) scale.

const WLK_NONE = 0
const WLK_DISPLAY = 1
const WLK_REGISTRY = 2
const WLK_CALLBACK = 3
const WLK_COMPOSITOR = 4
const WLK_SHM = 5
const WLK_POOL = 6
const WLK_BUFFER = 7
const WLK_SURFACE = 8
const WLK_WM_BASE = 9
const WLK_XDG_SURFACE = 10
const WLK_TOPLEVEL = 11
const WLK_SEAT = 12
const WLK_POINTER = 13
const WLK_KEYBOARD = 14
const WLK_DECO_MANAGER = 15
const WLK_DECORATION = 16
const WLK_FRAME = 17           # a frame callback
const WLK_CURSOR_MGR = 18      # wp_cursor_shape_manager_v1
const WLK_CURSOR_DEV = 19
const WLK_DMABUF = 20          # zwp_linux_dmabuf_v1
const WLK_DMABUF_PARAMS = 21
const WLK_VIEWPORTER = 22
const WLK_VIEWPORT = 23
const WLK_FRAC_MGR = 24        # wp_fractional_scale_manager_v1
const WLK_FRAC = 25
const WLK_DDM = 26             # wl_data_device_manager (the clipboard)
const WLK_DDEV = 27
const WLK_DSOURCE = 28
const WLK_DOFFER = 29
const WL_BAR = 30
const WL_EDGE = 8              # pixels along the window's border that resize it

window.jo

Windows, the frame loop and input. Native: X11 + EGL (OpenGL ES 3) loaded from the
system at runtime. Web: a <canvas> with WebGL2 driven by requestAnimationFrame.

enum Key:
enum Mouse: left, middle, right
key_down = (k: Key) -> bool    # true while the key is held
key_pressed = (k: Key) -> bool    # true only in the frame the key was pressed
key_released = (k: Key) -> bool
key_typed = (k: Key) -> bool    # true in the frame the key was pressed, and again each time it repeats while held down (for moving through text or menus); characters typed arrive through text_input()
mouse_pos = () -> vec2    # (in the 2D units of the screen: see screen_size and screen.jo)
mouse_delta = () -> vec2
mouse_wheel = () -> f64
mouse_down = (b: Mouse) -> bool
mouse_pressed = (b: Mouse) -> bool
mouse_released = (b: Mouse) -> bool
text_input = () -> str    # characters typed this frame
input_axis = () -> vec2    # arrows/WASD (or a gamepad's left stick and d-pad) as a direction vector (y down)
screen_width = () -> int    # The screen's size in 2D drawing units: the size given to window() (the design size), whatever the window's real size; screen.jo says how it maps to the window (screen_fit). pixel_size() is the window's real size.
screen_height = () -> int
screen_size = () -> vec2
pixel_size = () -> vec2
aspect_ratio = () -> f64    # width / height of the picture 3D draws into (for a projection: perspective(fov, aspect_ratio(), ...)): the window's, or the design area's when the screen fit shows just that (screen.jo)
visible_rect = () -> vec4    # the part of the 2D plane the screen shows, as (x, y, width, height): with screen_fit(.expand) (the default) the design area centered in a window of any shape and what is around it (draw backgrounds over this to fill the window); with bars, just the design area
frame_time = () -> f64
fps = () -> f64
elapsed = () -> f64
frame_number = () -> int
quit = ()
set_fullscreen = (on: bool = true)
toggle_fullscreen = ()
is_fullscreen = () -> bool
clipboard = () -> str    # the text on the system clipboard ("" if none, or not text). In a web page the browser lets a page read it only as it is pasted: this is the text last pasted (ctrl+V) into the page.
set_clipboard = (s: str)
window = (title: str = "", width: int = 1280, height: int = 720)    # Open the game window (called automatically when the program defines update/draw).