flan/vendor/raylib/raylib.flan
Joseph Ferano 9223c9002a An enum is four bytes, and the header check now reads the constants
Two gaps the raylib examples hit.

The layout check compared a Flan enum against the header's `int` and
called it a disagreement. It is not one: Shim.cty lowers a defenum to
int32_t in a struct field exactly as it does in a parameter, which is
what the signature check already knew and the layout check did not. One
predicate now serves both, symmetric, and tolerant of a 32-bit integer
and nothing else -- f64 against the library's float still fails, in the
very struct whose other field is an enum. Camera3D.projection is a
CameraProjection again and rl/camera-projection is gone with it, so
`.projection :perspective` resolves at the construction site.

And generate-c's claim said nothing about a defconst or a defenum
member, so a wrong flag bit was completely silent. `bindings` gained
`enum`, `const` and `constant` lines saying what a Flan constant is
called in C -- the prefix is nowhere in the Flan name, so it is declared
rather than guessed. Nothing goes quiet in either direction: a name the
rule builds and the header lacks is reported, a rule that reaches
nothing is reported, and a defenum with no line is itself a finding,
because otherwise the silence just moves up one level.

clang's dump gives anonymous EnumDecls for every raylib enum and no
value at all for an enumerator written without `= n`, so the constants
are one flat table and the values are counted the way C counts them.
cache_format bumped with the dump type.
2026-09-13 14:11:35 +07:00

1182 lines
58 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.
;; Vector3 sits here rather than in the 3D section below because it is a
;; vector before it is a camera's business: nothing about it is 3D-only. Three
;; floats in x/y/z order, and — like the three Vector3 fields of Camera3D —
;; nothing in the acceptance table can tell a permutation of them apart,
;; because every permutation has the same layout.
(defstruct Vector2 [x f32 y f32])
(defstruct Vector3 [x f32 y f32 z 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
left-shift 340
;; KEY_NULL is not a key. It is the value set-exit-key takes to mean "no
;; key closes the window", which is the only thing that ever passes it.
null 0])
(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")
;; False before init-window and after close-window, true between. A game loop
;; started twice is the thing this answers — an engine that can be re-entered
;; from a REPL or a dev session asks it before opening a second window onto
;; the same context.
(declare-c window-ready? [] bool "IsWindowReady")
(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: set-config-flags 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. Afterwards is what the generated
;; set-window-state, clear-window-state and window-state? are for, and they
;; take these same bits, which is why the four sand.flan sets grew into the
;; whole of ConfigFlags: a subset has its hole exactly where the next caller
;; looks. Every value below was read off raylib.h 5.5.
;;
;; The values are NOT in bit order in the header and are not reordered here:
;; FLAG_VSYNC_HINT is 0x40 and FLAG_FULLSCREEN_MODE is 0x02.
(defconst flag-vsync-hint u32 64)
(defconst flag-fullscreen-mode u32 2)
(defconst flag-window-resizable u32 4)
(defconst flag-window-undecorated u32 8)
(defconst flag-window-hidden u32 128)
(defconst flag-window-minimized u32 512)
(defconst flag-window-maximized u32 1024)
(defconst flag-window-unfocused u32 2048)
(defconst flag-window-topmost u32 4096)
(defconst flag-window-always-run u32 256)
(defconst flag-window-transparent u32 16)
(defconst flag-window-highdpi u32 8192)
(defconst flag-window-mouse-passthrough u32 16384)
(defconst flag-borderless-windowed-mode u32 32768)
(defconst flag-msaa-4x-hint u32 32)
(defconst flag-interlaced-hint u32 65536)
(declare-c set-config-flags [flags u32] "SetConfigFlags")
;; ── Input ───────────────────────────────────────────────────────────
;; Which key closes the window, ESCAPE by default. Hand-written rather than
;; left to the generated half because the Flan face is the difference: raylib
;; declares it `void SetExitKey(int key)` and the generated line therefore
;; takes an i32, where this one takes a Key and so accepts :escape and refuses
;; a typo. `:null` is how a program says "no key does that" and takes the
;; close over itself.
(declare-c set-exit-key [key Key] "SetExitKey")
(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")
;; Scissor: everything drawn between these two is clipped to the rectangle,
;; in screen pixels with y down from the top — which is NOT the GL convention
;; underneath, and raylib flips it for you. It is drawing state like
;; begin-mode-2d is, so the pair is hand-written for the same reason: both
;; halves of a begin/end pair sit together, and both are on a frame's path.
;;
;; Nothing headless can assert this. A clip that is off by the window height
;; — the flip not applied — draws a perfectly plausible picture in the wrong
;; half of the screen, and only looking catches it.
(declare-c begin-scissor-mode
[x i32 y i32 width i32 height i32]
"BeginScissorMode")
(declare-c end-scissor-mode [] "EndScissorMode")
;; ── 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")
;; ── Camera3D ────────────────────────────────────────────────────────
;;
;; raylib's `Camera` is a typedef for this, and every 3D call takes it by
;; value: position, the point it looks at, an up vector, a vertical
;; field-of-view in degrees, and which projection to build. Field order is
;; read off raylib.h 5.5 and is position/target/up/fovy/projection.
;;
;; What nothing here can pin, said the way the Texture2D note above says it:
;; the first three fields are the same type and the same size, so a permuted
;; Camera3D has the identical layout and every acceptance case that could
;; exist would pass. A camera that looks from the wrong place is a picture,
;; not a number.
;;
;; `projection` is `int` in the header and `CameraProjection` here, which is
;; the same four bytes with a face on it: a Flan defenum lowers to int32_t in
;; a struct field exactly as it does in a parameter, and the layout check
;; knows that, so it accepts an enum where the header says `int` and still
;; refuses an `f64` where the header says `float`. What it buys is at the
;; construction site — `.projection :perspective` resolves against the members
;; below and a typo is a compile error there, where an i32 field would have
;; taken any number at all.
(defenum CameraProjection [perspective 0 orthographic 1])
;; UpdateCamera's mode. :custom means it does nothing and the program moves
;; the camera itself.
(defenum CameraMode
[custom 0 free 1 orbital 2 first-person 3 third-person 4])
(defstruct Camera3D
[position Vector3 target Vector3 up Vector3 fovy f32 projection CameraProjection])
;; The mode's built-in controls, applied to the camera in place — hence
;; (Ptr Camera3D), on the rule the Images section states: a call that mutates
;; takes a pointer at the Flan face. It reads the mouse and the keyboard
;; itself, which is what makes it a per-frame call and not a setup one.
(declare-c update-camera
[camera (Ptr Camera3D) mode CameraMode]
"UpdateCamera")
(declare-c begin-mode-3d [camera Camera3D] "BeginMode3D")
(declare-c end-mode-3d [] "EndMode3D")
;; Where a world point lands on the screen, for the current camera and the
;; current window size. Unlike get-world-to-screen-2d this one is not pure
;; arithmetic over its arguments — it reads the window's dimensions — so it
;; needs a window, and headless it answers about a 0x0 screen.
(declare-c get-world-to-screen
[position Vector3 camera Camera3D] Vector2
"GetWorldToScreen")
;; The three 3D draws the examples use. All of them are immediate-mode
;; geometry and all of them need a GL context, so like the shapes above they
;; are link-checked and nothing more.
(declare-c draw-cube
[position Vector3 width f32 height f32 length f32 color Color]
"DrawCube")
(declare-c draw-cube-wires
[position Vector3 width f32 height f32 length f32 color Color]
"DrawCubeWires")
;; `slices` squares each way from the origin, `spacing` world units apart, on
;; the XZ plane.
(declare-c draw-grid [slices i32 spacing f32] "DrawGrid")
;; ── 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")
;; The two above, in one call, and the only one of the four that both takes a
;; source rectangle and scales: `source` picks a cell out of an atlas, `dest`
;; says where on the screen it lands and how big, so a 16px tile drawn at 4x is
;; a dest four times the source. draw-texture-rec has the source and no scale;
;; draw-texture-ex has the scale and no source. Neither half is usable alone
;; for a tilemap, which is why this is the draw call a grid-based game makes
;; every frame and for every tile.
;;
;; `origin` is the point within *dest* that lands on dest's x,y and that
;; `rotation` (degrees, clockwise) turns about — {0 0} draws from the corner,
;; and half the dest size spins a tile about its middle. A negative source
;; width or height flips, the same as in draw-texture-rec.
(declare-c draw-texture-pro
[texture Texture2D source Rectangle dest Rectangle origin Vector2
rotation f32 tint Color]
"DrawTexturePro")
;; ── 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")
;; The non-mutating form of the line above, and the reason it is worth having
;; both: image-crop changes the image it is given, so carving a sheet into
;; twenty tiles with it destroys the sheet on the first one. This returns a
;; fresh Image and leaves the original alone. There is no ImageCopy in 5.5, so
;; this is also how a whole image is duplicated — a rec covering all of it.
;;
;; The result owns its own buffer: unload-image it, like anything else that
;; allocated.
(declare-c image-from-image
[image Image rec Rectangle] Image
"ImageFromImage")
(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.