Keyboard shortcuts

Press or to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

Input Handling

RDPE provides a simple input system for keyboard and mouse interaction. Input state is available in your update callback via UpdateContext.

Basic Usage

#![allow(unused)]
fn main() {
Simulation::<MyParticle>::new()
    .with_uniform::<f32>("burst", 0.0)
    .with_uniform::<[f32; 2]>("attractor", [0.0, 0.0])
    .with_update(|ctx| {
        // React to space bar press
        if ctx.input.key_pressed(KeyCode::Space) {
            ctx.set("burst", 1.0);
        }

        // Track mouse position while held
        if ctx.input.mouse_held(MouseButton::Left) {
            let pos = ctx.input.mouse_ndc();
            ctx.set("attractor", [pos.x, pos.y]);
        }
    })
    .with_rule(Rule::Custom(r#"
        // Use input in shader
        if uniforms.burst > 0.5 {
            p.velocity *= 2.0;
        }

        let target = vec3<f32>(uniforms.attractor[0], uniforms.attractor[1], 0.0);
        p.velocity += normalize(target - p.position) * 0.1;
    "#.into()))
    .run();
}

Keyboard Input

Key States

Three types of key queries are available:

MethodReturns true when
key_pressed(key)Key was just pressed this frame
key_held(key)Key is currently down
key_released(key)Key was just released this frame
#![allow(unused)]
fn main() {
.with_update(|ctx| {
    // Toggle on key press (not hold)
    if ctx.input.key_pressed(KeyCode::T) {
        // Toggle something once per press
    }

    // Continuous action while held
    if ctx.input.key_held(KeyCode::W) {
        // Move forward every frame
    }

    // Cleanup on release
    if ctx.input.key_released(KeyCode::Shift) {
        // Stop sprint mode
    }
})
}

Available Keys

#![allow(unused)]
fn main() {
use rdpe::prelude::KeyCode;

// Letters
KeyCode::A, KeyCode::B, ... KeyCode::Z

// Numbers
KeyCode::Key0, KeyCode::Key1, ... KeyCode::Key9

// Function keys
KeyCode::F1, KeyCode::F2, ... KeyCode::F12

// Arrows
KeyCode::Up, KeyCode::Down, KeyCode::Left, KeyCode::Right

// Common keys
KeyCode::Space, KeyCode::Enter, KeyCode::Escape
KeyCode::Tab, KeyCode::Backspace, KeyCode::Delete
KeyCode::Shift, KeyCode::Control, KeyCode::Alt
}

Mouse Input

Button States

Mouse buttons work the same as keys:

MethodReturns true when
mouse_pressed(button)Button was just clicked
mouse_held(button)Button is currently down
mouse_released(button)Button was just released
#![allow(unused)]
fn main() {
use rdpe::prelude::MouseButton;

.with_update(|ctx| {
    if ctx.input.mouse_pressed(MouseButton::Left) {
        // Click action
    }

    if ctx.input.mouse_held(MouseButton::Right) {
        // Drag action
    }
})
}

Available buttons: MouseButton::Left, MouseButton::Right, MouseButton::Middle

Mouse Position

Several position formats are available:

MethodReturns
mouse_position()Screen pixels Vec2
mouse_ndc()Normalized coordinates (-1 to 1) Vec2
mouse_delta()Movement since last frame in pixels Vec2
scroll_delta()Scroll wheel movement f32
#![allow(unused)]
fn main() {
.with_update(|ctx| {
    // NDC is most useful for particle interactions
    // Center of screen = (0, 0)
    // X: -1 (left) to +1 (right)
    // Y: -1 (bottom) to +1 (top)
    let pos = ctx.input.mouse_ndc();

    // Pass to shader as attractor point
    ctx.set("mouse_x", pos.x);
    ctx.set("mouse_y", pos.y);

    // Check for scroll zoom
    let scroll = ctx.input.scroll_delta();
    if scroll != 0.0 {
        // Zoom in/out
    }
})
}

Common Patterns

Mouse Attractor

Particles attracted to mouse position:

#![allow(unused)]
fn main() {
Simulation::<Particle>::new()
    .with_uniform::<[f32; 2]>("mouse", [0.0, 0.0])
    .with_uniform::<f32>("attract_strength", 0.0)
    .with_update(|ctx| {
        let pos = ctx.input.mouse_ndc();
        ctx.set("mouse", [pos.x, pos.y]);

        // Only attract while clicking
        let strength = if ctx.input.mouse_held(MouseButton::Left) { 2.0 } else { 0.0 };
        ctx.set("attract_strength", strength);
    })
    .with_rule(Rule::Custom(r#"
        let target = vec3<f32>(uniforms.mouse[0], uniforms.mouse[1], 0.0);
        let dir = target - p.position;
        p.velocity += normalize(dir) * uniforms.attract_strength * uniforms.delta_time;
    "#.into()))
    .run();
}

WASD Movement

Move a point of interest with keyboard:

#![allow(unused)]
fn main() {
Simulation::<Particle>::new()
    .with_uniform::<[f32; 2]>("focus", [0.0, 0.0])
    .with_update(|ctx| {
        let mut focus = [0.0_f32, 0.0_f32];
        let speed = 2.0 * ctx.time.delta_time();

        if ctx.input.key_held(KeyCode::W) { focus[1] += speed; }
        if ctx.input.key_held(KeyCode::S) { focus[1] -= speed; }
        if ctx.input.key_held(KeyCode::A) { focus[0] -= speed; }
        if ctx.input.key_held(KeyCode::D) { focus[0] += speed; }

        // Accumulate movement
        // In practice, you'd store this in shared state
        ctx.set("focus", focus);
    })
    .run();
}

Toggle Effects

Toggle particle behavior with key presses:

#![allow(unused)]
fn main() {
use std::sync::{Arc, Mutex};

let gravity_on = Arc::new(Mutex::new(true));
let gravity_clone = gravity_on.clone();

Simulation::<Particle>::new()
    .with_uniform::<f32>("gravity", 9.8)
    .with_update(move |ctx| {
        let mut on = gravity_clone.lock().unwrap();

        // Toggle with G key
        if ctx.input.key_pressed(KeyCode::G) {
            *on = !*on;
        }

        ctx.set("gravity", if *on { 9.8 } else { 0.0 });
    })
    .run();
}

Notes

  • Input is processed once per frame before the update callback runs
  • key_pressed and mouse_pressed only return true for one frame
  • Mouse NDC coordinates assume a standard coordinate system (Y-up)
  • The scroll delta is positive for scrolling up/forward