1502 lines
75 KiB
Plaintext
1502 lines
75 KiB
Plaintext
;;;; 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.
|
||
;;;;
|
||
;;;; Some bindings keep a hand-written Flan wrapper, because their Flan face
|
||
;;;; is deliberately not raylib's. Three shapes of that, and every wrapper in
|
||
;;;; this file is one of them:
|
||
;;;;
|
||
;;;; - a slice where C takes a pointer and a count — collision-point-poly?,
|
||
;;;; load-image-from-memory, load-font-ex, and the eleven vector-array
|
||
;;;; drawing calls under "A slice where raylib wants a pointer and a
|
||
;;;; count";
|
||
;;;; - an Option where C signals failure by a bool out-parameter or a
|
||
;;;; sentinel — collision-lines, get-key-pressed, get-char-pressed;
|
||
;;;; - an enum where the header says `int`. These are NOT wrappers: a C
|
||
;;;; enum parameter has an int's ABI, so the hand-written declare-c with
|
||
;;;; the Flan type on it is the whole fix, and set-exit-key,
|
||
;;;; set-mouse-cursor, key-up? and mouse-button-up? are all that.
|
||
;;;;
|
||
;;;; What is NOT here, and was asked for: with-drawing and with-mode-2d over
|
||
;;;; raylib's begin/end pairs. An unbalanced pair is a real bug and a macro
|
||
;;;; removes it, but a macro cannot live in a package — the expander collects
|
||
;;;; defmacros from the prelude and from the file being compiled, and a
|
||
;;;; defmacro in an imported package is refused by name
|
||
;;;; (test/programs/pkg-macro.flan, an acceptance case whose whole content is
|
||
;;;; the refusal). So these have to be written in the program that uses them,
|
||
;;;; or wait for macros to be importable, and neither is this file's to do.
|
||
|
||
;; 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")
|
||
|
||
;; The other two halves of that family, hand-written for exactly the reason
|
||
;; above and added late: they were generated, so they took an i32, so
|
||
;; `(rl/key-up? :space)` did not compile while `(rl/key-down? :space)` did.
|
||
;; That is a hole in a family rather than a missing convenience — a caller
|
||
;; who has used key-down? has no reason to expect the sibling to be spelled
|
||
;; differently, and what they get instead of a keyword is a number nobody
|
||
;; checks. Nothing wraps these: the ABI of a C enum parameter is the ABI of
|
||
;; an int, so the declaration IS the fix and a defn around it would only be
|
||
;; a rename.
|
||
(declare-c key-up? [key Key] bool "IsKeyUp")
|
||
(declare-c key-pressed-repeat? [key Key] bool "IsKeyPressedRepeat")
|
||
|
||
(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 mouse-button-up? [button MouseButton] bool "IsMouseButtonUp")
|
||
|
||
;; ── Draining raylib's two input queues ──────────────────────────────
|
||
;;
|
||
;; Both of these answer "nothing left" with 0, and 0 is also a value the
|
||
;; caller could otherwise have to think about — KEY_NULL for one, the NUL
|
||
;; byte for the other. An Option says which of the two it is in the type, so
|
||
;; the loop that drains the queue cannot read the sentinel as a key or as a
|
||
;; character: `while (> key 0)` is a comparison a reader has to know the
|
||
;; convention to trust, and `(while-some ...)` — or the `if-let` shape the
|
||
;; examples use — is one a reader can check.
|
||
;;
|
||
;; The generated declarations are still what call C; only their names moved
|
||
;; aside, to -raw, via the `name` lines in `bindings`. Nothing about the C
|
||
;; signature was wrong, so hand-writing it would have taken the generated
|
||
;; half's agreement-by-construction with the header and given nothing back.
|
||
;;
|
||
;; get-key-pressed answers an i32 and not a Key. A Key is a *closed* set the
|
||
;; package names a subset of, and this queue reports every key on the
|
||
;; keyboard including the ones no member covers, so the enum would be a
|
||
;; promise the value does not keep. Comparing the answer against `:space`
|
||
;; would be the reason to want it, and that is what key-pressed? is for.
|
||
(defn get-key-pressed [] (Option i32)
|
||
(let [k (get-key-pressed-raw)]
|
||
(if (= k 0) None (Some k))))
|
||
|
||
;; Unicode codepoint, not a byte: raylib decodes the platform's input, so a
|
||
;; value above 127 is a real codepoint and not the first byte of one.
|
||
(defn get-char-pressed [] (Option i32)
|
||
(let [c (get-char-pressed-raw)]
|
||
(if (= c 0) None (Some c))))
|
||
|
||
(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. Those two — DisableCursor and
|
||
;; EnableCursor — are in the generated half rather than here: they take no
|
||
;; arguments at all, so there is no Flan face for a hand-written line to
|
||
;; improve, which is the same rule that leaves GetMouseX there.
|
||
;; examples/core-3d-picking.flan toggles them from the right mouse button.
|
||
(declare-c show-cursor [] "ShowCursor")
|
||
(declare-c hide-cursor [] "HideCursor")
|
||
(declare-c cursor-hidden? [] bool "IsCursorHidden")
|
||
|
||
;; The shape the pointer takes. raylib's SetMouseCursor says `int` and means
|
||
;; one of these eleven, so the Flan face is the enum for the same reason
|
||
;; set-exit-key takes a Key: the call is made every frame from a hover test,
|
||
;; and `(rl/set-mouse-cursor :ibeam)` is checked against the members where an
|
||
;; i32 would take any number at all — including the one off-by-one that picks
|
||
;; the arrow instead of the I-beam and looks like nothing at all went wrong.
|
||
;;
|
||
;; All eleven are here and not a subset, unlike Key: the enum is closed and
|
||
;; eleven members is the whole of it.
|
||
(defenum MouseCursor
|
||
[default 0 arrow 1 ibeam 2 crosshair 3 pointing-hand 4
|
||
resize-ew 5 resize-ns 6 resize-nwse 7 resize-nesw 8 resize-all 9
|
||
not-allowed 10])
|
||
|
||
(declare-c set-mouse-cursor [cursor MouseCursor] "SetMouseCursor")
|
||
|
||
;; ── 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")
|
||
|
||
(declare-c draw-cube-v [position Vector3 size Vector3 color Color] "DrawCubeV")
|
||
|
||
;; DrawSphere is DrawSphereEx with rings and slices fixed at 16; the wires
|
||
;; form takes them because the wireframe is the only place the tessellation is
|
||
;; visible. Both are here rather than generated for the reason above: they are
|
||
;; inside a frame, in an example that ships in examples/.
|
||
(declare-c draw-sphere
|
||
[center Vector3 radius f32 color Color]
|
||
"DrawSphere")
|
||
(declare-c draw-sphere-wires
|
||
[center Vector3 radius f32 rings i32 slices i32 color Color]
|
||
"DrawSphereWires")
|
||
|
||
;; `slices` squares each way from the origin, `spacing` world units apart, on
|
||
;; the XZ plane.
|
||
(declare-c draw-grid [slices i32 spacing f32] "DrawGrid")
|
||
|
||
;; ── Rays and boxes ──────────────────────────────────────────────────
|
||
;;
|
||
;; Three small aggregates, added together because the picking call produces
|
||
;; all three: a Ray goes in, a BoundingBox says what to test it against, and
|
||
;; a RayCollision comes back. Every field is read off raylib.h 5.5 and every
|
||
;; one of them is a Vector3 or a scalar — there is no owned memory anywhere
|
||
;; here, which is exactly what separates these three from Model and Mesh, and
|
||
;; why these could be described and those still cannot.
|
||
;;
|
||
;; What the layout check can and cannot say about them, on the same rule the
|
||
;; Camera3D note states: BoundingBox's two Vector3s are the same type, so a
|
||
;; permuted BoundingBox has the identical layout and nothing would catch it.
|
||
;; RayCollision is the opposite and is the one worth having checked — `hit` is
|
||
;; a C `bool`, one byte, and `distance` is a float, so the three padding bytes
|
||
;; between them are a real claim about the struct that a permutation breaks.
|
||
(defstruct BoundingBox [min Vector3 max Vector3])
|
||
(defstruct Ray [position Vector3 direction Vector3])
|
||
(defstruct RayCollision
|
||
[hit bool distance f32 point Vector3 normal Vector3])
|
||
|
||
;; The inverse of get-world-to-screen above, and hand-written beside it for
|
||
;; the reason stated there: the two read the same camera and the same window
|
||
;; dimensions, so they belong to the same half of the file. raylib 5.5 keeps
|
||
;; GetMouseRay as a #define onto this name; the define is not a symbol and
|
||
;; there is nothing to bind it to.
|
||
(declare-c get-screen-to-world-ray
|
||
[position Vector2 camera Camera3D] Ray
|
||
"GetScreenToWorldRay")
|
||
|
||
;; Per-frame and inside BeginMode3D, like the cube draws. raylib draws the
|
||
;; ray as a line a thousand units long, so it is a debugging aid and not
|
||
;; geometry with an end.
|
||
(declare-c draw-ray [ray Ray color Color] "DrawRay")
|
||
|
||
;; ── 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")
|
||
|
||
;; How a texture is sampled when it is drawn at anything other than its own
|
||
;; size. The header says `int` on SetTextureFilter and means one of these six.
|
||
;;
|
||
;; The value of the enum face here is not a typo caught at the call site so
|
||
;; much as a *readable* one: the fog-of-war example renders its fog into a
|
||
;; 25x15 render texture and scales it to 800x450, and the entire visual point
|
||
;; of the example is that `:bilinear` smooths the tile edges where the
|
||
;; default `:point` would show 32-pixel squares. A bare `1` in that call says
|
||
;; nothing; the member name says the whole thing.
|
||
;;
|
||
;; TEXTURE_FILTER_ANISOTROPIC_* are the mipmapped modes and need a texture
|
||
;; with mipmaps generated, which nothing here makes — they are listed because
|
||
;; the enum is closed, not because anything calls them.
|
||
(defenum TextureFilter
|
||
[point 0 bilinear 1 trilinear 2
|
||
anisotropic-4x 3 anisotropic-8x 4 anisotropic-16x 5])
|
||
|
||
(declare-c set-texture-filter
|
||
[texture Texture2D filter TextureFilter]
|
||
"SetTextureFilter")
|
||
|
||
(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])
|
||
|
||
;; The codes that `format` field carries, named. The header says `int format`
|
||
;; everywhere one is passed, and there is nothing in an `int` to say that 7 is
|
||
;; the one a texture upload requires — which is exactly the trade
|
||
;; TextureFilter and MouseCursor already made, and the reason this is a
|
||
;; defenum rather than a row of defconsts.
|
||
;;
|
||
;; The set is closed and is here whole, compressed members included, because a
|
||
;; subset would put the hole where the next caller looks: `format` is a field
|
||
;; programs *read* off an Image they did not make, and a loaded .ktx or .dds
|
||
;; answers with one of the compressed codes. Nothing here converts to one —
|
||
;; raylib's own ImageFormat only moves between the uncompressed formats — so
|
||
;; the compressed half is for reading rather than for asking.
|
||
;;
|
||
;; `uncompressed-r8g8b8a8` is 7, the one GenImageColor makes and the one
|
||
;; LoadTextureFromImage and UpdateTexture want. The `x` in the two ASTC names
|
||
;; is lowercase in raylib.h where every other letter in that enum is upper, so
|
||
;; those two are mapped by name in `bindings` — the same narrow exception
|
||
;; GESTURE_DOUBLETAP already has, and for the same reason.
|
||
(defenum PixelFormat
|
||
[uncompressed-grayscale 1
|
||
uncompressed-gray-alpha 2
|
||
uncompressed-r5g6b5 3
|
||
uncompressed-r8g8b8 4
|
||
uncompressed-r5g5b5a1 5
|
||
uncompressed-r4g4b4a4 6
|
||
uncompressed-r8g8b8a8 7
|
||
uncompressed-r32 8
|
||
uncompressed-r32g32b32 9
|
||
uncompressed-r32g32b32a32 10
|
||
uncompressed-r16 11
|
||
uncompressed-r16g16b16 12
|
||
uncompressed-r16g16b16a16 13
|
||
compressed-dxt1-rgb 14
|
||
compressed-dxt1-rgba 15
|
||
compressed-dxt3-rgba 16
|
||
compressed-dxt5-rgba 17
|
||
compressed-etc1-rgb 18
|
||
compressed-etc2-rgb 19
|
||
compressed-etc2-eac-rgba 20
|
||
compressed-pvrt-rgb 21
|
||
compressed-pvrt-rgba 22
|
||
compressed-astc-4x4-rgba 23
|
||
compressed-astc-8x8-rgba 24])
|
||
|
||
;; Reformats the pixels in place, reallocating the buffer, so the Image's
|
||
;; `data`, `format` and — for a compressed source — its size all change under
|
||
;; the caller. Hand-written rather than generated for the enum: the header's
|
||
;; `int newFormat` takes any integer at all and only one of twenty-four is the
|
||
;; conversion a given program meant.
|
||
(declare-c image-format
|
||
[image (Ptr Image) new-format PixelFormat]
|
||
"ImageFormat")
|
||
|
||
(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. A rec covering the whole image
|
||
;; duplicates it, which is what this was originally reached for — though
|
||
;; image-copy in the generated half says that in one argument and is the
|
||
;; better call for it. (An earlier comment here said 5.5 had no ImageCopy. It
|
||
;; does — raylib-5.5.h line 1348 — and generated.flan has bound it all along.)
|
||
;;
|
||
;; 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")
|
||
|
||
;; ── A slice where raylib wants a pointer and a count ─────────────────
|
||
;;
|
||
;; Eleven entry points take an array of vectors as a pointer plus an `int`
|
||
;; count. A Flan slice already carries both, so every call site that does not
|
||
;; go through a wrapper has to take the slice apart itself — `(addr (at pts
|
||
;; 0))` and `(len pts)`, twice, in the right order — and the compiler cannot
|
||
;; check that the two halves came from the same slice. The wrapper is where
|
||
;; that idiom lives, which is the rule collision-point-poly? set.
|
||
;;
|
||
;; It also guards the empty case, which is the part a hand-written call site
|
||
;; gets wrong rather than merely writes out. raylib takes a count of 0 and
|
||
;; draws nothing, but `(at pts 0)` on an empty slice is out of bounds before
|
||
;; raylib is ever reached: the safe call is "do not call at all", and it is
|
||
;; written once here instead of at every use.
|
||
;;
|
||
;; All eleven and not the three anybody has called. A subset would have its
|
||
;; hole exactly where the next caller looks, which is the argument this file
|
||
;; already makes about ConfigFlags, and the eleven are one family — there is
|
||
;; no line to draw between DrawSplineLinear and DrawSplineBasis that a reader
|
||
;; would predict.
|
||
;;
|
||
;; `bindings` makes the opposite argument a few lines above its own list —
|
||
;; that hand-writing the variants of a family "would widen the half that has
|
||
;; to be maintained by hand for nothing the examples ask for" — and it is
|
||
;; right there and does not reach here. That paragraph is about hand-written
|
||
;; `declare-c` lines, which are exactly the half a header change can falsify.
|
||
;; None of these eleven is one: each is a `name` directive, so the generated
|
||
;; declaration keeps the C symbol and its checked signature and gives up only
|
||
;; its Flan name. The hand-maintained half does not widen at all — what is
|
||
;; written below is Flan calling Flan, and it cannot disagree with raylib. They sit together here rather than each in its own section
|
||
;; for the same reason: the justification above is one argument about a shape
|
||
;; that cuts across Shapes, Images and 3D, and splitting the family would
|
||
;; mean writing it three times or leaving two thirds of it unexplained.
|
||
;;
|
||
;; Each -raw below is a generated declaration whose name moved aside; see the
|
||
;; `name` lines at the foot of `bindings`.
|
||
|
||
(defn draw-line-strip [points [Vector2] color Color] ()
|
||
(when (> (len points) 0)
|
||
(draw-line-strip-raw (addr (at points 0)) (len points) color)))
|
||
|
||
(defn draw-triangle-fan [points [Vector2] color Color] ()
|
||
(when (> (len points) 0)
|
||
(draw-triangle-fan-raw (addr (at points 0)) (len points) color)))
|
||
|
||
(defn draw-triangle-strip [points [Vector2] color Color] ()
|
||
(when (> (len points) 0)
|
||
(draw-triangle-strip-raw (addr (at points 0)) (len points) color)))
|
||
|
||
(defn draw-triangle-strip-3d [points [Vector3] color Color] ()
|
||
(when (> (len points) 0)
|
||
(draw-triangle-strip-3d-raw (addr (at points 0)) (len points) color)))
|
||
|
||
;; The five spline drawers. raylib reads the same point array five different
|
||
;; ways; the only difference between these wrappers is which one it calls.
|
||
(defn draw-spline-linear [points [Vector2] thick f32 color Color] ()
|
||
(when (> (len points) 0)
|
||
(draw-spline-linear-raw (addr (at points 0)) (len points) thick color)))
|
||
|
||
(defn draw-spline-basis [points [Vector2] thick f32 color Color] ()
|
||
(when (> (len points) 0)
|
||
(draw-spline-basis-raw (addr (at points 0)) (len points) thick color)))
|
||
|
||
(defn draw-spline-catmull-rom [points [Vector2] thick f32 color Color] ()
|
||
(when (> (len points) 0)
|
||
(draw-spline-catmull-rom-raw (addr (at points 0)) (len points) thick color)))
|
||
|
||
(defn draw-spline-bezier-quadratic [points [Vector2] thick f32 color Color] ()
|
||
(when (> (len points) 0)
|
||
(draw-spline-bezier-quadratic-raw
|
||
(addr (at points 0)) (len points) thick color)))
|
||
|
||
(defn draw-spline-bezier-cubic [points [Vector2] thick f32 color Color] ()
|
||
(when (> (len points) 0)
|
||
(draw-spline-bezier-cubic-raw
|
||
(addr (at points 0)) (len points) thick color)))
|
||
|
||
;; The same two into an Image rather than the frame. `dst` stays a pointer:
|
||
;; it is the thing being written, not an array, and raylib's convention for
|
||
;; an in-place Image is the whole Image* family in this file.
|
||
(defn image-draw-triangle-fan [dst (Ptr Image) points [Vector2] color Color] ()
|
||
(when (> (len points) 0)
|
||
(image-draw-triangle-fan-raw dst (addr (at points 0)) (len points) color)))
|
||
|
||
(defn image-draw-triangle-strip
|
||
[dst (Ptr Image) points [Vector2] color Color] ()
|
||
(when (> (len points) 0)
|
||
(image-draw-triangle-strip-raw dst (addr (at points 0)) (len points) color)))
|
||
|
||
;; ── 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")
|
||
|
||
;; `recs` and `glyphs` are the two places in this whole package where a C
|
||
;; pointer's length is knowable and the language could not say it. Both arrays
|
||
;; hold exactly `glyph-count` entries — raylib allocates them that way in
|
||
;; LoadFontData and every one of its own loops uses that bound — and
|
||
;; `glyph-count` is a sibling *field*, which is why naming a count argument in
|
||
;; `bindings` could never have covered these two. `slice-from-ptr` can.
|
||
;;
|
||
;; **These two wrappers are where the promise is made, and they are the reason
|
||
;; the promise is safe to make**: a caller of `font-recs` is trusting raylib's
|
||
;; own invariant rather than remembering a number, and there is one place to
|
||
;; fix if raylib ever changes it. Prefer them to writing `slice-from-ptr` at a
|
||
;; call site.
|
||
;;
|
||
;; The one way to break them is to call either on a Font that was unloaded, or
|
||
;; on a zeroed one: `unload-font` frees both arrays and does not clear the
|
||
;; pointers, so the slice would be a promise about freed memory. That is the
|
||
;; ordinary use-after-free a (Ptr T) already had; the slice does not own the
|
||
;; storage and freeing through one is not expressible.
|
||
(defn font-recs [font Font] [Rectangle]
|
||
(slice-from-ptr (.recs font) (.glyph-count font)))
|
||
|
||
(defn font-glyphs [font Font] [GlyphInfo]
|
||
(slice-from-ptr (.glyphs font) (.glyph-count font)))
|
||
|
||
;; 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.
|