Source Code
src/pages/projects/keybind_plugin.rs
use leptos::*;
use leptos_meta::{Meta, Title};
use super::data::ALL_PROJECTS;
#[component]
pub fn KeybindPlugin() -> impl IntoView {
let project = ALL_PROJECTS
.iter()
.find(|p| p.slug == "keybind-plugin")
.unwrap();
let skills_view = project
.skills
.iter()
.map(|&s| view! { <li>{s}</li> })
.collect::<Vec<_>>();
view! {
<Title text="Keybind Plugin – Peter Pinto"/>
<Meta name="description" content="A Bevy plugin that maps keyboard, mouse, gamepad, and touch inputs to named actions and emits typed events, enabling rebindable control schemes."/>
<div class="page">
<span class="eyebrow">"Projects"</span>
<h1>"Keybind " <em style="font-style:italic; color: var(--accent)">"Plugin"</em></h1>
<p class="lead">
"A Bevy plugin that maps physical inputs — keyboard keys, mouse buttons,
gamepad axes, touch — to named actions. Game code listens to actions,
not hardware; rebinding is a runtime change with no game logic involved."
</p>
<ul class="skills-list" style="margin-top: 1.5rem;">
{skills_view}
</ul>
<hr class="divider"/>
// ── Actions ───────────────────────────────────────────
<section class="project-section">
<span class="eyebrow">"Design"</span>
<h2>"Action-Based Binding"</h2>
<p>
"Inputs are stored in a "
<code class="inline-code">"KeyBindings"</code>
" resource as a map from action name to a list of "
<code class="inline-code">"InputType"</code>
" values. One action can be triggered by multiple inputs simultaneously —
"
<code class="inline-code">"\"zoom_in\""</code>
" could be bound to both scroll wheel up and the "
<code class="inline-code">"+"</code>
" key without any special-casing. The plugin reads all registered
bindings each frame and emits a "
<code class="inline-code">"MessageEvent"</code>
" for any action whose input is active."
</p>
</section>
<hr class="divider"/>
// ── Input Types ───────────────────────────────────────
<section class="project-section">
<span class="eyebrow">"Coverage"</span>
<h2>"Input Types"</h2>
<p>
"The "
<code class="inline-code">"InputType"</code>
" enum covers every major input source Bevy exposes:"
</p>
<ol class="project-steps">
<li><strong>"Keyboard"</strong>" — any "
<code class="inline-code">"KeyCode"</code></li>
<li><strong>"MouseButton"</strong>" — left, right, middle, or extra buttons"</li>
<li><strong>"MouseWheel"</strong>" — up/down scroll delta"</li>
<li><strong>"MouseMotion"</strong>" — raw cursor delta for look/orbit"</li>
<li><strong>"GamepadButton"</strong>" — any button on any connected gamepad"</li>
<li><strong>"GamepadAxis"</strong>" — analogue stick or trigger with deadzone"</li>
<li><strong>"TouchInput"</strong>" — touch phase detection"</li>
</ol>
</section>
<hr class="divider"/>
// ── State ─────────────────────────────────────────────
<section class="project-section">
<span class="eyebrow">"State Tracking"</span>
<h2>"Pressed / JustPressed / Released"</h2>
<p>
"Each action emits an "
<code class="inline-code">"ActionState"</code>
" alongside the action name: "
<code class="inline-code">"JustPressed"</code>
" fires once on the first frame the input is active, "
<code class="inline-code">"Pressed"</code>
" fires every frame it remains held, and "
<code class="inline-code">"JustReleased"</code>
" fires once when the input is lifted. This mirrors Bevy's own input
API so game systems can use familiar patterns without querying raw
hardware."
</p>
</section>
<hr class="divider"/>
// ── Integration ───────────────────────────────────────
<section class="project-section">
<span class="eyebrow">"Integration"</span>
<h2>"Orbit Camera"</h2>
<p>
"The keybind plugin is the default input provider for the "
<a href="/projects/orbit-camera" class="prose-link">"Orbit Camera"</a>
" plugin. "
<code class="inline-code">"KeybindOrbitInput"</code>
" implements "
<code class="inline-code">"OrbitCameraInput"</code>
" by reading "
<code class="inline-code">"MessageEvent"</code>
"s and translating them into camera deltas. Rebinding the camera
controls at runtime requires only updating the "
<code class="inline-code">"KeyBindings"</code>
" resource — neither the camera plugin nor the game loop need to know."
</p>
</section>
</div>
}
}