bevy_input_bindings 0.0.2

High level, flexible and shareable input binding library for the Bevy game engine
Documentation
use bevy::app::{App, Plugin, Update};
use bevy::ecs::component::{Component, Mutable};
use bevy::ecs::entity::Entity;
use bevy::ecs::message::MessageReader;
use bevy::ecs::schedule::IntoScheduleConfigs;
use bevy::ecs::system::{Commands, Query, Res};
use bevy::input::gamepad::{Gamepad, GamepadConnection, GamepadConnectionEvent};
use bevy::time::Time;

use crate::action_event::ActionEvent;
use crate::controller_interface::ControllerInterface;
use crate::controller_layout::ControllerLayout;
use crate::controller_values::{ControllerValues, VariantValue};
use crate::gamepad::binding::GamepadBinding;
use crate::relation::Controlling;
use crate::shared::ControllersSet;

/// Plugin to use gamepad as a controller.
///
/// # Generic `ActionKey`
/// Action event data to be sent when the actions are triggered. For instance:
///
/// ```rust
/// enum MyActionKey {
///     Jump,
///     Shoot,
/// }
/// ```
///
/// # Generic `ValueKey`
/// Key to identify the values. For instance:
///
/// ```rust
/// #[derive(Hash, PartialEq, Eq)]
/// enum MyValueKey {
///     Move,
///     Camera,
/// }
/// ```
///
/// # Generic `Binding`
/// Bindings to link triggers to the actions
///
pub struct GamepadPlugin<ActionKey, ValueKey, Binding>
where
    Binding: GamepadBinding<ActionKey, ValueKey>,
{
    _action_key: std::marker::PhantomData<ActionKey>,
    _value_key: std::marker::PhantomData<ValueKey>,
    _binding_key: std::marker::PhantomData<Binding>,
}

impl<ActionKey, ValueKey, Binding> GamepadPlugin<ActionKey, ValueKey, Binding>
where
    Binding: GamepadBinding<ActionKey, ValueKey>,
{
    pub fn new() -> Self {
        Self {
            _action_key: Default::default(),
            _value_key: Default::default(),
            _binding_key: Default::default(),
        }
    }
}

impl<ActionKey, ValueKey, Binding> Plugin for GamepadPlugin<ActionKey, ValueKey, Binding>
where
    ActionKey: Sync + Send + 'static,
    ValueKey: std::hash::Hash + PartialEq + Eq + Sync + Send + 'static,
    Binding: GamepadBinding<ActionKey, ValueKey>
        + Sync
        + Send
        + 'static
        + Component<Mutability = Mutable>,
{
    fn build(&self, app: &mut App) {
        app.add_systems(
            Update,
            (
                gamepad_connections::<ActionKey, ValueKey, Binding>,
                gamepad_system::<ActionKey, ValueKey, Binding>,
            )
                .in_set(ControllersSet),
        );
    }
}

/// System to handle stateful gamepad actions
fn gamepad_system<ActionKey, ValueKey, Binding>(
    time: Res<Time>,
    mut commands: Commands,
    mut gamepads: Query<(
        Entity,
        &Gamepad,
        &mut Binding,
        &mut ControllerValues<ValueKey>,
        Option<&Controlling>,
    )>,
) where
    ActionKey: Sync + Send + 'static,
    ValueKey: std::hash::Hash + PartialEq + Eq + Sync + Send + 'static,
    Binding: GamepadBinding<ActionKey, ValueKey> + Component<Mutability = Mutable>,
{
    for (controller_id, gamepad, mut binding, mut controller_values, controlling) in
        gamepads.iter_mut()
    {
        // Get the controlled entities if required
        let controlled_entities = std::cell::LazyCell::new(|| {
            let Some(controlling) = controlling else {
                return Vec::new();
            };
            return controlling.controlling().clone();
        });
        // Actions
        for (trigger, action_key) in binding.get_action_bindings() {
            if trigger.is_triggered(gamepad, &time.delta()) {
                let action_event = ActionEvent {
                    controller: controller_id,
                    controlling: controlled_entities.clone(),
                    action_key,
                };
                commands.trigger(action_event);
            }
        }
        // Values
        for (value_type, value_key) in binding.get_value_bindings() {
            // If the value does not yet exist, create it
            if let Some(value) = controller_values.values.get_mut(&value_key) {
                value_type.update_value(gamepad, &time.delta(), value);
            } else {
                let mut new_value = VariantValue::None;
                value_type.update_value(gamepad, &time.delta(), &mut new_value);
                controller_values.values.insert(value_key, new_value);
            };
        }
    }
}

/// System to handle gamepad connection. Note that deconnection does not remove the gamepad
/// interface.
fn gamepad_connections<ActionKey, ValueKey, Binding>(
    mut commands: Commands,
    mut connection_events: MessageReader<GamepadConnectionEvent>,
) where
    ActionKey: Sync + Send + 'static,
    ValueKey: std::hash::Hash + PartialEq + Eq + Sync + Send + 'static,
    Binding: GamepadBinding<ActionKey, ValueKey>,
{
    for connection_event in connection_events.read() {
        match &connection_event.connection {
            GamepadConnection::Connected {
                name,
                vendor_id,
                product_id,
            } => {
                println!(
                    "New gamepad connected: {:?}, name: {}, vendor_id: {:?}, product_id: {:?}",
                    connection_event.gamepad, name, vendor_id, product_id,
                );
                // Insert the controller with default binding
                commands.entity(connection_event.gamepad).insert_if_new((
                    Binding::default(),
                    ControllerInterface::new(ControllerLayout::UnknownGamepad),
                    ControllerValues::<ValueKey>::new(),
                ));
            },
            GamepadConnection::Disconnected => {},
        }
    }
}