245 lines
12 KiB
Plaintext
245 lines
12 KiB
Plaintext
;;;; 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.
|
|
;;;;
|
|
;;;; **The name is drawn and not matched on.** get-gamepad-name copies
|
|
;;;; raylib's static text into the context allocator, which this program never
|
|
;;;; frees — so the name is read once per frame into the frame arena, and the
|
|
;;;; branches that matched on it are the artwork's and are gone with it.
|
|
;;;;
|
|
;;;; **The VIBRATE button is drawn and inert.** SetGamepadVibration is the one
|
|
;;;; call in the C this refuses to bind, and vendor/raylib/raylib.fln 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.fln"
|
|
|
|
const screen-width = 800
|
|
const screen-height = 450
|
|
|
|
const stick-deadzone: f32 = 0.1
|
|
const trigger-deadzone: f32 = -0.9
|
|
|
|
once 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.
|
|
fn deadzone(v: f32, limit: f32) -> f32
|
|
if v > 0.0 - limit and v < limit then 0.0 else v
|
|
|
|
;; The triggers rest at -1 rather than 0, which raylib does not normalise —
|
|
;; see the note on GamepadAxis in vendor/raylib/raylib.fln. So their deadzone
|
|
;; is a floor near the resting end and not a band around the middle.
|
|
fn trigger-deadzoned(v: f32) -> f32
|
|
if v < trigger-deadzone then -1.0 else 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.
|
|
fn axis-at(pad: i32, index: i32) -> f32
|
|
rl/get-gamepad-axis-movement(pad, rl/GamepadAxis(index))
|
|
|
|
fn 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)
|
|
|
|
fn draw-pad-buttons() -> ()
|
|
if rl/is-gamepad-button-down(gamepad, :button-middle-left)
|
|
rl/draw-circle(365, 170, 10.0, rl/red)
|
|
if rl/is-gamepad-button-down(gamepad, :button-middle)
|
|
rl/draw-circle(405, 170, 10.0, rl/green)
|
|
if rl/is-gamepad-button-down(gamepad, :button-middle-right)
|
|
rl/draw-circle(445, 170, 10.0, rl/blue)
|
|
if rl/is-gamepad-button-down(gamepad, :button-right-face-left)
|
|
rl/draw-circle(516, 191, 15.0, rl/gold)
|
|
if rl/is-gamepad-button-down(gamepad, :button-right-face-down)
|
|
rl/draw-circle(551, 227, 15.0, rl/blue)
|
|
if rl/is-gamepad-button-down(gamepad, :button-right-face-right)
|
|
rl/draw-circle(587, 191, 15.0, rl/green)
|
|
if rl/is-gamepad-button-down(gamepad, :button-right-face-up)
|
|
rl/draw-circle(551, 155, 15.0, rl/red)
|
|
if rl/is-gamepad-button-down(gamepad, :button-left-face-up)
|
|
rl/draw-rectangle(247, 147, 24, 29, rl/red)
|
|
if rl/is-gamepad-button-down(gamepad, :button-left-face-down)
|
|
rl/draw-rectangle(247, 201, 24, 30, rl/red)
|
|
if rl/is-gamepad-button-down(gamepad, :button-left-face-left)
|
|
rl/draw-rectangle(217, 176, 30, 25, rl/red)
|
|
if rl/is-gamepad-button-down(gamepad, :button-left-face-right)
|
|
rl/draw-rectangle(271, 176, 30, 25, rl/red)
|
|
if rl/is-gamepad-button-down(gamepad, :button-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)
|
|
if rl/is-gamepad-button-down(gamepad, :button-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)
|
|
|
|
fn 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 then rl/red else rl/black)
|
|
|
|
fn 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()
|
|
;; The frame arena holds the pad's name for one frame.
|
|
free-all(context/temp)
|
|
;; Update
|
|
if rl/is-key-pressed(:key-left) and gamepad > 0
|
|
gamepad -= 1
|
|
if rl/is-key-pressed(:key-right)
|
|
gamepad += 1
|
|
let axis-count = min(6, rl/get-gamepad-axis-count(gamepad))
|
|
let vibrate-rect = rl/Rectangle{.x 10.0, .y 90.0 + 20.0 * f32(axis-count), .width 75.0, .height 24.0}
|
|
;; Draw
|
|
rl/with-drawing:
|
|
rl/clear-background(rl/raywhite)
|
|
if rl/is-gamepad-available(gamepad)
|
|
;; The C draws `TextFormat("GP%d: %s", gamepad, GetGamepadName(...))`.
|
|
;; TextFormat is variadic and unbound, so the pieces are drawn in turn.
|
|
rl/draw-text("GP", 10, 10, 10, rl/black)
|
|
let x = 10 + rl/measure-text("GP", 10)
|
|
let x = x + d/draw-int(gamepad, x, 10, 10, rl/black)
|
|
rl/draw-text(": ", x, 10, 10, rl/black)
|
|
rl/draw-text(with-allocator(context/temp, rl/get-gamepad-name(gamepad)),
|
|
x + rl/measure-text(": ", 10), 10, 10, rl/black)
|
|
let lx = deadzone(rl/get-gamepad-axis-movement(gamepad, :axis-left-x),
|
|
stick-deadzone)
|
|
let ly = deadzone(rl/get-gamepad-axis-movement(gamepad, :axis-left-y),
|
|
stick-deadzone)
|
|
let rx = deadzone(rl/get-gamepad-axis-movement(gamepad, :axis-right-x),
|
|
stick-deadzone)
|
|
let ry = deadzone(rl/get-gamepad-axis-movement(gamepad, :axis-right-y),
|
|
stick-deadzone)
|
|
let lt = trigger-deadzoned(rl/get-gamepad-axis-movement(gamepad, :axis-left-trigger))
|
|
let rt = trigger-deadzoned(rl/get-gamepad-axis-movement(gamepad, :axis-right-trigger))
|
|
draw-pad-background()
|
|
draw-pad-buttons()
|
|
draw-stick(345, 260, lx, ly,
|
|
rl/is-gamepad-button-down(gamepad, :button-left-thumb))
|
|
draw-stick(465, 260, rx, ry,
|
|
rl/is-gamepad-button-down(gamepad, :button-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)
|
|
for i in range(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
|
|
x += d/draw-int(i, x, y, 10, rl/darkgray)
|
|
rl/draw-text(": ", x, y, 10, rl/darkgray)
|
|
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(vibrate-rect.x) + 14,
|
|
i32(vibrate-rect.y) + 1, 10, rl/darkgray)
|
|
rl/draw-text("(not bound: raylib stub)", 95, i32(vibrate-rect.y) + 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 `()` is the other.
|
|
let b = rl/get-gamepad-button-pressed()
|
|
if b >= 0
|
|
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)
|
|
()
|
|
else
|
|
rl/draw-text("DETECTED BUTTON: NONE", 10, 430, 10, rl/gray)
|
|
else
|
|
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)
|