;;;; 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, which is what this is: an integer ;; converts to an enum when the conversion is written by name. ;; ;; It used to be a six-armed cond over the members, because an enum is its own ;; type in the checker and a bare i32 does not fit one: ;; ;; expected rl/GamepadAxis, found i32 ;; ;; That rule is unchanged and should be. It is what makes a misspelled ;; `:lft-x` an error *here*, at the call site, rather than a wrong axis read ;; later — which is the whole reason an enum is a type at all rather than a ;; pile of i32 constants. What changed is that the conversion can be said out ;; loud: the rule was never "an integer is dangerous", it was "an integer must ;; not arrive silently", and (rl/GamepadAxis index) is not silent. ;; ;; (The other escape stays closed, and should: a second declare-c of ;; GetGamepadAxisMovement with an i32 face is still refused — one declare-c ;; per C function — and a defn wrapper still cannot retype a parameter. It is ;; no longer needed.) ;; ;; An index raylib does not name converts too, rather than being refused, and ;; raylib bounds-checks its own axis and answers 0.0. So the (min 6 ...) clamp ;; at the call site is what keeps the read-out to the six axes raylib names — ;; load-bearing now rather than cosmetic, since it replaced the :else arm. (defn axis-at [pad i32 index i32] f32 (rl/get-gamepad-axis-movement pad (rl/GamepadAxis index))) (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 () and the `if` will not ;; typecheck — "expected i32, found ()". 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))))