Installing Raylib ↩
The first issue I had to tackle was the fact that I don't have Raylib on my machine. So I was reading the README and following along with the windows instructions as best I could. They have a few options, and after skimming each I decided to go with the "Manual Setup with W64Devkit" option.
Why? Because I liked the idea of being able to build the source and then use the library. With the IRC client Halloy, I built it from source and have been very happy with the ability to look at its code or tweak things as needed. So I figure, why not, this would be great for that too! So, I popped over to skeeto's repo to pick up the dev kit and downloaded the EXE and ran it. 1
This hooked me up with a shell and I downloaded the raylib repository and went over to the raylib/src/ directory and after a minute or so, it popped out a happy success message letting me know it was done.
~/Documents/Code/OpenSource/raylib/src $ make ar rcs ../src/libraylib.a rcore.o rshapes.o rtextures.o rtext.o utils.o rglfw.o rmodels.o raudio.o "raylib static library generated (libraylib.a) in ../src!" ~/Documents/Code/OpenSource/raylib/src $
Great! That was surprisingly easy (I always expect the worse when building any C, C++, or C# project on Windows) So I read the instructions on how to build the examples and of course, seeing as how I only wanted the gamepad sample, I figured I'd just do the instructions on building just that.
gcc core_basic_window.c -lraylib -lgdi32 -lwinmm
And when I tried it?
~/Documents/Code/OpenSource/raylib/src $ cd ../examples/core/ ~/Documents/Code/OpenSource/raylib/examples/core $ gcc core_input_gamepad.c -lraylib -lgdi32 -lwinmm core_input_gamepad.c:22:10: fatal error: raylib.h: No such file or directory 22 | #include "raylib.h" | ^~~~~~~~~~ compilation terminated.
Erm. Well. Okay. It's been a long time since I wrote some C code that needed linking, I think the last time was when I wrote an http server in C in 2014. So you'll have to forgive me that it took a bit to remember how to use link flags. MySQL kind of handled a lot of that hard work when I did that server and it's been over a decade since I had to do this. That being the case, I backed up, and tried out the other option in the README file: building ALL the examples at once:
mingw32-make PLATFORM=PLATFORM_DESKTOP
This worked! And so, I know that raylib is in the right place for the makefile, but just not for if I want to try to compile things from the core folder without specifying where the library lives. Giving up on compiling just the one example for now, I checked to see if the EXE worked and

Wonderful. But, looking at the link errors, it made me think that unless I figured out how to properly set things up, and read that really long Makefile in the examples folder to figure out exactly how it was referencing the raylib library I had built myself... I was going to have trouble making my own little program and customizing it however I wanted.
At which point, I asked myself the question that everyone seems to be asking now-a-days. "Can I just port this to rust?"
Installing the rust crate for raylib ↩
I asked LCOLONQ's discord/irc channel that question. I think the closet I've ever come to foreign function interfaces was at my first internship when I had to setup some kind of machine learning related C++ code to be called from a Java application in a sort of R&D situation. I also found a new job somewhat close to that time, and so I never really got too far there.
Luckily for me though, someone has already done the actual hard part here. There's a raylib crate! and it's setup to provide a rust-like interface to the library, as well as offering an escape hatch to call the methods directly if needed. Awesome!
cargo new gamepad cargo add raylib
Cargo setup a hellow world for me, downloaded the raylib rust crate for me, and then I happily ran build to verify things were good and
cargo build > thread 'main' panicked at .cargo\registry\src\index.crates.io-1949cf8c6b5b557f\bindgen-0.70.1\lib.rs:622:27: Unable to find libclang: "couldn't find any valid shared libraries matching: ['clang.dll', 'libclang.dll'],
things were not good.
Apparently, I can't escape the "you don't have the right library on your system" problem even with rust. That said, this is notably different since it's not a static library I'm missing, but rather a compiler. The raylib crate relies on bindgen, and binggen requires clang. Thankfully, we can use chocolately for that. and installing chocolately is one cmd.exe command away!
@"%SystemRoot%\System32\WindowsPowerShell\v1.0\powershell.exe" -NoProfile -InputFormat None -ExecutionPolicy Bypass -Command "[System.Net.ServicePointManager]::SecurityProtocol = 3072; iex ((New-Object System.Net.WebClient).DownloadString('https://community.chocolatey.org/install.ps1'))" && SET "PATH=%PATH%;%ALLUSERSPROFILE%\chocolatey\bin"
And when that nightmare of a thing that I'm going to trust is right2 finished. I went ahead and ran the much simpler and easy to ready:
choco install llvm
Then, tried one more time.
$ cargo build Compiling raylib-sys v5.5.1 Compiling raylib v5.5.1 Compiling gamepad v0.1.0 (Code\Personal\tools\gamepad) Finished `dev` profile [unoptimized + debuginfo] target(s) in 4.90s
Beautiful!
Porting the Example ↩
With the ability to compile a program including the raylib library in it. I went ahead and swapped the
autogenerated "Hello world!" test from cargo init
for the test example from the raylib
documentation:
use raylib::prelude::*; fn main() { let (mut rl, thread) = raylib::init() .size(640, 480) .title("Hello, World") .build(); while !rl.window_should_close() { let mut d = rl.begin_drawing(&thread); d.clear_background(Color::WHITE); d.draw_text("Hello, world!", 12, 12, 20, Color::BLACK); } }
This is surprisingly easy to read and simple example. Normally getting a window up and running in windows is a lot of work. It makes me a bit sad that I didn't see this library when I was working on that sentiment vtuber project a while ago. Anyway, the most important question is if this actually runs though.

Perfect.
Now we can being porting the bits of the actual example c code over for fun. Let's start with something simple first. The window size and that config flag. The C code looks like this:
// Initialization //-------------------------------------------------------------------------------------- const int screenWidth = 800; const int screenHeight = 450; SetConfigFlags(FLAG_MSAA_4X_HINT); // Set MSAA 4X hint before windows creation InitWindow(screenWidth, screenHeight, "raylib [core] example - gamepad input");
I moved the consts up to the top level of the program, since I don't think it's worth it to declare them only within the main function. But, what type are they? I mean, they're ints in C, but did the rust interface change anything around to make them unsigned (a negative screen height or width doesn't make sense to me after all).
const SCREEN_WIDTH: i32 = 800; const SCREEN_HEIGHT: i32 = 450; ... fn main() { let (mut rl, thread) = raylib::init() .size(SCREEN_WIDTH, SCREEN_HEIGHT) .title("Game Pad output")
After the compiler yelled at me for passing in u32, (it makes sense, an int in c is signed by default after all) I then had to turn to the mysterious MSAA 4X hint. I don't... know what that means. I mean, it has to happen before the window exists. So it's got something to do with the window itself, maybe graphical. Searching for "msaa" in the rust crate turns up it's documentation
Hints that 4x MSAA (anti-aliasing) should be enabled. The system’s graphics drivers may override this setting.
Ah, anti-aliasing! And this is a method on the RaylibBuilder
which is what this ::init()
method
has handed to us, so it's just another simple line on our code so far!
let (mut rl, thread) = raylib::init() .size(SCREEN_WIDTH, SCREEN_HEIGHT) .title("Game Pad output") .msaa_4x() .build();
Well that was easy! And hey, the compiler is still happy with me too. Cool. Next up in the C code is something slightly more interesting:
Texture2D texPs3Pad = LoadTexture("resources/ps3.png"); Texture2D texXboxPad = LoadTexture("resources/xbox.png");
And, this being C, if you load up something into memory, you've got to remove it from memory too. At the bottom of the Raylib example is
// De-Initialization //-------------------------------------------------------------------------------------- UnloadTexture(texPs3Pad); UnloadTexture(texXboxPad); CloseWindow(); // Close window and OpenGL context
So, I did a quick look for the word "texture" in the crate docs
and spotted the load_texture
method of the RaylibHandle
. Raylib handle... that rl
variable from the hello world feels sort of like that.
Looking at the return type for the build method (RaylibHandle, RaylibThread)
confirms this. And so, porting the c code
to load the resources is easy!
let xbox_texture = rl.load_texture(&thread, "resources/xbox.png") .expect("Cannot run program if gamepad texture (xbox) missing"); let ps3_texture = rl.load_texture(&thread, "resources/ps3.png") .expect("Cannot run program if gamepad texture (ps3) missing");
The load_texture
method returns a Result
, if I can't load up the background file for the
gamepad there's no point in running the program. So I just let it crash. I probably don't need to port the PS3 related
resource since my gamepad is an xbox style one, but I'm just following along for now and that's not too much more to
type. Running the code with these two lines added in let's me confirm that I've got the files in the right place:

Looks like putting my resources folder next to src was the right place. And the logging in raylib is really nice. I wasn't drawing the textures yet since we haven't ported that part of the code yet, but I can clearly see the log telling me that it found and loaded up the image succesfully. Awesome.
Now, what about UnloadTexture(...)
? Well, there is
an unload_texture
method on the handle we can use. But, if I try to pass in xbox_texture
to it
error[E0308]: mismatched types --> gamepad\src\main.rs:49:32 | 49 | rl.unload_texture(&thread, xbox_texture); | -------------- ^^^^^^^^^^^^ expected `WeakTexture2D`, found `Texture2D` | | | arguments to this method are incorrect
Blindly following this, I thought to myself: "well, how do I get a weak texture then"? A quick search in the create found make_weak defined on the texture itself. Sweet! Well that's easy, let's just call that and
call to unsafe function `Texture2D::make_weak` is unsafe and requires unsafe block --> gamepad\src\main.rs:49:32 | 49 | rl.unload_texture(&thread, xbox_texture.make_weak()); | ^^^^^^^^^^^^^^^^^^^^^^^^ call to unsafe function | = note: consult the function's documentation for information on how to avoid undefined behavior
Wait, wait, wait. Unsafe? Let's back up and think for a second on the question I should have asked when I saw the other error. What the heck is a weak reference in the first place? The doc says a few things in the conversion methods on the type. Specifically mentioning that you have to manually deal with the memory yourself if you're using it.
So... that would imply then that with a non weak reference, I don't have to deal with unloading it. Right? I went about trying to confirm this and popped open the hood on the source code of the Texture2D struct and what did I find?
make_thin_wrapper!( /// Texture, tex data stored in GPU memory (VRAM) Texture2D, ffi::Texture2D, ffi::UnloadTexture );
Macros! This macro is doing... something. It's making a thin wrapper I guess based on the name. So, what's that exactly? Well, looking at that it's full of stuff I don't understand and makes my eyes go crosseyed:
macro_rules! make_thin_wrapper { ($(#[$attrs:meta])* $name:ident, $t:ty, $dropfunc:expr) => { make_thin_wrapper!($(#[$attrs])* $name, $t, $dropfunc, true); }; ($(#[$attrs:meta])* $name:ident, $t:ty, $dropfunc:expr, false) => { $(#[$attrs])* #[repr(transparent)] #[derive(Debug)] pub struct $name(pub(crate) $t); impl_wrapper!($name, $t, $dropfunc, 0); gen_from_raw_wrapper!($name, $t, $dropfunc, 0); }; ($(#[$attrs:meta])* $name:ident, $t:ty, $dropfunc:expr, true) => { $(#[$attrs])* #[repr(transparent)] #[derive(Debug)] pub struct $name(pub(crate) $t); impl_wrapper!($name, $t, $dropfunc, 0); deref_impl_wrapper!($name, $t, $dropfunc, 0); gen_from_raw_wrapper!($name, $t, $dropfunc, 0); }; }
But, what I can tell just based on the structure of this and the names, is that that third
argument is a drop function expression. Which implies to me that that previous callsite we saw is
giving ffi::UnloadTexture
as the function to become the Drop method for the implementation
of this thin wrapper around the Texture2D
struct. That means that the unload is called
when the variable is dropped, confirming that we don't need to call that ourselves and we can let
the lifetime of the variable deal with the clean up. This is confirmed by the logging as well:

With that potential memory leak behind us, I can bring over a bit more from the C code.
// Set axis deadzones const float leftStickDeadzoneX = 0.1f; const float leftStickDeadzoneY = 0.1f; const float rightStickDeadzoneX = 0.1f; const float rightStickDeadzoneY = 0.1f; const float leftTriggerDeadzone = -0.9f; const float rightTriggerDeadzone = -0.9f; SetTargetFPS(60); // Set our game to run at 60 frames-per-second
More constants which aren't worth mentioning, and one call to configure the FPS. As before,
I did a search for the phrase "fps" and turned up set_target_fps
on the raylib handle,
which makes for a pretty analogous port:
rl.set_target_fps(TARGET_FPS);
That's it for the configuration, now we get to the slightly interactive part. In the C code, within the main loop, we need to make sure the gamepad is connected.
int gamepad = 0; // which gamepad to display // Main game loop while (!WindowShouldClose()) // Detect { BeginDrawing(); ClearBackground(RAYWHITE); if (IsKeyPressed(KEY_LEFT) && gamepad > 0) gamepad--; if (IsKeyPressed(KEY_RIGHT)) gamepad++; if (IsGamepadAvailable(gamepad)) { ... } else { DrawText(TextFormat("GP%d: NOT DETECTED", gamepad), 10, 10, 10, GRAY); DrawTexture(texXboxPad, 0, 0, LIGHTGRAY); } EndDrawing(); }
I'm omitting a lot, but my first question was if that gamepad
variable
is somehow being changed by anything besides the arrow keys. I have a vague recollection
of some DirectX code I wrote from a book before where one passed in a reference and then
out popped the ID of something into the variable you gave to the method. I don't think
that's what's going on here though. But most likely this more relates to how many game
pads are connected?
This gamepad--
and gamepad++
on the arrow keys is swapping the number around,
but what is it. Digging deeper into raylib's source
I can see
// Check if a gamepad is available bool IsGamepadAvailable(int gamepad) { bool result = false; if ((gamepad < MAX_GAMEPADS) && CORE.Input.Gamepad.ready[gamepad]) result = true; return result; }
So it's an index. Into an array where the max gamepads is the limit. And that would be 4. Okay.
So, I suppose that deep within the bowels of the library, within its event loop, we're listening to game pads connecting. If we've got multiple pads plugged in (up to 4) then we'll potentially have those to loop through, and that way this little demo can you show which is which and what each one is doing. Neat. Mystery solved, but also, I own one gamepad. I don't care about the others, so I think we can ditch the key presses here unless I'm worried that somehow my gamepad might be registered as number 2.
I doubt it. So, movin along then. Let's just make it easy to tell that the program has detected the pad or not within the rust code.
// Raylib supports up to 4 gamepads, if you just have one connected, it's ID 0. let gamepad_to_display = 0; while !rl.window_should_close() { let mut d = rl.begin_drawing(&thread); if d.is_gamepad_available(gamepad_to_display) { d.draw_text(&format!("Gamepad: {gamepad_to_display}"), 60, 60, 20, Color::BLACK); } else { d.draw_text(&format!("No Gamepad: {gamepad_to_display}"), 60, 60, 20, Color::BLACK); } d.clear_background(Color::WHITE); d.draw_text("Hello, world!", 12, 12, 20, Color::BLACK); }
Since is_gamepad_available
is declared on the RaylibHandle
, I initially attempted to use
rl.is_gamepad_available
and, well:
error[E0502]: cannot borrow `rl` as immutable because it is also borrowed as mutable --> gamepad\src\main.rs:37:12 | 35 | let mut d = rl.begin_drawing(&thread); | -- mutable borrow occurs here 36 | 37 | if rl.is_gamepad_available(gamepad_to_display) { | ^^ immutable borrow occurs here ... 40 | d.draw_text(&format!("No Gamepad: {gamepad_to_display}"), 6, 6, 20, Color::BLACK); | - mutable borrow later used here
That netted me an error because it looks like once we start drawing, we've borrowed the handle and now need to
use the d
which allows for the underlying drawing contexts to change and mutate. The documentation
for the method
states
Setup canvas (framebuffer) to start drawing. Prefer using the closure version, RaylibHandle::draw. This version returns a handle that calls raylib_sys::EndDrawing at the end of the scope and is provided as a fallback incase you run into issues with closures(such as lifetime or performance reasons)
Well. Preference or not, I'm working with the example they gave us so I'm going to continue using this bit for now. If I can move to the closure version later, then maybe I will if it makes sense to. But then again, the docstring seems to imply that it's more efficient to not use closures here? So maybe I'll just back away slowly and leave it alone.
Running the code and connecting, then disconnecting my gamepad shows the code is working:


And so we can move on to grabbing the inputs for the various sticks. The C code is straightforward, we need to grab both axes of each stick, and then the two triggers also have a float value since one can press them gently. So all of these values are between -1 to 1. Even the triggers. They range from -1 to 1, where 0 is actually the midpoint in the squeeze on the trigger.
// Get axis values float leftStickX = GetGamepadAxisMovement(gamepad, GAMEPAD_AXIS_LEFT_X); float leftStickY = GetGamepadAxisMovement(gamepad, GAMEPAD_AXIS_LEFT_Y); float rightStickX = GetGamepadAxisMovement(gamepad, GAMEPAD_AXIS_RIGHT_X); float rightStickY = GetGamepadAxisMovement(gamepad, GAMEPAD_AXIS_RIGHT_Y); float leftTrigger = GetGamepadAxisMovement(gamepad, GAMEPAD_AXIS_LEFT_TRIGGER); float rightTrigger = GetGamepadAxisMovement(gamepad, GAMEPAD_AXIS_RIGHT_TRIGGER);
The rust binding is kind to us, we've got an enumeration to use for the buttons and as it seems to be the case with most of these functions, we want to call these through the library handle:
let left_stick_x = rl.get_gamepad_axis_movement(gamepad_to_display, GamepadAxis::GAMEPAD_AXIS_LEFT_X); let left_stick_y = rl.get_gamepad_axis_movement(gamepad_to_display, GamepadAxis::GAMEPAD_AXIS_LEFT_Y); let right_stick_x = rl.get_gamepad_axis_movement(gamepad_to_display, GamepadAxis::GAMEPAD_AXIS_RIGHT_X); let right_stick_y = rl.get_gamepad_axis_movement(gamepad_to_display, GamepadAxis::GAMEPAD_AXIS_RIGHT_Y); let left_trigger = rl.get_gamepad_axis_movement(gamepad_to_display, GamepadAxis::GAMEPAD_AXIS_LEFT_TRIGGER); let right_trigger = rl.get_gamepad_axis_movement(gamepad_to_display, GamepadAxis::GAMEPAD_AXIS_RIGHT_TRIGGER);
So, we can call these before the call to begin_drawing
if we want to use the immutable
reference to the handle, and it doesn't really seem to do any harm if we want to do so after. But, that just
feels wrong to me. For some reason, I have this sneaking suspicion that doing any additional computations in
the middle of telling the computer to draw something is probably a silly idea.
What's also a silly idea is expecting these sticks and triggers to return a perfect 1 or -1. There's a reason we've got constants for dead zones. The C code clamps the values to 0 or -1 depending on if its a stick or trigger and if it's hit the range we setup constants for:
// Calculate deadzones if (leftStickX > -leftStickDeadzoneX && leftStickX < leftStickDeadzoneX) leftStickX = 0.0f; if (leftStickY > -leftStickDeadzoneY && leftStickY < leftStickDeadzoneY) leftStickY = 0.0f; if (rightStickX > -rightStickDeadzoneX && rightStickX < rightStickDeadzoneX) rightStickX = 0.0f; if (rightStickY > -rightStickDeadzoneY && rightStickY < rightStickDeadzoneY) rightStickY = 0.0f; if (leftTrigger < leftTriggerDeadzone) leftTrigger = -1.0f; if (rightTrigger < rightTriggerDeadzone) rightTrigger = -1.0f;
This code can be brought over pretty much verbatim, provided we update all the variabels we just declared to be mut
// Then filter out noise via deadzones if -DEADZONE_LEFT_STICK_X < left_stick_x && left_stick_x < DEADZONE_LEFT_STICK_X { left_stick_x = 0.0; } if -DEADZONE_LEFT_STICK_Y < left_stick_y && left_stick_y < DEADZONE_LEFT_STICK_Y { left_stick_y = 0.0; } if -DEADZONE_RIGHT_STICK_X < right_stick_x && right_stick_x < DEADZONE_RIGHT_STICK_X { right_stick_x = 0.0; } if -DEADZONE_RIGHT_STICK_Y < right_stick_y && right_stick_y < DEADZONE_RIGHT_STICK_Y { right_stick_y = 0.0; } if left_trigger < DEADZONE_LEFT_TRIGGER { left_trigger = -1.0; } if right_trigger < DEADZONE_RIGHT_TRIGGER { right_trigger = -1.0; }
Though, given the way rust doesn't use parenthesis around conditions makes this feel a bit more vertically
bulky than the C code. Ah well. I'm sure that somewhere out there, someone is screaming at the screen to
me, but I can't hear you. Though if you know how to write this sort of inverse clamp with a built in method
let me know. I did go look at float
's std library page to see if I could spot anything useful
and failed.
Putting aside code aesthetics, rather than doing the equavalent of println to the window with text. Let's Go ahead and jump ahead to grab the stick rendering code from the C rather than proceed to the button input fetching:
if (TextFindIndex(TextToLower(GetGamepadName(gamepad)), XBOX_ALIAS_1) > -1 || TextFindIndex(TextToLower(GetGamepadName(gamepad)), XBOX_ALIAS_2) > -1) { DrawTexture(texXboxPad, 0, 0, DARKGRAY); // Draw axis: left joystick Color leftGamepadColor = BLACK; if (IsGamepadButtonDown(gamepad, GAMEPAD_BUTTON_LEFT_THUMB)) leftGamepadColor = RED; DrawCircle(259, 152, 39, BLACK); DrawCircle(259, 152, 34, LIGHTGRAY); DrawCircle(259 + (int)(leftStickX*20), 152 + (int)(leftStickY*20), 25, leftGamepadColor); // Draw axis: right joystick Color rightGamepadColor = BLACK; if (IsGamepadButtonDown(gamepad, GAMEPAD_BUTTON_RIGHT_THUMB)) rightGamepadColor = RED; DrawCircle(461, 237, 38, BLACK); DrawCircle(461, 237, 33, LIGHTGRAY); DrawCircle(461 + (int)(rightStickX*20), 237 + (int)(rightStickY*20), 25, rightGamepadColor); // Draw axis: left-right triggers DrawRectangle(170, 30, 15, 70, GRAY); DrawRectangle(604, 30, 15, 70, GRAY); DrawRectangle(170, 30, 15, (int)(((1 + leftTrigger)/2)*70), RED); DrawRectangle(604, 30, 15, (int)(((1 + rightTrigger)/2)*70), RED);
We have, not only the axis values we just pulled out in use here, but also the color changing based on whether or not the stick is currently clicked in or not. So this gives us an example of how to check for buttons being down as well. How nice! This means I can probably skip the explanations for some of the other bits of this unless anything stands out as notable. But let's dig in and port this over to rust. None of the magic numbers are going to change here So really it's just a matter of finding the right methods on the handle to use.
As I've said earlier, I don't actually care about the PS3. So, that first conditional that's checking that the index is what's expected seems unneccesary to me for my purposes. So let's ditch it, and just assume.
d.draw_texture(&xbox_texture, 0, 0, Color::DARKGRAY);
Drawing the PNG we have from one corner of the box up works just fine since those SCREEN_WIDTH
and SCREEN_HEIGHT
are actually just the size of the PNG resource we've got. Simple. Tinting it dark grey allows the white game pad of
the original image to take on a color that's nice to look at. Right?

Right. That's the one texture. Let's translate the sticks and triggers then, I'll start off with the left stick since the right will just be a mirror of it:
let left_gamepad_color = if rl.is_gamepad_button_down(gamepad_to_display, GamepadButton::GAMEPAD_BUTTON_LEFT_THUMB) { Color::RED; } else { Color::BLACK; }; ... after the drawing start ... d.draw_circle(259, 152, 39.0, Color::BLACK); d.draw_circle(259, 152, 34.0, Color::LIGHTGRAY); d.draw_circle( 259 + (left_stick_x as i32 * 20), 152 + (left_stick_y as i32 * 20), 25.0, left_gamepad_color );
And I should have a left stick...

Ah, wups. I rounded off our float too soon!
d.draw_circle(259, 152, 39.0, Color::BLACK); d.draw_circle(259, 152, 34.0, Color::LIGHTGRAY); d.draw_circle( 259 + (left_stick_x * 20.0) as i32, 152 + (left_stick_y * 20.0) as i32, 25.0, left_gamepad_color );

Much better. You can see how much more smooth it is. Believe it or not, that janky snapping in the first one is the same sort of movements. The right stick is the same, but slightly different numbers for placement. 259 beomes 461, 152 to 237, 39 to 38, and 34 to 33. The rest stays the same.
The trigger code is nearly the same as the C code, we just have to put .0
next to
more things in order for the rust compiler not to get mad at us:
// left and right triggers d.draw_rectangle(170, 30, 15, 70, Color::GRAY); d.draw_rectangle(604, 30, 15, 70, Color::GRAY); d.draw_rectangle(170, 30, 15, ((1.0 + left_trigger) /2.0 * 70.0) as i32, Color::RED); d.draw_rectangle(604, 30, 15, ((1.0 + right_trigger) /2.0 * 70.0) as i32, Color::RED);
The way this code works is that when the trigger is fully pulled in, the value will be 1. This results in us doing 1 + 1, then dividing that 2 by a 2 to get a 1. Which results in the height parameter becoming the same as the background gray color (70 pixels tall).

When the trigger isn't pulled, and we're in the dead zone, we've got a -1, and so we end up with a 0 being multiplied by the rest of the terms, resulting in no height at all. Which shows just the gray background to the user. Neat right?
The only thing left of course are the buttons. This includes start, select, the D-pad, the ABYX, and the shoulder buttons. All of these are straightforward ports from C
// Draw buttons: basic if (IsGamepadButtonDown(gamepad, GAMEPAD_BUTTON_MIDDLE_RIGHT)) DrawCircle(436, 150, 9, RED); if (IsGamepadButtonDown(gamepad, GAMEPAD_BUTTON_MIDDLE_LEFT)) DrawCircle(352, 150, 9, RED); if (IsGamepadButtonDown(gamepad, GAMEPAD_BUTTON_RIGHT_FACE_LEFT)) DrawCircle(501, 151, 15, BLUE); if (IsGamepadButtonDown(gamepad, GAMEPAD_BUTTON_RIGHT_FACE_DOWN)) DrawCircle(536, 187, 15, LIME); if (IsGamepadButtonDown(gamepad, GAMEPAD_BUTTON_RIGHT_FACE_RIGHT)) DrawCircle(572, 151, 15, MAROON); if (IsGamepadButtonDown(gamepad, GAMEPAD_BUTTON_RIGHT_FACE_UP)) DrawCircle(536, 115, 15, GOLD); // Draw buttons: d-pad DrawRectangle(317, 202, 19, 71, BLACK); DrawRectangle(293, 228, 69, 19, BLACK); if (IsGamepadButtonDown(gamepad, GAMEPAD_BUTTON_LEFT_FACE_UP)) DrawRectangle(317, 202, 19, 26, RED); if (IsGamepadButtonDown(gamepad, GAMEPAD_BUTTON_LEFT_FACE_DOWN)) DrawRectangle(317, 202 + 45, 19, 26, RED); if (IsGamepadButtonDown(gamepad, GAMEPAD_BUTTON_LEFT_FACE_LEFT)) DrawRectangle(292, 228, 25, 19, RED); if (IsGamepadButtonDown(gamepad, GAMEPAD_BUTTON_LEFT_FACE_RIGHT)) DrawRectangle(292 + 44, 228, 26, 19, RED); // Draw buttons: left-right back if (IsGamepadButtonDown(gamepad, GAMEPAD_BUTTON_LEFT_TRIGGER_1)) DrawCircle(259, 61, 20, RED); if (IsGamepadButtonDown(gamepad, GAMEPAD_BUTTON_RIGHT_TRIGGER_1)) DrawCircle(536, 61, 20, RED);
to rust
// select and start if d.is_gamepad_button_down(gamepad_to_display, GamepadButton::GAMEPAD_BUTTON_MIDDLE_LEFT) { d.draw_circle(436, 150, 9.0, Color::RED); } if d.is_gamepad_button_down(gamepad_to_display, GamepadButton::GAMEPAD_BUTTON_MIDDLE_RIGHT) { d.draw_circle(352, 150, 9.0, Color::RED); } // face buttons if d.is_gamepad_button_down(gamepad_to_display, GamepadButton::GAMEPAD_BUTTON_RIGHT_FACE_LEFT) { d.draw_circle(501, 151, 15.0, Color::BLUE); } if d.is_gamepad_button_down(gamepad_to_display, GamepadButton::GAMEPAD_BUTTON_RIGHT_FACE_DOWN) { d.draw_circle(536, 187, 15.0, Color::LIME); } if d.is_gamepad_button_down(gamepad_to_display, GamepadButton::GAMEPAD_BUTTON_RIGHT_FACE_RIGHT) { d.draw_circle(572, 151, 15.0, Color::MAROON); } if d.is_gamepad_button_down(gamepad_to_display, GamepadButton::GAMEPAD_BUTTON_RIGHT_FACE_UP) { d.draw_circle(536, 115, 15.0, Color::GOLD); } // Draw buttons: d-pad d.draw_rectangle(317, 202, 19, 71, Color::BLACK); d.draw_rectangle(293, 228, 69, 19, Color::BLACK); if d.is_gamepad_button_down(gamepad_to_display, GamepadButton::GAMEPAD_BUTTON_LEFT_FACE_UP) { d.draw_rectangle(317, 202, 19, 26, Color::RED); } if d.is_gamepad_button_down(gamepad_to_display, GamepadButton::GAMEPAD_BUTTON_LEFT_FACE_DOWN) { d.draw_rectangle(317, 202 + 45, 19, 26, Color::RED); } if d.is_gamepad_button_down(gamepad_to_display, GamepadButton::GAMEPAD_BUTTON_LEFT_FACE_LEFT) { d.draw_rectangle(292, 228, 25, 19, Color::RED); } if d.is_gamepad_button_down(gamepad_to_display, GamepadButton::GAMEPAD_BUTTON_LEFT_FACE_RIGHT) { d.draw_rectangle(292 + 44, 228, 26, 19, Color::RED); } // Draw buttons: left-right shoulder buttons if d.is_gamepad_button_down(gamepad_to_display, GamepadButton::GAMEPAD_BUTTON_LEFT_TRIGGER_1) { d.draw_circle(239, 82, 20.0, Color::RED); } if d.is_gamepad_button_down(gamepad_to_display, GamepadButton::GAMEPAD_BUTTON_RIGHT_TRIGGER_1) { d.draw_circle(557, 82, 20.0, Color::RED); }
With the result being exactly what you'd expect:

Wrap up
Now, the reason I'd want this, like I said before. Is to show what inputs I'm sending when I play a game that requires tight timing and skill. Mainly because I've gone through several controllers over the years and they've worn down over time, which sometimes results in me pressing the buttons and it no registering, or worse, pressing on the stick, but the dead zones being miscalibrated from drift over the years and resulting in nonsense happening.
Either way, this sort of thing is handy. Not having to run a tab in a browser to get one of those game input sites to show a nice gamepad is really great. A browser window is going to take up half a gig of memory nowadays if it's doing anything interesting it seems. So something that's lightweight and takes up a miniscule amount of memory and CPU is even better in making sure that I don't get lag when I stream and play.
I wasn't sure if the capturing would work once the gamepad was no longer in focus, so I booted up Celeste to check it out: