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
12pub 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 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 pub fn from_winit_window(window: &winit::window::Window) -> Self {
62 Self::new(window.inner_size(), window.scale_factor())
63 }
64
65 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 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 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 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 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#[derive(Debug, Copy, Clone, PartialEq)]
528struct LogicalPoint {
529 x: f32,
531 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}