Skip to main content

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                    // Normalize scroll deltas so one "tick" produces similar values across platforms.
193                    // Values determined experimentally
194                    const LINE_HEIGHT: f64 = 24.0;
195                    const BROWSER_LINE_HEIGHT: f64 = 100.0;
196                    let (x, y) = match delta {
197                        winit::event::MouseScrollDelta::LineDelta(x, y) => {
198                            ((*x as f64) * LINE_HEIGHT, (*y as f64) * LINE_HEIGHT)
199                        }
200                        winit::event::MouseScrollDelta::PixelDelta(delta) => {
201                            let d = delta.to_logical::<f64>(self.device_pixel_ratio);
202                            (
203                                d.x * LINE_HEIGHT / BROWSER_LINE_HEIGHT,
204                                d.y * LINE_HEIGHT / BROWSER_LINE_HEIGHT,
205                            )
206                        }
207                    };
208                    self.events.push(crate::Event::MouseWheel {
209                        delta: (x as f32, y as f32),
210                        position: position.into(),
211                        modifiers: self.modifiers,
212                        handled: false,
213                    });
214                }
215            }
216            WindowEvent::TouchpadMagnify { delta, .. } => {
217                // Renamed to PinchGesture in winit 0.30.0
218                if let Some(position) = self.cursor_pos {
219                    let d = *delta as f32;
220                    self.events.push(crate::Event::PinchGesture {
221                        delta: d,
222                        position: position.into(),
223                        modifiers: self.modifiers,
224                        handled: false,
225                    });
226                }
227            }
228            WindowEvent::TouchpadRotate { delta, .. } => {
229                // Renamed to RotationGesture in winit 0.30.0
230                if let Some(position) = self.cursor_pos {
231                    let d = radians(*delta);
232                    self.events.push(crate::Event::RotationGesture {
233                        delta: d,
234                        position: position.into(),
235                        modifiers: self.modifiers,
236                        handled: false,
237                    });
238                }
239            }
240            WindowEvent::MouseInput { state, button, .. } => {
241                if let Some(position) = self.cursor_pos {
242                    let button = match button {
243                        winit::event::MouseButton::Left => Some(crate::MouseButton::Left),
244                        winit::event::MouseButton::Middle => Some(crate::MouseButton::Middle),
245                        winit::event::MouseButton::Right => Some(crate::MouseButton::Right),
246                        _ => None,
247                    };
248                    if let Some(b) = button {
249                        self.events
250                            .push(if *state == winit::event::ElementState::Pressed {
251                                self.mouse_pressed = Some(b);
252                                crate::Event::MousePress {
253                                    button: b,
254                                    position: position.into(),
255                                    modifiers: self.modifiers,
256                                    handled: false,
257                                }
258                            } else {
259                                self.mouse_pressed = None;
260                                crate::Event::MouseRelease {
261                                    button: b,
262                                    position: position.into(),
263                                    modifiers: self.modifiers,
264                                    handled: false,
265                                }
266                            });
267                    }
268                }
269            }
270            WindowEvent::CursorMoved { position, .. } => {
271                let p = position.to_logical(self.device_pixel_ratio);
272                let delta = if let Some(last_pos) = self.cursor_pos {
273                    (p.x - last_pos.x, p.y - last_pos.y)
274                } else {
275                    (0.0, 0.0)
276                };
277                let position = LogicalPoint {
278                    x: p.x,
279                    y: p.y,
280                    device_pixel_ratio: self.device_pixel_ratio as f32,
281                    height: self.viewport.height as f32,
282                };
283                self.events.push(crate::Event::MouseMotion {
284                    button: self.mouse_pressed,
285                    delta,
286                    position: position.into(),
287                    modifiers: self.modifiers,
288                    handled: false,
289                });
290                self.cursor_pos = Some(position);
291            }
292            WindowEvent::ReceivedCharacter(ch)
293                if is_printable_char(*ch) && !self.modifiers.ctrl && !self.modifiers.command =>
294            {
295                self.events.push(crate::Event::Text(ch.to_string()));
296            }
297            WindowEvent::CursorEntered { .. } => {
298                self.events.push(crate::Event::MouseEnter);
299            }
300            WindowEvent::CursorLeft { .. } => {
301                self.mouse_pressed = None;
302                self.events.push(crate::Event::MouseLeave);
303            }
304            WindowEvent::Touch(touch) => {
305                let position = touch.location.to_logical::<f32>(self.device_pixel_ratio);
306                let position = LogicalPoint {
307                    x: position.x,
308                    y: position.y,
309                    device_pixel_ratio: self.device_pixel_ratio as f32,
310                    height: self.viewport.height as f32,
311                };
312                match touch.phase {
313                    TouchPhase::Started => {
314                        if self.finger_id.is_none() {
315                            self.events.push(crate::Event::MousePress {
316                                button: MouseButton::Left,
317                                position: position.into(),
318                                modifiers: self.modifiers,
319                                handled: false,
320                            });
321                            self.cursor_pos = Some(position);
322                            self.finger_id = Some(touch.id);
323                        } else if self.secondary_finger_id.is_none() {
324                            self.secondary_cursor_pos = Some(position);
325                            self.secondary_finger_id = Some(touch.id);
326                        }
327                    }
328                    TouchPhase::Ended | TouchPhase::Cancelled => {
329                        if self.finger_id.map(|id| id == touch.id).unwrap_or(false) {
330                            self.events.push(crate::Event::MouseRelease {
331                                button: MouseButton::Left,
332                                position: position.into(),
333                                modifiers: self.modifiers,
334                                handled: false,
335                            });
336                            self.cursor_pos = None;
337                            self.finger_id = None;
338                        } else if self
339                            .secondary_finger_id
340                            .map(|id| id == touch.id)
341                            .unwrap_or(false)
342                        {
343                            self.secondary_cursor_pos = None;
344                            self.secondary_finger_id = None;
345                        }
346                    }
347                    TouchPhase::Moved => {
348                        if self.finger_id.map(|id| id == touch.id).unwrap_or(false) {
349                            let last_pos = self.cursor_pos.unwrap();
350                            if let Some(p) = self.secondary_cursor_pos {
351                                self.events.push(crate::Event::MouseWheel {
352                                    position: position.into(),
353                                    modifiers: self.modifiers,
354                                    handled: false,
355                                    delta: (
356                                        (position.x - p.x).abs() - (last_pos.x - p.x).abs(),
357                                        (position.y - p.y).abs() - (last_pos.y - p.y).abs(),
358                                    ),
359                                });
360                            } else {
361                                self.events.push(crate::Event::MouseMotion {
362                                    button: Some(MouseButton::Left),
363                                    position: position.into(),
364                                    modifiers: self.modifiers,
365                                    handled: false,
366                                    delta: (position.x - last_pos.x, position.y - last_pos.y),
367                                });
368                            }
369                            self.cursor_pos = Some(position);
370                        } else if self
371                            .secondary_finger_id
372                            .map(|id| id == touch.id)
373                            .unwrap_or(false)
374                        {
375                            let last_pos = self.secondary_cursor_pos.unwrap();
376                            if let Some(p) = self.cursor_pos {
377                                self.events.push(crate::Event::MouseWheel {
378                                    position: p.into(),
379                                    modifiers: self.modifiers,
380                                    handled: false,
381                                    delta: (
382                                        (position.x - p.x).abs() - (last_pos.x - p.x).abs(),
383                                        (position.y - p.y).abs() - (last_pos.y - p.y).abs(),
384                                    ),
385                                });
386                            }
387                            self.secondary_cursor_pos = Some(position);
388                        }
389                    }
390                }
391            }
392            _ => (),
393        }
394    }
395}
396
397fn is_printable_char(chr: char) -> bool {
398    let is_in_private_use_area = ('\u{e000}'..='\u{f8ff}').contains(&chr)
399        || ('\u{f0000}'..='\u{ffffd}').contains(&chr)
400        || ('\u{100000}'..='\u{10fffd}').contains(&chr);
401
402    !is_in_private_use_area && !chr.is_ascii_control()
403}
404
405fn translate_virtual_key_code(key: winit::event::VirtualKeyCode) -> Option<crate::Key> {
406    use winit::event::VirtualKeyCode::*;
407
408    Some(match key {
409        Down => Key::ArrowDown,
410        Left => Key::ArrowLeft,
411        Right => Key::ArrowRight,
412        Up => Key::ArrowUp,
413
414        Escape => Key::Escape,
415        Tab => Key::Tab,
416        Back => Key::Backspace,
417        Return | NumpadEnter => Key::Enter,
418        Space => Key::Space,
419
420        Insert => Key::Insert,
421        Delete => Key::Delete,
422        Home => Key::Home,
423        End => Key::End,
424        PageUp => Key::PageUp,
425        PageDown => Key::PageDown,
426        Snapshot | Sysrq => Key::Snapshot,
427
428        Mute => Key::Mute,
429        VolumeDown => Key::VolumeDown,
430        VolumeUp => Key::VolumeUp,
431
432        Copy => Key::Copy,
433        Paste => Key::Paste,
434        Cut => Key::Cut,
435
436        Equals | NumpadEquals => Key::Equals,
437        Minus | NumpadSubtract => Key::Minus,
438        Plus | NumpadAdd => Key::Plus,
439
440        Key0 | Numpad0 => Key::Num0,
441        Key1 | Numpad1 => Key::Num1,
442        Key2 | Numpad2 => Key::Num2,
443        Key3 | Numpad3 => Key::Num3,
444        Key4 | Numpad4 => Key::Num4,
445        Key5 | Numpad5 => Key::Num5,
446        Key6 | Numpad6 => Key::Num6,
447        Key7 | Numpad7 => Key::Num7,
448        Key8 | Numpad8 => Key::Num8,
449        Key9 | Numpad9 => Key::Num9,
450
451        A => Key::A,
452        B => Key::B,
453        C => Key::C,
454        D => Key::D,
455        E => Key::E,
456        F => Key::F,
457        G => Key::G,
458        H => Key::H,
459        I => Key::I,
460        J => Key::J,
461        K => Key::K,
462        L => Key::L,
463        M => Key::M,
464        N => Key::N,
465        O => Key::O,
466        P => Key::P,
467        Q => Key::Q,
468        R => Key::R,
469        S => Key::S,
470        T => Key::T,
471        U => Key::U,
472        V => Key::V,
473        W => Key::W,
474        X => Key::X,
475        Y => Key::Y,
476        Z => Key::Z,
477
478        F1 => Key::F1,
479        F2 => Key::F2,
480        F3 => Key::F3,
481        F4 => Key::F4,
482        F5 => Key::F5,
483        F6 => Key::F6,
484        F7 => Key::F7,
485        F8 => Key::F8,
486        F9 => Key::F9,
487        F10 => Key::F10,
488        F11 => Key::F11,
489        F12 => Key::F12,
490        F13 => Key::F13,
491        F14 => Key::F14,
492        F15 => Key::F15,
493        F16 => Key::F16,
494        F17 => Key::F17,
495        F18 => Key::F18,
496        F19 => Key::F19,
497        F20 => Key::F20,
498        F21 => Key::F21,
499        F22 => Key::F22,
500        F23 => Key::F23,
501        F24 => Key::F24,
502
503        Apostrophe => Key::Apostrophe,
504        Asterisk | NumpadMultiply => Key::Asterisk,
505        Backslash => Key::Backslash,
506        Caret => Key::Caret,
507        Colon => Key::Colon,
508        Comma => Key::Comma,
509        Grave => Key::Grave,
510        LBracket => Key::LBracket,
511        Period | NumpadDecimal | NumpadComma => Key::Period,
512        RBracket => Key::RBracket,
513        Semicolon => Key::Semicolon,
514        Slash | NumpadDivide => Key::Slash,
515        Underline => Key::Underline,
516
517        _ => {
518            return None;
519        }
520    })
521}
522
523///
524/// A pixel coordinate in logical pixels, where `x` is on the horizontal axis with zero being at the left edge
525/// and `y` is on the vertical axis with zero being at top edge.
526///
527#[derive(Debug, Copy, Clone, PartialEq)]
528struct LogicalPoint {
529    /// The horizontal pixel distance from the left edge.
530    x: f32,
531    /// The vertical pixel distance from the top edge.
532    y: f32,
533    device_pixel_ratio: f32,
534    height: f32,
535}
536
537impl From<LogicalPoint> for PhysicalPoint {
538    fn from(value: LogicalPoint) -> Self {
539        Self {
540            x: value.x * value.device_pixel_ratio,
541            y: value.height - value.y * value.device_pixel_ratio,
542        }
543    }
544}