1use bevy_app::prelude::*;
15use bevy_camera::RenderTarget;
16use bevy_ecs::prelude::*;
17use bevy_input::{
18 mouse::MouseWheel,
19 prelude::*,
20 touch::{TouchInput, TouchPhase},
21 ButtonState,
22};
23use bevy_math::Vec2;
24use bevy_platform::collections::{HashMap, HashSet};
25use bevy_reflect::prelude::*;
26use bevy_window::{PrimaryWindow, WindowEvent, WindowRef};
27use tracing::debug;
28
29use crate::pointer::{
30 Location, PointerAction, PointerButton, PointerId, PointerInput, PointerLocation,
31};
32
33use crate::PickingSystems;
34
35pub mod prelude {
39 pub use crate::input::PointerInputPlugin;
40}
41
42#[derive(Copy, Clone, Resource, Debug, Reflect)]
43#[reflect(Resource, Default, Clone)]
44pub struct PointerInputSettings {
59 pub is_touch_enabled: bool,
61 pub is_mouse_enabled: bool,
63}
64
65impl PointerInputSettings {
66 fn is_mouse_enabled(state: Res<Self>) -> bool {
67 state.is_mouse_enabled
68 }
69
70 fn is_touch_enabled(state: Res<Self>) -> bool {
71 state.is_touch_enabled
72 }
73}
74
75impl Default for PointerInputSettings {
76 fn default() -> Self {
77 Self {
78 is_touch_enabled: true,
79 is_mouse_enabled: true,
80 }
81 }
82}
83
84pub struct PointerInputPlugin;
94
95impl Plugin for PointerInputPlugin {
96 fn build(&self, app: &mut App) {
97 app.init_resource::<PointerInputSettings>()
98 .add_systems(Startup, spawn_mouse_pointer)
99 .add_systems(
100 First,
101 (
102 mouse_pick_events.run_if(PointerInputSettings::is_mouse_enabled),
103 touch_pick_events.run_if(PointerInputSettings::is_touch_enabled),
104 )
105 .chain()
106 .in_set(PickingSystems::Input),
107 )
108 .add_systems(
109 Last,
110 deactivate_touch_pointers.run_if(PointerInputSettings::is_touch_enabled),
111 );
112 }
113}
114
115pub fn spawn_mouse_pointer(mut commands: Commands) {
117 commands.spawn(PointerId::Mouse);
118}
119
120pub fn mouse_pick_events(
122 mut window_events: MessageReader<WindowEvent>,
124 primary_window: Query<Entity, With<PrimaryWindow>>,
125 mut cursor_last: Local<Vec2>,
127 mut pointer_inputs: MessageWriter<PointerInput>,
129) {
130 for window_event in window_events.read() {
131 match window_event {
132 WindowEvent::CursorMoved(event) => {
134 let location = Location {
135 target: match RenderTarget::Window(WindowRef::Entity(event.window))
136 .normalize(primary_window.single().ok())
137 {
138 Some(target) => target,
139 None => continue,
140 },
141 position: event.position,
142 };
143 pointer_inputs.write(PointerInput::new(
144 PointerId::Mouse,
145 location,
146 PointerAction::Move {
147 delta: event.position - *cursor_last,
148 },
149 ));
150 *cursor_last = event.position;
151 }
152 WindowEvent::MouseButtonInput(input) => {
154 let location = Location {
155 target: match RenderTarget::Window(WindowRef::Entity(input.window))
156 .normalize(primary_window.single().ok())
157 {
158 Some(target) => target,
159 None => continue,
160 },
161 position: *cursor_last,
162 };
163 let button = match input.button {
164 MouseButton::Left => PointerButton::Primary,
165 MouseButton::Right => PointerButton::Secondary,
166 MouseButton::Middle => PointerButton::Middle,
167 MouseButton::Other(_) | MouseButton::Back | MouseButton::Forward => continue,
168 };
169 let action = match input.state {
170 ButtonState::Pressed => PointerAction::Press(button),
171 ButtonState::Released => PointerAction::Release(button),
172 };
173 pointer_inputs.write(PointerInput::new(PointerId::Mouse, location, action));
174 }
175 WindowEvent::MouseWheel(event) => {
176 let MouseWheel { unit, x, y, window } = *event;
177
178 let location = Location {
179 target: match RenderTarget::Window(WindowRef::Entity(window))
180 .normalize(primary_window.single().ok())
181 {
182 Some(target) => target,
183 None => continue,
184 },
185 position: *cursor_last,
186 };
187
188 let action = PointerAction::Scroll { x, y, unit };
189
190 pointer_inputs.write(PointerInput::new(PointerId::Mouse, location, action));
191 }
192 _ => {}
193 }
194 }
195}
196
197pub fn touch_pick_events(
199 mut window_events: MessageReader<WindowEvent>,
201 primary_window: Query<Entity, With<PrimaryWindow>>,
202 mut touch_cache: Local<HashMap<u64, TouchInput>>,
204 mut commands: Commands,
206 mut pointer_inputs: MessageWriter<PointerInput>,
207) {
208 for window_event in window_events.read() {
209 if let WindowEvent::TouchInput(touch) = window_event {
210 let pointer = PointerId::Touch(touch.id);
211 let location = Location {
212 target: match RenderTarget::Window(WindowRef::Entity(touch.window))
213 .normalize(primary_window.single().ok())
214 {
215 Some(target) => target,
216 None => continue,
217 },
218 position: touch.position,
219 };
220 match touch.phase {
221 TouchPhase::Started => {
222 debug!("Spawning pointer {:?}", pointer);
223 commands.spawn((pointer, PointerLocation::new(location.clone())));
224
225 pointer_inputs.write(PointerInput::new(
226 pointer,
227 location,
228 PointerAction::Press(PointerButton::Primary),
229 ));
230
231 touch_cache.insert(touch.id, *touch);
232 }
233 TouchPhase::Moved => {
234 if let Some(last_touch) = touch_cache.get(&touch.id) {
236 if last_touch == touch {
237 continue;
238 }
239 pointer_inputs.write(PointerInput::new(
240 pointer,
241 location,
242 PointerAction::Move {
243 delta: touch.position - last_touch.position,
244 },
245 ));
246 }
247 touch_cache.insert(touch.id, *touch);
248 }
249 TouchPhase::Ended => {
250 pointer_inputs.write(PointerInput::new(
251 pointer,
252 location,
253 PointerAction::Release(PointerButton::Primary),
254 ));
255 touch_cache.remove(&touch.id);
256 }
257 TouchPhase::Canceled => {
258 pointer_inputs.write(PointerInput::new(
259 pointer,
260 location,
261 PointerAction::Cancel,
262 ));
263 touch_cache.remove(&touch.id);
264 }
265 }
266 }
267 }
268}
269
270pub fn deactivate_touch_pointers(
275 mut commands: Commands,
276 mut despawn_list: Local<HashSet<(Entity, PointerId)>>,
277 pointers: Query<(Entity, &PointerId)>,
278 mut touches: MessageReader<TouchInput>,
279) {
280 for touch in touches.read() {
281 if let TouchPhase::Ended | TouchPhase::Canceled = touch.phase {
282 for (entity, pointer) in &pointers {
283 if pointer.get_touch_id() == Some(touch.id) {
284 despawn_list.insert((entity, *pointer));
285 }
286 }
287 }
288 }
289 for (entity, pointer) in despawn_list.drain() {
291 debug!("Despawning pointer {:?}", pointer);
292 commands.entity(entity).despawn();
293 }
294}