OverviewThis WebsiteHome LabTrading BotEcosimProcedural Generation
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>
    }
}