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}