1use std::path::PathBuf;
2
3use dioxus_core::VirtualDom;
4use freya_core::{
5 accessibility::AccessibilityFocusStrategy,
6 dom::SafeDOM,
7 event_loop_messages::EventLoopMessage,
8 events::{
9 EventName,
10 PlatformEvent,
11 PlatformEventData,
12 },
13 platform_state::NavigationMode,
14};
15use freya_elements::events::{
16 Code,
17 Key,
18};
19use torin::geometry::CursorPoint;
20use winit::{
21 application::ApplicationHandler,
22 event::{
23 ElementState,
24 Ime,
25 KeyEvent,
26 MouseButton,
27 MouseScrollDelta,
28 StartCause,
29 Touch,
30 TouchPhase,
31 WindowEvent,
32 },
33 event_loop::{
34 EventLoop,
35 EventLoopProxy,
36 },
37 keyboard::ModifiersState,
38};
39
40use crate::{
41 app::AccessibilityTask,
42 devtools::{
43 Devtools,
44 HoveredNode,
45 },
46 keyboard::{
47 map_winit_key,
48 map_winit_modifiers,
49 map_winit_physical_key,
50 },
51 window_state::{
52 CreatedState,
53 NotCreatedState,
54 WindowState,
55 },
56 LaunchConfig,
57};
58
59const WHEEL_SPEED_MODIFIER: f64 = 53.0;
60const TOUCHPAD_SPEED_MODIFIER: f64 = 2.0;
61
62pub struct WinitRenderer<'a, State: Clone + 'static> {
64 pub(crate) event_loop_proxy: EventLoopProxy<EventLoopMessage>,
65 pub(crate) state: WindowState<'a, State>,
66 pub(crate) hovered_node: HoveredNode,
67 pub(crate) cursor_pos: CursorPoint,
68 pub(crate) mouse_state: ElementState,
69 pub(crate) modifiers_state: ModifiersState,
70 pub(crate) dropped_file_path: Option<PathBuf>,
71 pub(crate) custom_scale_factor: f64,
72}
73
74impl<'a, State: Clone + 'static> WinitRenderer<'a, State> {
75 pub fn launch(
77 vdom: VirtualDom,
78 sdom: SafeDOM,
79 mut config: LaunchConfig<State>,
80 devtools: Option<Devtools>,
81 hovered_node: HoveredNode,
82 ) {
83 let mut event_loop_builder = EventLoop::<EventLoopMessage>::with_user_event();
84 let event_loop_builder_hook = config.window_config.event_loop_builder_hook.take();
85 if let Some(event_loop_builder_hook) = event_loop_builder_hook {
86 event_loop_builder_hook(&mut event_loop_builder);
87 }
88 let event_loop = event_loop_builder
89 .build()
90 .expect("Failed to create event loop.");
91 let proxy = event_loop.create_proxy();
92
93 let mut winit_renderer =
94 WinitRenderer::new(vdom, sdom, config, devtools, hovered_node, proxy);
95
96 event_loop.run_app(&mut winit_renderer).unwrap();
97 }
98
99 pub fn new(
100 vdom: VirtualDom,
101 sdom: SafeDOM,
102 config: LaunchConfig<'a, State>,
103 devtools: Option<Devtools>,
104 hovered_node: HoveredNode,
105 proxy: EventLoopProxy<EventLoopMessage>,
106 ) -> Self {
107 WinitRenderer {
108 state: WindowState::NotCreated(NotCreatedState {
109 sdom,
110 devtools,
111 vdom,
112 config,
113 }),
114 hovered_node,
115 event_loop_proxy: proxy,
116 cursor_pos: CursorPoint::default(),
117 mouse_state: ElementState::Released,
118 modifiers_state: ModifiersState::default(),
119 dropped_file_path: None,
120 custom_scale_factor: 0.,
121 }
122 }
123
124 fn send_event(&mut self, event: PlatformEvent) {
126 let scale_factor = self.scale_factor();
127 self.state
128 .created_state()
129 .app
130 .send_event(event, scale_factor);
131 }
132
133 fn scale_factor(&self) -> f64 {
135 match &self.state {
136 WindowState::Created(CreatedState { window, .. }) => {
137 window.scale_factor() + self.custom_scale_factor
138 }
139 _ => 0.0,
140 }
141 }
142
143 pub fn run_on_setup(&mut self) {
145 let state = self.state.created_state();
146 if let Some(on_setup) = state.window_config.on_setup.take() {
147 (on_setup)(&mut state.window)
148 }
149 }
150
151 pub fn run_on_exit(&mut self) {
153 let state = self.state.created_state();
154 if let Some(on_exit) = state.window_config.on_exit.take() {
155 (on_exit)(&mut state.window)
156 }
157 }
158}
159
160impl<State: Clone> ApplicationHandler<EventLoopMessage> for WinitRenderer<'_, State> {
161 fn resumed(&mut self, event_loop: &winit::event_loop::ActiveEventLoop) {
162 if !self.state.has_been_created() {
163 self.state.create(event_loop, &self.event_loop_proxy);
164 self.run_on_setup();
165 }
166 }
167
168 fn new_events(
169 &mut self,
170 _event_loop: &winit::event_loop::ActiveEventLoop,
171 cause: winit::event::StartCause,
172 ) {
173 if cause == StartCause::Init {
174 self.event_loop_proxy
175 .send_event(EventLoopMessage::PollVDOM)
176 .ok();
177 }
178 }
179
180 fn user_event(
181 &mut self,
182 event_loop: &winit::event_loop::ActiveEventLoop,
183 event: EventLoopMessage,
184 ) {
185 let scale_factor = self.scale_factor();
186 let CreatedState { window, app, .. } = self.state.created_state();
187 match event {
188 EventLoopMessage::FocusAccessibilityNode(strategy) => {
189 app.request_focus_node(strategy);
190 window.request_redraw();
191 }
192 EventLoopMessage::RequestRerender => {
193 window.request_redraw();
194 }
195 EventLoopMessage::RequestFullRerender => {
196 app.resize(window);
197 window.request_redraw();
198 }
199 EventLoopMessage::InvalidateArea(mut area) => {
200 let fdom = app.sdom.get();
201 area.size *= scale_factor as f32;
202 let mut compositor_dirty_area = fdom.compositor_dirty_area();
203 compositor_dirty_area.unite_or_insert(&area)
204 }
205 EventLoopMessage::RemeasureTextGroup(text_id) => {
206 app.measure_text_group(text_id, scale_factor);
207 }
208 EventLoopMessage::Accessibility(accesskit_winit::WindowEvent::ActionRequested(
209 request,
210 )) => {
211 if accesskit::Action::Focus == request.action {
212 app.request_focus_node(AccessibilityFocusStrategy::Node(request.target));
213 window.request_redraw();
214 }
215 }
216 EventLoopMessage::Accessibility(accesskit_winit::WindowEvent::InitialTreeRequested) => {
217 app.init_accessibility_on_next_render = true;
218 }
219 EventLoopMessage::SetCursorIcon(icon) => window.set_cursor(icon),
220 EventLoopMessage::WithWindow(use_window) => (use_window)(window),
221 EventLoopMessage::ExitApp => event_loop.exit(),
222 EventLoopMessage::PlatformEvent(platform_event) => self.send_event(platform_event),
223 EventLoopMessage::PollVDOM => {
224 app.poll_vdom(window);
225 }
226 _ => {}
227 }
228 }
229
230 fn window_event(
231 &mut self,
232 event_loop: &winit::event_loop::ActiveEventLoop,
233 _window_id: winit::window::WindowId,
234 event: winit::event::WindowEvent,
235 ) {
236 let scale_factor = self.scale_factor();
237 let CreatedState {
238 surface,
239 dirty_surface,
240 window,
241 window_config,
242 app,
243 is_window_focused,
244 graphics_driver,
245 ..
246 } = self.state.created_state();
247 app.accessibility
248 .process_accessibility_event(&event, window);
249 match event {
250 WindowEvent::ThemeChanged(theme) => {
251 app.platform_sender.send_modify(|state| {
252 state.preferred_theme = theme.into();
253 });
254 }
255 WindowEvent::CloseRequested => event_loop.exit(),
256 WindowEvent::Ime(Ime::Commit(text)) => {
257 self.send_event(PlatformEvent {
258 name: EventName::KeyDown,
259 data: PlatformEventData::Keyboard {
260 key: Key::Character(text),
261 code: Code::Unidentified,
262 modifiers: map_winit_modifiers(self.modifiers_state),
263 },
264 });
265 }
266 WindowEvent::RedrawRequested => {
267 app.platform_sender.send_if_modified(|state| {
268 let scale_factor_is_different = state.scale_factor == scale_factor;
269 state.scale_factor = scale_factor;
270 scale_factor_is_different
271 });
272
273 if app.process_layout_on_next_render {
274 app.process_layout(window.inner_size(), scale_factor);
275
276 app.process_layout_on_next_render = false;
277 }
278
279 match app.process_accessibility_task_on_next_render {
280 AccessibilityTask::ProcessWithMode(navigation_mode) => {
281 app.process_accessibility(window);
282 app.set_navigation_mode(navigation_mode);
283 }
284 AccessibilityTask::Process => {
285 app.process_accessibility(window);
286 }
287 AccessibilityTask::None => {}
288 }
289
290 if app.init_accessibility_on_next_render {
291 app.init_accessibility();
292 app.init_accessibility_on_next_render = false;
293 }
294
295 graphics_driver.make_current();
296
297 app.render(
298 &self.hovered_node,
299 window_config.background,
300 surface,
301 dirty_surface,
302 window,
303 scale_factor,
304 );
305
306 app.event_loop_tick();
307 window.pre_present_notify();
308 graphics_driver.flush_and_submit();
309 }
310 WindowEvent::MouseInput { state, button, .. } => {
311 app.set_navigation_mode(NavigationMode::NotKeyboard);
312
313 self.mouse_state = state;
314
315 let name = match state {
316 ElementState::Pressed => EventName::MouseDown,
317 ElementState::Released => match button {
318 MouseButton::Middle => EventName::MiddleClick,
319 MouseButton::Right => EventName::RightClick,
320 MouseButton::Left => EventName::MouseUp,
321 _ => EventName::PointerUp,
322 },
323 };
324
325 self.send_event(PlatformEvent {
326 name,
327 data: PlatformEventData::Mouse {
328 cursor: self.cursor_pos,
329 button: Some(button),
330 },
331 });
332 }
333 WindowEvent::MouseWheel { delta, phase, .. } => {
334 if TouchPhase::Moved == phase {
335 let scroll_data = {
336 match delta {
337 MouseScrollDelta::LineDelta(x, y) => (
338 (x as f64 * WHEEL_SPEED_MODIFIER),
339 (y as f64 * WHEEL_SPEED_MODIFIER),
340 ),
341 MouseScrollDelta::PixelDelta(pos) => (
342 (pos.x * TOUCHPAD_SPEED_MODIFIER),
343 (pos.y * TOUCHPAD_SPEED_MODIFIER),
344 ),
345 }
346 };
347
348 self.send_event(PlatformEvent {
349 name: EventName::Wheel,
350 data: PlatformEventData::Wheel {
351 scroll: CursorPoint::from(scroll_data),
352 cursor: self.cursor_pos,
353 },
354 });
355 }
356 }
357 WindowEvent::ModifiersChanged(modifiers) => {
358 self.modifiers_state = modifiers.state();
359 }
360 WindowEvent::KeyboardInput {
361 event:
362 KeyEvent {
363 physical_key,
364 logical_key,
365 state,
366 ..
367 },
368 ..
369 } => {
370 if !*is_window_focused {
371 return;
372 }
373
374 #[cfg(not(feature = "disable-zoom-shortcuts"))]
375 {
376 let is_control_pressed = {
377 if cfg!(target_os = "macos") {
378 self.modifiers_state.super_key()
379 } else {
380 self.modifiers_state.control_key()
381 }
382 };
383
384 if is_control_pressed && state == ElementState::Pressed {
385 let ch = logical_key.to_text();
386 let render_with_new_scale_factor = if ch == Some("+") {
387 self.custom_scale_factor =
388 (self.custom_scale_factor + 0.10).clamp(-1.0, 5.0);
389 true
390 } else if ch == Some("-") {
391 self.custom_scale_factor =
392 (self.custom_scale_factor - 0.10).clamp(-1.0, 5.0);
393 true
394 } else {
395 false
396 };
397
398 if render_with_new_scale_factor {
399 app.resize(window);
400 window.request_redraw();
401 }
402 }
403 }
404
405 let name = match state {
406 ElementState::Pressed => EventName::KeyDown,
407 ElementState::Released => EventName::KeyUp,
408 };
409 self.send_event(PlatformEvent {
410 name,
411 data: PlatformEventData::Keyboard {
412 key: map_winit_key(&logical_key),
413 code: map_winit_physical_key(&physical_key),
414 modifiers: map_winit_modifiers(self.modifiers_state),
415 },
416 })
417 }
418 WindowEvent::CursorLeft { .. } => {
419 if self.mouse_state == ElementState::Released {
420 self.cursor_pos = CursorPoint::new(-1.0, -1.0);
421
422 self.send_event(PlatformEvent {
423 name: EventName::MouseMove,
424 data: PlatformEventData::Mouse {
425 cursor: self.cursor_pos,
426 button: None,
427 },
428 });
429 }
430 }
431 WindowEvent::CursorMoved { position, .. } => {
432 self.cursor_pos = CursorPoint::from((position.x, position.y));
433
434 self.send_event(PlatformEvent {
435 name: EventName::MouseMove,
436 data: PlatformEventData::Mouse {
437 cursor: self.cursor_pos,
438 button: None,
439 },
440 });
441
442 if let Some(dropped_file_path) = self.dropped_file_path.take() {
443 self.send_event(PlatformEvent {
444 name: EventName::FileDrop,
445 data: PlatformEventData::File {
446 file_path: Some(dropped_file_path),
447 cursor: self.cursor_pos,
448 },
449 });
450 }
451 }
452 WindowEvent::Touch(Touch {
453 location,
454 phase,
455 id,
456 force,
457 ..
458 }) => {
459 self.cursor_pos = CursorPoint::from((location.x, location.y));
460
461 let name = match phase {
462 TouchPhase::Cancelled => EventName::TouchCancel,
463 TouchPhase::Ended => EventName::TouchEnd,
464 TouchPhase::Moved => EventName::TouchMove,
465 TouchPhase::Started => EventName::TouchStart,
466 };
467
468 self.send_event(PlatformEvent {
469 name,
470 data: PlatformEventData::Touch {
471 location: self.cursor_pos,
472 finger_id: id,
473 phase,
474 force,
475 },
476 });
477 }
478 WindowEvent::Resized(size) => {
479 let (new_surface, new_dirty_surface) = graphics_driver.resize(size);
480
481 *surface = new_surface;
482 *dirty_surface = new_dirty_surface;
483
484 window.request_redraw();
485
486 app.resize(window);
487 }
488 WindowEvent::DroppedFile(file_path) => {
489 self.dropped_file_path = Some(file_path);
490 }
491 WindowEvent::HoveredFile(file_path) => {
492 self.send_event(PlatformEvent {
493 name: EventName::GlobalFileHover,
494 data: PlatformEventData::File {
495 file_path: Some(file_path),
496 cursor: self.cursor_pos,
497 },
498 });
499 }
500 WindowEvent::HoveredFileCancelled => {
501 self.send_event(PlatformEvent {
502 name: EventName::GlobalFileHoverCancelled,
503 data: PlatformEventData::File {
504 file_path: None,
505 cursor: self.cursor_pos,
506 },
507 });
508 }
509 WindowEvent::Focused(is_focused) => {
510 *is_window_focused = is_focused;
511 }
512 _ => {}
513 }
514 }
515
516 fn exiting(&mut self, _event_loop: &winit::event_loop::ActiveEventLoop) {
517 self.run_on_exit();
518 }
519}