three_d/window/winit_window/
frame_input_generator.rs

1use super::FrameInput;
2use crate::control::*;
3use crate::core::*;
4#[cfg(target_arch = "wasm32")]
5use instant::Instant;
6#[cfg(not(target_arch = "wasm32"))]
7use std::time::Instant;
8use winit::dpi::PhysicalSize;
9use winit::event::TouchPhase;
10use winit::event::WindowEvent;
11
12///
13/// Use this to generate [FrameInput] for a new frame with a custom [winit](https://crates.io/crates/winit) window.
14/// [FrameInput] is automatically generated if using the default [Window](crate::window::Window).
15///
16pub struct FrameInputGenerator {
17    last_time: Instant,
18    first_frame: bool,
19    events: Vec<Event>,
20    accumulated_time: f64,
21    viewport: Viewport,
22    window_width: u32,
23    window_height: u32,
24    device_pixel_ratio: f64,
25    cursor_pos: Option<LogicalPoint>,
26    finger_id: Option<u64>,
27    secondary_cursor_pos: Option<LogicalPoint>,
28    secondary_finger_id: Option<u64>,
29    modifiers: Modifiers,
30    mouse_pressed: Option<MouseButton>,
31}
32
33impl FrameInputGenerator {
34    ///
35    /// Creates a new frame input generator.
36    ///
37    fn new(size: PhysicalSize<u32>, device_pixel_ratio: f64) -> Self {
38        let (window_width, window_height): (u32, u32) =
39            size.to_logical::<f32>(device_pixel_ratio).into();
40        Self {
41            events: Vec::new(),
42            accumulated_time: 0.0,
43            viewport: Viewport::new_at_origo(size.width, size.height),
44            window_width,
45            window_height,
46            device_pixel_ratio,
47            first_frame: true,
48            last_time: Instant::now(),
49            cursor_pos: None,
50            finger_id: None,
51            secondary_cursor_pos: None,
52            secondary_finger_id: None,
53            modifiers: Modifiers::default(),
54            mouse_pressed: None,
55        }
56    }
57
58    ///
59    /// Creates a new frame input generator from a [winit](https://crates.io/crates/winit) window.
60    ///
61    pub fn from_winit_window(window: &winit::window::Window) -> Self {
62        Self::new(window.inner_size(), window.scale_factor())
63    }
64
65    ///
66    /// Generates [FrameInput] for a new frame. This should be called each frame and the generated data should only be used for one frame.
67    ///
68    pub fn generate(&mut self, context: &Context) -> FrameInput {
69        let now = Instant::now();
70        let duration = now.duration_since(self.last_time);
71        let elapsed_time =
72            duration.as_secs() as f64 * 1000.0 + duration.subsec_nanos() as f64 * 1e-6;
73        self.accumulated_time += elapsed_time;
74        self.last_time = now;
75
76        let frame_input = FrameInput {
77            events: self.events.drain(..).collect(),
78            elapsed_time,
79            accumulated_time: self.accumulated_time,
80            viewport: self.viewport,
81            window_width: self.window_width,
82            window_height: self.window_height,
83            device_pixel_ratio: self.device_pixel_ratio as f32,
84            first_frame: self.first_frame,
85            context: context.clone(),
86        };
87        self.first_frame = false;
88
89        #[cfg(not(target_arch = "wasm32"))]
90        if let Some(exit_time) = option_env!("THREE_D_EXIT").map(|v| v.parse::<f64>().unwrap()) {
91            if exit_time < frame_input.accumulated_time {
92                #[cfg(feature = "image")]
93                if let Some(path) = option_env!("THREE_D_SCREENSHOT") {
94                    let pixels = frame_input.screen().read_color::<[u8; 4]>();
95                    let img = image::DynamicImage::ImageRgba8(
96                        image::ImageBuffer::from_raw(
97                            frame_input.viewport.width,
98                            frame_input.viewport.height,
99                            pixels.into_iter().flatten().collect::<Vec<_>>(),
100                        )
101                        .unwrap(),
102                    );
103                    img.resize(
104                        frame_input.window_width,
105                        frame_input.window_height,
106                        image::imageops::FilterType::Triangle,
107                    )
108                    .save(path)
109                    .unwrap();
110                }
111                std::process::exit(0);
112            }
113        }
114        frame_input
115    }
116
117    ///
118    /// Handle the [WindowEvent] generated by a [winit](https://crates.io/crates/winit) event loop.
119    ///
120    pub fn handle_winit_window_event(&mut self, event: &WindowEvent) {
121        match event {
122            WindowEvent::Resized(physical_size) => {
123                self.viewport = Viewport::new_at_origo(physical_size.width, physical_size.height);
124                let logical_size = physical_size.to_logical(self.device_pixel_ratio);
125                self.window_width = logical_size.width;
126                self.window_height = logical_size.height;
127            }
128            WindowEvent::ScaleFactorChanged {
129                scale_factor,
130                new_inner_size,
131            } => {
132                self.device_pixel_ratio = *scale_factor;
133                self.viewport = Viewport::new_at_origo(new_inner_size.width, new_inner_size.height);
134                let logical_size = new_inner_size.to_logical(self.device_pixel_ratio);
135                self.window_width = logical_size.width;
136                self.window_height = logical_size.height;
137            }
138            WindowEvent::Occluded(false) => {
139                self.first_frame = true;
140            }
141            WindowEvent::KeyboardInput { input, .. } => {
142                if let Some(keycode) = input.virtual_keycode {
143                    use winit::event::VirtualKeyCode;
144                    let state = input.state == winit::event::ElementState::Pressed;
145                    if let Some(kind) = translate_virtual_key_code(keycode) {
146                        self.events.push(if state {
147                            crate::Event::KeyPress {
148                                kind,
149                                modifiers: self.modifiers,
150                                handled: false,
151                            }
152                        } else {
153                            crate::Event::KeyRelease {
154                                kind,
155                                modifiers: self.modifiers,
156                                handled: false,
157                            }
158                        });
159                    } else if keycode == VirtualKeyCode::LControl
160                        || keycode == VirtualKeyCode::RControl
161                    {
162                        self.modifiers.ctrl = state;
163                        if !cfg!(target_os = "macos") {
164                            self.modifiers.command = state;
165                        }
166                        self.events.push(crate::Event::ModifiersChange {
167                            modifiers: self.modifiers,
168                        });
169                    } else if keycode == VirtualKeyCode::LAlt || keycode == VirtualKeyCode::RAlt {
170                        self.modifiers.alt = state;
171                        self.events.push(crate::Event::ModifiersChange {
172                            modifiers: self.modifiers,
173                        });
174                    } else if keycode == VirtualKeyCode::LShift || keycode == VirtualKeyCode::RShift
175                    {
176                        self.modifiers.shift = state;
177                        self.events.push(crate::Event::ModifiersChange {
178                            modifiers: self.modifiers,
179                        });
180                    } else if (keycode == VirtualKeyCode::LWin || keycode == VirtualKeyCode::RWin)
181                        && cfg!(target_os = "macos")
182                    {
183                        self.modifiers.command = state;
184                        self.events.push(crate::Event::ModifiersChange {
185                            modifiers: self.modifiers,
186                        });
187                    }
188                }
189            }
190            WindowEvent::MouseWheel { delta, .. } => {
191                if let Some(position) = self.cursor_pos {
192                    match delta {
193                        winit::event::MouseScrollDelta::LineDelta(x, y) => {
194                            let line_height = 24.0; // TODO
195                            self.events.push(crate::Event::MouseWheel {
196                                delta: (*x * line_height, *y * line_height),
197                                position: position.into(),
198                                modifiers: self.modifiers,
199                                handled: false,
200                            });
201                        }
202                        winit::event::MouseScrollDelta::PixelDelta(delta) => {
203                            let d = delta.to_logical(self.device_pixel_ratio);
204                            self.events.push(crate::Event::MouseWheel {
205                                delta: (d.x, d.y),
206                                position: position.into(),
207                                modifiers: self.modifiers,
208                                handled: false,
209                            });
210                        }
211                    }
212                }
213            }
214            WindowEvent::TouchpadMagnify { delta, .. } => {
215                // Renamed to PinchGesture in winit 0.30.0
216                if let Some(position) = self.cursor_pos {
217                    let d = *delta as f32;
218                    self.events.push(crate::Event::PinchGesture {
219                        delta: d,
220                        position: position.into(),
221                        modifiers: self.modifiers,
222                        handled: false,
223                    });
224                }
225            }
226            WindowEvent::TouchpadRotate { delta, .. } => {
227                // Renamed to RotationGesture in winit 0.30.0
228                if let Some(position) = self.cursor_pos {
229                    let d = radians(*delta);
230                    self.events.push(crate::Event::RotationGesture {
231                        delta: d,
232                        position: position.into(),
233                        modifiers: self.modifiers,
234                        handled: false,
235                    });
236                }
237            }
238            WindowEvent::MouseInput { state, button, .. } => {
239                if let Some(position) = self.cursor_pos {
240                    let button = match button {
241                        winit::event::MouseButton::Left => Some(crate::MouseButton::Left),
242                        winit::event::MouseButton::Middle => Some(crate::MouseButton::Middle),
243                        winit::event::MouseButton::Right => Some(crate::MouseButton::Right),
244                        _ => None,
245                    };
246                    if let Some(b) = button {
247                        self.events
248                            .push(if *state == winit::event::ElementState::Pressed {
249                                self.mouse_pressed = Some(b);
250                                crate::Event::MousePress {
251                                    button: b,
252                                    position: position.into(),
253                                    modifiers: self.modifiers,
254                                    handled: false,
255                                }
256                            } else {
257                                self.mouse_pressed = None;
258                                crate::Event::MouseRelease {
259                                    button: b,
260                                    position: position.into(),
261                                    modifiers: self.modifiers,
262                                    handled: false,
263                                }
264                            });
265                    }
266                }
267            }
268            WindowEvent::CursorMoved { position, .. } => {
269                let p = position.to_logical(self.device_pixel_ratio);
270                let delta = if let Some(last_pos) = self.cursor_pos {
271                    (p.x - last_pos.x, p.y - last_pos.y)
272                } else {
273                    (0.0, 0.0)
274                };
275                let position = LogicalPoint {
276                    x: p.x,
277                    y: p.y,
278                    device_pixel_ratio: self.device_pixel_ratio as f32,
279                    height: self.viewport.height as f32,
280                };
281                self.events.push(crate::Event::MouseMotion {
282                    button: self.mouse_pressed,
283                    delta,
284                    position: position.into(),
285                    modifiers: self.modifiers,
286                    handled: false,
287                });
288                self.cursor_pos = Some(position);
289            }
290            WindowEvent::ReceivedCharacter(ch) => {
291                if is_printable_char(*ch) && !self.modifiers.ctrl && !self.modifiers.command {
292                    self.events.push(crate::Event::Text(ch.to_string()));
293                }
294            }
295            WindowEvent::CursorEntered { .. } => {
296                self.events.push(crate::Event::MouseEnter);
297            }
298            WindowEvent::CursorLeft { .. } => {
299                self.mouse_pressed = None;
300                self.events.push(crate::Event::MouseLeave);
301            }
302            WindowEvent::Touch(touch) => {
303                let position = touch.location.to_logical::<f32>(self.device_pixel_ratio);
304                let position = LogicalPoint {
305                    x: position.x,
306                    y: position.y,
307                    device_pixel_ratio: self.device_pixel_ratio as f32,
308                    height: self.viewport.height as f32,
309                };
310                match touch.phase {
311                    TouchPhase::Started => {
312                        if self.finger_id.is_none() {
313                            self.events.push(crate::Event::MousePress {
314                                button: MouseButton::Left,
315                                position: position.into(),
316                                modifiers: self.modifiers,
317                                handled: false,
318                            });
319                            self.cursor_pos = Some(position);
320                            self.finger_id = Some(touch.id);
321                        } else if self.secondary_finger_id.is_none() {
322                            self.secondary_cursor_pos = Some(position);
323                            self.secondary_finger_id = Some(touch.id);
324                        }
325                    }
326                    TouchPhase::Ended | TouchPhase::Cancelled => {
327                        if self.finger_id.map(|id| id == touch.id).unwrap_or(false) {
328                            self.events.push(crate::Event::MouseRelease {
329                                button: MouseButton::Left,
330                                position: position.into(),
331                                modifiers: self.modifiers,
332                                handled: false,
333                            });
334                            self.cursor_pos = None;
335                            self.finger_id = None;
336                        } else if self
337                            .secondary_finger_id
338                            .map(|id| id == touch.id)
339                            .unwrap_or(false)
340                        {
341                            self.secondary_cursor_pos = None;
342                            self.secondary_finger_id = None;
343                        }
344                    }
345                    TouchPhase::Moved => {
346                        if self.finger_id.map(|id| id == touch.id).unwrap_or(false) {
347                            let last_pos = self.cursor_pos.unwrap();
348                            if let Some(p) = self.secondary_cursor_pos {
349                                self.events.push(crate::Event::MouseWheel {
350                                    position: position.into(),
351                                    modifiers: self.modifiers,
352                                    handled: false,
353                                    delta: (
354                                        (position.x - p.x).abs() - (last_pos.x - p.x).abs(),
355                                        (position.y - p.y).abs() - (last_pos.y - p.y).abs(),
356                                    ),
357                                });
358                            } else {
359                                self.events.push(crate::Event::MouseMotion {
360                                    button: Some(MouseButton::Left),
361                                    position: position.into(),
362                                    modifiers: self.modifiers,
363                                    handled: false,
364                                    delta: (position.x - last_pos.x, position.y - last_pos.y),
365                                });
366                            }
367                            self.cursor_pos = Some(position);
368                        } else if self
369                            .secondary_finger_id
370                            .map(|id| id == touch.id)
371                            .unwrap_or(false)
372                        {
373                            let last_pos = self.secondary_cursor_pos.unwrap();
374                            if let Some(p) = self.cursor_pos {
375                                self.events.push(crate::Event::MouseWheel {
376                                    position: p.into(),
377                                    modifiers: self.modifiers,
378                                    handled: false,
379                                    delta: (
380                                        (position.x - p.x).abs() - (last_pos.x - p.x).abs(),
381                                        (position.y - p.y).abs() - (last_pos.y - p.y).abs(),
382                                    ),
383                                });
384                            }
385                            self.secondary_cursor_pos = Some(position);
386                        }
387                    }
388                }
389            }
390            _ => (),
391        }
392    }
393}
394
395fn is_printable_char(chr: char) -> bool {
396    let is_in_private_use_area = ('\u{e000}'..='\u{f8ff}').contains(&chr)
397        || ('\u{f0000}'..='\u{ffffd}').contains(&chr)
398        || ('\u{100000}'..='\u{10fffd}').contains(&chr);
399
400    !is_in_private_use_area && !chr.is_ascii_control()
401}
402
403fn translate_virtual_key_code(key: winit::event::VirtualKeyCode) -> Option<crate::Key> {
404    use winit::event::VirtualKeyCode::*;
405
406    Some(match key {
407        Down => Key::ArrowDown,
408        Left => Key::ArrowLeft,
409        Right => Key::ArrowRight,
410        Up => Key::ArrowUp,
411
412        Escape => Key::Escape,
413        Tab => Key::Tab,
414        Back => Key::Backspace,
415        Return => Key::Enter,
416        Space => Key::Space,
417
418        Insert => Key::Insert,
419        Delete => Key::Delete,
420        Home => Key::Home,
421        End => Key::End,
422        PageUp => Key::PageUp,
423        PageDown => Key::PageDown,
424
425        Key0 | Numpad0 => Key::Num0,
426        Key1 | Numpad1 => Key::Num1,
427        Key2 | Numpad2 => Key::Num2,
428        Key3 | Numpad3 => Key::Num3,
429        Key4 | Numpad4 => Key::Num4,
430        Key5 | Numpad5 => Key::Num5,
431        Key6 | Numpad6 => Key::Num6,
432        Key7 | Numpad7 => Key::Num7,
433        Key8 | Numpad8 => Key::Num8,
434        Key9 | Numpad9 => Key::Num9,
435
436        A => Key::A,
437        B => Key::B,
438        C => Key::C,
439        D => Key::D,
440        E => Key::E,
441        F => Key::F,
442        G => Key::G,
443        H => Key::H,
444        I => Key::I,
445        J => Key::J,
446        K => Key::K,
447        L => Key::L,
448        M => Key::M,
449        N => Key::N,
450        O => Key::O,
451        P => Key::P,
452        Q => Key::Q,
453        R => Key::R,
454        S => Key::S,
455        T => Key::T,
456        U => Key::U,
457        V => Key::V,
458        W => Key::W,
459        X => Key::X,
460        Y => Key::Y,
461        Z => Key::Z,
462
463        _ => {
464            return None;
465        }
466    })
467}
468
469///
470/// A pixel coordinate in logical pixels, where `x` is on the horizontal axis with zero being at the left edge
471/// and `y` is on the vertical axis with zero being at top edge.
472///
473#[derive(Debug, Copy, Clone, PartialEq)]
474struct LogicalPoint {
475    /// The horizontal pixel distance from the left edge.
476    x: f32,
477    /// The vertical pixel distance from the top edge.
478    y: f32,
479    device_pixel_ratio: f32,
480    height: f32,
481}
482
483impl From<LogicalPoint> for PhysicalPoint {
484    fn from(value: LogicalPoint) -> Self {
485        Self {
486            x: value.x * value.device_pixel_ratio,
487            y: value.height - value.y * value.device_pixel_ratio,
488        }
489    }
490}