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