Skip to main content

freya_winit/
renderer.rs

1use std::{
2    borrow::Cow,
3    fmt,
4    pin::Pin,
5    task::Waker,
6};
7
8use accesskit_winit::WindowEvent as AccessibilityWindowEvent;
9use freya_core::integration::*;
10use freya_engine::prelude::{
11    FontCollection,
12    FontMgr,
13};
14use futures_lite::future::FutureExt as _;
15use futures_util::{
16    FutureExt as _,
17    StreamExt,
18    select,
19};
20use ragnarok::{
21    EventsExecutorRunner,
22    EventsMeasurerRunner,
23};
24use rustc_hash::FxHashMap;
25use torin::prelude::{
26    CursorPoint,
27    Size2D,
28};
29#[cfg(all(feature = "tray", not(target_os = "linux")))]
30use tray_icon::TrayIcon;
31use winit::{
32    application::ApplicationHandler,
33    dpi::{
34        LogicalPosition,
35        LogicalSize,
36    },
37    event::{
38        ElementState,
39        Ime,
40        MouseScrollDelta,
41        Touch,
42        TouchPhase,
43        WindowEvent,
44    },
45    event_loop::{
46        ActiveEventLoop,
47        EventLoopProxy,
48    },
49    window::{
50        Theme,
51        WindowId,
52    },
53};
54
55use crate::{
56    accessibility::AccessibilityTask,
57    config::{
58        CloseDecision,
59        WindowConfig,
60    },
61    drivers::GraphicsDriver,
62    integration::is_ime_role,
63    plugins::{
64        PluginEvent,
65        PluginHandle,
66        PluginsManager,
67    },
68    window::AppWindow,
69    winit_mappings::{
70        self,
71        map_winit_mouse_button,
72        map_winit_touch_force,
73        map_winit_touch_phase,
74    },
75};
76
77pub struct WinitRenderer {
78    pub windows_configs: Vec<WindowConfig>,
79    #[cfg(feature = "tray")]
80    pub(crate) tray: (
81        Option<crate::config::TrayIconGetter>,
82        Option<crate::config::TrayHandler>,
83    ),
84    #[cfg(all(feature = "tray", not(target_os = "linux")))]
85    pub(crate) tray_icon: Option<TrayIcon>,
86    pub resumed: bool,
87    pub windows: FxHashMap<WindowId, AppWindow>,
88    pub proxy: EventLoopProxy<NativeEvent>,
89    pub plugins: PluginsManager,
90    pub fallback_fonts: Vec<Cow<'static, str>>,
91    pub font_manager: FontMgr,
92    pub font_collection: FontCollection,
93    pub futures: Vec<Pin<Box<dyn std::future::Future<Output = ()>>>>,
94    pub waker: Waker,
95    pub exit_on_close: bool,
96    pub gpu_resource_cache_limit: usize,
97}
98
99pub struct RendererContext<'a> {
100    pub windows: &'a mut FxHashMap<WindowId, AppWindow>,
101    pub proxy: &'a mut EventLoopProxy<NativeEvent>,
102    pub plugins: &'a mut PluginsManager,
103    pub fallback_fonts: &'a mut Vec<Cow<'static, str>>,
104    pub font_manager: &'a mut FontMgr,
105    pub font_collection: &'a mut FontCollection,
106    pub active_event_loop: &'a ActiveEventLoop,
107    pub gpu_resource_cache_limit: usize,
108}
109
110impl RendererContext<'_> {
111    pub fn launch_window(&mut self, window_config: WindowConfig) -> WindowId {
112        let app_window = AppWindow::new(
113            window_config,
114            self.active_event_loop,
115            self.proxy,
116            self.plugins,
117            self.font_collection,
118            self.font_manager,
119            self.fallback_fonts,
120            self.gpu_resource_cache_limit,
121        );
122
123        let window_id = app_window.window.id();
124
125        self.proxy
126            .send_event(NativeEvent::Window(NativeWindowEvent {
127                window_id,
128                action: NativeWindowEventAction::PollRunner,
129            }))
130            .ok();
131
132        self.windows.insert(window_id, app_window);
133
134        window_id
135    }
136
137    pub fn windows(&self) -> &FxHashMap<WindowId, AppWindow> {
138        self.windows
139    }
140
141    pub fn windows_mut(&mut self) -> &mut FxHashMap<WindowId, AppWindow> {
142        self.windows
143    }
144
145    pub fn exit(&mut self) {
146        self.active_event_loop.exit();
147    }
148}
149
150#[derive(Debug)]
151pub enum NativeWindowEventAction {
152    PollRunner,
153
154    Accessibility(AccessibilityWindowEvent),
155
156    PlatformEvent(PlatformEvent),
157
158    User(UserEvent),
159}
160
161/// Proxy wrapper provided to launch tasks so they can post callbacks executed inside the renderer.
162#[derive(Clone)]
163pub struct LaunchProxy(pub EventLoopProxy<NativeEvent>);
164
165impl LaunchProxy {
166    /// Queue a callback to be run on the renderer thread with access to a [`RendererContext`].
167    ///
168    /// The call dispatches an event to the winit event loop and returns right away; the
169    /// callback runs later, when the event loop picks it up. Its return value is delivered
170    /// through the returned oneshot [`Receiver`](futures_channel::oneshot::Receiver), which
171    /// can be `.await`ed or dropped.
172    ///
173    /// The callback runs outside any component scope, so you can't call `Platform::get` or
174    /// consume context from inside it; use the [`RendererContext`] argument instead.
175    pub fn post_callback<F, T: 'static>(&self, f: F) -> futures_channel::oneshot::Receiver<T>
176    where
177        F: FnOnce(&mut RendererContext) -> T + 'static,
178    {
179        let (tx, rx) = futures_channel::oneshot::channel::<T>();
180        let cb = Box::new(move |ctx: &mut RendererContext| {
181            let res = (f)(ctx);
182            let _ = tx.send(res);
183        });
184        let _ = self
185            .0
186            .send_event(NativeEvent::Generic(NativeGenericEvent::RendererCallback(
187                cb,
188            )));
189        rx
190    }
191}
192
193pub type RendererCallback = Box<dyn FnOnce(WindowId, &mut RendererContext) + 'static>;
194
195pub enum NativeWindowErasedEventAction {
196    LaunchWindow {
197        window_config: WindowConfig,
198        ack: futures_channel::oneshot::Sender<WindowId>,
199    },
200    CloseWindow(WindowId),
201    RendererCallback(RendererCallback),
202}
203
204#[derive(Debug)]
205pub struct NativeWindowEvent {
206    pub window_id: WindowId,
207    pub action: NativeWindowEventAction,
208}
209
210#[cfg(feature = "tray")]
211#[derive(Debug)]
212pub enum NativeTrayEventAction {
213    TrayEvent(tray_icon::TrayIconEvent),
214    MenuEvent(tray_icon::menu::MenuEvent),
215    LaunchWindow(SingleThreadErasedEvent),
216}
217
218#[cfg(feature = "tray")]
219#[derive(Debug)]
220pub struct NativeTrayEvent {
221    pub action: NativeTrayEventAction,
222}
223
224pub enum NativeGenericEvent {
225    PollFutures,
226    RendererCallback(Box<dyn FnOnce(&mut RendererContext) + 'static>),
227}
228
229impl fmt::Debug for NativeGenericEvent {
230    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
231        match self {
232            NativeGenericEvent::PollFutures => f.write_str("PollFutures"),
233            NativeGenericEvent::RendererCallback(_) => f.write_str("RendererCallback"),
234        }
235    }
236}
237
238/// # Safety
239/// The values are never sent, received or accessed by other threads other than the main thread.
240/// This is needed to send `Rc<T>` and other non-Send and non-Sync values.
241unsafe impl Send for NativeGenericEvent {}
242unsafe impl Sync for NativeGenericEvent {}
243
244#[derive(Debug)]
245pub enum NativeEvent {
246    Window(NativeWindowEvent),
247    #[cfg(feature = "tray")]
248    Tray(NativeTrayEvent),
249    Generic(NativeGenericEvent),
250    Preferences(mundy::Preferences),
251}
252
253impl From<accesskit_winit::Event> for NativeEvent {
254    fn from(event: accesskit_winit::Event) -> Self {
255        NativeEvent::Window(NativeWindowEvent {
256            window_id: event.window_id,
257            action: NativeWindowEventAction::Accessibility(event.window_event),
258        })
259    }
260}
261
262impl ApplicationHandler<NativeEvent> for WinitRenderer {
263    fn resumed(&mut self, active_event_loop: &winit::event_loop::ActiveEventLoop) {
264        if !self.resumed {
265            #[cfg(feature = "tray")]
266            {
267                #[cfg(not(target_os = "linux"))]
268                if let Some(tray_icon) = self.tray.0.take() {
269                    self.tray_icon = Some((tray_icon)());
270                }
271
272                #[cfg(target_os = "macos")]
273                {
274                    use objc2_core_foundation::CFRunLoop;
275
276                    let rl = CFRunLoop::main().expect("Failed to run CFRunLoop");
277                    CFRunLoop::wake_up(&rl);
278                }
279            }
280
281            for window_config in self.windows_configs.drain(..) {
282                let app_window = AppWindow::new(
283                    window_config,
284                    active_event_loop,
285                    &self.proxy,
286                    &mut self.plugins,
287                    &mut self.font_collection,
288                    &self.font_manager,
289                    &self.fallback_fonts,
290                    self.gpu_resource_cache_limit,
291                );
292
293                self.proxy
294                    .send_event(NativeEvent::Window(NativeWindowEvent {
295                        window_id: app_window.window.id(),
296                        action: NativeWindowEventAction::PollRunner,
297                    }))
298                    .ok();
299
300                self.windows.insert(app_window.window.id(), app_window);
301            }
302            self.resumed = true;
303
304            subscribe_preferences(self.proxy.clone());
305
306            let _ = self
307                .proxy
308                .send_event(NativeEvent::Generic(NativeGenericEvent::PollFutures));
309        } else {
310            // [Android] Recreate the GraphicsDriver when the app gets brought into the foreground after being suspended,
311            // so we don't end up with a completely black surface with broken rendering.
312            let old_windows: Vec<_> = self.windows.drain().collect();
313            for (_, mut app_window) in old_windows {
314                let (new_driver, new_window) = GraphicsDriver::new(
315                    active_event_loop,
316                    app_window.window_attributes.clone(),
317                    self.gpu_resource_cache_limit,
318                );
319
320                let new_id = new_window.id();
321                app_window.driver = new_driver;
322                app_window.window = new_window;
323                app_window.process_layout_on_next_render = true;
324                app_window.tree.layout.reset();
325
326                self.windows.insert(new_id, app_window);
327
328                self.proxy
329                    .send_event(NativeEvent::Window(NativeWindowEvent {
330                        window_id: new_id,
331                        action: NativeWindowEventAction::PollRunner,
332                    }))
333                    .ok();
334            }
335        }
336    }
337
338    fn user_event(
339        &mut self,
340        active_event_loop: &winit::event_loop::ActiveEventLoop,
341        event: NativeEvent,
342    ) {
343        match event {
344            NativeEvent::Generic(NativeGenericEvent::RendererCallback(cb)) => {
345                let mut renderer_context = RendererContext {
346                    fallback_fonts: &mut self.fallback_fonts,
347                    active_event_loop,
348                    windows: &mut self.windows,
349                    proxy: &mut self.proxy,
350                    plugins: &mut self.plugins,
351                    font_manager: &mut self.font_manager,
352                    font_collection: &mut self.font_collection,
353                    gpu_resource_cache_limit: self.gpu_resource_cache_limit,
354                };
355                (cb)(&mut renderer_context);
356            }
357            NativeEvent::Generic(NativeGenericEvent::PollFutures) => {
358                let mut cx = std::task::Context::from_waker(&self.waker);
359                self.futures
360                    .retain_mut(|fut| fut.poll(&mut cx).is_pending());
361            }
362            NativeEvent::Preferences(prefs) => {
363                for app in self.windows.values_mut() {
364                    app.platform
365                        .accent_color
366                        .set_if_modified(prefs.accent_color);
367                }
368            }
369            #[cfg(feature = "tray")]
370            NativeEvent::Tray(NativeTrayEvent { action }) => {
371                let renderer_context = RendererContext {
372                    fallback_fonts: &mut self.fallback_fonts,
373                    active_event_loop,
374                    windows: &mut self.windows,
375                    proxy: &mut self.proxy,
376                    plugins: &mut self.plugins,
377                    font_manager: &mut self.font_manager,
378                    font_collection: &mut self.font_collection,
379                    gpu_resource_cache_limit: self.gpu_resource_cache_limit,
380                };
381                match action {
382                    NativeTrayEventAction::TrayEvent(icon_event) => {
383                        use crate::tray::TrayEvent;
384                        if let Some(tray_handler) = &mut self.tray.1 {
385                            (tray_handler)(TrayEvent::Icon(icon_event), renderer_context)
386                        }
387                    }
388                    NativeTrayEventAction::MenuEvent(menu_event) => {
389                        use crate::tray::TrayEvent;
390                        if let Some(tray_handler) = &mut self.tray.1 {
391                            (tray_handler)(TrayEvent::Menu(menu_event), renderer_context)
392                        }
393                    }
394                    NativeTrayEventAction::LaunchWindow(data) => {
395                        let window_config = data
396                            .0
397                            .downcast::<WindowConfig>()
398                            .expect("Expected WindowConfig");
399                        let app_window = AppWindow::new(
400                            *window_config,
401                            active_event_loop,
402                            &self.proxy,
403                            &mut self.plugins,
404                            &mut self.font_collection,
405                            &self.font_manager,
406                            &self.fallback_fonts,
407                            self.gpu_resource_cache_limit,
408                        );
409
410                        self.proxy
411                            .send_event(NativeEvent::Window(NativeWindowEvent {
412                                window_id: app_window.window.id(),
413                                action: NativeWindowEventAction::PollRunner,
414                            }))
415                            .ok();
416
417                        self.windows.insert(app_window.window.id(), app_window);
418                    }
419                }
420            }
421            NativeEvent::Window(NativeWindowEvent { action, window_id }) => {
422                if let Some(app) = &mut self.windows.get_mut(&window_id) {
423                    match action {
424                        NativeWindowEventAction::PollRunner => {
425                            let mut cx = std::task::Context::from_waker(&app.waker);
426
427                            #[cfg(feature = "hotreload")]
428                            let hotreload_triggered = app
429                                .hot_reload_pending
430                                .swap(false, std::sync::atomic::Ordering::AcqRel);
431
432                            #[cfg(feature = "hotreload")]
433                            if hotreload_triggered {
434                                app.runner.reload();
435                            }
436
437                            {
438                                let fut = std::pin::pin!(async {
439                                    select! {
440                                        events_chunk = app.events_receiver.next() => {
441                                            match events_chunk {
442                                                Some(EventsChunk::Processed(processed_events)) => {
443                                                    let events_executor_adapter = EventsExecutorAdapter {
444                                                        runner: &mut app.runner,
445                                                    };
446                                                    events_executor_adapter.run(&mut app.nodes_state, processed_events);
447                                                }
448                                                Some(EventsChunk::Batch(events)) => {
449                                                    for event in events {
450                                                        app.runner.handle_event(event.node_id, event.name, event.data, event.bubbles);
451                                                    }
452                                                }
453                                                _ => {}
454                                            }
455                                        },
456                                        _ = app.runner.handle_events().fuse() => {},
457                                    }
458                                });
459
460                                match fut.poll(&mut cx) {
461                                    std::task::Poll::Ready(_) => {
462                                        self.proxy
463                                            .send_event(NativeEvent::Window(NativeWindowEvent {
464                                                window_id: app.window.id(),
465                                                action: NativeWindowEventAction::PollRunner,
466                                            }))
467                                            .ok();
468                                    }
469                                    std::task::Poll::Pending => {}
470                                }
471                            }
472
473                            self.plugins.send(
474                                PluginEvent::StartedUpdatingTree {
475                                    window: &app.window,
476                                    tree: &app.tree,
477                                },
478                                PluginHandle::new(&self.proxy),
479                            );
480                            let mutations = app.runner.sync_and_update();
481                            let result = app.runner.run_in(|| app.tree.apply_mutations(mutations));
482                            if result.needs_render {
483                                app.process_layout_on_next_render = true;
484                                app.window.request_redraw();
485                            }
486                            #[cfg(feature = "hotreload")]
487                            if hotreload_triggered {
488                                // Hot-patches can change closure bodies and custom `ElementExt` impls
489                                // that `PartialEq` can't observe, so force a layout + redraw.
490                                app.process_layout_on_next_render = true;
491                                app.window.request_redraw();
492                            }
493                            if result.needs_accessibility {
494                                app.accessibility_tasks_for_next_render |=
495                                    AccessibilityTask::ProcessUpdate { mode: None };
496                                app.window.request_redraw();
497                            }
498                            self.plugins.send(
499                                PluginEvent::FinishedUpdatingTree {
500                                    window: &app.window,
501                                    tree: &app.tree,
502                                },
503                                PluginHandle::new(&self.proxy),
504                            );
505                            #[cfg(debug_assertions)]
506                            {
507                                tracing::info!("Updated app tree.");
508                                tracing::info!("{:#?}", app.tree);
509                                tracing::info!("{:#?}", app.runner);
510                            }
511                        }
512                        NativeWindowEventAction::Accessibility(
513                            accesskit_winit::WindowEvent::AccessibilityDeactivated,
514                        ) => {
515                            app.screen_reader.set(false);
516                        }
517                        NativeWindowEventAction::Accessibility(
518                            accesskit_winit::WindowEvent::ActionRequested(_),
519                        ) => {}
520                        NativeWindowEventAction::Accessibility(
521                            accesskit_winit::WindowEvent::InitialTreeRequested,
522                        ) => {
523                            app.accessibility_tasks_for_next_render = AccessibilityTask::Init;
524                            app.window.request_redraw();
525                        }
526                        NativeWindowEventAction::User(user_event) => match user_event {
527                            UserEvent::RequestRedraw => {
528                                app.window.request_redraw();
529                            }
530                            UserEvent::FocusAccessibilityNode(strategy) => {
531                                let task = match strategy {
532                                    AccessibilityFocusStrategy::Backward(_)
533                                    | AccessibilityFocusStrategy::Forward(_) => {
534                                        AccessibilityTask::ProcessUpdate {
535                                            mode: Some(NavigationMode::Keyboard),
536                                        }
537                                    }
538                                    _ => AccessibilityTask::ProcessUpdate { mode: None },
539                                };
540                                app.tree.accessibility_diff.request_focus(strategy);
541                                app.accessibility_tasks_for_next_render = task;
542                                app.window.request_redraw();
543                            }
544                            UserEvent::SetCursorIcon(cursor_icon) => {
545                                app.window.set_cursor(cursor_icon);
546                            }
547                            UserEvent::Erased(data) => {
548                                let action = data
549                                    .0
550                                    .downcast::<NativeWindowErasedEventAction>()
551                                    .expect("Expected NativeWindowErasedEventAction");
552                                match *action {
553                                    NativeWindowErasedEventAction::LaunchWindow {
554                                        window_config,
555                                        ack,
556                                    } => {
557                                        let app_window = AppWindow::new(
558                                            window_config,
559                                            active_event_loop,
560                                            &self.proxy,
561                                            &mut self.plugins,
562                                            &mut self.font_collection,
563                                            &self.font_manager,
564                                            &self.fallback_fonts,
565                                            self.gpu_resource_cache_limit,
566                                        );
567
568                                        let window_id = app_window.window.id();
569
570                                        let _ = self.proxy.send_event(NativeEvent::Window(
571                                            NativeWindowEvent {
572                                                window_id,
573                                                action: NativeWindowEventAction::PollRunner,
574                                            },
575                                        ));
576
577                                        self.windows.insert(window_id, app_window);
578                                        let _ = ack.send(window_id);
579                                    }
580                                    NativeWindowErasedEventAction::CloseWindow(window_id) => {
581                                        // Its fine to ignore if the window doesnt exist anymore
582                                        let _ = self.windows.remove(&window_id);
583                                        let has_windows = !self.windows.is_empty();
584
585                                        let has_tray = {
586                                            #[cfg(feature = "tray")]
587                                            {
588                                                self.tray.1.is_some()
589                                            }
590                                            #[cfg(not(feature = "tray"))]
591                                            {
592                                                false
593                                            }
594                                        };
595
596                                        // Only exit when there is no window and no tray
597                                        if !has_windows && !has_tray && self.exit_on_close {
598                                            active_event_loop.exit();
599                                        }
600                                    }
601                                    NativeWindowErasedEventAction::RendererCallback(cb) => {
602                                        let window_id = app.window.id();
603                                        let mut renderer_context = RendererContext {
604                                            fallback_fonts: &mut self.fallback_fonts,
605                                            active_event_loop,
606                                            windows: &mut self.windows,
607                                            proxy: &mut self.proxy,
608                                            plugins: &mut self.plugins,
609                                            font_manager: &mut self.font_manager,
610                                            font_collection: &mut self.font_collection,
611                                            gpu_resource_cache_limit: self.gpu_resource_cache_limit,
612                                        };
613                                        (cb)(window_id, &mut renderer_context);
614                                    }
615                                }
616                            }
617                        },
618                        NativeWindowEventAction::PlatformEvent(platform_event) => {
619                            let mut events_measurer_adapter = EventsMeasurerAdapter {
620                                scale_factor: app.effective_scale_factor(),
621                                tree: &mut app.tree,
622                            };
623                            let processed_events = events_measurer_adapter.run(
624                                &mut vec![platform_event],
625                                &mut app.nodes_state,
626                                app.accessibility.focused_node_id(),
627                            );
628                            app.events_sender
629                                .unbounded_send(EventsChunk::Processed(processed_events))
630                                .unwrap();
631                        }
632                    }
633                }
634            }
635        }
636    }
637
638    fn window_event(
639        &mut self,
640        event_loop: &winit::event_loop::ActiveEventLoop,
641        window_id: winit::window::WindowId,
642        event: winit::event::WindowEvent,
643    ) {
644        if let Some(app) = &mut self.windows.get_mut(&window_id) {
645            app.accessibility_adapter.process_event(&app.window, &event);
646            match event {
647                WindowEvent::ThemeChanged(theme) => {
648                    app.platform.preferred_theme.set(match theme {
649                        Theme::Light => PreferredTheme::Light,
650                        Theme::Dark => PreferredTheme::Dark,
651                    });
652                }
653                WindowEvent::ScaleFactorChanged { .. } => {
654                    app.sync_scale_factor();
655                    app.window.request_redraw();
656                    app.process_layout_on_next_render = true;
657                    app.tree.layout.reset();
658                    app.tree.text_cache.reset();
659                }
660                WindowEvent::CloseRequested => {
661                    let mut on_close_hook = self
662                        .windows
663                        .get_mut(&window_id)
664                        .and_then(|app| app.on_close.take());
665
666                    let decision = if let Some(ref mut on_close) = on_close_hook {
667                        let renderer_context = RendererContext {
668                            fallback_fonts: &mut self.fallback_fonts,
669                            active_event_loop: event_loop,
670                            windows: &mut self.windows,
671                            proxy: &mut self.proxy,
672                            plugins: &mut self.plugins,
673                            font_manager: &mut self.font_manager,
674                            font_collection: &mut self.font_collection,
675                            gpu_resource_cache_limit: self.gpu_resource_cache_limit,
676                        };
677                        on_close(renderer_context, window_id)
678                    } else {
679                        CloseDecision::Close
680                    };
681
682                    if matches!(decision, CloseDecision::KeepOpen)
683                        && let Some(app) = self.windows.get_mut(&window_id)
684                    {
685                        app.on_close = on_close_hook;
686                    }
687
688                    if matches!(decision, CloseDecision::Close) {
689                        self.windows.remove(&window_id);
690                        let has_windows = !self.windows.is_empty();
691
692                        let has_tray = {
693                            #[cfg(feature = "tray")]
694                            {
695                                self.tray.1.is_some()
696                            }
697                            #[cfg(not(feature = "tray"))]
698                            {
699                                false
700                            }
701                        };
702
703                        // Only exit when there is no windows and no tray
704                        if !has_windows && !has_tray && self.exit_on_close {
705                            event_loop.exit();
706                        }
707                    }
708                }
709                WindowEvent::ModifiersChanged(modifiers) => {
710                    app.modifiers_state = modifiers.state();
711                }
712                WindowEvent::Focused(is_focused) => {
713                    app.platform.is_app_focused.set_if_modified(is_focused);
714                }
715                WindowEvent::RedrawRequested => {
716                    let scale_factor = app.effective_scale_factor();
717                    hotpath::measure_block!("RedrawRequested", {
718                        if app.process_layout_on_next_render {
719                            self.plugins.send(
720                                PluginEvent::StartedMeasuringLayout {
721                                    window: &app.window,
722                                    tree: &app.tree,
723                                },
724                                PluginHandle::new(&self.proxy),
725                            );
726                            let size: Size2D = (
727                                app.window.inner_size().width as f32,
728                                app.window.inner_size().height as f32,
729                            )
730                                .into();
731
732                            app.tree.measure_layout(
733                                size,
734                                &mut self.font_collection,
735                                &self.font_manager,
736                                &app.events_sender,
737                                scale_factor,
738                                &self.fallback_fonts,
739                            );
740                            app.platform.root_size.set_if_modified(size);
741                            app.process_layout_on_next_render = false;
742                            self.plugins.send(
743                                PluginEvent::FinishedMeasuringLayout {
744                                    window: &app.window,
745                                    tree: &app.tree,
746                                },
747                                PluginHandle::new(&self.proxy),
748                            );
749                        }
750
751                        app.driver.present(
752                            app.window.inner_size().cast(),
753                            &app.window,
754                            |surface| {
755                                self.plugins.send(
756                                    PluginEvent::BeforeRender {
757                                        window: &app.window,
758                                        canvas: surface.canvas(),
759                                        font_collection: &self.font_collection,
760                                        tree: &app.tree,
761                                    },
762                                    PluginHandle::new(&self.proxy),
763                                );
764
765                                let render_pipeline = RenderPipeline {
766                                    font_collection: &mut self.font_collection,
767                                    font_manager: &self.font_manager,
768                                    tree: &app.tree,
769                                    canvas: surface.canvas(),
770                                    scale_factor,
771                                    background: app.background,
772                                };
773
774                                render_pipeline.render();
775
776                                self.plugins.send(
777                                    PluginEvent::AfterRender {
778                                        window: &app.window,
779                                        canvas: surface.canvas(),
780                                        font_collection: &self.font_collection,
781                                        tree: &app.tree,
782                                        animation_clock: &app.animation_clock,
783                                    },
784                                    PluginHandle::new(&self.proxy),
785                                );
786                                self.plugins.send(
787                                    PluginEvent::BeforePresenting {
788                                        window: &app.window,
789                                        font_collection: &self.font_collection,
790                                        tree: &app.tree,
791                                    },
792                                    PluginHandle::new(&self.proxy),
793                                );
794                            },
795                        );
796                        self.plugins.send(
797                            PluginEvent::AfterPresenting {
798                                window: &app.window,
799                                font_collection: &self.font_collection,
800                                tree: &app.tree,
801                            },
802                            PluginHandle::new(&self.proxy),
803                        );
804
805                        self.plugins.send(
806                            PluginEvent::BeforeAccessibility {
807                                window: &app.window,
808                                font_collection: &self.font_collection,
809                                tree: &app.tree,
810                            },
811                            PluginHandle::new(&self.proxy),
812                        );
813
814                        match app.accessibility_tasks_for_next_render.take() {
815                            AccessibilityTask::ProcessUpdate { mode } => {
816                                let update = app
817                                    .accessibility
818                                    .process_updates(&mut app.tree, &app.events_sender);
819                                app.platform
820                                    .focused_accessibility_id
821                                    .set_if_modified(update.focus);
822                                let node_id = app.accessibility.focused_node_id().unwrap();
823                                let layout_node = app.tree.layout.get(&node_id).unwrap();
824                                let focused_node =
825                                    AccessibilityTree::create_node(node_id, layout_node, &app.tree);
826                                app.window.set_ime_allowed(is_ime_role(focused_node.role()));
827                                app.platform
828                                    .focused_accessibility_node
829                                    .set_if_modified(focused_node);
830                                if let Some(mode) = mode {
831                                    app.platform.navigation_mode.set(mode);
832                                }
833
834                                let area = layout_node.visible_area();
835                                app.window.set_ime_cursor_area(
836                                    LogicalPosition::new(area.min_x(), area.min_y()),
837                                    LogicalSize::new(area.width(), area.height()),
838                                );
839
840                                if app.screen_reader.is_on() {
841                                    app.accessibility_adapter.update_if_active(|| update);
842                                }
843                            }
844                            AccessibilityTask::Init => {
845                                let update = app.accessibility.init(&mut app.tree);
846                                app.platform
847                                    .focused_accessibility_id
848                                    .set_if_modified(update.focus);
849                                let node_id = app.accessibility.focused_node_id().unwrap();
850                                let layout_node = app.tree.layout.get(&node_id).unwrap();
851                                let focused_node =
852                                    AccessibilityTree::create_node(node_id, layout_node, &app.tree);
853                                app.window.set_ime_allowed(is_ime_role(focused_node.role()));
854                                app.platform
855                                    .focused_accessibility_node
856                                    .set_if_modified(focused_node);
857
858                                let area = layout_node.visible_area();
859                                app.window.set_ime_cursor_area(
860                                    LogicalPosition::new(area.min_x(), area.min_y()),
861                                    LogicalSize::new(area.width(), area.height()),
862                                );
863
864                                app.screen_reader.set(true);
865                                app.accessibility_adapter.update_if_active(|| update);
866                            }
867                            AccessibilityTask::None => {}
868                        }
869
870                        self.plugins.send(
871                            PluginEvent::AfterAccessibility {
872                                window: &app.window,
873                                font_collection: &self.font_collection,
874                                tree: &app.tree,
875                            },
876                            PluginHandle::new(&self.proxy),
877                        );
878
879                        app.ticker_sender.send(()).ok();
880
881                        self.plugins.send(
882                            PluginEvent::AfterRedraw {
883                                window: &app.window,
884                                font_collection: &self.font_collection,
885                                tree: &app.tree,
886                            },
887                            PluginHandle::new(&self.proxy),
888                        );
889                    });
890                }
891                WindowEvent::Resized(size) => {
892                    app.driver.resize(size);
893
894                    app.window.request_redraw();
895
896                    app.process_layout_on_next_render = true;
897                    app.tree.layout.clear_dirty();
898                    app.tree.layout.invalidate(NodeId::ROOT);
899                }
900
901                WindowEvent::MouseInput { state, button, .. } => {
902                    app.mouse_state = state;
903                    app.platform
904                        .navigation_mode
905                        .set(NavigationMode::NotKeyboard);
906
907                    let name = if state == ElementState::Pressed {
908                        MouseEventName::MouseDown
909                    } else {
910                        MouseEventName::MouseUp
911                    };
912                    let platform_event = PlatformEvent::Mouse {
913                        name,
914                        cursor: (app.position.x, app.position.y).into(),
915                        button: Some(map_winit_mouse_button(button)),
916                    };
917                    let mut events_measurer_adapter = EventsMeasurerAdapter {
918                        scale_factor: app.effective_scale_factor(),
919                        tree: &mut app.tree,
920                    };
921                    let processed_events = events_measurer_adapter.run(
922                        &mut vec![platform_event],
923                        &mut app.nodes_state,
924                        app.accessibility.focused_node_id(),
925                    );
926                    app.events_sender
927                        .unbounded_send(EventsChunk::Processed(processed_events))
928                        .unwrap();
929                }
930
931                WindowEvent::KeyboardInput {
932                    event,
933                    is_synthetic,
934                    ..
935                } => {
936                    // Ignore synthetic presses (e.g. Tab on alt-tab) but keep synthetic releases so keys don't get stuck.
937                    if is_synthetic && event.state == ElementState::Pressed {
938                        return;
939                    }
940
941                    let name = match event.state {
942                        ElementState::Pressed => KeyboardEventName::KeyDown,
943                        ElementState::Released => KeyboardEventName::KeyUp,
944                    };
945                    let key = winit_mappings::map_winit_key(&event.logical_key);
946                    let code = winit_mappings::map_winit_physical_key(&event.physical_key);
947                    let modifiers = winit_mappings::map_winit_modifiers(app.modifiers_state);
948
949                    #[cfg(feature = "zoom-shortcuts")]
950                    if app.try_handle_zoom_shortcut(&key, modifiers, event.state.is_pressed()) {
951                        return;
952                    }
953
954                    self.plugins.send(
955                        PluginEvent::KeyboardInput {
956                            window: &app.window,
957                            key: key.clone(),
958                            code,
959                            modifiers,
960                            is_pressed: event.state.is_pressed(),
961                        },
962                        PluginHandle::new(&self.proxy),
963                    );
964
965                    let platform_event = PlatformEvent::Keyboard {
966                        name,
967                        key,
968                        code,
969                        modifiers,
970                    };
971                    let mut events_measurer_adapter = EventsMeasurerAdapter {
972                        scale_factor: app.effective_scale_factor(),
973                        tree: &mut app.tree,
974                    };
975                    let processed_events = events_measurer_adapter.run(
976                        &mut vec![platform_event],
977                        &mut app.nodes_state,
978                        app.accessibility.focused_node_id(),
979                    );
980                    app.events_sender
981                        .unbounded_send(EventsChunk::Processed(processed_events))
982                        .unwrap();
983                }
984
985                WindowEvent::MouseWheel { delta, phase, .. } => {
986                    const WHEEL_SPEED_MODIFIER: f64 = 53.0;
987                    const TOUCHPAD_SPEED_MODIFIER: f64 = 2.0;
988
989                    if TouchPhase::Moved == phase {
990                        let scroll_data = {
991                            match delta {
992                                MouseScrollDelta::LineDelta(x, y) => (
993                                    (x as f64 * WHEEL_SPEED_MODIFIER),
994                                    (y as f64 * WHEEL_SPEED_MODIFIER),
995                                ),
996                                MouseScrollDelta::PixelDelta(pos) => (
997                                    (pos.x * TOUCHPAD_SPEED_MODIFIER),
998                                    (pos.y * TOUCHPAD_SPEED_MODIFIER),
999                                ),
1000                            }
1001                        };
1002
1003                        let platform_event = PlatformEvent::Wheel {
1004                            name: WheelEventName::Wheel,
1005                            scroll: scroll_data.into(),
1006                            cursor: app.position,
1007                            source: WheelSource::Device,
1008                        };
1009                        let mut events_measurer_adapter = EventsMeasurerAdapter {
1010                            scale_factor: app.effective_scale_factor(),
1011                            tree: &mut app.tree,
1012                        };
1013                        let processed_events = events_measurer_adapter.run(
1014                            &mut vec![platform_event],
1015                            &mut app.nodes_state,
1016                            app.accessibility.focused_node_id(),
1017                        );
1018                        app.events_sender
1019                            .unbounded_send(EventsChunk::Processed(processed_events))
1020                            .unwrap();
1021                    }
1022                }
1023
1024                WindowEvent::CursorLeft { .. } => {
1025                    if app.mouse_state == ElementState::Released {
1026                        app.position = CursorPoint::from((-1., -1.));
1027                        let platform_event = PlatformEvent::Mouse {
1028                            name: MouseEventName::MouseMove,
1029                            cursor: app.position,
1030                            button: None,
1031                        };
1032                        let mut events_measurer_adapter = EventsMeasurerAdapter {
1033                            scale_factor: app.effective_scale_factor(),
1034                            tree: &mut app.tree,
1035                        };
1036                        let processed_events = events_measurer_adapter.run(
1037                            &mut vec![platform_event],
1038                            &mut app.nodes_state,
1039                            app.accessibility.focused_node_id(),
1040                        );
1041                        app.events_sender
1042                            .unbounded_send(EventsChunk::Processed(processed_events))
1043                            .unwrap();
1044                    }
1045                }
1046                WindowEvent::CursorMoved { position, .. } => {
1047                    app.position = CursorPoint::from((position.x, position.y));
1048
1049                    let mut platform_event = vec![PlatformEvent::Mouse {
1050                        name: MouseEventName::MouseMove,
1051                        cursor: app.position,
1052                        button: None,
1053                    }];
1054
1055                    for dropped_file_path in app.dropped_file_paths.drain(..) {
1056                        platform_event.push(PlatformEvent::File {
1057                            name: FileEventName::FileDrop,
1058                            file_path: Some(dropped_file_path),
1059                            cursor: app.position,
1060                        });
1061                    }
1062
1063                    let mut events_measurer_adapter = EventsMeasurerAdapter {
1064                        scale_factor: app.effective_scale_factor(),
1065                        tree: &mut app.tree,
1066                    };
1067                    let processed_events = events_measurer_adapter.run(
1068                        &mut platform_event,
1069                        &mut app.nodes_state,
1070                        app.accessibility.focused_node_id(),
1071                    );
1072                    app.events_sender
1073                        .unbounded_send(EventsChunk::Processed(processed_events))
1074                        .unwrap();
1075                }
1076
1077                WindowEvent::Touch(Touch {
1078                    location,
1079                    phase,
1080                    id,
1081                    force,
1082                    ..
1083                }) => {
1084                    app.position = CursorPoint::from((location.x, location.y));
1085
1086                    let name = match phase {
1087                        TouchPhase::Cancelled => TouchEventName::TouchCancel,
1088                        TouchPhase::Ended => TouchEventName::TouchEnd,
1089                        TouchPhase::Moved => TouchEventName::TouchMove,
1090                        TouchPhase::Started => TouchEventName::TouchStart,
1091                    };
1092
1093                    let platform_event = PlatformEvent::Touch {
1094                        name,
1095                        location: app.position,
1096                        finger_id: id,
1097                        phase: map_winit_touch_phase(phase),
1098                        force: force.map(map_winit_touch_force),
1099                    };
1100                    let mut events_measurer_adapter = EventsMeasurerAdapter {
1101                        scale_factor: app.effective_scale_factor(),
1102                        tree: &mut app.tree,
1103                    };
1104                    let processed_events = events_measurer_adapter.run(
1105                        &mut vec![platform_event],
1106                        &mut app.nodes_state,
1107                        app.accessibility.focused_node_id(),
1108                    );
1109                    app.events_sender
1110                        .unbounded_send(EventsChunk::Processed(processed_events))
1111                        .unwrap();
1112                    app.position = CursorPoint::from((location.x, location.y));
1113                }
1114                WindowEvent::Ime(Ime::Commit(text)) => {
1115                    let platform_event = PlatformEvent::Keyboard {
1116                        name: KeyboardEventName::KeyDown,
1117                        key: keyboard_types::Key::Character(text),
1118                        code: keyboard_types::Code::Unidentified,
1119                        modifiers: winit_mappings::map_winit_modifiers(app.modifiers_state),
1120                    };
1121                    let mut events_measurer_adapter = EventsMeasurerAdapter {
1122                        scale_factor: app.effective_scale_factor(),
1123                        tree: &mut app.tree,
1124                    };
1125                    let processed_events = events_measurer_adapter.run(
1126                        &mut vec![platform_event],
1127                        &mut app.nodes_state,
1128                        app.accessibility.focused_node_id(),
1129                    );
1130                    app.events_sender
1131                        .unbounded_send(EventsChunk::Processed(processed_events))
1132                        .unwrap();
1133                }
1134                WindowEvent::Ime(Ime::Preedit(text, pos)) => {
1135                    let platform_event = PlatformEvent::ImePreedit {
1136                        name: ImeEventName::Preedit,
1137                        text,
1138                        cursor: pos,
1139                    };
1140                    let mut events_measurer_adapter = EventsMeasurerAdapter {
1141                        scale_factor: app.effective_scale_factor(),
1142                        tree: &mut app.tree,
1143                    };
1144                    let processed_events = events_measurer_adapter.run(
1145                        &mut vec![platform_event],
1146                        &mut app.nodes_state,
1147                        app.accessibility.focused_node_id(),
1148                    );
1149                    app.events_sender
1150                        .unbounded_send(EventsChunk::Processed(processed_events))
1151                        .unwrap();
1152                }
1153                WindowEvent::DroppedFile(file_path) => {
1154                    app.dropped_file_paths.push(file_path);
1155                }
1156                WindowEvent::HoveredFile(file_path) => {
1157                    let platform_event = PlatformEvent::File {
1158                        name: FileEventName::FileHover,
1159                        file_path: Some(file_path),
1160                        cursor: app.position,
1161                    };
1162                    let mut events_measurer_adapter = EventsMeasurerAdapter {
1163                        scale_factor: app.effective_scale_factor(),
1164                        tree: &mut app.tree,
1165                    };
1166                    let processed_events = events_measurer_adapter.run(
1167                        &mut vec![platform_event],
1168                        &mut app.nodes_state,
1169                        app.accessibility.focused_node_id(),
1170                    );
1171                    app.events_sender
1172                        .unbounded_send(EventsChunk::Processed(processed_events))
1173                        .unwrap();
1174                }
1175                WindowEvent::HoveredFileCancelled => {
1176                    let platform_event = PlatformEvent::File {
1177                        name: FileEventName::FileHoverCancelled,
1178                        file_path: None,
1179                        cursor: app.position,
1180                    };
1181                    let mut events_measurer_adapter = EventsMeasurerAdapter {
1182                        scale_factor: app.effective_scale_factor(),
1183                        tree: &mut app.tree,
1184                    };
1185                    let processed_events = events_measurer_adapter.run(
1186                        &mut vec![platform_event],
1187                        &mut app.nodes_state,
1188                        app.accessibility.focused_node_id(),
1189                    );
1190                    app.events_sender
1191                        .unbounded_send(EventsChunk::Processed(processed_events))
1192                        .unwrap();
1193                }
1194                _ => {}
1195            }
1196        }
1197    }
1198}
1199
1200fn subscribe_preferences(proxy: EventLoopProxy<NativeEvent>) {
1201    let subscription = mundy::Preferences::subscribe(mundy::Interest::AccentColor, move |prefs| {
1202        let _ = proxy.send_event(NativeEvent::Preferences(prefs));
1203    });
1204    std::mem::forget(subscription);
1205}