1#![cfg_attr(not(test), warn(unused_crate_dependencies))]
15#![warn(clippy::print_stdout, clippy::print_stderr)]
17#![cfg_attr(target_pointer_width = "64", warn(clippy::trivially_copy_pass_by_ref))]
19#![cfg_attr(docsrs, feature(doc_auto_cfg))]
21#![no_std]
22
23pub mod keyboard;
24pub mod pointer;
25
26extern crate alloc;
27use alloc::{vec, vec::Vec};
28use dpi::PhysicalPosition;
29
30#[cfg(not(target_arch = "wasm32"))]
31extern crate std;
32
33#[cfg(not(target_arch = "wasm32"))]
34pub use std::time::Instant;
35
36#[cfg(target_arch = "wasm32")]
37pub use web_time::Instant;
38
39use ui_events::{
40 ScrollDelta,
41 keyboard::KeyboardEvent,
42 pointer::{
43 PointerButtonEvent, PointerEvent, PointerGesture, PointerGestureEvent, PointerId,
44 PointerInfo, PointerScrollEvent, PointerState, PointerType, PointerUpdate,
45 },
46};
47use winit::{
48 event::{ButtonSource, ElementState, Force, MouseScrollDelta, PointerSource, WindowEvent},
49 keyboard::ModifiersState,
50};
51
52#[derive(Debug, Default)]
70pub struct WindowEventReducer {
71 modifiers: ModifiersState,
73 primary_state: PointerState,
75 counter: TapCounter,
77 first_instant: Option<Instant>,
79}
80
81#[allow(clippy::cast_possible_truncation)]
82impl WindowEventReducer {
83 pub fn reduce(
85 &mut self,
86 scale_factor: f64,
87 we: &WindowEvent,
88 ) -> Option<WindowEventTranslation> {
89 const PRIMARY_MOUSE: PointerInfo = PointerInfo {
90 pointer_id: Some(PointerId::PRIMARY),
91 persistent_device_id: None,
93 pointer_type: PointerType::Mouse,
94 };
95
96 self.primary_state.scale_factor = scale_factor;
97
98 let time = Instant::now()
99 .duration_since(*self.first_instant.get_or_insert_with(Instant::now))
100 .as_nanos() as u64;
101
102 self.primary_state.time = time;
103
104 match we {
105 WindowEvent::ModifiersChanged(m) => {
106 self.modifiers = m.state();
107 self.primary_state.modifiers = keyboard::from_winit_modifier_state(self.modifiers);
108 None
109 }
110 WindowEvent::KeyboardInput { event, .. } => Some(WindowEventTranslation::Keyboard(
111 keyboard::from_winit_keyboard_event(event.clone(), self.modifiers),
112 )),
113 WindowEvent::PointerEntered { .. } => Some(WindowEventTranslation::Pointer(
114 PointerEvent::Enter(PRIMARY_MOUSE),
115 )),
116 WindowEvent::PointerLeft { .. } => Some(WindowEventTranslation::Pointer(
117 PointerEvent::Leave(PRIMARY_MOUSE),
118 )),
119 WindowEvent::PointerMoved {
120 position,
121 source: PointerSource::Mouse,
122 ..
123 } => {
124 self.primary_state.position = PhysicalPosition::new(position.x, position.y);
125
126 let info = PRIMARY_MOUSE;
127
128 Some(WindowEventTranslation::Pointer(self.counter.attach_count(
129 scale_factor,
130 PointerEvent::Move(PointerUpdate {
131 pointer: info,
132 current: self.primary_state.clone(),
133 coalesced: vec![],
134 predicted: vec![],
135 }),
136 )))
137 }
138
139 WindowEvent::PointerMoved {
140 position,
141 source: PointerSource::Touch { finger_id, force },
142 ..
143 } => {
144 self.primary_state.position = PhysicalPosition::new(position.x, position.y);
145
146 self.primary_state.pressure = {
147 match force {
148 Some(Force::Calibrated { force, .. }) => (force * 0.5) as f32,
149 Some(Force::Normalized(q)) => *q as f32,
150 _ => 0.5,
151 }
152 };
153
154 let info = PointerInfo {
155 pointer_id: PointerId::new(finger_id.into_raw() as u64),
156 persistent_device_id: None,
158 pointer_type: PointerType::Touch,
159 };
160
161 Some(WindowEventTranslation::Pointer(self.counter.attach_count(
162 scale_factor,
163 PointerEvent::Move(PointerUpdate {
164 pointer: info,
165 current: self.primary_state.clone(),
166 coalesced: vec![],
167 predicted: vec![],
168 }),
169 )))
170 }
171
172 WindowEvent::PointerButton {
173 state,
174 button: ButtonSource::Touch { finger_id, force },
175 position,
176 ..
177 } => {
178 let info = PointerInfo {
179 pointer_id: PointerId::new(finger_id.into_raw() as u64),
180 persistent_device_id: None,
182 pointer_type: PointerType::Touch,
183 };
184
185 let pointer_state = PointerState {
186 time,
187 position: PhysicalPosition::new(position.x, position.y),
188 modifiers: self.primary_state.modifiers,
189 pressure: {
190 match force {
191 Some(Force::Calibrated { force, .. }) => (force * 0.5) as f32,
192 Some(Force::Normalized(q)) => *q as f32,
193 _ => 0.5,
194 }
195 },
196 ..Default::default()
197 };
198
199 Some(WindowEventTranslation::Pointer(self.counter.attach_count(
200 scale_factor,
201 match state {
202 ElementState::Pressed => PointerEvent::Down(PointerButtonEvent {
203 pointer: info,
204 button: None,
205 state: pointer_state,
206 }),
207 ElementState::Released => PointerEvent::Up(PointerButtonEvent {
208 pointer: info,
209 button: None,
210 state: pointer_state,
211 }),
212 },
213 )))
214 }
215 WindowEvent::PointerButton {
216 state: ElementState::Pressed,
217 button: ButtonSource::Mouse(button),
218 ..
219 } => {
220 let button = pointer::try_from_winit_button(*button);
221 if let Some(button) = button {
222 self.primary_state.buttons.insert(button);
223 }
224
225 Some(WindowEventTranslation::Pointer(self.counter.attach_count(
226 scale_factor,
227 PointerEvent::Down(PointerButtonEvent {
228 pointer: PRIMARY_MOUSE,
229 button,
230 state: self.primary_state.clone(),
231 }),
232 )))
233 }
234 WindowEvent::PointerButton {
235 state: ElementState::Released,
236 button: ButtonSource::Mouse(button),
237 ..
238 } => {
239 let button = pointer::try_from_winit_button(*button);
240 if let Some(button) = button {
241 self.primary_state.buttons.remove(button);
242 }
243
244 Some(WindowEventTranslation::Pointer(self.counter.attach_count(
245 scale_factor,
246 PointerEvent::Up(PointerButtonEvent {
247 pointer: PRIMARY_MOUSE,
248 button,
249 state: self.primary_state.clone(),
250 }),
251 )))
252 }
253 WindowEvent::MouseWheel { delta, .. } => Some(WindowEventTranslation::Pointer(
254 PointerEvent::Scroll(PointerScrollEvent {
255 pointer: PRIMARY_MOUSE,
256 delta: match *delta {
257 MouseScrollDelta::LineDelta(x, y) => ScrollDelta::LineDelta(x, y),
258 MouseScrollDelta::PixelDelta(p) => {
259 ScrollDelta::PixelDelta(PhysicalPosition::new(p.x, p.y))
260 }
261 },
262 state: self.primary_state.clone(),
263 }),
264 )),
265 WindowEvent::PinchGesture { delta, .. } if delta.is_finite() => Some(
267 WindowEventTranslation::Pointer(PointerEvent::Gesture(PointerGestureEvent {
268 pointer: PRIMARY_MOUSE,
269 gesture: PointerGesture::Pinch(*delta as f32),
270 state: self.primary_state.clone(),
271 })),
272 ),
273 WindowEvent::RotationGesture { delta, .. } if delta.is_finite() => {
275 Some(WindowEventTranslation::Pointer(PointerEvent::Gesture(
276 PointerGestureEvent {
277 pointer: PRIMARY_MOUSE,
278 gesture: PointerGesture::Rotate((-*delta).to_radians()),
280 state: self.primary_state.clone(),
281 },
282 )))
283 }
284
285 _ => None,
286 }
287 }
288}
289
290#[derive(Debug)]
292pub enum WindowEventTranslation {
293 Keyboard(KeyboardEvent),
295 Pointer(PointerEvent),
297}
298
299#[derive(Clone, Debug)]
300struct TapState {
301 pointer_id: Option<PointerId>,
303 down_time: u64,
305 up_time: u64,
309 count: u8,
311 x: f64,
313 y: f64,
315}
316
317#[derive(Debug, Default)]
318struct TapCounter {
319 taps: Vec<TapState>,
320}
321
322impl TapCounter {
323 fn attach_count(&mut self, scale_factor: f64, e: PointerEvent) -> PointerEvent {
325 match e {
326 PointerEvent::Down(mut event) => {
327 let pointer_id = event.pointer.pointer_id;
328 let position = event.state.position;
329 let time = event.state.time;
330
331 let slop = match event.pointer.pointer_type {
332 PointerType::Touch => 12.0,
335 PointerType::Pen => 6.0,
336 _ => 2.0,
341 } * core::f64::consts::SQRT_2
342 * scale_factor;
343
344 if let Some(tap) =
345 self.taps.iter_mut().find(|TapState { x, y, up_time, .. }| {
346 let dx = (x - position.x).abs();
347 let dy = (y - position.y).abs();
348 (dx * dx + dy * dy).sqrt() < slop && (up_time + 500_000_000) > time
349 })
350 {
351 let count = tap.count + 1;
352 event.state.count = count;
353 tap.count = count;
354 tap.pointer_id = pointer_id;
355 tap.down_time = time;
356 tap.up_time = time;
357 tap.x = position.x;
358 tap.y = position.y;
359 } else {
360 let s = TapState {
361 pointer_id,
362 down_time: time,
363 up_time: time,
364 count: 1,
365 x: position.x,
366 y: position.y,
367 };
368 self.taps.push(s);
369 event.state.count = 1;
370 };
371 self.clear_expired(time);
372 PointerEvent::Down(event)
373 }
374 PointerEvent::Up(mut event) => {
375 let p_id = event.pointer.pointer_id;
376 if let Some(tap) = self.taps.iter_mut().find(|state| state.pointer_id == p_id) {
377 tap.up_time = event.state.time;
378 event.state.count = tap.count;
379 }
380 PointerEvent::Up(event)
381 }
382 PointerEvent::Move(PointerUpdate {
383 pointer,
384 mut current,
385 mut coalesced,
386 mut predicted,
387 }) => {
388 if let Some(TapState { count, .. }) = self
389 .taps
390 .iter()
391 .find(
392 |TapState {
393 pointer_id,
394 down_time,
395 up_time,
396 ..
397 }| {
398 *pointer_id == pointer.pointer_id && down_time == up_time
399 },
400 )
401 .cloned()
402 {
403 current.count = count;
404 for event in coalesced.iter_mut() {
405 event.count = count;
406 }
407 for event in predicted.iter_mut() {
408 event.count = count;
409 }
410 PointerEvent::Move(PointerUpdate {
411 pointer,
412 current,
413 coalesced,
414 predicted,
415 })
416 } else {
417 PointerEvent::Move(PointerUpdate {
418 pointer,
419 current,
420 coalesced,
421 predicted,
422 })
423 }
424 }
425 PointerEvent::Cancel(p) => {
426 self.taps
427 .retain(|TapState { pointer_id, .. }| *pointer_id != p.pointer_id);
428 PointerEvent::Cancel(p)
429 }
430 PointerEvent::Leave(p) => {
431 self.taps
432 .retain(|TapState { pointer_id, .. }| *pointer_id != p.pointer_id);
433 PointerEvent::Leave(p)
434 }
435 e
436 @ (PointerEvent::Enter(..) | PointerEvent::Scroll(..) | PointerEvent::Gesture(..)) => e,
437 }
438 }
439
440 fn clear_expired(&mut self, t: u64) {
445 self.taps.retain(
446 |TapState {
447 down_time, up_time, ..
448 }| { down_time == up_time || (up_time + 500_000_000) > t },
449 );
450 }
451}
452
453#[cfg(test)]
454mod tests {
455 #[test]
458 fn dummy_test_until_we_have_a_real_test() {}
459}