fj_window/
display.rs

1use fj_interop::Model;
2use fj_viewer::{
3    InputEvent, NormalizedScreenPosition, RendererInitError, Screen,
4    ScreenSize, Viewer,
5};
6use futures::executor::block_on;
7use winit::{
8    dpi::PhysicalPosition,
9    error::EventLoopError,
10    event::{
11        ElementState, Event, KeyEvent, MouseButton, MouseScrollDelta,
12        WindowEvent,
13    },
14    event_loop::EventLoop,
15    keyboard::{Key, NamedKey},
16};
17
18use crate::window::{self, Window};
19
20/// Display the provided mesh in a window that processes input
21pub fn display(model: Model, invert_zoom: bool) -> Result<(), Error> {
22    let event_loop = EventLoop::new()?;
23    let window = Window::new(&event_loop)?;
24    let mut viewer = block_on(Viewer::new(&window))?;
25
26    viewer.handle_model_update(model);
27
28    let mut held_mouse_button = None;
29    let mut new_size = None;
30    let mut stop_drawing = false;
31
32    event_loop.run(move |event, event_loop_window_target| {
33        let input_event = input_event(
34            &event,
35            &window,
36            &held_mouse_button,
37            viewer.cursor(),
38            invert_zoom,
39        );
40        if let Some(input_event) = input_event {
41            viewer.handle_input_event(input_event);
42        }
43
44        match event {
45            Event::WindowEvent {
46                event: WindowEvent::CloseRequested,
47                ..
48            } => {
49                event_loop_window_target.exit();
50            }
51            Event::WindowEvent {
52                event:
53                    WindowEvent::KeyboardInput {
54                        event:
55                            KeyEvent {
56                                logical_key,
57                                state: ElementState::Pressed,
58                                ..
59                            },
60                        ..
61                    },
62                ..
63            } => match logical_key.as_ref() {
64                Key::Named(NamedKey::Escape) => {
65                    event_loop_window_target.exit();
66                }
67                Key::Character("1") => {
68                    viewer.toggle_draw_model();
69                }
70                Key::Character("2") => {
71                    viewer.toggle_draw_mesh();
72                }
73                _ => {}
74            },
75            Event::WindowEvent {
76                event: WindowEvent::Resized(size),
77                ..
78            } => {
79                new_size = Some(ScreenSize {
80                    width: size.width,
81                    height: size.height,
82                });
83            }
84            Event::WindowEvent {
85                event: WindowEvent::MouseInput { state, button, .. },
86                ..
87            } => match state {
88                ElementState::Pressed => {
89                    held_mouse_button = Some(button);
90                    viewer.add_focus_point();
91                }
92                ElementState::Released => {
93                    held_mouse_button = None;
94                    viewer.remove_focus_point();
95                }
96            },
97            Event::WindowEvent {
98                event: WindowEvent::MouseWheel { .. },
99                ..
100            } => viewer.add_focus_point(),
101            Event::AboutToWait => {
102                window.window().request_redraw();
103            }
104            Event::WindowEvent {
105                event: WindowEvent::RedrawRequested,
106                ..
107            } => {
108                // Only do a screen resize once per frame. This protects against
109                // spurious resize events that cause issues with the renderer.
110                if let Some(size) = new_size.take() {
111                    stop_drawing = size.width == 0 || size.height == 0;
112                    if !stop_drawing {
113                        viewer.handle_screen_resize(size);
114                    }
115                }
116
117                if !stop_drawing {
118                    viewer.draw();
119                }
120            }
121            _ => {}
122        }
123    })?;
124
125    Ok(())
126}
127
128/// Main loop initialization error
129#[derive(Debug, thiserror::Error)]
130pub enum Error {
131    /// Error initializing event loop
132    #[error("Error initializing event loop")]
133    EventLoop(#[from] EventLoopError),
134
135    /// Error initializing window
136    #[error("Error initializing window")]
137    Window(#[from] window::WindowError),
138
139    /// Error initializing graphics
140    #[error("Error initializing graphics")]
141    Graphics(#[from] RendererInitError),
142}
143
144fn input_event<T>(
145    event: &Event<T>,
146    window: &Window,
147    held_mouse_button: &Option<MouseButton>,
148    previous_cursor: &mut Option<NormalizedScreenPosition>,
149    invert_zoom: bool,
150) -> Option<InputEvent> {
151    match event {
152        Event::WindowEvent {
153            event: WindowEvent::CursorMoved { position, .. },
154            ..
155        } => {
156            let [width, height] = window.size().as_f64();
157            let aspect_ratio = width / height;
158
159            // Cursor position in normalized coordinates (-1 to +1) with
160            // aspect ratio taken into account.
161            let current = NormalizedScreenPosition {
162                x: position.x / width * 2. - 1.,
163                y: -(position.y / height * 2. - 1.) / aspect_ratio,
164            };
165            let event = match (*previous_cursor, held_mouse_button) {
166                (Some(previous), Some(button)) => match button {
167                    MouseButton::Left => {
168                        let diff_x = current.x - previous.x;
169                        let diff_y = current.y - previous.y;
170                        let angle_x = -diff_y * ROTATION_SENSITIVITY;
171                        let angle_y = diff_x * ROTATION_SENSITIVITY;
172
173                        Some(InputEvent::Rotation { angle_x, angle_y })
174                    }
175                    MouseButton::Right => {
176                        Some(InputEvent::Translation { previous, current })
177                    }
178                    _ => None,
179                },
180                _ => None,
181            };
182            *previous_cursor = Some(current);
183            event
184        }
185        Event::WindowEvent {
186            event: WindowEvent::MouseWheel { delta, .. },
187            ..
188        } => {
189            let delta = match delta {
190                MouseScrollDelta::LineDelta(_, y) => {
191                    f64::from(*y) * ZOOM_FACTOR_LINE
192                }
193                MouseScrollDelta::PixelDelta(PhysicalPosition {
194                    y, ..
195                }) => y * ZOOM_FACTOR_PIXEL,
196            };
197
198            let delta = if invert_zoom { -delta } else { delta };
199
200            Some(InputEvent::Zoom(delta))
201        }
202        _ => None,
203    }
204}
205
206/// Affects the speed of zoom movement given a scroll wheel input in lines.
207///
208/// Smaller values will move the camera less with the same input.
209/// Larger values will move the camera more with the same input.
210const ZOOM_FACTOR_LINE: f64 = 0.075;
211
212/// Affects the speed of zoom movement given a scroll wheel input in pixels.
213///
214/// Smaller values will move the camera less with the same input.
215/// Larger values will move the camera more with the same input.
216const ZOOM_FACTOR_PIXEL: f64 = 0.005;
217
218/// Affects the speed of rotation given a change in normalized screen position [-1, 1]
219///
220/// Smaller values will move the camera less with the same input.
221/// Larger values will move the camera more with the same input.
222const ROTATION_SENSITIVITY: f64 = 5.;