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