1use bevy_app::prelude::*;
15use bevy_ecs::prelude::*;
16use bevy_hierarchy::DespawnRecursiveExt;
17use bevy_input::{
18 prelude::*,
19 touch::{TouchInput, TouchPhase},
20 ButtonState,
21};
22use bevy_math::Vec2;
23use bevy_reflect::prelude::*;
24use bevy_render::camera::RenderTarget;
25use bevy_utils::{tracing::debug, HashMap, HashSet};
26use bevy_window::{PrimaryWindow, WindowEvent, WindowRef};
27
28use crate::pointer::{
29 Location, PointerAction, PointerButton, PointerId, PointerInput, PointerLocation,
30 PressDirection,
31};
32
33use crate::PickSet;
34
35pub mod prelude {
39 pub use crate::input::PointerInputPlugin;
40}
41
42#[derive(Copy, Clone, Resource, Debug, Reflect)]
51#[reflect(Resource, Default)]
52pub struct PointerInputPlugin {
53 pub is_touch_enabled: bool,
55 pub is_mouse_enabled: bool,
57}
58
59impl PointerInputPlugin {
60 fn is_mouse_enabled(state: Res<Self>) -> bool {
61 state.is_mouse_enabled
62 }
63
64 fn is_touch_enabled(state: Res<Self>) -> bool {
65 state.is_touch_enabled
66 }
67}
68
69impl Default for PointerInputPlugin {
70 fn default() -> Self {
71 Self {
72 is_touch_enabled: true,
73 is_mouse_enabled: true,
74 }
75 }
76}
77
78impl Plugin for PointerInputPlugin {
79 fn build(&self, app: &mut App) {
80 app.insert_resource(*self)
81 .add_systems(Startup, spawn_mouse_pointer)
82 .add_systems(
83 First,
84 (
85 mouse_pick_events.run_if(PointerInputPlugin::is_mouse_enabled),
86 touch_pick_events.run_if(PointerInputPlugin::is_touch_enabled),
87 )
88 .chain()
89 .in_set(PickSet::Input),
90 )
91 .add_systems(
92 Last,
93 deactivate_touch_pointers.run_if(PointerInputPlugin::is_touch_enabled),
94 )
95 .register_type::<Self>()
96 .register_type::<PointerInputPlugin>();
97 }
98}
99
100pub fn spawn_mouse_pointer(mut commands: Commands) {
102 commands.spawn(PointerId::Mouse);
103}
104
105pub fn mouse_pick_events(
107 mut window_events: EventReader<WindowEvent>,
109 primary_window: Query<Entity, With<PrimaryWindow>>,
110 mut cursor_last: Local<Vec2>,
112 mut pointer_events: EventWriter<PointerInput>,
114) {
115 for window_event in window_events.read() {
116 match window_event {
117 WindowEvent::CursorMoved(event) => {
119 let location = Location {
120 target: match RenderTarget::Window(WindowRef::Entity(event.window))
121 .normalize(primary_window.get_single().ok())
122 {
123 Some(target) => target,
124 None => continue,
125 },
126 position: event.position,
127 };
128 pointer_events.send(PointerInput::new(
129 PointerId::Mouse,
130 location,
131 PointerAction::Moved {
132 delta: event.position - *cursor_last,
133 },
134 ));
135 *cursor_last = event.position;
136 }
137 WindowEvent::MouseButtonInput(input) => {
139 let location = Location {
140 target: match RenderTarget::Window(WindowRef::Entity(input.window))
141 .normalize(primary_window.get_single().ok())
142 {
143 Some(target) => target,
144 None => continue,
145 },
146 position: *cursor_last,
147 };
148 let button = match input.button {
149 MouseButton::Left => PointerButton::Primary,
150 MouseButton::Right => PointerButton::Secondary,
151 MouseButton::Middle => PointerButton::Middle,
152 MouseButton::Other(_) | MouseButton::Back | MouseButton::Forward => continue,
153 };
154 let direction = match input.state {
155 ButtonState::Pressed => PressDirection::Down,
156 ButtonState::Released => PressDirection::Up,
157 };
158 pointer_events.send(PointerInput::new(
159 PointerId::Mouse,
160 location,
161 PointerAction::Pressed { direction, button },
162 ));
163 }
164 _ => {}
165 }
166 }
167}
168
169pub fn touch_pick_events(
171 mut window_events: EventReader<WindowEvent>,
173 primary_window: Query<Entity, With<PrimaryWindow>>,
174 mut touch_cache: Local<HashMap<u64, TouchInput>>,
176 mut commands: Commands,
178 mut pointer_events: EventWriter<PointerInput>,
179) {
180 for window_event in window_events.read() {
181 if let WindowEvent::TouchInput(touch) = window_event {
182 let pointer = PointerId::Touch(touch.id);
183 let location = Location {
184 target: match RenderTarget::Window(WindowRef::Entity(touch.window))
185 .normalize(primary_window.get_single().ok())
186 {
187 Some(target) => target,
188 None => continue,
189 },
190 position: touch.position,
191 };
192 match touch.phase {
193 TouchPhase::Started => {
194 debug!("Spawning pointer {:?}", pointer);
195 commands.spawn((pointer, PointerLocation::new(location.clone())));
196
197 pointer_events.send(PointerInput::new(
198 pointer,
199 location,
200 PointerAction::Pressed {
201 direction: PressDirection::Down,
202 button: PointerButton::Primary,
203 },
204 ));
205
206 touch_cache.insert(touch.id, *touch);
207 }
208 TouchPhase::Moved => {
209 if let Some(last_touch) = touch_cache.get(&touch.id) {
211 if last_touch == touch {
212 continue;
213 }
214 pointer_events.send(PointerInput::new(
215 pointer,
216 location,
217 PointerAction::Moved {
218 delta: touch.position - last_touch.position,
219 },
220 ));
221 }
222 touch_cache.insert(touch.id, *touch);
223 }
224 TouchPhase::Ended => {
225 pointer_events.send(PointerInput::new(
226 pointer,
227 location,
228 PointerAction::Pressed {
229 direction: PressDirection::Up,
230 button: PointerButton::Primary,
231 },
232 ));
233 touch_cache.remove(&touch.id);
234 }
235 TouchPhase::Canceled => {
236 pointer_events.send(PointerInput::new(
237 pointer,
238 location,
239 PointerAction::Canceled,
240 ));
241 touch_cache.remove(&touch.id);
242 }
243 }
244 }
245 }
246}
247
248pub fn deactivate_touch_pointers(
253 mut commands: Commands,
254 mut despawn_list: Local<HashSet<(Entity, PointerId)>>,
255 pointers: Query<(Entity, &PointerId)>,
256 mut touches: EventReader<TouchInput>,
257) {
258 for touch in touches.read() {
259 if let TouchPhase::Ended | TouchPhase::Canceled = touch.phase {
260 for (entity, pointer) in &pointers {
261 if pointer.get_touch_id() == Some(touch.id) {
262 despawn_list.insert((entity, *pointer));
263 }
264 }
265 }
266 }
267 for (entity, pointer) in despawn_list.drain() {
269 debug!("Despawning pointer {:?}", pointer);
270 commands.entity(entity).despawn_recursive();
271 }
272}