freya_winit/
renderer.rs

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
62/// Window renderer using Skia, Glutin and Winit.
63pub 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    /// Run the Winit Renderer.
76    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    // Send and process an event
125    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    /// Get the current scale factor of the Window
134    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    /// Run the `on_setup` callback that was passed to the launch function
144    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    /// Run the `on_exit` callback that was passed to the launch function
152    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}