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#[derive(Clone)]
169pub struct LaunchProxy(pub EventLoopProxy<NativeEvent>);
170
171impl LaunchProxy {
172 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
244unsafe 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 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 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 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 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 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 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 if needs_recovery && let Some(mut app) = self.windows.remove(&window_id) {
1222 let transparent = app.window_attributes.transparent;
1223 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}