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