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
20pub 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 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#[derive(Debug, thiserror::Error)]
130pub enum Error {
131 #[error("Error initializing event loop")]
133 EventLoop(#[from] EventLoopError),
134
135 #[error("Error initializing window")]
137 Window(#[from] window::WindowError),
138
139 #[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 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
206const ZOOM_FACTOR_LINE: f64 = 0.075;
211
212const ZOOM_FACTOR_PIXEL: f64 = 0.005;
217
218const ROTATION_SENSITIVITY: f64 = 5.;