Skip to main content

cloudiful_bevy_camera/
input.rs

1use crate::events::SwitchCameraRequest;
2use crate::plugin::CameraSwitchSystemSet;
3use bevy::input::ButtonInput;
4use bevy::input::gamepad::{Gamepad, GamepadButton};
5use bevy::input::keyboard::KeyCode;
6use bevy::prelude::*;
7
8#[derive(Debug, Clone, Copy, PartialEq, Eq)]
9pub struct CameraSlotKeyBinding {
10    pub slot: u8,
11    pub key: KeyCode,
12}
13
14#[derive(Debug, Clone, Copy, PartialEq, Eq)]
15pub struct CameraSlotGamepadBinding {
16    pub slot: u8,
17    pub button: GamepadButton,
18}
19
20#[derive(Debug, Clone, PartialEq, Eq, Default)]
21pub struct CameraGamepadBindings {
22    pub slots: Vec<CameraSlotGamepadBinding>,
23    pub next: Vec<GamepadButton>,
24    pub prev: Vec<GamepadButton>,
25}
26
27impl CameraGamepadBindings {
28    pub fn bind_slot(mut self, button: GamepadButton, slot: u8) -> Self {
29        self.slots.push(CameraSlotGamepadBinding { slot, button });
30        self
31    }
32
33    pub fn bind_next(mut self, button: GamepadButton) -> Self {
34        self.next.push(button);
35        self
36    }
37
38    pub fn bind_prev(mut self, button: GamepadButton) -> Self {
39        self.prev.push(button);
40        self
41    }
42}
43
44#[derive(Resource, Debug, Clone, PartialEq, Eq, Default)]
45pub struct CameraInputBindings {
46    pub slots: Vec<CameraSlotKeyBinding>,
47    pub next: Vec<KeyCode>,
48    pub prev: Vec<KeyCode>,
49    pub gamepad: Option<CameraGamepadBindings>,
50}
51
52impl CameraInputBindings {
53    pub fn bind_slot(mut self, key: KeyCode, slot: u8) -> Self {
54        self.slots.push(CameraSlotKeyBinding { slot, key });
55        self
56    }
57
58    pub fn bind_next(mut self, key: KeyCode) -> Self {
59        self.next.push(key);
60        self
61    }
62
63    pub fn bind_prev(mut self, key: KeyCode) -> Self {
64        self.prev.push(key);
65        self
66    }
67
68    pub fn with_gamepad(mut self, gamepad: CameraGamepadBindings) -> Self {
69        self.gamepad = Some(gamepad);
70        self
71    }
72}
73
74pub struct CameraInputBindingsPlugin;
75
76impl Plugin for CameraInputBindingsPlugin {
77    fn build(&self, app: &mut App) {
78        app.init_resource::<ButtonInput<KeyCode>>()
79            .init_resource::<CameraInputBindings>()
80            .add_systems(
81                Update,
82                emit_switch_camera_requests_from_input.in_set(CameraSwitchSystemSet::EmitRequests),
83            );
84    }
85}
86
87fn emit_switch_camera_requests_from_input(
88    bindings: Res<CameraInputBindings>,
89    keyboard: Res<ButtonInput<KeyCode>>,
90    gamepads: Query<&Gamepad>,
91    mut requests: MessageWriter<SwitchCameraRequest>,
92) {
93    let Some(request) = resolve_input_request(&bindings, &keyboard, &gamepads) else {
94        return;
95    };
96
97    requests.write(request);
98}
99
100fn resolve_input_request(
101    bindings: &CameraInputBindings,
102    keyboard: &ButtonInput<KeyCode>,
103    gamepads: &Query<&Gamepad>,
104) -> Option<SwitchCameraRequest> {
105    if let Some(slot) = bindings
106        .slots
107        .iter()
108        .find(|binding| keyboard.just_pressed(binding.key))
109        .map(|binding| binding.slot)
110    {
111        return Some(SwitchCameraRequest::ToSlot(slot));
112    }
113
114    if keyboard.any_just_pressed(bindings.next.iter().copied()) {
115        return Some(SwitchCameraRequest::CycleNext);
116    }
117
118    if keyboard.any_just_pressed(bindings.prev.iter().copied()) {
119        return Some(SwitchCameraRequest::CyclePrev);
120    }
121
122    let Some(gamepad) = &bindings.gamepad else {
123        return None;
124    };
125
126    if let Some(slot) = gamepad
127        .slots
128        .iter()
129        .find(|binding| any_gamepad_just_pressed(gamepads, [binding.button]))
130        .map(|binding| binding.slot)
131    {
132        return Some(SwitchCameraRequest::ToSlot(slot));
133    }
134
135    if any_gamepad_just_pressed(gamepads, gamepad.next.iter().copied()) {
136        return Some(SwitchCameraRequest::CycleNext);
137    }
138
139    if any_gamepad_just_pressed(gamepads, gamepad.prev.iter().copied()) {
140        return Some(SwitchCameraRequest::CyclePrev);
141    }
142
143    None
144}
145
146fn any_gamepad_just_pressed(
147    gamepads: &Query<&Gamepad>,
148    buttons: impl IntoIterator<Item = GamepadButton>,
149) -> bool {
150    let buttons = buttons.into_iter().collect::<Vec<_>>();
151    !buttons.is_empty()
152        && gamepads
153            .iter()
154            .any(|gamepad| gamepad.any_just_pressed(buttons.iter().copied()))
155}
156
157#[cfg(test)]
158mod tests {
159    use super::*;
160    use crate::{CameraSwitchPlugin, SwitchableCamera};
161    use bevy::camera::Camera;
162
163    fn test_app(bindings: CameraInputBindings) -> App {
164        let mut app = App::new();
165        app.add_plugins(MinimalPlugins)
166            .add_plugins((CameraSwitchPlugin, CameraInputBindingsPlugin))
167            .insert_resource(bindings);
168        app
169    }
170
171    fn spawn_switchable_camera(
172        app: &mut App,
173        slot: Option<u8>,
174        order_key: i32,
175        is_active: bool,
176    ) -> Entity {
177        app.world_mut()
178            .spawn((
179                Camera {
180                    is_active,
181                    ..default()
182                },
183                SwitchableCamera { slot, order_key },
184            ))
185            .id()
186    }
187
188    fn active_camera(app: &mut App) -> Option<Entity> {
189        let world = app.world_mut();
190        let mut query = world.query::<(Entity, &Camera)>();
191        query
192            .iter(world)
193            .find_map(|(entity, camera)| camera.is_active.then_some(entity))
194    }
195
196    fn press_key(app: &mut App, key: KeyCode) {
197        app.world_mut()
198            .resource_mut::<ButtonInput<KeyCode>>()
199            .press(key);
200    }
201
202    fn press_gamepad_button(app: &mut App, entity: Entity, button: GamepadButton) {
203        let mut gamepad = app.world_mut().get_mut::<Gamepad>(entity).unwrap();
204        gamepad.digital_mut().press(button);
205    }
206
207    #[test]
208    fn keyboard_slot_binding_emits_to_slot_request() {
209        let mut app = test_app(CameraInputBindings::default().bind_slot(KeyCode::Digit2, 2));
210        spawn_switchable_camera(&mut app, Some(1), 10, true);
211        let second = spawn_switchable_camera(&mut app, Some(2), 20, false);
212
213        press_key(&mut app, KeyCode::Digit2);
214        app.update();
215
216        assert_eq!(active_camera(&mut app), Some(second));
217    }
218
219    #[test]
220    fn keyboard_next_binding_cycles_forward() {
221        let mut app = test_app(CameraInputBindings::default().bind_next(KeyCode::KeyE));
222        spawn_switchable_camera(&mut app, Some(1), 10, true);
223        let second = spawn_switchable_camera(&mut app, Some(2), 20, false);
224
225        press_key(&mut app, KeyCode::KeyE);
226        app.update();
227
228        assert_eq!(active_camera(&mut app), Some(second));
229    }
230
231    #[test]
232    fn keyboard_prev_binding_cycles_backward() {
233        let mut app = test_app(CameraInputBindings::default().bind_prev(KeyCode::KeyQ));
234        let first = spawn_switchable_camera(&mut app, Some(1), 10, false);
235        spawn_switchable_camera(&mut app, Some(2), 20, true);
236
237        press_key(&mut app, KeyCode::KeyQ);
238        app.update();
239
240        assert_eq!(active_camera(&mut app), Some(first));
241    }
242
243    #[test]
244    fn gamepad_binding_cycles_forward() {
245        let bindings = CameraInputBindings::default()
246            .with_gamepad(CameraGamepadBindings::default().bind_next(GamepadButton::RightTrigger));
247        let mut app = test_app(bindings);
248        spawn_switchable_camera(&mut app, Some(1), 10, true);
249        let second = spawn_switchable_camera(&mut app, Some(2), 20, false);
250        let gamepad = app.world_mut().spawn(Gamepad::default()).id();
251
252        press_gamepad_button(&mut app, gamepad, GamepadButton::RightTrigger);
253        app.update();
254
255        assert_eq!(active_camera(&mut app), Some(second));
256    }
257}