Ten raylib examples, and what they could not say

The first ten of raylib's core list, ported. Seven new bindings and the
named colour palette; nothing else was added, because a binding called
by nothing is the same as not having bound it.

The gaps they found are the point. No number reaches draw-text: i64->bytes
answers [u8], draw-text wants a string, and nothing bridges — five of the
ten wanted TextFormat and got a glyph table instead. And an enum parameter
cannot be driven by a loop variable: the index is an i32, the parameter is
an enum, neither converts, and a second declare-c with an i32 face is
refused because one C function gets one binding. Two correct rules that
compose into a wall.

None of the gaps expected blocked anything: no generics, no allocator, no
Vec, no escaping closure, no block-scoped defer. These are input-and-draw
programs over fixed-size state, which is the shape the language has.
This commit is contained in:
Joseph Ferano 2026-09-12 05:06:01 +07:00
parent 2f8436018c
commit afec482722
16 changed files with 1629 additions and 1 deletions

View File

@ -302,7 +302,9 @@ that calls an existing binding is unaffected.
34 51 68`, four separate bytes — a `Color` is *not* the little-endian reading of the packed integer, so an identity
would have passed a weaker test. That case is in the acceptance table, skipped if `libraylib` is not installed.
The bindings are 164 calls across thirteen structs: window, keyboard and mouse; drawing (rectangles, circles, lines, triangles, rings, ellipses, text); the eleven `collision-*` predicates; textures; the Image family; `Camera2D`; `RenderTexture2D`; the whole audio surface (device, `Wave`, `Sound`, `Music`); fonts and glyphs; and gamepads, touch and gestures — plus the `Key`, `MouseButton`, `TraceLogLevel`, `GamepadButton`, `GamepadAxis` and `Gesture` enums. Adding one is a single `declare-c` line; there is no C to write.
The bindings are 171 calls across thirteen structs: window, keyboard and mouse; drawing (rectangles, circles, lines, triangles, rings, ellipses, text); the eleven `collision-*` predicates; textures; the Image family; `Camera2D`; `RenderTexture2D`; the whole audio surface (device, `Wave`, `Sound`, `Music`); fonts and glyphs; and gamepads, touch and gestures — plus the `Key`, `MouseButton`, `TraceLogLevel`, `GamepadButton`, `GamepadAxis` and `Gesture` enums, raylib's own named colour palette, and the `FLAG_` window hints. Adding one is a single `declare-c` line; there is no C to write.
Two things the ported examples in `examples/` wanted and could not have, both refused for reasons that are right. `GetGamepadName` returns a `char *` into raylib's static storage: *the return type of get-gamepad-name is a string, and a string only crosses as a parameter — a C function that* returns *one returns something Flan has no owner for*. And an enum parameter cannot be indexed — `GetGamepadAxisMovement` takes a `GamepadAxis`, a loop variable is an `i32`, *expected rl/GamepadAxis, found i32*, and a second `declare-c` of the same symbol with an `i32` face is refused too: *one declare-c per C function, and another Flan name for it is a defn* — which cannot help, because a wrapper renames and does not retype. The caller spells the loop as a `cond` over the members it knows.
The texture calls are the first ones with no headless test, because loading one needs a GL context. What the acceptance
case does instead is pin the two new struct layouts using the only things raylib computes from those fields without a

View File

@ -0,0 +1,32 @@
;;;; raylib [core] example - basic window
;;;;
;;;; examples/core/core_basic_window.c, line for line. Nothing here needed
;;;; anything the language did not already have, which is the point of doing it
;;;; first: if this one does not run, none of the others will either.
;;;;
;;;; The one structural difference from the C is the loop: the C writes
;;;; `while (!WindowShouldClose())` and Flan has `until`, which is the same
;;;; thing without the negation. sand.flan does likewise.
;;;;
;;;; `defer` closes the window. It is function-scoped — it runs when `main`
;;;; returns and not at the end of any inner block — which is exactly what the
;;;; C's trailing CloseWindow() means here.
(import rl "vendor:raylib")
(defconst screen-width 800)
(defconst screen-height 450)
(defn main []
(rl/init-window screen-width screen-height
"raylib [core] example - basic window")
(defer (rl/close-window))
(rl/set-target-fps 60)
(until (rl/window-should-close?)
(rl/begin-drawing)
(rl/clear-background rl/raywhite)
(rl/draw-text "Congrats! You created your first window!" 190 200 20
rl/lightgray)
(rl/end-drawing)))

View File

@ -0,0 +1,115 @@
;;;; raylib [core] example - delta time
;;;;
;;;; examples/core/core_delta_time.c. Needed get-mouse-wheel-move and get-fps,
;;;; both now bound — draw-fps was already there, but it puts the number on the
;;;; screen itself and this example wants to compare it against the target it
;;;; set, so it needs the number back.
;;;;
;;;; Three TextFormats in the C, all drawn here by examples/digits.flan.
;;;;
;;;; One faithfulness note that is a bug in the C and is kept anyway: it draws
;;;; `TextFormat("Frame time: %02.02f ms", GetFrameTime())` — GetFrameTime is
;;;; in SECONDS, so at 60 fps the C's own screen reads "0.02 ms" for what is
;;;; really 16.7 ms. Porting it as-is means the two programs show the same
;;;; thing, which is what a port is for; the milliseconds are drawn beside it
;;;; here rather than instead of it, so the file is not quietly asserting that
;;;; the original was right.
;;;;
;;;; The two circle positions are `defvar`s for the usual reason: they are
;;;; state between frames and a `let` inside the loop would reset them.
(import rl "vendor:raylib")
(import d "digits.flan")
(defconst screen-width 800)
(defconst screen-height 450)
(defconst speed f32 10.0)
(defconst circle-radius f32 32.0)
(defvar delta-circle rl/Vector2)
(defvar frame-circle rl/Vector2)
(defvar current-fps i32)
(defn main []
(rl/init-window screen-width screen-height
"raylib [core] example - delta time")
(defer (rl/close-window))
(set current-fps 60)
(set delta-circle (rl/Vector2 {:x 0.0 :y (/ (f32 screen-height) 3.0)}))
(set frame-circle (rl/Vector2 {:x 0.0
:y (* (f32 screen-height) (/ 2.0 3.0))}))
(rl/set-target-fps current-fps)
(until (rl/window-should-close?)
;; Update
(let [wheel (rl/get-mouse-wheel-move)]
(unless (= wheel 0.0)
(set current-fps (+ current-fps (i32 wheel)))
(when (< current-fps 0) (set current-fps 0))
(rl/set-target-fps current-fps)))
;; The whole point of the example: one circle is scaled by the frame time
;; and one is not, so lowering the target with the wheel makes the second
;; one crawl while the first keeps its speed.
(set (.x delta-circle)
(+ (.x delta-circle) (* (rl/get-frame-time) (* 6.0 speed))))
(set (.x frame-circle) (+ (.x frame-circle) (* 0.1 speed)))
(when (> (.x delta-circle) (f32 screen-width)) (set (.x delta-circle) 0.0))
(when (> (.x frame-circle) (f32 screen-width)) (set (.x frame-circle) 0.0))
(when (rl/key-pressed? :r)
(set (.x delta-circle) 0.0)
(set (.x frame-circle) 0.0))
;; Draw
(rl/begin-drawing)
(rl/clear-background rl/raywhite)
(rl/draw-circle-v delta-circle circle-radius rl/red)
(rl/draw-circle-v frame-circle circle-radius rl/blue)
;; "FPS: unlimited (%i)" when the target is 0, "FPS: %i (target: %i)"
;; otherwise — the C's own branch, reassembled out of literals and
;; draw-int. `x` walks along the line as each piece is drawn, which is what
;; the returned width from draw-int is for.
(if (<= current-fps 0)
(let [x 10]
(rl/draw-text "FPS: unlimited (" x 10 20 rl/darkgray)
(set x (+ x (rl/measure-text "FPS: unlimited (" 20)))
(set x (+ x (d/draw-int (rl/get-fps) x 10 20 rl/darkgray)))
(rl/draw-text ")" x 10 20 rl/darkgray))
(let [x 10]
(rl/draw-text "FPS: " x 10 20 rl/darkgray)
(set x (+ x (rl/measure-text "FPS: " 20)))
(set x (+ x (d/draw-int (rl/get-fps) x 10 20 rl/darkgray)))
(rl/draw-text " (target: " x 10 20 rl/darkgray)
(set x (+ x (rl/measure-text " (target: " 20)))
(set x (+ x (d/draw-int current-fps x 10 20 rl/darkgray)))
(rl/draw-text ")" x 10 20 rl/darkgray)))
;; The C's line, kept exactly — seconds, labelled ms.
(let [x 10]
(rl/draw-text "Frame time: " x 30 20 rl/darkgray)
(set x (+ x (rl/measure-text "Frame time: " 20)))
(set x (+ x (d/draw-f32 (rl/get-frame-time) 2 x 30 20 rl/darkgray)))
(rl/draw-text " ms" x 30 20 rl/darkgray))
;; And the same number in the unit the label claims, so the file does not
;; have to be read to notice.
(let [x 10]
(rl/draw-text "(really " x 50 20 rl/lightgray)
(set x (+ x (rl/measure-text "(really " 20)))
(set x (+ x (d/draw-f32 (* (rl/get-frame-time) 1000.0) 2 x 50 20
rl/lightgray)))
(rl/draw-text " ms)" x 50 20 rl/lightgray))
(rl/draw-text "Use the scroll wheel to change the fps limit, r to reset"
10 70 20 rl/darkgray)
(rl/draw-text "FUNC: x += GetFrameTime()*speed" 10 110 20 rl/red)
(rl/draw-text "FUNC: x += speed" 10 260 20 rl/blue)
(rl/end-drawing)))

View File

@ -0,0 +1,280 @@
;;;; raylib [core] example - input gamepad
;;;;
;;;; examples/core/core_input_gamepad.c. Needed set-config-flags (and the
;;;; FLAG_ constants), both now bound.
;;;;
;;;; Three deliberate differences from the C, all of them things the port
;;;; could not or should not follow.
;;;;
;;;; **The pad artwork is gone, and with it the xbox/PS branches.** The C draws
;;;; `resources/ps3.png` or `resources/xbox.png` and picks between them by
;;;; matching `GetGamepadName` against four substrings. Neither image is in
;;;; this repository, and importing two binaries for one example is worse than
;;;; the alternative: the C has a third branch, for a pad it does not
;;;; recognise, which draws the whole thing out of rounded rectangles and
;;;; circles and needs no asset at all. That branch is what this ports, in
;;;; full. It is also the branch a Linux desktop usually takes anyway.
;;;;
;;;; **GetGamepadName is not bound, and cannot be.** It is what the C matches
;;;; on, and declare-c refuses it by name:
;;;;
;;;; the return type of get-gamepad-name is a string, and a string only
;;;; crosses as a parameter — a C function that *returns* one returns
;;;; something Flan has no owner for
;;;;
;;;; which is correct: raylib hands back a pointer into its own static storage
;;;; and Flan has nothing that owns a borrowed C string. So the pad's name is
;;;; not on screen and the branch that used it is not here.
;;;;
;;;; **The VIBRATE button is drawn and inert.** SetGamepadVibration is the one
;;;; call in the C this refuses to bind, and vendor/raylib/raylib.flan already
;;;; carries the paragraph saying why: its arity differs between raylib
;;;; versions with no 5.5 header here to settle it, so a guess is a corrupted
;;;; stack frame rather than a link error — and the symbol in libraylib.so.550
;;;; disassembles to a single TraceLog call and a jump. Binding it would be
;;;; binding a warning. The button is drawn because taking it out would hide
;;;; the finding; it is labelled so.
;;;;
;;;; With no pad attached this shows "GP0: NOT DETECTED" and nothing else,
;;;; which is the C's behaviour minus the greyed-out artwork.
(import rl "vendor:raylib")
(import d "digits.flan")
(defconst screen-width 800)
(defconst screen-height 450)
(defconst stick-deadzone f32 0.1)
(defconst trigger-deadzone f32 -0.9)
(defvar gamepad i32)
;; The C's deadzone test, which is a band around zero and not a clamp: inside
;; it the axis reads exactly 0, outside it the raw value passes through
;; unscaled. That is a step at the edge of the band and the C has it too.
(defn deadzone [v f32 limit f32] f32
(if (and (> v (- 0.0 limit)) (< v limit)) 0.0 v))
;; The triggers rest at -1 rather than 0, which raylib does not normalise —
;; see the note on GamepadAxis in vendor/raylib/raylib.flan. So their deadzone
;; is a floor near the resting end and not a band around the middle.
(defn trigger-deadzoned [v f32] f32
(if (< v trigger-deadzone) -1.0 v))
;; The C's read-out loop is `for (i = 0; i < GetGamepadAxisCount(gamepad); i++)
;; DrawText(TextFormat("AXIS %i: %.02f", i, GetGamepadAxisMovement(gamepad, i)))`
;; — an axis selected by a loop variable. That cannot go through the binding,
;; twice over:
;;
;; expected rl/GamepadAxis, found i32
;;
;; because an integer does not convert to an enum and a keyword can only name
;; one member; and a second declare-c of the same C function with an i32
;; parameter is refused as well —
;;
;; 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 wrapper cannot change a parameter's type. So the index is
;; turned back into a member here, by hand, which is the only shape left. It
;; covers the six axes raylib names; a pad reporting more reads 0.0 for the
;; extras, and the count is clamped below so they are not drawn at all.
(defn axis-at [pad i32 index i32] f32
(cond
(= index 0) (rl/get-gamepad-axis-movement pad :left-x)
(= index 1) (rl/get-gamepad-axis-movement pad :left-y)
(= index 2) (rl/get-gamepad-axis-movement pad :right-x)
(= index 3) (rl/get-gamepad-axis-movement pad :right-y)
(= index 4) (rl/get-gamepad-axis-movement pad :left-trigger)
(= index 5) (rl/get-gamepad-axis-movement pad :right-trigger)
:else 0.0))
(defn draw-pad-background []
(rl/draw-rectangle-rounded
(rl/Rectangle {:x 175.0 :y 110.0 :width 460.0 :height 220.0})
0.3 16 rl/darkgray)
;; The three middle buttons and the four face buttons, as outlines. The
;; filled overlays go on top only while the button is down.
(rl/draw-circle 365 170 12.0 rl/raywhite)
(rl/draw-circle 405 170 12.0 rl/raywhite)
(rl/draw-circle 445 170 12.0 rl/raywhite)
(rl/draw-circle 516 191 17.0 rl/raywhite)
(rl/draw-circle 551 227 17.0 rl/raywhite)
(rl/draw-circle 587 191 17.0 rl/raywhite)
(rl/draw-circle 551 155 17.0 rl/raywhite)
;; The d-pad cross: a light plate with a dark one inset, so a pressed
;; direction has something to show up against.
(rl/draw-rectangle 245 145 28 88 rl/raywhite)
(rl/draw-rectangle 215 174 88 29 rl/raywhite)
(rl/draw-rectangle 247 147 24 84 rl/black)
(rl/draw-rectangle 217 176 84 25 rl/black)
(rl/draw-rectangle-rounded
(rl/Rectangle {:x 215.0 :y 98.0 :width 100.0 :height 10.0})
0.5 16 rl/darkgray)
(rl/draw-rectangle-rounded
(rl/Rectangle {:x 495.0 :y 98.0 :width 100.0 :height 10.0})
0.5 16 rl/darkgray))
(defn draw-pad-buttons []
(when (rl/gamepad-button-down? gamepad :middle-left)
(rl/draw-circle 365 170 10.0 rl/red))
(when (rl/gamepad-button-down? gamepad :middle)
(rl/draw-circle 405 170 10.0 rl/green))
(when (rl/gamepad-button-down? gamepad :middle-right)
(rl/draw-circle 445 170 10.0 rl/blue))
(when (rl/gamepad-button-down? gamepad :right-face-left)
(rl/draw-circle 516 191 15.0 rl/gold))
(when (rl/gamepad-button-down? gamepad :right-face-down)
(rl/draw-circle 551 227 15.0 rl/blue))
(when (rl/gamepad-button-down? gamepad :right-face-right)
(rl/draw-circle 587 191 15.0 rl/green))
(when (rl/gamepad-button-down? gamepad :right-face-up)
(rl/draw-circle 551 155 15.0 rl/red))
(when (rl/gamepad-button-down? gamepad :left-face-up)
(rl/draw-rectangle 247 147 24 29 rl/red))
(when (rl/gamepad-button-down? gamepad :left-face-down)
(rl/draw-rectangle 247 201 24 30 rl/red))
(when (rl/gamepad-button-down? gamepad :left-face-left)
(rl/draw-rectangle 217 176 30 25 rl/red))
(when (rl/gamepad-button-down? gamepad :left-face-right)
(rl/draw-rectangle 271 176 30 25 rl/red))
(when (rl/gamepad-button-down? gamepad :left-trigger-1)
(rl/draw-rectangle-rounded
(rl/Rectangle {:x 215.0 :y 98.0 :width 100.0 :height 10.0})
0.5 16 rl/red))
(when (rl/gamepad-button-down? gamepad :right-trigger-1)
(rl/draw-rectangle-rounded
(rl/Rectangle {:x 495.0 :y 98.0 :width 100.0 :height 10.0})
0.5 16 rl/red)))
(defn draw-stick [cx i32 cy i32 ax f32 ay f32 thumb-down bool]
(rl/draw-circle cx cy 40.0 rl/black)
(rl/draw-circle cx cy 35.0 rl/lightgray)
(rl/draw-circle (+ cx (i32 (* ax 20.0))) (+ cy (i32 (* ay 20.0))) 25.0
(if thumb-down rl/red rl/black)))
(defn main []
;; Before init-window, and it has to be: raylib reads the flags while it is
;; creating the context, so the same call afterwards is accepted, logged, and
;; has no effect on the window that already exists.
(rl/set-config-flags rl/flag-msaa-4x-hint)
(rl/init-window screen-width screen-height
"raylib [core] example - input gamepad")
(defer (rl/close-window))
(rl/set-target-fps 60)
(until (rl/window-should-close?)
;; Update
(when (and (rl/key-pressed? :left) (> gamepad 0))
(set gamepad (- gamepad 1)))
(when (rl/key-pressed? :right) (set gamepad (+ gamepad 1)))
(let [axis-count (min 6 (rl/get-gamepad-axis-count gamepad))
vibrate-rect (rl/Rectangle {:x 10.0
:y (+ 90.0 (* 20.0 (f32 axis-count)))
:width 75.0 :height 24.0})]
;; Draw
(rl/begin-drawing)
(rl/clear-background rl/raywhite)
(if (rl/gamepad-available? gamepad)
(do
;; The C draws `TextFormat("GP%d: %s", gamepad, GetGamepadName(...))`.
;; The name is unbindable — see the header — so this is the index and
;; the word the C would have followed it with.
(rl/draw-text "GP" 10 10 10 rl/black)
(let [x (+ 10 (rl/measure-text "GP" 10))]
(rl/draw-text ": CONNECTED (name unbindable)"
(+ x (d/draw-int gamepad x 10 10 rl/black))
10 10 rl/black))
(let [lx (deadzone (rl/get-gamepad-axis-movement gamepad :left-x)
stick-deadzone)
ly (deadzone (rl/get-gamepad-axis-movement gamepad :left-y)
stick-deadzone)
rx (deadzone (rl/get-gamepad-axis-movement gamepad :right-x)
stick-deadzone)
ry (deadzone (rl/get-gamepad-axis-movement gamepad :right-y)
stick-deadzone)
lt (trigger-deadzoned
(rl/get-gamepad-axis-movement gamepad :left-trigger))
rt (trigger-deadzoned
(rl/get-gamepad-axis-movement gamepad :right-trigger))]
(draw-pad-background)
(draw-pad-buttons)
(draw-stick 345 260 lx ly
(rl/gamepad-button-down? gamepad :left-thumb))
(draw-stick 465 260 rx ry
(rl/gamepad-button-down? gamepad :right-thumb))
;; The triggers as bars filling upward from a grey track. The +1
;; and the halving turn raylib's [-1, 1] into [0, 1].
(rl/draw-rectangle 151 110 15 70 rl/gray)
(rl/draw-rectangle 644 110 15 70 rl/gray)
(rl/draw-rectangle 151 110 15 (i32 (* (/ (+ 1.0 lt) 2.0) 70.0))
rl/red)
(rl/draw-rectangle 644 110 15 (i32 (* (/ (+ 1.0 rt) 2.0) 70.0))
rl/red))
(rl/draw-text "DETECTED AXIS [" 10 50 10 rl/maroon)
(let [x (+ 10 (rl/measure-text "DETECTED AXIS [" 10))]
(rl/draw-text "]:" (+ x (d/draw-int axis-count x 50 10 rl/maroon))
50 10 rl/maroon))
(dotimes [i axis-count]
(rl/draw-text "AXIS " 20 (+ 70 (* 20 i)) 10 rl/darkgray)
(let [x (+ 20 (rl/measure-text "AXIS " 10))
y (+ 70 (* 20 i))]
(set x (+ x (d/draw-int i x y 10 rl/darkgray)))
(rl/draw-text ": " x y 10 rl/darkgray)
(set x (+ x (rl/measure-text ": " 10)))
;; The raw reading, not the deadzoned one — the C shows the raw
;; value here too, which is what makes the deadzone visible as a
;; difference between this row and the stick.
(d/draw-f32 (axis-at gamepad i) 2 x y 10
rl/darkgray)))
;; Drawn and inert. See the header: SetGamepadVibration is a stub in
;; this raylib and is not bound.
(rl/draw-rectangle-rec vibrate-rect rl/skyblue)
(rl/draw-text "VIBRATE" (+ (i32 (.x vibrate-rect)) 14)
(+ (i32 (.y vibrate-rect)) 1) 10 rl/darkgray)
(rl/draw-text "(not bound: raylib stub)" 95
(+ (i32 (.y vibrate-rect)) 7) 10 rl/gray)
;; -1 when nothing is pressed, which is why the binding answers an
;; i32 and not a GamepadButton.
;; draw-int answers the width it drew, so a branch that ends in one
;; has type i32 while its sibling has type Unit and the `if` will not
;; typecheck — "expected i32, found Unit". Both arms end in a
;; draw-text here, which is the tidy way out; where that is awkward a
;; trailing `(do)` is the other.
(let [b (rl/get-gamepad-button-pressed)]
(if (>= b 0)
(do (rl/draw-text "DETECTED BUTTON: " 10 430 10 rl/red)
(d/draw-int b (+ 10 (rl/measure-text "DETECTED BUTTON: " 10))
430 10 rl/red)
(do))
(rl/draw-text "DETECTED BUTTON: NONE" 10 430 10 rl/gray))))
(do
(rl/draw-text "GP" 10 10 10 rl/gray)
(let [x (+ 10 (rl/measure-text "GP" 10))]
(rl/draw-text ": NOT DETECTED"
(+ x (d/draw-int gamepad x 10 10 rl/gray))
10 10 rl/gray))
(rl/draw-text "left/right arrows select another pad" 10 30 10
rl/lightgray)))
(rl/end-drawing))))

View File

@ -0,0 +1,345 @@
;;;; raylib [core] example - input gestures testbed
;;;;
;;;; examples/core/core_input_gestures_testbed.c, the four-star one and the
;;;; hardest of the ten. It runs, and it is the example that pushed hardest on
;;;; the language. Four things it needed, in descending order of how much they
;;;; cost.
;;;;
;;;; **An enum does not convert to an integer.** The C treats the gesture as
;;;; the raw bitfield it is and compares it with `<` and `>`:
;;;; `currentGesture > 255` picks out the two pinches, `> 15` the four swipes,
;;;; `!= 4` excludes hold, `< 3` admits tap and double-tap. `get-gesture-
;;;; detected` answers an `rl/Gesture`, and
;;;;
;;;; i32 converts a number, found rl/Gesture
;;;;
;;;; so none of those comparisons can be written. They are spelled out below as
;;;; named predicates over keyword equalities — `pinch?`, `swipe?`, `tapish?`.
;;;; That is arguably better source than the magic numbers were, and it is
;;;; strictly more checkable, but it is not a choice: it is the only shape
;;;; available, and a gesture raylib adds later would silently fall out of
;;;; `swipe?` where the C's `> 15` would have caught it.
;;;;
;;;; **No sin or cos.** The prelude has sqrt-f32 — one `declare` over libm,
;;;; with a comment explaining why it is not a builtin — and nothing else
;;;; transcendental. The protractor needs both, so they are two more `declare`
;;;; lines here, in the same shape. They link: libm is already on the line.
;;;;
;;;; **No string formatting**, as everywhere. The C prints the angle with
;;;; `TextFormat("%f", ...)`, finds the decimal point with `TextFindIndex` and
;;;; cuts two digits past it with `TextSubtext`. `draw-f32` in
;;;; examples/digits.flan does the whole thing in one call, and rounds rather
;;;; than truncating — which is the one place this screen differs from the C's
;;;; by a digit.
;;;;
;;;; **No local fixed arrays.** The C declares `char gestureLog[20][12]` and
;;;; `Vector2 touchPosition[32]` inside main. A `let` binding takes no type
;;;; annotation, so a fixed array can only be a top-level `defvar` or a literal
;;;; with every element written out — thirty-two Vector2s, here. They are
;;;; `defvar`s, which is what the C's storage amounts to anyway.
;;;;
;;;; The log itself came out simpler than the C's: the names are compile-time
;;;; literals, so a slot holds a `string` and there is no TextCopy and no
;;;; twelve-byte truncation.
(import rl "vendor:raylib")
(import d "digits.flan")
;; libm, as the prelude declares sqrtf. Not declare-c: these take a float and
;; answer a float in C's own convention with no struct anywhere, which is what
;; plain `declare` is for.
(declare sin-f32 [x f32] f32 "sinf")
(declare cos-f32 [x f32] f32 "cosf")
(defconst screen-width 800)
(defconst screen-height 450)
(defconst pi f32 3.14159265)
(defconst gesture-log-size 20)
(defconst max-touch-count 32)
(defvar gesture-log [gesture-log-size string])
;; The C's inverted circular queue: the index counts DOWN and wraps at the top,
;; so the newest entry is always at gesture-log-index and the draw loop walks
;; forward from there. Starting at the size rather than at size-1 is the C's
;; too — the first write decrements before storing.
(defvar gesture-log-index i32)
(defvar previous-gesture rl/Gesture)
(defvar last-gesture rl/Gesture)
(defvar gesture-color rl/Color)
(defvar log-mode i32)
(defvar current-angle f32)
(defvar touch-positions [max-touch-count rl/Vector2])
;; ── The comparisons the C makes on the raw bitfield ─────────────────
;;
;; See the header: an rl/Gesture will not convert to an i32, so `> 255` and
;; friends are these instead.
(defn pinch? [g rl/Gesture] bool ; the C's `> 255`
(or (= g :pinch-in) (= g :pinch-out)))
(defn swipe? [g rl/Gesture] bool ; the C's `> 15`
(or (or (= g :swipe-right) (= g :swipe-left))
(or (= g :swipe-up) (= g :swipe-down))))
(defn tapish? [g rl/Gesture] bool ; the C's `< 3`
(or (= g :tap) (= g :double-tap)))
(defn gesture-name [g rl/Gesture] string
(cond
(= g :none) "None"
(= g :tap) "Tap"
(= g :double-tap) "Double Tap"
(= g :hold) "Hold"
(= g :drag) "Drag"
(= g :swipe-right) "Swipe Right"
(= g :swipe-left) "Swipe Left"
(= g :swipe-up) "Swipe Up"
(= g :swipe-down) "Swipe Down"
(= g :pinch-in) "Pinch In"
(= g :pinch-out) "Pinch Out"
:else "Unknown"))
(defn gesture-color-of [g rl/Gesture] rl/Color
(cond
(= g :tap) rl/blue
(= g :double-tap) rl/skyblue
(= g :drag) rl/lime
(swipe? g) rl/red
(= g :pinch-in) rl/violet
(= g :pinch-out) rl/orange
:else rl/black))
;; ── The log ─────────────────────────────────────────────────────────
;; The C's four log modes, as a truth table rather than as nested ifs:
;; 0 shows repeated events
;; 1 hides repeated events
;; 2 shows repeated events but hides hold
;; 3 hides repeated events and hides hold
(defn should-log? [g rl/Gesture] bool
(cond
(= g :none) false
(= log-mode 3) (or (and (not (= g :hold)) (not (= g previous-gesture)))
(tapish? g))
(= log-mode 2) (not (= g :hold))
(= log-mode 1) (not (= g previous-gesture))
:else true))
(defn push-log [g rl/Gesture]
(set previous-gesture g)
(set gesture-color (gesture-color-of g))
(when (<= gesture-log-index 0) (set gesture-log-index gesture-log-size))
(set gesture-log-index (- gesture-log-index 1))
(set (at gesture-log gesture-log-index) (gesture-name g)))
;; ── Drawing ─────────────────────────────────────────────────────────
(defconst last-x 165)
(defconst last-y 130)
(defconst prot-x f32 266.0)
(defconst prot-y f32 315.0)
(defconst angle-length f32 90.0)
(defn swipe-box [gx i32 gy i32 which rl/Gesture]
(rl/draw-rectangle gx gy 20 20
(if (= last-gesture which) rl/red rl/lightgray)))
(defn draw-last-gesture [touch-count i32]
(rl/draw-text "Last gesture" (+ last-x 33) (- last-y 47) 20 rl/black)
(rl/draw-text "Swipe Tap Pinch Touch" (+ last-x 17)
(- last-y 18) 10 rl/black)
(swipe-box (+ last-x 20) last-y :swipe-up)
(swipe-box last-x (+ last-y 20) :swipe-left)
(swipe-box (+ last-x 40) (+ last-y 20) :swipe-right)
(swipe-box (+ last-x 20) (+ last-y 40) :swipe-down)
(rl/draw-circle (+ last-x 80) (+ last-y 16) 10.0
(if (= last-gesture :tap) rl/blue rl/lightgray))
;; segments 0 lets raylib pick the count from the radius.
(rl/draw-ring (rl/Vector2 {:x (f32 (+ last-x 103)) :y (f32 (+ last-y 16))})
6.0 11.0 0.0 360.0 0
(if (= last-gesture :drag) rl/lime rl/lightgray))
(rl/draw-circle (+ last-x 80) (+ last-y 43) 10.0
(if (= last-gesture :double-tap) rl/skyblue rl/lightgray))
(rl/draw-circle (+ last-x 103) (+ last-y 43) 10.0
(if (= last-gesture :double-tap) rl/skyblue rl/lightgray))
;; The two pairs of arrowheads, pointing outward for pinch-out and inward
;; for pinch-in. Counter-clockwise winding, or raylib culls them.
(let [out-c (if (= last-gesture :pinch-out) rl/orange rl/lightgray)
in-c (if (= last-gesture :pinch-in) rl/violet rl/lightgray)]
(rl/draw-triangle (rl/Vector2 {:x (f32 (+ last-x 122)) :y (f32 (+ last-y 16))})
(rl/Vector2 {:x (f32 (+ last-x 137)) :y (f32 (+ last-y 26))})
(rl/Vector2 {:x (f32 (+ last-x 137)) :y (f32 (+ last-y 6))})
out-c)
(rl/draw-triangle (rl/Vector2 {:x (f32 (+ last-x 147)) :y (f32 (+ last-y 6))})
(rl/Vector2 {:x (f32 (+ last-x 147)) :y (f32 (+ last-y 26))})
(rl/Vector2 {:x (f32 (+ last-x 162)) :y (f32 (+ last-y 16))})
out-c)
(rl/draw-triangle (rl/Vector2 {:x (f32 (+ last-x 125)) :y (f32 (+ last-y 33))})
(rl/Vector2 {:x (f32 (+ last-x 125)) :y (f32 (+ last-y 53))})
(rl/Vector2 {:x (f32 (+ last-x 140)) :y (f32 (+ last-y 43))})
in-c)
(rl/draw-triangle (rl/Vector2 {:x (f32 (+ last-x 144)) :y (f32 (+ last-y 43))})
(rl/Vector2 {:x (f32 (+ last-x 159)) :y (f32 (+ last-y 53))})
(rl/Vector2 {:x (f32 (+ last-x 159)) :y (f32 (+ last-y 33))})
in-c))
;; Four pips, one per simultaneous touch raylib reports.
(dotimes [i 4]
(rl/draw-circle (+ last-x 180) (+ (+ last-y 7) (* i 15)) 5.0
(if (<= touch-count i) rl/lightgray gesture-color))))
(defn draw-log []
(rl/draw-text "Log" 10 10 20 rl/black)
;; Forward from the newest, wrapping — the inverted queue read the right way
;; round.
(dotimes [i gesture-log-size]
(let [ii (% (+ gesture-log-index i) gesture-log-size)]
(rl/draw-text (at gesture-log ii) 10 (- (+ 10 410) (* i 20)) 20
(if (= i 0) gesture-color rl/lightgray))))
;; The two mode buttons. Maroon means the mode that button controls is on.
(let [b1 (rl/Rectangle {:x 53.0 :y 7.0 :width 48.0 :height 26.0})
b2 (rl/Rectangle {:x 108.0 :y 7.0 :width 36.0 :height 26.0})]
(rl/draw-rectangle-rec b1 (if (or (= log-mode 1) (= log-mode 3))
rl/maroon rl/gray))
(rl/draw-text "Hide" 60 10 10 rl/white)
(rl/draw-text "Repeat" 60 20 10 rl/white)
(rl/draw-rectangle-rec b2 (if (or (= log-mode 2) (= log-mode 3))
rl/maroon rl/gray))
(rl/draw-text "Hide" 115 10 10 rl/white)
(rl/draw-text "Hold" 115 20 10 rl/white)))
(defn draw-protractor []
(rl/draw-text "Angle" (+ (i32 prot-x) 55) (+ (i32 prot-y) 76) 10 rl/black)
;; The C's TextFormat/TextFindIndex/TextSubtext dance to get two decimals,
;; in one call. It rounds where the C truncated, so the last digit can
;; differ by one.
(d/draw-f32 current-angle 2 (+ (i32 prot-x) 55) (+ (i32 prot-y) 92) 20
gesture-color)
(rl/draw-circle-v (rl/Vector2 {:x prot-x :y prot-y}) 80.0 rl/white)
(rl/draw-line-ex (rl/Vector2 {:x (- prot-x 90.0) :y prot-y})
(rl/Vector2 {:x (+ prot-x 90.0) :y prot-y}) 3.0 rl/lightgray)
(rl/draw-line-ex (rl/Vector2 {:x prot-x :y (- prot-y 90.0)})
(rl/Vector2 {:x prot-x :y (+ prot-y 90.0)}) 3.0 rl/lightgray)
(rl/draw-line-ex (rl/Vector2 {:x (- prot-x 80.0) :y (- prot-y 45.0)})
(rl/Vector2 {:x (+ prot-x 80.0) :y (+ prot-y 45.0)}) 3.0
rl/green)
(rl/draw-line-ex (rl/Vector2 {:x (- prot-x 80.0) :y (+ prot-y 45.0)})
(rl/Vector2 {:x (+ prot-x 80.0) :y (- prot-y 45.0)}) 3.0
rl/green)
(rl/draw-text "0" (+ (i32 prot-x) 96) (- (i32 prot-y) 9) 20 rl/black)
(rl/draw-text "30" (+ (i32 prot-x) 74) (- (i32 prot-y) 68) 20 rl/black)
(rl/draw-text "90" (- (i32 prot-x) 11) (- (i32 prot-y) 110) 20 rl/black)
(rl/draw-text "150" (- (i32 prot-x) 100) (- (i32 prot-y) 68) 20 rl/black)
(rl/draw-text "180" (- (i32 prot-x) 124) (- (i32 prot-y) 9) 20 rl/black)
(rl/draw-text "210" (- (i32 prot-x) 100) (+ (i32 prot-y) 50) 20 rl/black)
(rl/draw-text "270" (- (i32 prot-x) 18) (+ (i32 prot-y) 92) 20 rl/black)
(rl/draw-text "330" (+ (i32 prot-x) 72) (+ (i32 prot-y) 50) 20 rl/black)
;; The needle. The C's +90 puts 0 degrees on the right, and sin feeding x
;; against cos feeding y is what rotates it the way the dial is labelled.
(unless (= current-angle 0.0)
(let [rad (/ (* (+ current-angle 90.0) pi) 180.0)]
(rl/draw-line-ex (rl/Vector2 {:x prot-x :y prot-y})
(rl/Vector2 {:x (+ (* angle-length (sin-f32 rad)) prot-x)
:y (+ (* angle-length (cos-f32 rad)) prot-y)})
3.0 gesture-color))))
(defn main []
(rl/init-window screen-width screen-height
"raylib [core] example - input gestures testbed")
(defer (rl/close-window))
;; A zeroed Color has an alpha of 0 and is invisible; the C initialises this
;; to opaque black. Same for the log index, which starts at the SIZE.
(set gesture-color rl/black)
(set gesture-log-index gesture-log-size)
(set log-mode 1)
(rl/set-target-fps 60)
(let [b1 (rl/Rectangle {:x 53.0 :y 7.0 :width 48.0 :height 26.0})
b2 (rl/Rectangle {:x 108.0 :y 7.0 :width 36.0 :height 26.0})]
(until (rl/window-should-close?)
;; Update
(let [g (rl/get-gesture-detected)
touch-count (min max-touch-count (rl/get-touch-point-count))]
;; The C filters hold out of the "last gesture" display, because hold
;; repeats every frame and would drown everything else.
(when (and (and (not (= g :none)) (not (= g :hold)))
(not (= g previous-gesture)))
(set last-gesture g))
;; The two mode buttons toggle one bit each of log-mode.
(when (rl/mouse-button-released? :left)
(let [m (rl/get-mouse-position)]
(when (rl/collision-point-rec? m b1)
(set log-mode (cond (= log-mode 3) 2
(= log-mode 2) 3
(= log-mode 1) 0
:else 1)))
(when (rl/collision-point-rec? m b2)
(set log-mode (cond (= log-mode 3) 1
(= log-mode 2) 0
(= log-mode 1) 3
:else 2)))))
(when (should-log? g) (push-log g))
;; The protractor reads the pinch angle for a pinch, the drag angle for
;; a swipe, and sits at 0 for everything else. These are the C's
;; `> 255` / `> 15` / `> 0` — see the header for why they are spelled
;; this way.
(cond
(pinch? g) (set current-angle (rl/get-gesture-pinch-angle))
(swipe? g) (set current-angle (rl/get-gesture-drag-angle))
(not (= g :none)) (set current-angle 0.0)
:else (do))
(dotimes [i touch-count]
(set (at touch-positions i) (rl/get-touch-position i)))
;; Draw
(rl/begin-drawing)
(rl/clear-background rl/raywhite)
(rl/draw-text "*" 165 12 10 rl/black)
(rl/draw-text "Example optimized for Web/HTML5\non Smartphones with Touch Screen."
175 12 10 rl/black)
(rl/draw-text "*" 165 42 10 rl/black)
(rl/draw-text "While running on Desktop Web Browsers,\ninspect and turn on Touch Emulation."
175 42 10 rl/black)
(draw-last-gesture touch-count)
(draw-log)
(draw-protractor)
;; The pointer itself: every live touch, or the mouse when there is no
;; touchscreen. The halo is the gesture colour faded, which is what
;; `fade` was bound for.
(unless (= g :none)
(if (> touch-count 0)
(do
(dotimes [i touch-count]
(let [p (at touch-positions i)]
(rl/draw-circle-v p 50.0 (rl/fade gesture-color 0.5))
(rl/draw-circle-v p 5.0 gesture-color)))
;; Two fingers: the line between them, thinner while pinching
;; out, which is the C's only use of the raw 512.
(when (= touch-count 2)
(rl/draw-line-ex (at touch-positions 0) (at touch-positions 1)
(if (= g :pinch-out) 8.0 12.0) gesture-color)))
(let [m (rl/get-mouse-position)]
(rl/draw-circle-v m 35.0 (rl/fade gesture-color 0.5))
(rl/draw-circle-v m 5.0 gesture-color))))
(rl/end-drawing)))))

View File

@ -0,0 +1,110 @@
;;;; raylib [core] example - input gestures
;;;;
;;;; examples/core/core_input_gestures.c. Needed `fade`, now bound.
;;;;
;;;; The C keeps `char gestureStrings[20][32]` and TextCopy's a name into the
;;;; next slot. Flan has no mutable character buffer and no way to copy into
;;;; one, but it does not need either: the names are compile-time literals, so
;;;; the log is a `[20 string]` and a slot holds the literal itself. That is
;;;; strictly better than the C — no truncation at 32 bytes, no copy — and it
;;;; is only possible because every string that ever goes into the log is
;;;; known at compile time. A log of strings the program had BUILT could not be
;;;; written at all.
;;;;
;;;; The C's `switch (currentGesture)` over the ten gesture values is a `cond`
;;;; of keyword equalities. `get-gesture-detected` answers an `rl/Gesture` and
;;;; `(= g :tap)` compares against a member by name, checked at compile time —
;;;; so a typo here is an error and the C's `default: break` has nothing to
;;;; catch.
;;;;
;;;; A slot that has not been written yet holds a zero-length string, because a
;;;; `defvar` with no initialiser is all-bytes-zero and a string is ptr+len —
;;;; a null pointer with a length of 0. draw-text draws nothing for it. That is
;;;; the C's `{ "" }` initialiser arriving by a different route, and it is why
;;;; resetting the log below only has to reset the counter.
(import rl "vendor:raylib")
(defconst screen-width 800)
(defconst screen-height 450)
(defconst max-gesture-strings 20)
(defvar gesture-log [max-gesture-strings string])
(defvar gestures-count i32)
(defvar current-gesture rl/Gesture)
(defvar last-gesture rl/Gesture)
;; The C's switch, as a function. `:else` is its `default:` — an unnamed
;; gesture logs the empty string, which draws nothing, which is what falling
;; through the C's switch without a TextCopy leaves in the slot.
(defn gesture-name [g rl/Gesture] string
(cond
(= g :tap) "GESTURE TAP"
(= g :double-tap) "GESTURE DOUBLETAP"
(= g :hold) "GESTURE HOLD"
(= g :drag) "GESTURE DRAG"
(= g :swipe-right) "GESTURE SWIPE RIGHT"
(= g :swipe-left) "GESTURE SWIPE LEFT"
(= g :swipe-up) "GESTURE SWIPE UP"
(= g :swipe-down) "GESTURE SWIPE DOWN"
(= g :pinch-in) "GESTURE PINCH IN"
(= g :pinch-out) "GESTURE PINCH OUT"
:else ""))
(defn main []
(rl/init-window screen-width screen-height
"raylib [core] example - input gestures")
(defer (rl/close-window))
(rl/set-target-fps 60)
(let [touch-area (rl/Rectangle {:x 220.0 :y 10.0
:width (- (f32 screen-width) 230.0)
:height (- (f32 screen-height) 20.0)})]
(until (rl/window-should-close?)
;; Update
(set last-gesture current-gesture)
(set current-gesture (rl/get-gesture-detected))
(let [touch (rl/get-touch-position 0)]
(when (and (rl/collision-point-rec? touch touch-area)
(not (= current-gesture :none))
(not (= current-gesture last-gesture)))
(set (at gesture-log gestures-count) (gesture-name current-gesture))
(set gestures-count (+ gestures-count 1))
;; Full: start over. The stale slots below the counter are never
;; drawn, so they do not have to be cleared the way the C clears
;; them — the loop below is bounded by gestures-count.
(when (>= gestures-count max-gesture-strings)
(set gestures-count 0)))
;; Draw
(rl/begin-drawing)
(rl/clear-background rl/raywhite)
(rl/draw-rectangle-rec touch-area rl/gray)
(rl/draw-rectangle 225 15 (- screen-width 240) (- screen-height 30)
rl/raywhite)
(rl/draw-text "GESTURES TEST AREA" (- screen-width 270)
(- screen-height 40) 20 (rl/fade rl/gray 0.5))
(dotimes [i gestures-count]
(if (= 0 (% i 2))
(rl/draw-rectangle 10 (+ 30 (* 20 i)) 200 20
(rl/fade rl/lightgray 0.5))
(rl/draw-rectangle 10 (+ 30 (* 20 i)) 200 20
(rl/fade rl/lightgray 0.3)))
;; The newest entry in maroon and the rest in dark grey, as the C
;; has it.
(rl/draw-text (at gesture-log i) 35 (+ 36 (* 20 i)) 10
(if (< i (- gestures-count 1)) rl/darkgray rl/maroon)))
(rl/draw-rectangle-lines 10 29 200 (- screen-height 50) rl/gray)
(rl/draw-text "DETECTED GESTURES" 50 15 10 rl/gray)
(unless (= current-gesture :none)
(rl/draw-circle-v touch 30.0 rl/maroon))
(rl/end-drawing)))))

View File

@ -0,0 +1,47 @@
;;;; raylib [core] example - input keys
;;;;
;;;; examples/core/core_input_keys.c. Faithful, and the only thing worth noting
;;;; is a Flan rule rather than a raylib one.
;;;;
;;;; The C moves the ball by writing `ballPosition.x += 2.0f` on a local
;;;; struct. Flan has the same thing — a local IS an assignable place
;;;; (spec-memory.md) — but the local has to be a `defvar` here rather than a
;;;; `let` inside the loop, because a `let` binding is rebound every iteration
;;;; and the position has to survive between frames. The C's variable is
;;;; outside its while loop for the same reason; this is that, spelled with the
;;;; storage the language has.
;;;;
;;;; `(set (.x ball) ...)` works on a struct field of a global: a field of an
;;;; assignable place is itself one.
(import rl "vendor:raylib")
(defconst screen-width 800)
(defconst screen-height 450)
(defvar ball rl/Vector2)
(defn main []
(rl/init-window screen-width screen-height
"raylib [core] example - input keys")
(defer (rl/close-window))
(set ball (rl/Vector2 {:x (f32 (/ screen-width 2))
:y (f32 (/ screen-height 2))}))
(rl/set-target-fps 60)
(until (rl/window-should-close?)
;; Update. key-down? and not key-pressed?: this is meant to repeat for as
;; long as the key is held, which is the whole difference between the two.
(when (rl/key-down? :right) (set (.x ball) (+ (.x ball) 2.0)))
(when (rl/key-down? :left) (set (.x ball) (- (.x ball) 2.0)))
(when (rl/key-down? :up) (set (.y ball) (- (.y ball) 2.0)))
(when (rl/key-down? :down) (set (.y ball) (+ (.y ball) 2.0)))
;; Draw
(rl/begin-drawing)
(rl/clear-background rl/raywhite)
(rl/draw-text "move the ball with arrow keys" 10 10 20 rl/darkgray)
(rl/draw-circle-v ball 50.0 rl/maroon)
(rl/end-drawing)))

View File

@ -0,0 +1,56 @@
;;;; raylib [core] example - input mouse wheel
;;;;
;;;; examples/core/core_input_mouse_wheel.c. Needed get-mouse-wheel-move, now
;;;; bound.
;;;;
;;;; This is the first example with a `TextFormat` in it, and therefore the
;;;; first that runs into the gap: the C draws
;;;;
;;;; DrawText(TextFormat("Box position Y: %03i", boxPositionY), ...)
;;;;
;;;; and Flan has no way to make a string out of a number. The label is drawn
;;;; with draw-text and the number after it with draw-int-padded from
;;;; examples/digits.flan, which is one glyph per digit out of a [10 string]
;;;; table. The "%03i" is the `3` argument — the leading zeroes are there, and
;;;; a number wider than three digits is drawn in full, exactly as printf's
;;;; minimum-width means.
;;;;
;;;; The position is signed and goes negative as soon as the box scrolls past
;;;; the top, which is why draw-int-padded handles a sign at all.
(import rl "vendor:raylib")
(import d "digits.flan")
(defconst screen-width 800)
(defconst screen-height 450)
(defconst scroll-speed 4)
(defvar box-y i32)
(defn main []
(rl/init-window screen-width screen-height
"raylib [core] example - input mouse wheel")
(defer (rl/close-window))
(set box-y (- (/ screen-height 2) 40))
(rl/set-target-fps 60)
(until (rl/window-should-close?)
;; Update. The wheel reading is a per-frame delta, so it is 0.0 on every
;; frame the wheel did not turn and this is a no-op then.
(set box-y (- box-y (i32 (* (rl/get-mouse-wheel-move) (f32 scroll-speed)))))
;; Draw
(rl/begin-drawing)
(rl/clear-background rl/raywhite)
(rl/draw-rectangle (- (/ screen-width 2) 40) box-y 80 80 rl/maroon)
(rl/draw-text "Use mouse wheel to move the cube up and down!" 10 10 20
rl/gray)
;; The two halves of the C's one TextFormat call: the literal, measured,
;; and then the number starting where it ended.
(rl/draw-text "Box position Y: " 10 40 20 rl/lightgray)
(d/draw-int-padded box-y 3 (+ 10 (rl/measure-text "Box position Y: " 20))
40 20 rl/lightgray)
(rl/end-drawing)))

View File

@ -0,0 +1,61 @@
;;;; raylib [core] example - input mouse
;;;;
;;;; examples/core/core_input_mouse.c. Needed three new bindings — show-cursor,
;;;; hide-cursor and cursor-hidden? — which are now in vendor/raylib.
;;;;
;;;; The C's `else if` chain over the seven mouse buttons is a `cond` here.
;;;; That is not a workaround: `cond` is what Flan has and it is the same
;;;; first-match-wins shape, with `:else` where C falls off the end. The order
;;;; matters in both — two buttons pressed on the same frame give the earlier
;;;; one, which is the C's behaviour and not an accident of the port.
;;;;
;;;; The colour has to be a `defvar` rather than a `let`, for the reason
;;;; core-input-keys' position does: it is state between frames.
(import rl "vendor:raylib")
(defconst screen-width 800)
(defconst screen-height 450)
(defvar ball-color rl/Color)
(defn main []
(rl/init-window screen-width screen-height
"raylib [core] example - input mouse")
(defer (rl/close-window))
;; The C also initialises the ball's POSITION, to {-100,-100} so it starts
;; off-screen. That is dead there and is not here: the first thing the loop
;; does is overwrite it with get-mouse-position, before anything is drawn.
(set ball-color rl/darkblue)
(rl/set-target-fps 60)
(until (rl/window-should-close?)
;; Update
(when (rl/key-pressed? :h)
(if (rl/cursor-hidden?) (rl/show-cursor) (rl/hide-cursor)))
(let [ball (rl/get-mouse-position)]
(set ball-color
(cond
(rl/mouse-button-pressed? :left) rl/maroon
(rl/mouse-button-pressed? :middle) rl/lime
(rl/mouse-button-pressed? :right) rl/darkblue
(rl/mouse-button-pressed? :side) rl/purple
(rl/mouse-button-pressed? :extra) rl/yellow
(rl/mouse-button-pressed? :forward) rl/orange
(rl/mouse-button-pressed? :back) rl/beige
:else ball-color))
;; Draw
(rl/begin-drawing)
(rl/clear-background rl/raywhite)
(rl/draw-circle-v ball 40.0 ball-color)
(rl/draw-text "move ball with mouse and click mouse button to change color"
10 10 20 rl/darkgray)
(rl/draw-text "Press 'H' to toggle cursor visibility" 10 30 20 rl/darkgray)
(if (rl/cursor-hidden?)
(rl/draw-text "CURSOR HIDDEN" 20 60 20 rl/red)
(rl/draw-text "CURSOR VISIBLE" 20 60 20 rl/lime))
(rl/end-drawing))))

View File

@ -0,0 +1,69 @@
;;;; raylib [core] example - input multitouch
;;;;
;;;; examples/core/core_input_multitouch.c. No new bindings: the whole touch
;;;; surface was already there for sand.flan's read-out.
;;;;
;;;; The C keeps `Vector2 touchPositions[MAX_TOUCH_POINTS]`, and this is the
;;;; first example that needs a fixed array whose element type is a STRUCT
;;;; rather than a number. That works — `[10 rl/Vector2]` is ten Vector2s laid
;;;; out flat, no headers, and `(at touch-positions i)` is a place that can be
;;;; assigned a whole struct. Nothing in the repository used one before, so it
;;;; is worth saying that it does.
;;;;
;;;; It is a top-level `defvar` and not a local, which is NOT a stylistic
;;;; choice. A `let` binding takes no type annotation, so the only way to make
;;;; a fixed array inside a function is to initialise it from a literal with
;;;; every element written out — ten `(rl/Vector2 {:x 0.0 :y 0.0})`s here, and
;;;; thirty-two in the gestures testbed. A zeroed local array of a given type
;;;; cannot be spelled. Static storage is what the C's `= { 0 }` gets anyway.
;;;;
;;;; On a desktop with no touchscreen get-touch-point-count is 0 and this draws
;;;; nothing at all, which is correct and is also what the C does. The window
;;;; opening and the help text appearing is the whole of what can be checked
;;;; without hardware.
(import rl "vendor:raylib")
(import d "digits.flan")
(defconst screen-width 800)
(defconst screen-height 450)
(defconst max-touch-points 10)
(defvar touch-positions [max-touch-points rl/Vector2])
(defn main []
(rl/init-window screen-width screen-height
"raylib [core] example - input multitouch")
(defer (rl/close-window))
(rl/set-target-fps 60)
(until (rl/window-should-close?)
;; Update
(let [count (min max-touch-points (rl/get-touch-point-count))]
(dotimes [i count]
(set (at touch-positions i) (rl/get-touch-position i)))
;; Draw
(rl/begin-drawing)
(rl/clear-background rl/raywhite)
(dotimes [i count]
(let [p (at touch-positions i)]
;; raylib reports (0,0) for a slot that is not being touched, so the
;; C filters on it and so does this. It means a real touch in the
;; very top-left corner is dropped; that is raylib's ambiguity, not
;; something the port introduced.
(when (and (> (.x p) 0.0) (> (.y p) 0.0))
(rl/draw-circle-v p 34.0 rl/orange)
;; The C's TextFormat("%d", i) — one digit, drawn by the shared
;; helper rather than by a codepoint call, so every number on
;; screen in these ten files goes through the same path.
(d/draw-int i (- (i32 (.x p)) 10) (- (i32 (.y p)) 70) 40
rl/black))))
(rl/draw-text "touch the screen at multiple locations to get multiple balls"
10 10 20 rl/darkgray)
(rl/end-drawing))))

View File

@ -0,0 +1,195 @@
;;;; raylib [core] example - input virtual controls
;;;;
;;;; examples/core/core_input_virtual_controls.c. No new bindings.
;;;;
;;;; Split the way sand.flan is split: everything above the second banner is
;;;; arithmetic with no raylib calls in it, so test/programs/virtual-controls-
;;;; headless.flan can import this file as a package, drive the pointer over a
;;;; scripted path and hash where the player ended up. That is the only one of
;;;; the ten examples with a headless half, and it has one because it is the
;;;; only one whose interesting part — which D-pad button is under the pointer,
;;;; and what that does to the player — is a function of numbers rather than of
;;;; a frame buffer. The link follows what the program reaches, so the headless
;;;; driver pulls in neither a window nor libraylib.
;;;;
;;;; Two language notes, both visible below.
;;;;
;;;; **No `break`.** The C's nearest-button search is
;;;;
;;;; for (i = 0; i < BUTTON_MAX; i++) { if (...) { pressedButton = i; break; } }
;;;;
;;;; and `(break)` is refused: "break is not implemented yet (see the build
;;;; sequence in plan.org)". `return` from the function works and is what this
;;;; uses — which is why the search is its own `defn` and not written inline
;;;; the way the C has it. That is not a loss: the function is the thing the
;;;; headless case wants to call anyway. Where a loop genuinely cannot be a
;;;; function, the shape left is a flag and a compound loop condition.
;;;;
;;;; **No `fabsf`.** The prelude has sqrt-f32 and sign-f32 but no absolute
;;;; value for floats, and the comment there says why for integers. `(max v
;;;; (- 0.0 v))` is it, and unlike the integer case it has no edge: there is no
;;;; float whose negation is itself except -0.0, whose absolute value is 0.0
;;;; either way.
;;;;
;;;; The button geometry is `defconst` arrays of struct literals. That works —
;;;; and it is the only way a fixed array can be made inside anything but a
;;;; top-level `defvar`, since a `let` binding takes no type annotation.
(import rl "vendor:raylib")
(import d "digits.flan")
(defconst screen-width 800)
(defconst screen-height 450)
;; The C's PadButton enum. -1 is BUTTON_NONE and the four directions are 0..3,
;; in the order the arrays below are written. A `defenum` would not help: the
;; value is used as an index and an enum does not convert to an integer
;; ("i32 converts a number, found ..."), so it would have to be turned back
;; into one at every use.
(defconst button-none -1)
(defconst button-up 0)
(defconst button-left 1)
(defconst button-right 2)
(defconst button-down 3)
(defconst button-max 4)
(defconst button-radius f32 30.0)
;; padPosition is {100, 350} and the buttons sit a radius and a half away from
;; it on each axis. The C computes these at run time from padPosition; they are
;; written out here because a defconst is compile-time and the numbers are.
(defconst button-positions [button-max rl/Vector2]
[(rl/Vector2 {:x 100.0 :y 305.0}) ; up
(rl/Vector2 {:x 55.0 :y 350.0}) ; left
(rl/Vector2 {:x 145.0 :y 350.0}) ; right
(rl/Vector2 {:x 100.0 :y 395.0})]) ; down
(defconst player-speed f32 75.0)
;;;; ── The part with no raylib in it ──────────────────────────────────
(defvar player rl/Vector2)
(defn reset-player []
(set player (rl/Vector2 {:x (/ (f32 screen-width) 2.0)
:y (/ (f32 screen-height) 2.0)})))
(defn abs-f32 [v f32] f32
(max v (- 0.0 v)))
;; The C's search, with `return` where it had `break`. Manhattan distance and
;; not Euclidean — the C adds the two axis distances and compares against the
;; radius — so the hit area is a diamond, which is deliberate: it makes the
;; four buttons tile without overlapping.
(defn nearest-button [pos rl/Vector2] i32
(dotimes [i button-max]
(let [b (at button-positions i)
dist-x (abs-f32 (- (.x b) (.x pos)))
dist-y (abs-f32 (- (.y b) (.y pos)))]
(when (< (+ dist-x dist-y) button-radius)
(return i))))
button-none)
;; The C's switch on the pressed button. Nothing moves for button-none, which
;; is the `default: break`.
(defn move-player [button i32 dt f32]
(let [step (* player-speed dt)]
(cond
(= button button-up) (set (.y player) (- (.y player) step))
(= button button-left) (set (.x player) (- (.x player) step))
(= button button-right) (set (.x player) (+ (.x player) step))
(= button button-down) (set (.y player) (+ (.y player) step))
:else (do))))
;; FNV-1a over the player's two floats, so the headless driver has one number
;; to compare. The floats are read through their bit patterns rather than
;; rounded to integers: rounding would hide exactly the kind of drift — a
;; crossed x and y, a step applied twice — the case exists to catch.
(defconst fnv-offset u64 0xcbf29ce484222325)
(defconst fnv-prime u64 1099511628211)
(defn hash-f32 [h u64 v f32] u64
;; No float-to-bits cast in the language, so the value is scaled and
;; truncated instead. 1024 keeps three decimal places of a screen
;; coordinate, which is finer than any real difference between two correct
;; runs and coarser than the last bit of an f32 — so this is reproducible
;; across targets where a raw bit pattern would depend on the FPU.
(let [n (i64 (* v 1024.0))
g h]
(dotimes [b 8]
(set g (bit-xor g (u64 (bit-and (>> n (i64 (* b 8))) 255))))
(set g (* g fnv-prime)))
g))
(defn hash-player [] u64
(hash-f32 (hash-f32 fnv-offset (.x player)) (.y player)))
;;;; ── The raylib front-end ───────────────────────────────────────────
;; The arrowheads, one triangle of three points per button, in the winding
;; raylib wants — counter-clockwise, or it culls them and draws nothing. A
;; [4 [3 rl/Vector2]]: a fixed array of fixed arrays of a struct, which is the
;; deepest shape any of these ten examples asks for and which works.
(defconst arrow-tris [button-max [3 rl/Vector2]]
[[(rl/Vector2 {:x 100.0 :y 293.0})
(rl/Vector2 {:x 91.0 :y 314.0})
(rl/Vector2 {:x 109.0 :y 314.0})]
[(rl/Vector2 {:x 64.0 :y 341.0})
(rl/Vector2 {:x 43.0 :y 350.0})
(rl/Vector2 {:x 64.0 :y 359.0})]
[(rl/Vector2 {:x 157.0 :y 350.0})
(rl/Vector2 {:x 136.0 :y 341.0})
(rl/Vector2 {:x 136.0 :y 359.0})]
[(rl/Vector2 {:x 91.0 :y 386.0})
(rl/Vector2 {:x 100.0 :y 407.0})
(rl/Vector2 {:x 109.0 :y 386.0})]])
(defconst label-colors [button-max rl/Color]
[(rl/Color {:r 253 :g 249 :b 0 :a 255}) ; yellow, up
(rl/Color {:r 0 :g 121 :b 241 :a 255}) ; blue, left
(rl/Color {:r 230 :g 41 :b 55 :a 255}) ; red, right
(rl/Color {:r 0 :g 228 :b 48 :a 255})]) ; green, down
(defn main []
(rl/init-window screen-width screen-height
"raylib [core] example - input virtual controls")
(defer (rl/close-window))
(reset-player)
(rl/set-target-fps 60)
(until (rl/window-should-close?)
;; Update. Touch first, mouse as the desktop stand-in — and on the desktop
;; the left button has to be held, or the player would follow the cursor
;; wherever it went.
(let [touching (> (rl/get-touch-point-count) 0)
input (if touching
(rl/get-touch-position 0)
(rl/get-mouse-position))
pressed (if (or touching (rl/mouse-button-down? :left))
(nearest-button input)
button-none)]
(move-player pressed (rl/get-frame-time))
;; Draw
(rl/begin-drawing)
(rl/clear-background rl/raywhite)
(rl/draw-circle-v player 50.0 rl/maroon)
(dotimes [i button-max]
(rl/draw-circle-v (at button-positions i) button-radius
(if (= i pressed) rl/darkgray rl/black))
(let [t (at arrow-tris i)]
(rl/draw-triangle (at t 0) (at t 1) (at t 2)
(at label-colors i))))
(rl/draw-text "move the player with D-Pad buttons" 10 10 20 rl/darkgray)
;; Not in the C: which button the search picked, as a number, so the
;; headless case and the window agree about the same thing.
(rl/draw-text "button: " 10 34 20 rl/lightgray)
(d/draw-int pressed (+ 10 (rl/measure-text "button: " 20)) 34 20
rl/lightgray)
(rl/end-drawing))))

127
examples/digits.flan Normal file
View File

@ -0,0 +1,127 @@
;;;; Drawing a number, because the language cannot make one into a string.
;;;;
;;;; Five of the ten ported examples call raylib's `TextFormat` to put a number
;;;; on the screen. Flan has no string formatting and no way to reach it:
;;;;
;;;; - `i64->bytes` is a builtin and answers a `[u8]`;
;;;; - `draw-text` takes a `string`;
;;;; - nothing converts a `[u8]` into a `string`. A string is a compile-time
;;;; literal or a parameter, and there is no allocator to build one in.
;;;;
;;;; `TextFormat` itself is not bindable either, and not because of the FFI
;;;; rules: it is variadic, so its signature is not a signature — declaring it
;;;; with fixed arguments would be a claim about the ABI that is false on every
;;;; target at once, and it returns a `char *` into a rotating static buffer,
;;;; which `declare-c` refuses by name anyway ("a string only crosses as a
;;;; parameter — a C function that *returns* one returns something Flan has no
;;;; owner for").
;;;;
;;;; So a number reaches the screen one digit at a time, each digit drawn as a
;;;; one-character `string` out of the table below. sand.flan already does this
;;;; for a single digit with `draw-text-codepoint`; this is the same trick
;;;; generalised, in one place, so the gap shows up in the report as one gap
;;;; rather than as five separate improvisations.
;;;;
;;;; Everything here needs a window: `measure-text` answers 0 for every string
;;;; until init-window has loaded the default font, and a zero advance would
;;;; stack every digit on top of the first.
(import rl "vendor:raylib")
;; A `[10 string]` — a fixed array whose element type is `string`. That works,
;; which is worth recording: a string is ptr+len and the array is ten of those
;; laid out flat, with the bytes themselves in the module's constant data.
(defconst digit-glyphs [10 string]
["0" "1" "2" "3" "4" "5" "6" "7" "8" "9"])
(defconst minus-glyph "-")
(defconst dot-glyph ".")
;; One glyph, and how far the pen moved. raylib's default font is not
;; monospaced — "1" is narrower than "8" — so the advance is measured rather
;; than assumed, which is also what keeps the spacing identical to what
;; draw-text would have produced for the whole string at once.
(defn draw-glyph [g string x i32 y i32 size i32 color rl/Color] i32
(rl/draw-text g x y size color)
(rl/measure-text g size))
;; How many decimal digits `n` has, for n >= 0. 0 has one.
(defn digit-count [n i32] i32
(let [d 1
r (/ n 10)]
(while (> r 0)
(set d (+ d 1))
(set r (/ r 10)))
d))
(defn pow10 [e i32] i32
(let [p 1]
(dotimes [i e]
(set p (* p 10)))
p))
;; The whole point of the file. Answers the width drawn, so a caller can put
;; something after it — which is how the `TextFormat("%s: %i", ...)` shapes in
;; the C are reassembled here: draw the literal part with draw-text, then this
;; at x plus its width.
;;
;; Negative numbers get the sign and then the magnitude. i32's most negative
;; value is NOT handled: negating it wraps to itself, so it would print its own
;; bit pattern with a minus in front. Nothing here ever reaches it — these are
;; screen coordinates, frame counts and axis readings — and guarding it would
;; be a branch that no call site can take.
(defn draw-int [n i32 x i32 y i32 size i32 color rl/Color] i32
(let [cx x
v n]
(when (< v 0)
(set cx (+ cx (draw-glyph minus-glyph cx y size color)))
(set v (- 0 v)))
(let [count (digit-count v)]
(dotimes [i count]
(let [d (% (/ v (pow10 (- (- count 1) i))) 10)]
(set cx (+ cx (draw-glyph (at digit-glyphs d) cx y size color))))))
(- cx x)))
;; The same with a fixed number of leading zeroes — the C's "%03i". A number
;; wider than `width` is drawn in full rather than truncated, which is what
;; printf does too.
(defn draw-int-padded [n i32 width i32 x i32 y i32 size i32 color rl/Color]
i32
(let [cx x
v n]
(when (< v 0)
(set cx (+ cx (draw-glyph minus-glyph cx y size color)))
(set v (- 0 v)))
(let [count (max width (digit-count v))]
(dotimes [i count]
(let [d (% (/ v (pow10 (- (- count 1) i))) 10)]
(set cx (+ cx (draw-glyph (at digit-glyphs d) cx y size color))))))
(- cx x)))
;; "%.02f" and friends. `places` digits after the point, rounded by adding a
;; half at that scale before the split — so 0.999 at two places is "1.00" and
;; not "0.99", which is what the C's printf would have done and what a reader
;; comparing the two screens would expect.
;;
;; f32 and not f64 deliberately: every number this draws comes out of raylib,
;; and raylib's are floats. Widening them here would suggest a precision the
;; value does not have.
(defn draw-f32 [v f32 places i32 x i32 y i32 size i32 color rl/Color] i32
(let [cx x
av v]
(when (< av 0.0)
(set cx (+ cx (draw-glyph minus-glyph cx y size color)))
(set av (- 0.0 av)))
(let [scale (pow10 places)
;; The rounding and the split happen in one integer so the two halves
;; cannot disagree: rounding them separately is how "0.999" becomes
;; "0.100" — the fraction carries and the whole part does not hear
;; about it.
total (i32 (+ (* av (f32 scale)) 0.5))
whole (/ total scale)
frac (% total scale)]
(set cx (+ cx (draw-int whole cx y size color)))
(when (> places 0)
(set cx (+ cx (draw-glyph dot-glyph cx y size color)))
(set cx (+ cx (draw-int-padded frac places cx y size color)))))
(- cx x)))

View File

@ -14,6 +14,10 @@
(glob_files %{workspace_root}/vendor/agent/*)
; The EDN tokenizer, which programs/edn.flan imports.
(glob_files %{workspace_root}/vendor/edn/*)
; The ported raylib examples. Only one of them has a headless acceptance
; case, but it imports its example as a package and that example imports
; examples/digits.flan, so the directory has to be here whole.
(glob_files %{workspace_root}/examples/*)
(glob_files programs/*.flan)
; The reload primitive's host: a C main that dlopens what Build.shared made.
(file reload_host.c)

View File

@ -0,0 +1,59 @@
;;;; core-input-virtual-controls.flan's other half: no window, a scripted
;;;; pointer, hash where the player ended up.
;;;;
;;;; The same split sand.flan and sand-headless.flan already have, and it is
;;;; available here for the same reason: the interesting part of that example —
;;;; which D-pad button is under the pointer, and what a held button does to
;;;; the player — is arithmetic over numbers, so it runs with no window, no GL
;;;; context and no libraylib. The link follows what the program reaches:
;;;; nothing below calls into raylib, so no shim is compiled and no -lraylib is
;;;; passed, and the example's own drawing functions are never emitted — which
;;;; was checked with ldd on the built binary: libm and libc and nothing else.
;;;; The example's `main` is not exported, so the only main is this one.
;;;;
;;;; Unlike sand-headless there is no wasm32 case for it yet. Nothing here
;;;; should stop one, for exactly the reason above — but it has not been run,
;;;; so it is not claimed.
;;;;
;;;; It is the only one of the ten ported examples with a headless case, and
;;;; the honest reason is that it is the only one that earns it. The other nine
;;;; are input read straight into drawing calls: a test of them would assert
;;;; that raylib answers 0 for every input with no window open, which is also
;;;; what a binding with its arguments crossed would answer.
;;;;
;;;; What this DOES pin: that the four buttons are where the geometry says they
;;;; are, that the diamond hit test uses x and y the right way round, and that
;;;; the four directions move the player the four different ways. A crossed
;;;; axis anywhere in nearest-button or move-player changes the hash.
(import vc "../../examples/core-input-virtual-controls.flan")
(import rl "vendor:raylib")
;; A sixtieth of a second, fixed. The real program uses get-frame-time, which
;; is exactly the thing a headless run has no answer for.
(defconst dt f32 0.0166666)
;; A lawnmower sweep over the pad's corner of the screen, two pixels at a time.
;; It crosses all four buttons and the dead space between them, so every branch
;; of nearest-button is taken and button-none is taken most of all.
(defconst sweep-x0 20)
(defconst sweep-x1 180)
(defconst sweep-y0 280)
(defconst sweep-y1 420)
(defconst sweep-step 2)
(defn main [] i32
(vc/reset-player)
(let [y sweep-y0]
(while (<= y sweep-y1)
(let [x sweep-x0]
(while (<= x sweep-x1)
(let [p (rl/Vector2 {:x (f32 x) :y (f32 y)})]
(vc/move-player (vc/nearest-button p) dt))
(set x (+ x sweep-step))))
(set y (+ y sweep-step))))
;; write-stdout and i64->bytes are builtins (lib/check.ml), not prelude
;; functions, so this does not go through the printers.
(write-stdout (i64->bytes (i64 (vc/hash-player))))
(write-stdout (bytes "\n"))
0)

View File

@ -530,6 +530,23 @@ let () =
outputs "sand, headless" "programs/sand-headless.flan" sand_out;
outputs ~opt:"-O0" "sand, headless, -O0" "programs/sand-headless.flan" sand_out;
(* The ported raylib example that has a headless half. The other nine of
the ten in examples/ are input read straight into drawing calls, and a
test of those would be asserting that raylib answers 0 for every input
with no window open which is also what a binding with its arguments
crossed would answer. This one is different: which virtual D-pad button
sits under a pointer, and what a held button does to the player, is
arithmetic. The driver sweeps a pointer over the pad in a fixed grid,
so every branch of the search is taken, and hashes where the player
ended up. A crossed x and y anywhere in it changes the number. Like
sand-headless it imports the example and reaches no raylib call, so it
links neither a shim nor libraylib. *)
let vc_out = "-2146089238186896844\n" in
outputs "virtual controls, headless"
"programs/virtual-controls-headless.flan" vc_out;
outputs ~opt:"-O0" "virtual controls, headless, -O0"
"programs/virtual-controls-headless.flan" vc_out;
outputs ~opt:"-O0" "value semantics, -O0" "programs/values.flan" values_out;
outputs ~opt:"-O0" "machine surface, -O0" "programs/machine.flan" machine_out;

View File

@ -68,6 +68,23 @@
(declare-c set-target-fps [fps i32] "SetTargetFPS")
(declare-c set-trace-log-level [level TraceLogLevel] "SetTraceLogLevel")
;; A bitfield, like the gestures below and for the same reason: raylib wants
;; the OR of several and a keyword can only ever name one member, so the
;; parameter is a u32 and the members are defconsts rather than a defenum.
;;
;; The part that is NOT obvious from the signature: this has to be called
;; BEFORE init-window. raylib stores the flags and reads them while creating
;; the context, so setting them afterwards is accepted, logged at INFO, and
;; does nothing to the window that already exists — which looks exactly like
;; a binding that did not work. Only the four the examples ask for are here;
;; the rest are one line each when something calls them.
(defconst flag-fullscreen-mode u32 2)
(defconst flag-window-resizable u32 4)
(defconst flag-msaa-4x-hint u32 32)
(defconst flag-vsync-hint u32 64)
(declare-c set-config-flags [flags u32] "SetConfigFlags")
;; ── Input ───────────────────────────────────────────────────────────
(declare-c key-pressed? [key Key] bool "IsKeyPressed")
@ -84,6 +101,21 @@
(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
@ -92,9 +124,52 @@
(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")
@ -480,6 +555,14 @@
;; 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")
@ -537,6 +620,32 @@
[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,