flan/vendor/raylib/raylib.flan
Joseph Ferano 8e47356592 A field label is a dot now, and the colon is refused where one was
The delimiter is what disambiguates: (.x v) is a call and therefore an
access, {.x 1.0} is a brace form and therefore a construction. The colon
kept two jobs -- field label and enum member -- and this leaves it with
one, keys, which is what a map literal will want.

The old spelling is refused rather than quietly accepted, and the refusal
names the new one. Two accepted spellings is how two spellings become
permanent, and this repo rejects what it does not support and says why.

:keys keeps its colon. It names no field -- it is an instruction to the
compiler that happens to sit in the same brace -- so leaving it alone is
what lets the dot mean exactly one thing.

render.ml prints the dot too, or a struct the daemon shows would not be
Flan anyone could paste back.
2026-09-12 14:51:57 +07:00

1033 lines
50 KiB
Plaintext
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

;;;; raylib, declared for Flan. The directory is the package (plan.org,
;;;; Modules), and (import rl "vendor:raylib") qualifies all of it as rl/…
;;;;
;;;; Every binding here is one `declare-c` line naming raylib's own function
;;;; in raylib's own signature — Color by value, Vector2 returned by value —
;;;; and the compiler writes the C that flattens it. There is no shim.c in
;;;; this directory any more, and no hand-written wrapper at all.
;;;;
;;;; The shim itself did not go away, only the typing of it. A small
;;;; aggregate's calling convention is a per-target classification rather than
;;;; part of its layout: x86-64 hands Vector2 over as <2 x float> and returns
;;;; Rectangle as {i64,i64}, and arm64 and wasm32 each do something else.
;;;; Written in C, clang classifies every one of them correctly for whichever
;;;; target the build is for; written in emit.ml it would be three calling
;;;; conventions to reimplement and then keep correct forever, and a mistake
;;;; would read as a field full of garbage rather than as a link error. This
;;;; is "one narrow host ABI, implemented twice" (plan.org, Targets). See
;;;; lib/shim.ml.
;;;;
;;;; What that means for reading this file: the `defstruct`s below are the
;;;; only statement anywhere about raylib's layouts, and the generated C
;;;; typedefs are made from them. No raylib header is consulted — a build
;;;; needs libraylib linkable, not raylib-devel — so a wrong field order here
;;;; is wrong everywhere and nothing but a test can catch it. The acceptance
;;;; cases pin the layouts by making raylib *compute* with the fields, and
;;;; they go red when a struct below is permuted. Likewise a scalar's width:
;;;; f64 where raylib says float now emits `double` in the generated
;;;; prototype, and raylib reads garbage.
;;;;
;;;; Two bindings keep a hand-written Flan wrapper, both because their Flan
;;;; face is deliberately not raylib's: collision-point-poly? takes a slice,
;;;; and collision-lines answers with an Option. Both wrappers are Flan.
;; Layouts are C's — no object headers anywhere — so these are exactly
;; raylib's structs and nothing marshals.
(defstruct Vector2 [x f32 y f32])
(defstruct Color [r u8 g u8 b u8 a u8])
;; Texture2D is five 4-byte fields in a row, which is the layout most likely
;; to be silently wrong: permute two of them and every field still reads as a
;; plausible number. Rectangle is four floats in x/y/width/height order.
(defstruct Texture2D [id u32 width i32 height i32 mipmaps i32 format i32])
(defstruct Rectangle [x f32 y f32 width f32 height f32])
;; KeyboardKey, the subset sand.flan uses. A keyword at a call site resolves
;; against these members at compile time and a typo is an error there.
(defenum Key
[space 32 apostrophe 39 comma 44 minus 45 period 46 slash 47
zero 48 one 49 two 50 three 51 four 52
five 53 six 54 seven 55 eight 56 nine 57
a 65 b 66 c 67 d 68 e 69 f 70 g 71 h 72 i 73
j 74 k 75 l 76 m 77 n 78 o 79 p 80 q 81 r 82
s 83 t 84 u 85 v 86 w 87 x 88 y 89 z 90
escape 256 enter 257 tab 258 backspace 259
right 262 left 263 down 264 up 265])
(defenum MouseButton
[left 0 right 1 middle 2 side 3 extra 4 forward 5 back 6])
(defenum TraceLogLevel
[all 0 trace 1 debug 2 info 3 warning 4 error 5 fatal 6 none 7])
;; ── Window ──────────────────────────────────────────────────────────
(declare-c init-window [width i32 height i32 title string] "InitWindow")
(declare-c close-window [] "CloseWindow")
(declare-c window-should-close? [] bool "WindowShouldClose")
(declare-c set-target-fps [fps i32] "SetTargetFPS")
(declare-c set-trace-log-level [level TraceLogLevel] "SetTraceLogLevel")
;; A bitfield, like the gestures below and for the same reason: raylib wants
;; the OR of several and a keyword can only ever name one member, so the
;; parameter is a u32 and the members are defconsts rather than a defenum.
;;
;; The part that is NOT obvious from the signature: this has to be called
;; BEFORE init-window. raylib stores the flags and reads them while creating
;; the context, so setting them afterwards is accepted, logged at INFO, and
;; does nothing to the window that already exists — which looks exactly like
;; a binding that did not work. Only the four the examples ask for are here;
;; the rest are one line each when something calls them.
(defconst flag-fullscreen-mode u32 2)
(defconst flag-window-resizable u32 4)
(defconst flag-msaa-4x-hint u32 32)
(defconst flag-vsync-hint u32 64)
(declare-c set-config-flags [flags u32] "SetConfigFlags")
;; ── Input ───────────────────────────────────────────────────────────
(declare-c key-pressed? [key Key] bool "IsKeyPressed")
(declare-c key-down? [key Key] bool "IsKeyDown")
(declare-c key-released? [key Key] bool "IsKeyReleased")
(declare-c mouse-button-pressed?
[button MouseButton] bool
"IsMouseButtonPressed")
(declare-c mouse-button-down? [button MouseButton] bool "IsMouseButtonDown")
(declare-c mouse-button-released?
[button MouseButton] bool
"IsMouseButtonReleased")
(declare-c get-mouse-position [] Vector2 "GetMousePosition")
;; One notch of the wheel is 1.0 and there are no fractional notches on an
;; ordinary mouse, but it is a float because a trackpad's two-finger scroll
;; is continuous. It is the DELTA since the last frame, not an accumulated
;; position, so it reads 0.0 on every frame the wheel did not move — which is
;; why a caller that wants a running total keeps one itself.
(declare-c get-mouse-wheel-move [] f32 "GetMouseWheelMove")
;; The cursor's visibility is window state and not input, but it is read and
;; written by the same code that reads the mouse, so it sits here.
;; hide-cursor only hides it; it does not lock it to the window, which is
;; what raylib's separate DisableCursor does and which nothing here needs.
(declare-c show-cursor [] "ShowCursor")
(declare-c hide-cursor [] "HideCursor")
(declare-c cursor-hidden? [] bool "IsCursorHidden")
;; ── Colours ─────────────────────────────────────────────────────────
;;
;; A Color is four bytes in RGBA order, so it is *not* the little-endian
;; reading of the packed 0xRRGGBBAA integer — that is why get-color is a real
;; call and not a reinterpretation.
(declare-c get-color [hex u32] Color "GetColor")
;; The same colour at a different alpha: raylib multiplies `a` by the factor
;; and leaves r, g and b alone. It is NOT a blend against a background, so a
;; faded colour still needs something drawn behind it to fade against. Bound
;; because the gesture examples draw every overlay through it, and it is the
;; only call in the file that takes a Color and answers one — which makes it
;; the shortest statement anywhere that the Color crossing works in both
;; directions at once.
(declare-c fade [color Color alpha f32] Color "Fade")
(defconst black (Color {.r 0 .g 0 .b 0 .a 255}))
(defconst white (Color {.r 255 .g 255 .b 255 .a 255}))
;; raylib's own named palette, from raylib.h's CLITERAL macros. These are the
;; only entries in this file that are not a function, a layout or an enum, and
;; they are here for the reason the two above already were: every raylib
;; example is written in terms of them, so without them a port is a wall of
;; hex that cannot be diffed against the C it came from. They are values and
;; not calls — a Color is four bytes with no packing question — so unlike
;; get-color they cost nothing at run time and need no window.
(defconst lightgray (Color {.r 200 .g 200 .b 200 .a 255}))
(defconst gray (Color {.r 130 .g 130 .b 130 .a 255}))
(defconst darkgray (Color {.r 80 .g 80 .b 80 .a 255}))
(defconst yellow (Color {.r 253 .g 249 .b 0 .a 255}))
(defconst gold (Color {.r 255 .g 203 .b 0 .a 255}))
(defconst orange (Color {.r 255 .g 161 .b 0 .a 255}))
(defconst pink (Color {.r 255 .g 109 .b 194 .a 255}))
(defconst red (Color {.r 230 .g 41 .b 55 .a 255}))
(defconst maroon (Color {.r 190 .g 33 .b 55 .a 255}))
(defconst green (Color {.r 0 .g 228 .b 48 .a 255}))
(defconst lime (Color {.r 0 .g 158 .b 47 .a 255}))
(defconst darkgreen (Color {.r 0 .g 117 .b 44 .a 255}))
(defconst skyblue (Color {.r 102 .g 191 .b 255 .a 255}))
(defconst blue (Color {.r 0 .g 121 .b 241 .a 255}))
(defconst darkblue (Color {.r 0 .g 82 .b 172 .a 255}))
(defconst purple (Color {.r 200 .g 122 .b 255 .a 255}))
(defconst violet (Color {.r 135 .g 60 .b 190 .a 255}))
(defconst darkpurple (Color {.r 112 .g 31 .b 126 .a 255}))
(defconst beige (Color {.r 211 .g 176 .b 131 .a 255}))
(defconst brown (Color {.r 127 .g 106 .b 79 .a 255}))
(defconst darkbrown (Color {.r 76 .g 63 .b 47 .a 255}))
(defconst magenta (Color {.r 255 .g 0 .b 255 .a 255}))
;; Alpha 0, so it is invisible rather than a colour — raylib's own name for it.
(defconst blank (Color {.r 0 .g 0 .b 0 .a 0}))
;; raylib's off-white background, which is what every example clears to.
(defconst raywhite (Color {.r 245 .g 245 .b 245 .a 255}))
;; ── Drawing ─────────────────────────────────────────────────────────
(declare-c begin-drawing [] "BeginDrawing")
(declare-c end-drawing [] "EndDrawing")
(declare-c draw-fps [x i32 y i32] "DrawFPS")
(declare-c clear-background [color Color] "ClearBackground")
(declare-c draw-rectangle
[x i32 y i32 width i32 height i32 color Color]
"DrawRectangle")
;; ── Shapes texture ──────────────────────────────────────────────────
;;
;; raylib draws every shape from one atlas texture, and this pair sets and
;; reads it. It is bound here for a second reason: it is the only part of the
;; API that stores a Texture2D and a Rectangle and hands them back without
;; touching the GPU, so it is how the acceptance table checks both layouts
;; headlessly. Everything else that takes a texture needs a GL context.
;;
;; raylib substitutes a default ({1,1,1,1,7} / {0,0,1,1}) when the id or the
;; source's width or height is not positive, so a caller — and the test —
;; should keep clear of those values if it wants its own back.
(declare-c set-shapes-texture
[texture Texture2D source Rectangle]
"SetShapesTexture")
(declare-c get-shapes-texture [] Texture2D "GetShapesTexture")
(declare-c get-shapes-texture-rectangle
[] Rectangle
"GetShapesTextureRectangle")
;; ── Camera2D ────────────────────────────────────────────────────────
;;
;; The 2D camera: everything drawn between begin-mode-2d and end-mode-2d is
;; transformed by it. `offset` is where the camera's target lands on screen —
;; half the window size is what centres it — `target` is the world point that
;; goes there, and rotation is in degrees.
;;
;; `zoom` of 0 makes the transform singular and both conversions below hand
;; back NaN rather than failing. raylib does not guard it and neither does
;; this; 1.0 is the identity and a fresh (Camera2D {}) is therefore NOT usable
;; as one — it has to be given a zoom.
(defstruct Camera2D [offset Vector2 target Vector2 rotation f32 zoom f32])
(declare-c begin-mode-2d [camera Camera2D] "BeginMode2D")
(declare-c end-mode-2d [] "EndMode2D")
;; The two conversions are pure arithmetic over every field of the camera, so
;; unlike the rest of the camera they run with no window and no GL context.
;; That is what the acceptance table uses to pin Camera2D's layout, and — via
;; a rotated camera, which is the only call here that mixes x into y — it is
;; also the only thing that pins Vector2's two fields against each other.
(declare-c get-screen-to-world-2d
[position Vector2 camera Camera2D] Vector2
"GetScreenToWorld2D")
(declare-c get-world-to-screen-2d
[position Vector2 camera Camera2D] Vector2
"GetWorldToScreen2D")
;; ── Shapes ──────────────────────────────────────────────────────────
;;
;; Rectangle intersection, which raylib computes from all four fields in
;; different ways. It is the one Rectangle call that needs no GPU, so it is
;; also how the acceptance table pins the layout: a store-and-return check is
;; symmetric and a permuted layout survives it untouched.
(declare-c get-collision-rec
[a Rectangle b Rectangle] Rectangle
"GetCollisionRec")
;; ── Collision ───────────────────────────────────────────────────────
;;
;; All of these are pure geometry: no window, no GL context, no state. That
;; makes them the other half of what the acceptance table can assert, and the
;; only part of the 2D surface that is tested as thoroughly as it is bound.
;;
;; Each is declared exactly as raylib declares it. The pointers and the copies
;; the crossing needs — a parameter is not an assignable place
;; (spec-memory.md), so a struct argument has no address to take without one —
;; are in the generated halves and not here.
(declare-c collision-recs?
[a Rectangle b Rectangle] bool
"CheckCollisionRecs")
(declare-c collision-circles?
[c1 Vector2 r1 f32 c2 Vector2 r2 f32] bool
"CheckCollisionCircles")
(declare-c collision-circle-rec?
[center Vector2 radius f32 rec Rectangle] bool
"CheckCollisionCircleRec")
(declare-c collision-circle-line? [center Vector2 radius f32
p1 Vector2 p2 Vector2] bool "CheckCollisionCircleLine")
(declare-c collision-point-rec?
[point Vector2 rec Rectangle] bool
"CheckCollisionPointRec")
(declare-c collision-point-circle?
[point Vector2 center Vector2 radius f32] bool
"CheckCollisionPointCircle")
(declare-c collision-point-triangle? [point Vector2 a Vector2 b Vector2
c Vector2] bool "CheckCollisionPointTriangle")
;; `threshold` is in pixels, and it is not optional in practice: raylib's test
;; is a distance comparison in floats, so a point exactly on the line fails at
;; a threshold of 0. 1 is the useful smallest value.
(declare-c collision-point-line? [point Vector2 p1 Vector2 p2 Vector2
threshold i32] bool "CheckCollisionPointLine")
;; The one binding whose Flan face is not raylib's, and one of only two in
;; this file with a hand-written wrapper on top. A Flan slice crosses as
;; ptr+len with an i64 length; raylib wants a pointer and an `int` count, and
;; the generator refuses to guess what integer type a C count parameter is —
;; so the declaration says (Ptr Vector2) and a count, and the wrapper takes
;; the slice apart. The polygon is not closed explicitly; raylib joins the
;; last point to the first.
;;
;; Empty is answered here rather than passed on: (at points 0) would be an
;; out-of-bounds read, and raylib answers false for a polygon with no points
;; anyway.
(declare-c collision-point-poly?-raw
[point Vector2 points (Ptr Vector2) count i32] bool
"CheckCollisionPointPoly")
(defn collision-point-poly? [point Vector2 points [Vector2]] bool
(if (= (len points) 0)
false
(collision-point-poly?-raw point (addr (at points 0)) (len points))))
;; The one that answers with more than yes or no: where the two segments meet.
;; None is "they do not", so the point cannot be read when there isn't one —
;; raylib's own signature leaves the out-parameter untouched in that case and
;; a caller that forgets reads whatever was there.
(declare-c collision-lines-raw
[a1 Vector2 a2 Vector2 b1 Vector2 b2 Vector2 out (Ptr Vector2)] bool
"CheckCollisionLines")
(defn collision-lines [a1 Vector2 a2 Vector2 b1 Vector2 b2 Vector2]
(Option Vector2)
(let [out (Vector2 {})]
(if (collision-lines-raw a1 a2 b1 b2 (addr out))
(Some out)
None)))
;; ── Textures ────────────────────────────────────────────────────────
;;
;; Everything here needs a GL context, so a window has to be open first —
;; load-texture before init-window returns an id of 0 and raylib says so on
;; the log. texture-valid? is how that is noticed in the program rather than
;; only in the log; raylib 5.5 spells it IsTextureValid, and IsTextureReady,
;; which older code calls, does not exist in this version.
(declare-c load-texture [path string] Texture2D "LoadTexture")
(declare-c texture-valid? [texture Texture2D] bool "IsTextureValid")
(declare-c unload-texture [texture Texture2D] "UnloadTexture")
(declare-c draw-texture
[texture Texture2D x i32 y i32 tint Color]
"DrawTexture")
(declare-c draw-texture-v
[texture Texture2D position Vector2 tint Color]
"DrawTextureV")
(declare-c draw-texture-ex [texture Texture2D position Vector2 rotation f32
scale f32 tint Color] "DrawTextureEx")
;; A negative source width or height flips the sprite, which is how a sheet is
;; drawn facing the other way without a second image.
(declare-c draw-texture-rec [texture Texture2D source Rectangle position Vector2
tint Color] "DrawTextureRec")
;; ── Images ──────────────────────────────────────────────────────────
;;
;; An Image is pixels in RAM. Nothing here touches the GPU, which makes it the
;; one corner of the 2D surface a headless test can assert properly — raylib
;; *computes* with these, and a wrong answer is a wrong number rather than the
;; struct handed back unchanged.
;;
;; `data` is raylib's buffer and Flan never reads through it; it is here so
;; the struct is the right size and the four ints that follow are at the right
;; offsets. `format` is a PixelFormat code — GenImageColor makes 7, which is
;; uncompressed R8G8B8A8, one byte per channel.
;;
;; The split between by-value and by-pointer here is raylib's own and worth
;; keeping: a call that *mutates* the image takes (Ptr Image) at the Flan
;; level too, so a caller can see which ones change what they are given.
(defstruct Image [data (Ptr u8) width i32 height i32 mipmaps i32 format i32])
(declare-c load-image [path string] Image "LoadImage")
;; raylib 5.5 spells this IsImageValid. IsImageReady, which older code calls,
;; does not exist here — the same rename that took IsTextureReady.
(declare-c image-valid? [image Image] bool "IsImageValid")
;; The same decode, from bytes already in memory rather than from a path. This
;; is what an (embed "brush.png") is for, and it is the only route to a texture
;; on a target with no filesystem: a bare relative path has no meaning in a
;; browser, so LoadImage there opens nothing and hands back an image with a
;; null buffer.
;;
;; `fileType` is the extension *with* the dot — ".png" — because that is what
;; raylib compares against (rtextures.c, strcmp(fileType, ".png")). It is how
;; the decoder is chosen; there is no sniffing of the bytes.
;;
;; Declared with (Ptr u8) and an explicit count for the reason
;; collision-point-poly?-raw is: a slice crosses as ptr+len with an i64 length,
;; raylib wants a pointer and an `int`, and the shim generator refuses to guess
;; which integer type a C count parameter is. The Flan wrapper below takes the
;; slice apart, which is where that idiom lives everywhere else in this file.
(declare-c load-image-from-memory-raw
[file-type string file-data (Ptr u8) data-size i32] Image
"LoadImageFromMemory")
;; Empty is answered here rather than passed on, exactly as in
;; collision-point-poly?: (at data 0) on an empty slice is an out-of-bounds
;; read, and raylib's own answer to a zero-length buffer is an image with a
;; null buffer — which is what a zeroed one already is. image-valid? reports
;; false for it either way, so a caller that checks sees the same thing.
(defvar no-image Image)
(defn load-image-from-memory [file-type string data [u8]] Image
(if (= (len data) 0)
no-image
(load-image-from-memory-raw file-type (addr (at data 0)) (len data))))
;; By value, as raylib has it. The caller's copy is dangling afterwards —
;; `data` pointed at the buffer this just freed — so an Image is used or
;; unloaded, never both.
(declare-c unload-image [image Image] "UnloadImage")
;; The format is taken from the path's extension, so ".png" writes a PNG.
;; False means it could not be written.
(declare-c export-image [image Image path string] bool "ExportImage")
(declare-c gen-image-color
[width i32 height i32 color Color] Image
"GenImageColor")
;; Bicubic, so the pixels that come out are interpolated and only the new
;; width and height are exactly predictable. image-resize-nn is the
;; nearest-neighbour one, and it is the one to reach for when the colours
;; have to survive.
(declare-c image-resize
[image (Ptr Image) width i32 height i32]
"ImageResize")
(declare-c image-resize-nn
[image (Ptr Image) width i32 height i32]
"ImageResizeNN")
(declare-c image-crop [image (Ptr Image) crop Rectangle] "ImageCrop")
(declare-c image-flip-horizontal [image (Ptr Image)] "ImageFlipHorizontal")
(declare-c image-flip-vertical [image (Ptr Image)] "ImageFlipVertical")
(declare-c image-draw-pixel
[image (Ptr Image) x i32 y i32 color Color]
"ImageDrawPixel")
;; Out of bounds is not an error: raylib logs a warning and hands back a
;; transparent black, so a caller that is off by one gets zeroes rather than
;; somebody else's memory.
(declare-c get-image-color [image Image x i32 y i32] Color "GetImageColor")
;; The one call in this section that does need a GL context — it uploads. An
;; image loaded and edited on the CPU becomes something draw-texture can use.
(declare-c load-texture-from-image
[image Image] Texture2D
"LoadTextureFromImage")
;; ── Shapes ──────────────────────────────────────────────────────────
;;
;; Immediate-mode drawing: each of these needs a GL context, so a window has
;; to be open and none of them can be in the acceptance table. They are
;; exercised by running sand.flan and looking at it, which is the honest
;; description — "it links" is not "it draws the right thing".
;;
;; raylib's own naming is kept: a plain name fills, `-lines` outlines, and a
;; `-v` suffix takes Vector2s where the plain form takes integers.
;;
;; The one signature worth calling out is draw-rectangle-rounded-lines, which
;; in raylib 5.5 has NO thickness — it moved to the `-ex` form. The 5.1 header
;; still shows the five-argument version, and getting it wrong links cleanly
;; and draws nonsense, so this was read off the library with nm rather than
;; remembered.
(declare-c draw-pixel [x i32 y i32 color Color] "DrawPixel")
(declare-c draw-pixel-v [position Vector2 color Color] "DrawPixelV")
(declare-c draw-line [x1 i32 y1 i32 x2 i32 y2 i32 color Color] "DrawLine")
(declare-c draw-line-v [start Vector2 end Vector2 color Color] "DrawLineV")
;; The thick one is built from triangles rather than GL lines, which is why it
;; is a separate call and not a parameter on the one above.
(declare-c draw-line-ex
[start Vector2 end Vector2 thick f32 color Color]
"DrawLineEx")
(declare-c draw-circle [x i32 y i32 radius f32 color Color] "DrawCircle")
(declare-c draw-circle-v
[center Vector2 radius f32 color Color]
"DrawCircleV")
(declare-c draw-circle-lines
[x i32 y i32 radius f32 color Color]
"DrawCircleLines")
(declare-c draw-circle-lines-v
[center Vector2 radius f32 color Color]
"DrawCircleLinesV")
;; Two radii, horizontal then vertical. Equal radii is a circle, so a binding
;; that exchanged them would be invisible unless they differ — which is why
;; sand.flan's ellipse is deliberately wider than it is tall.
(declare-c draw-ellipse
[x i32 y i32 radius-h f32 radius-v f32 color Color]
"DrawEllipse")
(declare-c draw-ellipse-lines
[x i32 y i32 radius-h f32 radius-v f32 color Color]
"DrawEllipseLines")
;; Angles are degrees, clockwise from the +x axis, and `segments` is how many
;; straight pieces the arc is made of — 0 lets raylib pick from the radius.
(declare-c draw-ring [center Vector2 inner f32 outer f32 start f32 end f32
segments i32 color Color] "DrawRing")
(declare-c draw-ring-lines [center Vector2 inner f32 outer f32 start f32 end f32
segments i32 color Color] "DrawRingLines")
;; Counter-clockwise, and raylib means it: the clockwise winding is culled and
;; draws nothing at all, which looks exactly like a broken binding.
(declare-c draw-triangle
[v1 Vector2 v2 Vector2 v3 Vector2 color Color]
"DrawTriangle")
(declare-c draw-triangle-lines
[v1 Vector2 v2 Vector2 v3 Vector2 color Color]
"DrawTriangleLines")
(declare-c draw-rectangle-v
[position Vector2 size Vector2 color Color]
"DrawRectangleV")
(declare-c draw-rectangle-rec [rec Rectangle color Color] "DrawRectangleRec")
(declare-c draw-rectangle-lines
[x i32 y i32 width i32 height i32 color Color]
"DrawRectangleLines")
;; The one-pixel outline above is drawn with GL lines and sits *on* the
;; rectangle's edge; this one is drawn with quads and sits inside it, so the
;; two do not agree at thickness 1 and that is raylib's doing, not a bug here.
(declare-c draw-rectangle-lines-ex
[rec Rectangle thick f32 color Color]
"DrawRectangleLinesEx")
;; `roundness` is 0 to 1 as a fraction of the shorter side, so 0 is a plain
;; rectangle and 1 is a stadium.
(declare-c draw-rectangle-rounded [rec Rectangle roundness f32 segments i32
color Color] "DrawRectangleRounded")
;; No thickness here — see the section note. The `-ex` form below is the one
;; that takes it.
(declare-c draw-rectangle-rounded-lines [rec Rectangle roundness f32 segments i32
color Color] "DrawRectangleRoundedLines")
(declare-c draw-rectangle-rounded-lines-ex [rec Rectangle roundness f32
segments i32 thick f32 color Color] "DrawRectangleRoundedLinesEx")
;; ── Text ────────────────────────────────────────────────────────────
;;
;; Both of these use raylib's built-in font, and both therefore need
;; init-window — not for the GPU in measure-text's case, but because the
;; default font is only loaded as part of opening a window. Called headless,
;; measure-text answers 0 for every string, which was measured against
;; libraylib.so.550 and is why it is NOT in the acceptance table despite
;; looking like exactly the kind of call that could be.
;;
;; Fonts ARE bound now — see the section at the end of this file. The reason
;; they were not is worth keeping: a Font is three ints beside a Texture2D, a
;; Rectangle* and a GlyphInfo*, and a GlyphInfo embeds an Image, and that was
;; "two more aggregates and two owned arrays for something with no headless
;; test at the end of it". The generator takes all of that now, and the
;; headless test turned out to exist after all.
(declare-c draw-text
[text string x i32 y i32 font-size i32 color Color]
"DrawText")
(declare-c measure-text [text string font-size i32] i32 "MeasureText")
;; ── Timing and window state ─────────────────────────────────────────
;;
;; All four read state that init-window creates, so all four answer 0 before
;; there is a window — again measured, not assumed. get-frame-time is the
;; delta the last frame took, in seconds, which is what a simulation should
;; scale by instead of assuming the target fps was met.
(declare-c get-frame-time [] f32 "GetFrameTime")
;; The measured rate, as an integer, which is not 1/get-frame-time: raylib
;; averages the last handful of frames so the read-out does not flicker.
;; draw-fps was already bound and puts the same number on the screen itself;
;; this is the one that hands it back, for a program that wants to compare it
;; against a target it set.
(declare-c get-fps [] i32 "GetFPS")
(declare-c get-time [] f64 "GetTime")
(declare-c get-screen-width [] i32 "GetScreenWidth")
(declare-c get-screen-height [] i32 "GetScreenHeight")
;; ── Gamepads ────────────────────────────────────────────────────────
;;
;; Nothing here can be asserted headlessly and nothing here can be asserted
;; *at all* without a pad plugged in: with no gamepad, gamepad-available? is
;; false, every button predicate is false and every axis reads 0.0, which is
;; also exactly what a wrapper with its two int arguments exchanged would
;; report. So these are bound, wired into sand.flan's HUD, and honestly
;; described as untested — the only check they get is that a pad moves the
;; read-out.
;;
;; `pad` is an index from 0, not an enum: raylib's own parameter is an int and
;; how many are attached is a run-time question.
(defenum GamepadButton
[unknown 0
left-face-up 1 left-face-right 2 left-face-down 3 left-face-left 4
right-face-up 5 right-face-right 6 right-face-down 7 right-face-left 8
left-trigger-1 9 left-trigger-2 10
right-trigger-1 11 right-trigger-2 12
middle-left 13 middle 14 middle-right 15
left-thumb 16 right-thumb 17])
;; The triggers read -1 at rest and 1 fully pressed, unlike the sticks, which
;; are centred at 0. raylib does not normalise that and neither does this.
(defenum GamepadAxis
[left-x 0 left-y 1 right-x 2 right-y 3
left-trigger 4 right-trigger 5])
(declare-c gamepad-available? [pad i32] bool "IsGamepadAvailable")
(declare-c gamepad-button-pressed?
[pad i32 button GamepadButton] bool
"IsGamepadButtonPressed")
(declare-c gamepad-button-down?
[pad i32 button GamepadButton] bool
"IsGamepadButtonDown")
(declare-c gamepad-button-released?
[pad i32 button GamepadButton] bool
"IsGamepadButtonReleased")
(declare-c gamepad-button-up?
[pad i32 button GamepadButton] bool
"IsGamepadButtonUp")
;; -1 when nothing is pressed, so the answer is not a GamepadButton: raylib
;; returns an int outside the enum and the checker would have to be lied to.
(declare-c get-gamepad-button-pressed [] i32 "GetGamepadButtonPressed")
(declare-c get-gamepad-axis-count [pad i32] i32 "GetGamepadAxisCount")
(declare-c get-gamepad-axis-movement
[pad i32 axis GamepadAxis] f32
"GetGamepadAxisMovement")
;; An INTEGER-faced version of the call above is wanted and cannot be had, and
;; the pair of refusals that stops it is worth recording here because it is
;; structural rather than incidental.
;;
;; get-gamepad-axis-count answers how many axes the pad reports, and
;; core_input_gamepad's read-out walks 0..count-1 and asks for each. That loop
;; cannot go through the binding above: the index is an i32 and
;;
;; expected rl/GamepadAxis, found i32
;;
;; — an integer does not convert to an enum, and a keyword names exactly one
;; member so it cannot come from a loop variable either. The obvious fix, a
;; second declare-c of the same symbol with an i32 parameter, is refused too:
;;
;; rl/get-gamepad-axis-movement and rl/get-gamepad-axis-movement-by-index
;; both bind the C function GetGamepadAxisMovement — one declare-c per C
;; function, and another Flan name for it is a defn
;;
;; and a `defn` cannot help, because what has to change is the parameter's
;; TYPE and a wrapper can only rename. Nothing here is wrong: one declaration
;; per symbol is what keeps the generated prototype unique, and an enum that
;; silently accepted integers would give up what makes a keyword argument
;; checkable. The consequence is simply that an enum parameter cannot be
;; indexed, and the caller spells the loop as a cond over the members it
;; knows — which is what examples/core-input-gamepad.flan does.
;; SetGamepadVibration is NOT bound, and the reason is not the usual one. Two
;; things are wrong with it at once. Its arity changed — 5.1-dev takes three
;; floats and 6.1-dev takes four, there is no 5.5 header here to settle which,
;; and the generated prototype is what fixes the call, so a guess is a
;; corrupted stack frame rather than a link error. And it would not matter if
;; it were guessed right: the symbol in libraylib.so.550 disassembles to a
;; single TraceLog call and a jump — it is a stub that reports "not
;; implemented" and touches no motor. Binding it would be binding a warning.
;; ── Touch and gestures ──────────────────────────────────────────────
;;
;; Touch is a superset of the mouse on a desktop: with no touchscreen,
;; get-touch-point-count is 0 but get-touch-position 0 still tracks the mouse,
;; which is what makes it visible in sand.flan at all.
;;
;; The gesture system is fed by raylib's own event polling, so a gesture is
;; only ever detected inside a window's frame loop — nothing here is headless
;; material either.
;; A bitfield, not an ordinary enum: set-gestures-enabled takes the OR of
;; several and gesture-detected? tests against one. That is why the enabling
;; call below takes a u32 and not a Gesture — a keyword can only ever name one
;; member, and `all` is spelled out here so the common case still reads.
(defenum Gesture
[none 0 tap 1 double-tap 2 hold 4 drag 8
swipe-right 16 swipe-left 32 swipe-up 64 swipe-down 128
pinch-in 256 pinch-out 512])
(defconst gesture-all u32 1023)
(declare-c get-touch-position [index i32] Vector2 "GetTouchPosition")
(declare-c get-touch-x [] i32 "GetTouchX")
(declare-c get-touch-y [] i32 "GetTouchY")
(declare-c get-touch-point-count [] i32 "GetTouchPointCount")
(declare-c get-touch-point-id [index i32] i32 "GetTouchPointId")
(declare-c set-gestures-enabled [flags u32] "SetGesturesEnabled")
(declare-c gesture-detected? [gesture Gesture] bool "IsGestureDetected")
(declare-c get-gesture-detected [] Gesture "GetGestureDetected")
;; Degrees, and only meaningful while a drag is in progress.
(declare-c get-gesture-drag-vector [] Vector2 "GetGestureDragVector")
(declare-c get-gesture-drag-angle [] f32 "GetGestureDragAngle")
(declare-c get-gesture-pinch-vector [] Vector2 "GetGesturePinchVector")
(declare-c get-gesture-pinch-angle [] f32 "GetGesturePinchAngle")
(declare-c get-gesture-hold-duration [] f32 "GetGestureHoldDuration")
;; ── Render textures ─────────────────────────────────────────────────
;;
;; A framebuffer with two textures hanging off it: draw into it between
;; begin-texture-mode and end-texture-mode, then draw *it* like any other
;; texture. That is how a post-process pass and a pixel-perfect integer
;; upscale are both done.
;;
;; None of it is assertable here — LoadRenderTexture makes a GL framebuffer
;; object, so with no context it answers an id of 0 and every draw into it is
;; a no-op. `depth` is a renderbuffer rather than a real texture in raylib's
;; default configuration, so its id is the only field of it worth reading.
;;
;; The one thing the layout gets for free: RenderTexture2D is a u32 and two
;; Texture2Ds, and Texture2D is already pinned as far as anything headless can
;; pin it, so the only new claim here is the order of the three members.
;; sand.flan draws through one, which is where a wrong order shows up.
(defstruct RenderTexture2D [id u32 texture Texture2D depth Texture2D])
(declare-c load-render-texture
[width i32 height i32] RenderTexture2D
"LoadRenderTexture")
;; raylib 5.5 spells this IsRenderTextureValid; there is no IsRenderTextureReady
;; in this version, the same rename that took IsTextureReady and IsImageReady.
(declare-c render-texture-valid?
[target RenderTexture2D] bool
"IsRenderTextureValid")
(declare-c unload-render-texture
[target RenderTexture2D]
"UnloadRenderTexture")
;; Everything drawn between these two lands in the target instead of the
;; screen, and the target's texture comes out of the GPU upside down — raylib
;; renders it bottom-up — so drawing it back with a NEGATIVE source height is
;; not a flourish, it is the correction. sand.flan does exactly that.
(declare-c begin-texture-mode [target RenderTexture2D] "BeginTextureMode")
(declare-c end-texture-mode [] "EndTextureMode")
;; ── Audio ───────────────────────────────────────────────────────────
;;
;; The device first, and the split that matters for testing runs right
;; through this section: a **Wave** is samples in RAM and needs no device at
;; all, while a **Sound** is a buffer the mixer owns and a **Music** is a
;; decoder feeding one, and both of those are nothing without
;; init-audio-device having succeeded.
;;
;; That makes Wave the audio equivalent of the Image family — raylib
;; *computes* with it, headlessly — and it is the only part of this section
;; the acceptance table asserts. Everything from Sound down is exercised by
;; running sand.flan with a working sound server, and a machine without one
;; gets silence rather than a crash: init-audio-device logs a warning, every
;; load answers a zeroed struct and every play is a no-op.
(declare-c init-audio-device [] "InitAudioDevice")
(declare-c close-audio-device [] "CloseAudioDevice")
(declare-c audio-device-ready? [] bool "IsAudioDeviceReady")
;; 0 to 1, and it is a listener gain applied after every per-sound volume.
(declare-c set-master-volume [volume f32] "SetMasterVolume")
(declare-c get-master-volume [] f32 "GetMasterVolume")
;; A Wave is the CPU side: `data` is frame-count × channels samples of
;; sample-size bits each, and raylib reads every one of the four integers to
;; decide what those bytes mean. `data` is (Ptr u8) rather than a typed
;; pointer because its element type is `sample-size`, which is a run-time
;; number — 8, 16 or 32 bits — and there is no Flan type that says that.
;;
;; The consequence for a caller building one by hand: the bytes are written
;; as bytes, in the host's order. That is the shape the acceptance case uses,
;; and it is deliberate — it means the case says what it means about
;; little-endian 16-bit PCM instead of hiding it behind a cast.
(defstruct Wave [frame-count u32 sample-rate u32 sample-size u32
channels u32 data (Ptr u8)])
(declare-c load-wave [path string] Wave "LoadWave")
;; raylib 5.5 spells this IsWaveValid; IsWaveReady is gone, as everywhere else.
(declare-c wave-valid? [wave Wave] bool "IsWaveValid")
(declare-c unload-wave [wave Wave] "UnloadWave")
;; The extension picks the format, and raylib writes .wav and .qoa. This is
;; external ground truth for the layout: the header it writes carries
;; sample-rate, sample-size and channels, and the payload length carries
;; frame-count, so a permuted defstruct writes a file that reads back
;; differently — the same argument the PNG round trip makes for Image.
(declare-c export-wave [wave Wave path string] bool "ExportWave")
;; Allocates a copy of the buffer; the copy is unloaded on its own.
(declare-c wave-copy [wave Wave] Wave "WaveCopy")
;; In FRAMES, not samples — raylib renamed the parameters for 5.5 without
;; changing the signature, so the name is the only thing that says which. On
;; a mono wave the two readings coincide, which is what the acceptance case
;; uses, so nothing here depends on having guessed right.
(declare-c wave-crop
[wave (Ptr Wave) init-frame i32 final-frame i32]
"WaveCrop")
;; Resamples in place. This is the strongest headless shape available in this
;; section and the same one gen-image-color has: three scalars go in and four
;; fields come out, with frame-count *computed* from the sample-rate ratio, so
;; a permuted layout has nothing to cancel against.
(declare-c wave-format
[wave (Ptr Wave) sample-rate i32 sample-size i32 channels i32]
"WaveFormat")
;; Every sample as a float in [-1, 1], frame-count × channels of them,
;; whatever the wave's own sample-size. That is the one call that reads
;; *through* `data`, so it is what pins the pointer as a pointer rather than
;; as two integers that happen to sit at the end.
(declare-c load-wave-samples [wave Wave] (Ptr f32) "LoadWaveSamples")
(declare-c unload-wave-samples [samples (Ptr f32)] "UnloadWaveSamples")
;; A Sound is an AudioStream plus a frame count. The two leading pointers are
;; miniaudio's and Flan never reads through them — they are (Ptr u8) so the
;; struct is the right size and the three integers land at the right offsets,
;; exactly as Image's `data` is.
(defstruct AudioStream [buffer (Ptr u8) processor (Ptr u8)
sample-rate u32 sample-size u32 channels u32])
(defstruct Sound [stream AudioStream frame-count u32])
(declare-c load-sound [path string] Sound "LoadSound")
;; Note what this does to the frame count: the mixer resamples to the device's
;; own rate, so a sound made from an 8 kHz wave on a 48 kHz device reports six
;; times as many frames. Nothing should read `frame-count` expecting the
;; wave's.
(declare-c load-sound-from-wave [wave Wave] Sound "LoadSoundFromWave")
(declare-c sound-valid? [sound Sound] bool "IsSoundValid")
(declare-c unload-sound [sound Sound] "UnloadSound")
(declare-c play-sound [sound Sound] "PlaySound")
(declare-c stop-sound [sound Sound] "StopSound")
(declare-c pause-sound [sound Sound] "PauseSound")
(declare-c resume-sound [sound Sound] "ResumeSound")
(declare-c sound-playing? [sound Sound] bool "IsSoundPlaying")
;; Volume is a gain from 0, pitch is a rate multiplier where 1 is unchanged,
;; and pan is 0 hard left to 1 hard right with 0.5 centred — raylib's own
;; convention, and the one place in this file where 0 is not the neutral
;; value.
(declare-c set-sound-volume [sound Sound volume f32] "SetSoundVolume")
(declare-c set-sound-pitch [sound Sound pitch f32] "SetSoundPitch")
(declare-c set-sound-pan [sound Sound pan f32] "SetSoundPan")
;; A second voice over the same samples, so one sound can overlap itself. It
;; does NOT own the data, so unloading an alias must not unload the original —
;; which is why raylib has a separate call for it and why this one is bound.
(declare-c load-sound-alias [source Sound] Sound "LoadSoundAlias")
(declare-c unload-sound-alias [alias Sound] "UnloadSoundAlias")
;; Music is streamed rather than resident, which is the whole difference: the
;; buffer is refilled from the decoder and update-music-stream is what does
;; the refilling. Miss it for a frame and the music stops.
(defstruct Music [stream AudioStream frame-count u32 looping bool
ctx-type i32 ctx-data (Ptr u8)])
(declare-c load-music-stream [path string] Music "LoadMusicStream")
(declare-c music-valid? [music Music] bool "IsMusicValid")
(declare-c unload-music-stream [music Music] "UnloadMusicStream")
(declare-c play-music-stream [music Music] "PlayMusicStream")
;; Called once per frame, every frame, for as long as the music is meant to
;; play. This is the one binding in the section whose absence is silent.
(declare-c update-music-stream [music Music] "UpdateMusicStream")
(declare-c stop-music-stream [music Music] "StopMusicStream")
(declare-c pause-music-stream [music Music] "PauseMusicStream")
(declare-c resume-music-stream [music Music] "ResumeMusicStream")
(declare-c music-stream-playing? [music Music] bool "IsMusicStreamPlaying")
(declare-c set-music-volume [music Music volume f32] "SetMusicVolume")
(declare-c set-music-pitch [music Music pitch f32] "SetMusicPitch")
(declare-c set-music-pan [music Music pan f32] "SetMusicPan")
;; Seconds, both of them.
(declare-c seek-music-stream [music Music position f32] "SeekMusicStream")
(declare-c get-music-time-length [music Music] f32 "GetMusicTimeLength")
(declare-c get-music-time-played [music Music] f32 "GetMusicTimePlayed")
;; AudioStream itself — the raw callback-fed stream — is NOT bound. Its point
;; is set-audio-stream-callback, which takes a C function pointer, and a
;; callback is refused by the shim generator by name: `%s is a function type,
;; and a C callback is not implemented`. Binding the rest of the family
;; without it would be binding a stream that can only ever be fed by
;; update-audio-stream from the main thread, which is a worse Sound.
;; ── Fonts ───────────────────────────────────────────────────────────
;;
;; A previous pass refused this whole family by name, and the reason was that
;; a Font drags in two more aggregates and two owned arrays and there was
;; nothing headless to check them against. Both halves of that have changed.
;;
;; The generator takes it: a struct held by value is emitted after everything
;; it contains, a struct held by POINTER is forward-declared, and both the
;; Flan struct and the C typedef come from the same `defstruct`. Font holds a
;; Texture2D by value and points at Rectangle and GlyphInfo; GlyphInfo holds
;; an Image by value. Nothing here needed a generator change.
;;
;; And the test exists. raylib's text measuring is pure CPU arithmetic over
;; every field of a Font — it walks the glyph array looking for a codepoint,
;; reads the advance out of the glyph or the width out of the atlas rectangle,
;; and scales by the base size. The catch was that the calls that MAKE a font
;; all need something a headless run does not have: get-font-default needs
;; init-window, load-font-ex needs a TTF on disk. So the acceptance case does
;; not make one — it *builds* one, field by field, out of Flan arrays, and
;; hands it to raylib to compute with. Scalars in, numbers out, with no input
;; struct raylib produced for a permutation to cancel against.
;;
;; One trap found while doing that, and it is in raylib rather than here:
;; MeasureTextEx returns (0,0) immediately when `texture.id` is 0. A
;; hand-built font therefore has to claim a nonzero texture id even though
;; there is no texture — which is also what makes the case pin where the
;; Texture2D sits inside the Font.
;; `image` is the glyph's own pixels, and raylib owns them; it is here so the
;; four ints in front of it are at the right offsets and so a GlyphInfo is 40
;; bytes rather than 16. offset-x and offset-y shift the glyph when drawn;
;; advance-x is how far the pen moves after it, and when it is 0 raylib falls
;; back to the atlas rectangle's width plus offset-x.
(defstruct GlyphInfo [value i32 offset-x i32 offset-y i32 advance-x i32
image Image])
;; `recs` and `glyphs` are parallel arrays of glyph-count entries each: recs
;; says where the glyph is in the atlas texture, glyphs says what it is. A
;; codepoint raylib cannot find falls back to index 0 rather than reading out
;; of bounds.
(defstruct Font [base-size i32 glyph-count i32 glyph-padding i32
texture Texture2D recs (Ptr Rectangle)
glyphs (Ptr GlyphInfo)])
;; Needs a window: the default font is loaded as part of init-window and
;; LoadFontDefault is not exported, which is the same fact that makes
;; measure-text answer 0 headless.
(declare-c get-font-default [] Font "GetFontDefault")
(declare-c load-font [path string] Font "LoadFont")
;; The codepoint set is a C array plus an int count, so — like
;; collision-point-poly? — the declaration says (Ptr i32) and the Flan wrapper
;; below takes a slice apart. A slice parameter in a declare-c is refused by
;; name, because the C count's own type is not recoverable from [T].
;;
;; raylib's own convention is that a NULL pointer with a count of 0 means the
;; default ASCII set, and the wrapper keeps it — but Flan has no null pointer
;; literal, so the null comes from the one place the language does hand out
;; zeroed bytes: a `defvar` with no initialiser is BSS (plan.org, zero
;; values), and a zeroed (Ptr i32) is exactly a null one. It is never written
;; to and never read through; raylib only ever compares it against NULL.
(defvar default-codepoints (Ptr i32))
(declare-c load-font-ex-raw
[path string font-size i32 codepoints (Ptr i32) count i32] Font
"LoadFontEx")
(defn load-font-ex [path string font-size i32 codepoints [i32]] Font
(if (= (len codepoints) 0)
(load-font-ex-raw path font-size default-codepoints 0)
(load-font-ex-raw path font-size (addr (at codepoints 0)) (len codepoints))))
;; raylib 5.5 spells this IsFontValid. It reads the texture id and both
;; arrays, so a font that loaded but could not upload its atlas — which is
;; every font loaded without a GL context — is NOT valid by this test.
(declare-c font-valid? [font Font] bool "IsFontValid")
(declare-c unload-font [font Font] "UnloadFont")
;; `spacing` is extra pixels between glyphs, added per gap and not per glyph,
;; so a one-character string is unaffected by it. raylib's own DrawTextEx adds
;; it the same way measure-text-ex counts it, which is why the two agree.
(declare-c draw-text-ex [font Font text string position Vector2
font-size f32 spacing f32 tint Color] "DrawTextEx")
;; Pure arithmetic over the font — no GL, no window — and therefore the one
;; thing in this section the acceptance table can assert. See the note above:
;; it refuses to measure anything at all when the font's texture id is 0.
(declare-c measure-text-ex
[font Font text string font-size f32 spacing f32] Vector2
"MeasureTextEx")
;; The index into `recs` and `glyphs`, by linear search over glyph-count. Also
;; pure CPU, and it is what pins glyph-count as the loop bound.
(declare-c get-glyph-index [font Font codepoint i32] i32 "GetGlyphIndex")
(declare-c get-glyph-info [font Font codepoint i32] GlyphInfo "GetGlyphInfo")
(declare-c get-glyph-atlas-rec
[font Font codepoint i32] Rectangle
"GetGlyphAtlasRec")
(declare-c draw-text-codepoint [font Font codepoint i32 position Vector2
font-size f32 tint Color] "DrawTextCodepoint")
;; DrawTextCodepoints and LoadFontData are not bound. The first is the slice
;; problem again and adds nothing draw-text-ex does not already do from a
;; string; the second hands back a raw GlyphInfo array whose length is the
;; caller's to remember and whose lifetime is UnloadFontData's, and Flan has
;; no owning array type to give that to — a (Ptr GlyphInfo) with a separate
;; count is what the language would force, which is the C API with the safety
;; removed rather than a binding.