1use bevy_app::prelude::*;
15use bevy_ecs::prelude::*;
16use bevy_input::{
17 mouse::MouseWheel,
18 prelude::*,
19 touch::{TouchInput, TouchPhase},
20 ButtonState,
21};
22use bevy_math::Vec2;
23use bevy_platform::collections::{HashMap, HashSet};
24use bevy_reflect::prelude::*;
25use bevy_render::camera::RenderTarget;
26use bevy_window::{PrimaryWindow, WindowEvent, WindowRef};
27use tracing::debug;
28
29use crate::pointer::{
30 Location, PointerAction, PointerButton, PointerId, PointerInput, PointerLocation,
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, Clone)]
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.single().ok())
122 {
123 Some(target) => target,
124 None => continue,
125 },
126 position: event.position,
127 };
128 pointer_events.write(PointerInput::new(
129 PointerId::Mouse,
130 location,
131 PointerAction::Move {
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.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 action = match input.state {
155 ButtonState::Pressed => PointerAction::Press(button),
156 ButtonState::Released => PointerAction::Release(button),
157 };
158 pointer_events.write(PointerInput::new(PointerId::Mouse, location, action));
159 }
160 WindowEvent::MouseWheel(event) => {
161 let MouseWheel { unit, x, y, window } = *event;
162
163 let location = Location {
164 target: match RenderTarget::Window(WindowRef::Entity(window))
165 .normalize(primary_window.single().ok())
166 {
167 Some(target) => target,
168 None => continue,
169 },
170 position: *cursor_last,
171 };
172
173 let action = PointerAction::Scroll { x, y, unit };
174
175 pointer_events.write(PointerInput::new(PointerId::Mouse, location, action));
176 }
177 _ => {}
178 }
179 }
180}
181
182pub fn touch_pick_events(
184 mut window_events: EventReader<WindowEvent>,
186 primary_window: Query<Entity, With<PrimaryWindow>>,
187 mut touch_cache: Local<HashMap<u64, TouchInput>>,
189 mut commands: Commands,
191 mut pointer_events: EventWriter<PointerInput>,
192) {
193 for window_event in window_events.read() {
194 if let WindowEvent::TouchInput(touch) = window_event {
195 let pointer = PointerId::Touch(touch.id);
196 let location = Location {
197 target: match RenderTarget::Window(WindowRef::Entity(touch.window))
198 .normalize(primary_window.single().ok())
199 {
200 Some(target) => target,
201 None => continue,
202 },
203 position: touch.position,
204 };
205 match touch.phase {
206 TouchPhase::Started => {
207 debug!("Spawning pointer {:?}", pointer);
208 commands.spawn((pointer, PointerLocation::new(location.clone())));
209
210 pointer_events.write(PointerInput::new(
211 pointer,
212 location,
213 PointerAction::Press(PointerButton::Primary),
214 ));
215
216 touch_cache.insert(touch.id, *touch);
217 }
218 TouchPhase::Moved => {
219 if let Some(last_touch) = touch_cache.get(&touch.id) {
221 if last_touch == touch {
222 continue;
223 }
224 pointer_events.write(PointerInput::new(
225 pointer,
226 location,
227 PointerAction::Move {
228 delta: touch.position - last_touch.position,
229 },
230 ));
231 }
232 touch_cache.insert(touch.id, *touch);
233 }
234 TouchPhase::Ended => {
235 pointer_events.write(PointerInput::new(
236 pointer,
237 location,
238 PointerAction::Release(PointerButton::Primary),
239 ));
240 touch_cache.remove(&touch.id);
241 }
242 TouchPhase::Canceled => {
243 pointer_events.write(PointerInput::new(
244 pointer,
245 location,
246 PointerAction::Cancel,
247 ));
248 touch_cache.remove(&touch.id);
249 }
250 }
251 }
252 }
253}
254
255pub fn deactivate_touch_pointers(
260 mut commands: Commands,
261 mut despawn_list: Local<HashSet<(Entity, PointerId)>>,
262 pointers: Query<(Entity, &PointerId)>,
263 mut touches: EventReader<TouchInput>,
264) {
265 for touch in touches.read() {
266 if let TouchPhase::Ended | TouchPhase::Canceled = touch.phase {
267 for (entity, pointer) in &pointers {
268 if pointer.get_touch_id() == Some(touch.id) {
269 despawn_list.insert((entity, *pointer));
270 }
271 }
272 }
273 }
274 for (entity, pointer) in despawn_list.drain() {
276 debug!("Despawning pointer {:?}", pointer);
277 commands.entity(entity).despawn();
278 }
279}