use bevy::{input::mouse::MouseMotion, prelude::*, utils::HashSet}; pub struct StudioInputPlugin; impl Plugin for StudioInputPlugin { fn build(&self, app: &mut App) { app.add_event::(); app.add_systems( PreUpdate, translate_bevy_events.after(bevy::input::InputSystem), ); } } #[derive(Event, Clone, Copy)] pub struct InputAction(pub Lifecycle, pub Action); #[derive(Clone, Copy)] pub enum Lifecycle { Start, Ongoing, Stop, } #[derive(Clone, Copy)] pub enum Action { Camera(CameraMotion), } #[derive(Clone, Copy)] pub enum CameraMotion { Translate(Vec3), Pan(Vec2), Zoom(f32), Orbit(Vec2), Rotate(Vec2), } impl From for Action { fn from(value: CameraMotion) -> Self { Action::Camera(value) } } // TODO: Make the bindings of raw device inputs to InputActions configurable fn translate_bevy_events( mouse_motion: EventReader, mouse_buttons: Res>, keyboard: Res>, mut input: EventWriter, ) { // Camera Pan if mouse_buttons.pressed(MouseButton::Middle) { let delta = read_mouse_delta(mouse_motion); let lifecycle = if mouse_buttons.just_pressed(MouseButton::Middle) { Lifecycle::Start } else { Lifecycle::Ongoing }; input.send(InputAction(lifecycle, CameraMotion::Pan(delta).into())); } // Stop Camera Panning else if mouse_buttons.just_released(MouseButton::Middle) { input.send(InputAction( Lifecycle::Stop, CameraMotion::Pan(Vec2::ZERO).into(), )); } // Camera Orbit/Zoom else if keyboard.any_pressed([KeyCode::AltLeft, KeyCode::AltRight]) { if mouse_buttons.any_pressed([MouseButton::Left, MouseButton::Right]) { let delta = read_mouse_delta(mouse_motion); let lifecycle = if mouse_buttons.any_just_pressed([MouseButton::Left, MouseButton::Right]) { Lifecycle::Start } else { Lifecycle::Ongoing }; let motion = if mouse_buttons.pressed(MouseButton::Left) { CameraMotion::Orbit(delta) } else { CameraMotion::Zoom(delta.y) }; input.send(InputAction(lifecycle, motion.into())); } // Stop Camera Orbit else if mouse_buttons.just_released(MouseButton::Left) { input.send(InputAction( Lifecycle::Stop, CameraMotion::Orbit(Vec2::ZERO).into(), )); } // Stop Camera Zoom else if mouse_buttons.just_released(MouseButton::Right) { input.send(InputAction(Lifecycle::Stop, CameraMotion::Zoom(0.).into())); } } // Stop Camera Orbit & Zoom else if (keyboard.just_released(KeyCode::AltLeft) && !keyboard.pressed(KeyCode::AltRight)) || (keyboard.just_released(KeyCode::AltRight) && !keyboard.pressed(KeyCode::AltLeft)) { input.send(InputAction( Lifecycle::Stop, CameraMotion::Orbit(Vec2::ZERO).into(), )); input.send(InputAction(Lifecycle::Stop, CameraMotion::Zoom(0.).into())); } // Camera Rotation else if mouse_buttons.pressed(MouseButton::Right) { let lifecycle = if mouse_buttons.just_pressed(MouseButton::Right) { Lifecycle::Start } else { Lifecycle::Ongoing }; let delta = read_mouse_delta(mouse_motion); input.send(InputAction(lifecycle, CameraMotion::Rotate(delta).into())); } // Stop Camera Rotation else if mouse_buttons.just_released(MouseButton::Right) { input.send(InputAction( Lifecycle::Stop, CameraMotion::Rotate(Vec2::ZERO).into(), )) } // Camera Translation (FPS-like) use KeyCode::*; let wasd_keys = [W, A, S, D, E, Q]; let wasd_keys_set = wasd_keys.into_iter().collect::>(); // A little unfortunate that `get_pressed()` doesn't just return a reference // to the original HashSet >_< Maybe the compiler will be nice to us and // optimize away the redundant clone? let keys_pressed = keyboard.get_pressed().cloned().collect::>(); let wasd_keys_pressed = wasd_keys_set .intersection(&keys_pressed) .collect::>(); if !wasd_keys_pressed.is_empty() { let mut mvmt = Vec3::ZERO; #[rustfmt::skip] { if wasd_keys_pressed.contains(&W) { mvmt.z -= 1.; } if wasd_keys_pressed.contains(&A) { mvmt.x -= 1.; } if wasd_keys_pressed.contains(&S) { mvmt.z += 1.; } if wasd_keys_pressed.contains(&D) { mvmt.x += 1.; } if wasd_keys_pressed.contains(&E) { mvmt.y += 1.; } if wasd_keys_pressed.contains(&Q) { mvmt.y -= 1.; } }; let mvmt = mvmt.normalize_or_zero(); // Compare WASD keys pressed with WASD keys _just_ pressed to determine lifecycle let keys_just_pressed = keyboard.get_just_pressed().cloned().collect::>(); let wasd_keys_just_pressed = keys_just_pressed .intersection(&wasd_keys_set) .collect::>(); let lifecycle = if wasd_keys_pressed.len() == wasd_keys_just_pressed.len() { Lifecycle::Start } else { Lifecycle::Ongoing }; input.send(InputAction(lifecycle, CameraMotion::Translate(mvmt).into())); } // Stop Translation else if keyboard.any_just_released(wasd_keys) { input.send(InputAction( Lifecycle::Stop, CameraMotion::Translate(Vec3::ZERO).into(), )); } } #[inline] fn read_mouse_delta(mut mouse_motion: EventReader) -> Vec2 { mouse_motion .into_iter() .fold(Vec2::ZERO, |accum, current| accum + current.delta) }