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#![no_std]
21
22pub mod keyboard;
23pub mod pointer;
24
25extern crate alloc;
26use alloc::{vec, vec::Vec};
27
28#[cfg(not(target_arch = "wasm32"))]
29extern crate std;
30
31#[cfg(not(target_arch = "wasm32"))]
32pub use std::time::Instant;
33
34#[cfg(target_arch = "wasm32")]
35pub use web_time::Instant;
36
37use ui_events::{
38 ScrollDelta,
39 keyboard::KeyboardEvent,
40 pointer::{
41 PointerButtonEvent, PointerEvent, PointerGesture, PointerGestureEvent, PointerId,
42 PointerInfo, PointerScrollEvent, PointerState, PointerType, PointerUpdate,
43 },
44};
45use winit::{
46 event::{ElementState, Force, MouseScrollDelta, Touch, TouchPhase, WindowEvent},
47 keyboard::ModifiersState,
48};
49
50#[derive(Debug, Default)]
68pub struct WindowEventReducer {
69 modifiers: ModifiersState,
71 primary_state: PointerState,
73 counter: TapCounter,
75 first_instant: Option<Instant>,
77}
78
79#[allow(
80 clippy::cast_possible_truncation,
81 reason = "There is no alternative to truncation here."
82)]
83impl WindowEventReducer {
84 pub fn reduce(
86 &mut self,
87 scale_factor: f64,
88 we: &WindowEvent,
89 ) -> Option<WindowEventTranslation> {
90 const PRIMARY_MOUSE: PointerInfo = PointerInfo {
91 pointer_id: Some(PointerId::PRIMARY),
92 persistent_device_id: None,
94 pointer_type: PointerType::Mouse,
95 };
96
97 let time = Instant::now()
98 .duration_since(*self.first_instant.get_or_insert_with(Instant::now))
99 .as_nanos() as u64;
100
101 self.primary_state.time = time;
102 self.primary_state.scale_factor = scale_factor;
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::CursorEntered { .. } => Some(WindowEventTranslation::Pointer(
114 PointerEvent::Enter(PRIMARY_MOUSE),
115 )),
116 WindowEvent::CursorLeft { .. } => Some(WindowEventTranslation::Pointer(
117 PointerEvent::Leave(PRIMARY_MOUSE),
118 )),
119 WindowEvent::CursorMoved { position, .. } => {
120 self.primary_state.position = *position;
121
122 Some(WindowEventTranslation::Pointer(self.counter.attach_count(
123 scale_factor,
124 PointerEvent::Move(PointerUpdate {
125 pointer: PRIMARY_MOUSE,
126 current: self.primary_state.clone(),
127 coalesced: vec![],
128 predicted: vec![],
129 }),
130 )))
131 }
132 WindowEvent::MouseInput {
133 state: ElementState::Pressed,
134 button,
135 ..
136 } => {
137 let button = pointer::try_from_winit_button(*button);
138 if let Some(button) = button {
139 self.primary_state.buttons.insert(button);
140 }
141
142 Some(WindowEventTranslation::Pointer(self.counter.attach_count(
143 scale_factor,
144 PointerEvent::Down(PointerButtonEvent {
145 pointer: PRIMARY_MOUSE,
146 button,
147 state: self.primary_state.clone(),
148 }),
149 )))
150 }
151 WindowEvent::MouseInput {
152 state: ElementState::Released,
153 button,
154 ..
155 } => {
156 let button = pointer::try_from_winit_button(*button);
157 if let Some(button) = button {
158 self.primary_state.buttons.remove(button);
159 }
160
161 Some(WindowEventTranslation::Pointer(self.counter.attach_count(
162 scale_factor,
163 PointerEvent::Up(PointerButtonEvent {
164 pointer: PRIMARY_MOUSE,
165 button,
166 state: self.primary_state.clone(),
167 }),
168 )))
169 }
170 WindowEvent::MouseWheel { delta, .. } => Some(WindowEventTranslation::Pointer(
171 PointerEvent::Scroll(PointerScrollEvent {
172 pointer: PRIMARY_MOUSE,
173 delta: match *delta {
174 MouseScrollDelta::LineDelta(x, y) => ScrollDelta::LineDelta(x, y),
175 MouseScrollDelta::PixelDelta(p) => ScrollDelta::PixelDelta(p),
176 },
177 state: self.primary_state.clone(),
178 }),
179 )),
180 WindowEvent::PinchGesture { delta, .. } if delta.is_finite() => Some(
182 WindowEventTranslation::Pointer(PointerEvent::Gesture(PointerGestureEvent {
183 pointer: PRIMARY_MOUSE,
184 gesture: PointerGesture::Pinch(*delta as f32),
185 state: self.primary_state.clone(),
186 })),
187 ),
188 WindowEvent::RotationGesture { delta, .. } if delta.is_finite() => {
190 Some(WindowEventTranslation::Pointer(PointerEvent::Gesture(
191 PointerGestureEvent {
192 pointer: PRIMARY_MOUSE,
193 gesture: PointerGesture::Rotate((-*delta).to_radians()),
195 state: self.primary_state.clone(),
196 },
197 )))
198 }
199 WindowEvent::Touch(Touch {
200 phase,
201 id,
202 location,
203 force,
204 ..
205 }) => {
206 let pointer = PointerInfo {
207 pointer_id: PointerId::new(id.saturating_add(1)),
208 pointer_type: PointerType::Touch,
209 persistent_device_id: None,
210 };
211
212 use TouchPhase::*;
213
214 let state = PointerState {
215 time,
216 position: *location,
217 modifiers: self.primary_state.modifiers,
218 pressure: if matches!(phase, Ended | Cancelled) {
219 0.0
220 } else {
221 match force {
222 Some(Force::Calibrated { force, .. }) => (force * 0.5) as f32,
223 Some(Force::Normalized(q)) => *q as f32,
224 _ => 0.5,
225 }
226 },
227 ..Default::default()
228 };
229
230 Some(WindowEventTranslation::Pointer(self.counter.attach_count(
231 scale_factor,
232 match phase {
233 Started => PointerEvent::Down(PointerButtonEvent {
234 pointer,
235 button: None,
236 state,
237 }),
238 Moved => PointerEvent::Move(PointerUpdate {
239 pointer,
240 current: state,
241 coalesced: vec![],
242 predicted: vec![],
243 }),
244 Cancelled => PointerEvent::Cancel(pointer),
245 Ended => PointerEvent::Up(PointerButtonEvent {
246 pointer,
247 button: None,
248 state,
249 }),
250 },
251 )))
252 }
253 _ => None,
254 }
255 }
256}
257
258#[derive(Debug)]
260pub enum WindowEventTranslation {
261 Keyboard(KeyboardEvent),
263 Pointer(PointerEvent),
265}
266
267#[derive(Clone, Debug)]
268struct TapState {
269 pointer_id: Option<PointerId>,
271 down_time: u64,
273 up_time: u64,
277 count: u8,
279 x: f64,
281 y: f64,
283}
284
285#[derive(Debug, Default)]
286struct TapCounter {
287 taps: Vec<TapState>,
288}
289
290impl TapCounter {
291 fn attach_count(&mut self, scale_factor: f64, e: PointerEvent) -> PointerEvent {
293 match e {
294 PointerEvent::Down(mut event) => {
295 let pointer_id = event.pointer.pointer_id;
296 let position = event.state.position;
297 let time = event.state.time;
298
299 let slop = match event.pointer.pointer_type {
300 PointerType::Touch => 12.0,
303 PointerType::Pen => 6.0,
304 _ => 2.0,
309 } * core::f64::consts::SQRT_2
310 * scale_factor;
311
312 if let Some(tap) =
313 self.taps.iter_mut().find(|TapState { x, y, up_time, .. }| {
314 let dx = (x - position.x).abs();
315 let dy = (y - position.y).abs();
316 (dx * dx + dy * dy).sqrt() < slop && (up_time + 500_000_000) > time
317 })
318 {
319 let count = tap.count + 1;
320 event.state.count = count;
321 tap.count = count;
322 tap.pointer_id = pointer_id;
323 tap.down_time = time;
324 tap.up_time = time;
325 tap.x = position.x;
326 tap.y = position.y;
327 } else {
328 let s = TapState {
329 pointer_id,
330 down_time: time,
331 up_time: time,
332 count: 1,
333 x: position.x,
334 y: position.y,
335 };
336 if let Some(t) = self
337 .taps
338 .iter_mut()
339 .find(|state| state.pointer_id == pointer_id)
340 {
341 *t = s;
342 } else {
343 self.taps.push(s);
344 }
345 event.state.count = 1;
346 };
347 self.clear_expired(time);
348 PointerEvent::Down(event)
349 }
350 PointerEvent::Up(mut event) => {
351 let p_id = event.pointer.pointer_id;
352 if let Some(tap) = self.taps.iter_mut().find(|state| state.pointer_id == p_id) {
353 tap.up_time = event.state.time;
354 event.state.count = tap.count;
355 }
356 PointerEvent::Up(event)
357 }
358 PointerEvent::Move(PointerUpdate {
359 pointer,
360 mut current,
361 mut coalesced,
362 mut predicted,
363 }) => {
364 if let Some(TapState { count, .. }) = self
365 .taps
366 .iter()
367 .find(
368 |TapState {
369 pointer_id,
370 down_time,
371 up_time,
372 ..
373 }| {
374 *pointer_id == pointer.pointer_id && down_time == up_time
375 },
376 )
377 .cloned()
378 {
379 current.count = count;
380 for event in coalesced.iter_mut() {
381 event.count = count;
382 }
383 for event in predicted.iter_mut() {
384 event.count = count;
385 }
386 PointerEvent::Move(PointerUpdate {
387 pointer,
388 current,
389 coalesced,
390 predicted,
391 })
392 } else {
393 PointerEvent::Move(PointerUpdate {
394 pointer,
395 current,
396 coalesced,
397 predicted,
398 })
399 }
400 }
401 PointerEvent::Cancel(p) => {
402 self.taps
403 .retain(|TapState { pointer_id, .. }| *pointer_id != p.pointer_id);
404 PointerEvent::Cancel(p)
405 }
406 PointerEvent::Leave(p) => {
407 self.taps
408 .retain(|TapState { pointer_id, .. }| *pointer_id != p.pointer_id);
409 PointerEvent::Leave(p)
410 }
411 e
412 @ (PointerEvent::Enter(..) | PointerEvent::Scroll(..) | PointerEvent::Gesture(..)) => e,
413 }
414 }
415
416 fn clear_expired(&mut self, t: u64) {
421 self.taps.retain(
422 |TapState {
423 down_time, up_time, ..
424 }| { down_time == up_time || (up_time + 500_000_000) > t },
425 );
426 }
427}
428
429#[cfg(test)]
430mod tests {
431 #[test]
434 fn dummy_test_until_we_have_a_real_test() {}
435}