1use std::{
2 collections::VecDeque,
3 fmt, mem,
4 sync::{
5 Arc,
6 atomic::{AtomicBool, Ordering},
7 },
8};
9
10use tracing::span::EnteredSpan;
11use webrender::{
12 RenderApi, Renderer, Transaction, UploadMethod, VertexUsageHint,
13 api::{DocumentId, DynamicProperties, FontInstanceKey, FontKey, FontVariation, PipelineId},
14};
15
16use winit::{
17 event_loop::ActiveEventLoop,
18 monitor::{MonitorHandle, VideoModeHandle as GVideoMode},
19 window::{CustomCursor, Fullscreen, Icon, Window as GWindow, WindowAttributes},
20};
21use zng_txt::{ToTxt, Txt};
22use zng_unit::{DipPoint, DipRect, DipSideOffsets, DipSize, DipToPx, Factor, Px, PxPoint, PxRect, PxToDip, PxVector, Rgba};
23use zng_view_api::{
24 DeviceId, Event, ViewProcessGen,
25 api_extension::{ApiExtensionId, ApiExtensionPayload},
26 font::{FontFaceId, FontId, FontOptions, FontVariationName},
27 image::{ImageId, ImageLoadedData, ImageMaskMode, ImageTextureId},
28 window::{
29 CursorIcon, FocusIndicator, FrameCapture, FrameId, FrameRequest, FrameUpdateRequest, RenderMode, ResizeDirection, VideoMode,
30 WindowButton, WindowId, WindowRequest, WindowState, WindowStateAll,
31 },
32};
33
34use zng_view_api::dialog as dlg_api;
35
36#[cfg(windows)]
37use zng_view_api::keyboard::{Key, KeyCode, KeyState};
38
39use crate::{
40 AppEvent, AppEventSender, FrameReadyMsg, WrNotifier,
41 display_list::{DisplayListCache, display_list_to_webrender},
42 extensions::{
43 self, BlobExtensionsImgHandler, DisplayListExtAdapter, FrameReadyArgs, RedrawArgs, RendererCommandArgs, RendererConfigArgs,
44 RendererDeinitedArgs, RendererExtension, RendererInitedArgs, WindowCommandArgs, WindowConfigArgs, WindowDeinitedArgs,
45 WindowExtension, WindowInitedArgs,
46 },
47 gl::{GlContext, GlContextManager},
48 image_cache::{Image, ImageCache, ImageUseMap, WrImageCache},
49 px_wr::PxToWr as _,
50 util::{
51 CursorToWinit, DipToWinit, PxToWinit, ResizeDirectionToWinit as _, WindowButtonsToWinit as _, WinitToDip, WinitToPx,
52 frame_render_reasons, frame_update_render_reasons,
53 },
54};
55
56pub(crate) struct Window {
58 id: WindowId,
59 pipeline_id: PipelineId,
60 document_id: DocumentId,
61
62 api: RenderApi,
63 image_use: ImageUseMap,
64
65 display_list_cache: DisplayListCache,
66 clear_color: Option<Rgba>,
67
68 context: GlContext, window: GWindow,
70 renderer: Option<Renderer>,
71 window_exts: Vec<(ApiExtensionId, Box<dyn WindowExtension>)>,
72 renderer_exts: Vec<(ApiExtensionId, Box<dyn RendererExtension>)>,
73 external_images: extensions::ExternalImages,
74 capture_mode: bool,
75
76 pending_frames: VecDeque<(FrameId, FrameCapture, Option<EnteredSpan>)>,
77 rendered_frame_id: FrameId,
78 kiosk: bool,
79
80 resized: bool,
81
82 video_mode: VideoMode,
83
84 state: WindowStateAll,
85
86 prev_pos: PxPoint, prev_size: DipSize,
88
89 prev_monitor: Option<MonitorHandle>,
90
91 visible: bool,
92 is_always_on_top: bool,
93 waiting_first_frame: bool,
94 steal_init_focus: bool,
95 init_focus_request: Option<FocusIndicator>,
96
97 taskbar_visible: bool,
98
99 movable: bool,
100
101 cursor_pos: DipPoint,
102 cursor_device: DeviceId,
103 cursor_over: bool,
104
105 touch_pos: Vec<((DeviceId, u64), DipPoint)>,
106
107 focused: Option<bool>,
108
109 render_mode: RenderMode,
110
111 modal_dialog_active: Arc<AtomicBool>,
112
113 access: Option<accesskit_winit::Adapter>, ime_area: Option<DipRect>,
116 #[cfg(windows)]
117 has_shutdown_warn: bool,
118
119 cursor: Option<CursorIcon>,
120 cursor_img: Option<CustomCursor>,
121
122 #[cfg(any(
123 target_os = "linux",
124 target_os = "dragonfly",
125 target_os = "freebsd",
126 target_os = "netbsd",
127 target_os = "openbsd"
128 ))]
129 xlib_maximize: bool,
130}
131impl fmt::Debug for Window {
132 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
133 f.debug_struct("Window")
134 .field("id", &self.id)
135 .field("pipeline_id", &self.pipeline_id)
136 .field("document_id", &self.document_id)
137 .finish_non_exhaustive()
138 }
139}
140impl Window {
141 #[expect(clippy::too_many_arguments)]
142 pub fn open(
143 vp_gen: ViewProcessGen,
144 cfg_icon: Option<Icon>,
145 cfg_cursor_image: Option<CustomCursor>,
146 cfg: WindowRequest,
147 winit_loop: &ActiveEventLoop,
148 gl_manager: &mut GlContextManager,
149 mut window_exts: Vec<(ApiExtensionId, Box<dyn WindowExtension>)>,
150 mut renderer_exts: Vec<(ApiExtensionId, Box<dyn RendererExtension>)>,
151 event_sender: AppEventSender,
152 ) -> Self {
153 let id = cfg.id;
154
155 let window_scope = tracing::trace_span!("glutin").entered();
156
157 let mut winit = WindowAttributes::default()
159 .with_title(cfg.title)
160 .with_resizable(cfg.resizable)
161 .with_transparent(cfg.transparent && cfg!(not(target_os = "android")))
162 .with_window_icon(cfg_icon);
163
164 let mut s = cfg.state;
165 s.clamp_size();
166
167 let mut monitor = winit_loop.primary_monitor();
168 let mut monitor_is_primary = true;
169 for m in winit_loop.available_monitors() {
170 let pos = m.position();
171 let size = m.size();
172 let rect = PxRect::new(pos.to_px(), size.to_px());
173
174 if rect.contains(s.global_position) {
175 let m = Some(m);
176 monitor_is_primary = m == monitor;
177 monitor = m;
178 break;
179 }
180 }
181 let monitor = monitor;
182 let monitor_is_primary = monitor_is_primary;
183
184 if let WindowState::Normal = s.state {
185 winit = winit
186 .with_min_inner_size(s.min_size.to_winit())
187 .with_max_inner_size(s.max_size.to_winit())
188 .with_inner_size(s.restore_rect.size.to_winit());
189
190 if let Some(m) = monitor {
191 if cfg.default_position {
192 if (cfg!(windows) && !monitor_is_primary) || cfg!(target_os = "linux") {
193 let mut pos = m.position();
197 pos.x += 120;
198 pos.y += 80;
199 winit = winit.with_position(pos);
200 }
201 } else {
202 let mut pos_in_monitor = s.restore_rect.origin.to_px(Factor(m.scale_factor() as _));
203
204 let monitor_size = m.size();
205 if pos_in_monitor.x.0 > monitor_size.width as _ {
206 pos_in_monitor.x.0 = 120;
207 }
208 if pos_in_monitor.y.0 > monitor_size.height as _ {
209 pos_in_monitor.y.0 = 80;
210 }
211
212 let mut pos = m.position();
213 pos.x += pos_in_monitor.x.0;
214 pos.y += pos_in_monitor.y.0;
215
216 winit = winit.with_position(pos);
217 }
218 }
219 } else if let Some(m) = monitor {
220 let screen_size = m.size().to_px().to_dip(Factor(m.scale_factor() as _));
222 s.restore_rect.origin.x = (screen_size.width - s.restore_rect.size.width) / 2.0;
223 s.restore_rect.origin.y = (screen_size.height - s.restore_rect.size.height) / 2.0;
224
225 winit = winit.with_position(m.position());
227 }
228
229 winit = winit
230 .with_decorations(s.chrome_visible)
231 .with_visible(cfg!(target_os = "android") || cfg.kiosk || matches!(s.state, WindowState::Exclusive));
236
237 let mut render_mode = cfg.render_mode;
238 if !cfg!(feature = "software") && render_mode == RenderMode::Software {
239 tracing::warn!("ignoring `RenderMode::Software` because did not build with \"software\" feature");
240 render_mode = RenderMode::Integrated;
241 }
242
243 #[cfg(windows)]
244 let mut prefer_egl = false;
245 #[cfg(not(windows))]
246 let prefer_egl = false;
247
248 for (id, ext) in &mut window_exts {
249 ext.configure(&mut WindowConfigArgs {
250 config: cfg.extensions.iter().find(|(k, _)| k == id).map(|(_, p)| p),
251 window: Some(&mut winit),
252 });
253
254 #[cfg(windows)]
255 if let Some(ext) = ext.as_any().downcast_ref::<crate::extensions::PreferAngleExt>() {
256 prefer_egl = ext.prefer_egl;
257 }
258 }
259
260 let (winit_window, mut context) = gl_manager.create_headed(id, winit, winit_loop, render_mode, &event_sender, prefer_egl);
261
262 render_mode = context.render_mode();
263
264 window_exts.retain_mut(|(_, ext)| {
265 ext.window_inited(&mut WindowInitedArgs {
266 window: &winit_window,
267 context: &mut context,
268 });
269 !ext.is_init_only()
270 });
271
272 #[cfg(windows)]
276 {
277 let event_sender = event_sender.clone();
278
279 let mut first_focus = false;
280
281 let window_id = winit_window.id();
282 let hwnd = crate::util::winit_to_hwnd(&winit_window);
283 crate::util::set_raw_windows_event_handler(hwnd, u32::from_ne_bytes(*b"alf4") as _, move |_, msg, wparam, _| {
284 if !first_focus && unsafe { windows_sys::Win32::UI::WindowsAndMessaging::GetForegroundWindow() } == hwnd {
285 first_focus = true;
292 let _ = event_sender.send(AppEvent::WinitFocused(window_id, true));
293 }
294
295 match msg {
296 windows_sys::Win32::UI::WindowsAndMessaging::WM_SYSKEYDOWN => {
297 if wparam as windows_sys::Win32::UI::Input::KeyboardAndMouse::VIRTUAL_KEY
298 == windows_sys::Win32::UI::Input::KeyboardAndMouse::VK_F4
299 {
300 let _ = event_sender.send(AppEvent::Notify(Event::KeyboardInput {
303 window: id,
304 device: DeviceId::INVALID, key_code: KeyCode::F4,
306 state: KeyState::Pressed,
307 key: Key::F4,
308 key_modified: Key::F4,
309 text: Txt::from_static(""),
310 key_location: zng_view_api::keyboard::KeyLocation::Standard,
311 }));
312 return Some(0);
313 }
314 }
315 windows_sys::Win32::UI::WindowsAndMessaging::WM_QUERYENDSESSION => {
316 let mut reason = [0u16; 256];
317 let mut reason_size = reason.len() as u32;
318 let ok = unsafe {
319 windows_sys::Win32::System::Shutdown::ShutdownBlockReasonQuery(hwnd, reason.as_mut_ptr(), &mut reason_size)
320 };
321 if ok != 0 {
322 match windows::core::HSTRING::from_wide(&reason) {
323 Ok(s) => {
324 tracing::warn!("blocked system shutdown, reason: {}", s);
325 }
326 Err(e) => {
327 tracing::error!("blocked system shutdown, error retrieving reason: {e}");
328 }
329 }
330 let _ = event_sender.send(AppEvent::Notify(Event::WindowCloseRequested(id)));
332 return Some(0);
333 }
334 }
335 _ => {}
336 }
337
338 None
339 });
340 }
341
342 drop(window_scope);
343 let wr_scope = tracing::trace_span!("webrender").entered();
344
345 let device_size = winit_window.inner_size().to_px().to_wr_device();
348
349 let mut opts = webrender::WebRenderOptions {
350 enable_aa: true,
352 enable_subpixel_aa: cfg!(not(target_os = "android")),
353
354 renderer_id: Some(((vp_gen.get() as u64) << 32) | id.get() as u64),
355
356 clear_color: webrender::api::ColorF::new(0.0, 0.0, 0.0, 0.0),
358
359 allow_advanced_blend_equation: context.is_software(),
360 clear_caches_with_quads: !context.is_software(),
361 enable_gpu_markers: !context.is_software(),
362
363 upload_method: UploadMethod::PixelBuffer(VertexUsageHint::Dynamic),
365
366 workers: Some(crate::util::wr_workers()),
368
369 #[cfg(target_os = "android")]
373 use_optimized_shaders: true,
374
375 ..Default::default()
377 };
378 let mut blobs = BlobExtensionsImgHandler(vec![]);
379 for (id, ext) in &mut renderer_exts {
380 ext.configure(&mut RendererConfigArgs {
381 config: cfg.extensions.iter().find(|(k, _)| k == id).map(|(_, p)| p),
382 options: &mut opts,
383 blobs: &mut blobs.0,
384 window: Some(&winit_window),
385 context: &mut context,
386 });
387 }
388 if !opts.enable_multithreading {
389 for b in &mut blobs.0 {
390 b.enable_multithreading(false);
391 }
392 }
393 opts.blob_image_handler = Some(Box::new(blobs));
394
395 let (mut renderer, sender) =
396 webrender::create_webrender_instance(context.gl().clone(), WrNotifier::create(id, event_sender.clone()), opts, None).unwrap();
397 renderer.set_external_image_handler(WrImageCache::new_boxed());
398
399 let mut external_images = extensions::ExternalImages::default();
400
401 let mut api = sender.create_api();
402 let document_id = api.add_document(device_size);
403 let pipeline_id = webrender::api::PipelineId(vp_gen.get(), id.get());
404
405 renderer_exts.retain_mut(|(_, ext)| {
406 ext.renderer_inited(&mut RendererInitedArgs {
407 renderer: &mut renderer,
408 external_images: &mut external_images,
409 api_sender: &sender,
410 api: &mut api,
411 document_id,
412 pipeline_id,
413 window: Some(&winit_window),
414 context: &mut context,
415 });
416 !ext.is_init_only()
417 });
418
419 drop(wr_scope);
420
421 let access = accesskit_winit::Adapter::with_direct_handlers(
422 winit_loop,
423 &winit_window,
424 AccessActivateHandler {
425 id,
426 event_sender: event_sender.clone(),
427 },
428 AccessActionSender {
429 id,
430 event_sender: event_sender.clone(),
431 },
432 AccessDeactivateHandler { id, event_sender },
433 );
434
435 let mut win = Self {
436 id,
437 image_use: ImageUseMap::default(),
438 prev_pos: winit_window.inner_position().unwrap_or_default().to_px(),
439 prev_size: winit_window.inner_size().to_px().to_dip(Factor(winit_window.scale_factor() as _)),
440 prev_monitor: winit_window.current_monitor(),
441 state: s,
442 kiosk: cfg.kiosk,
443 window: winit_window,
444 context,
445 capture_mode: cfg.capture_mode,
446 renderer: Some(renderer),
447 window_exts,
448 renderer_exts,
449 external_images,
450 video_mode: cfg.video_mode,
451 display_list_cache: DisplayListCache::new(pipeline_id, api.get_namespace_id()),
452 api,
453 document_id,
454 pipeline_id,
455 resized: true,
456 waiting_first_frame: true,
457 steal_init_focus: cfg.focus,
458 init_focus_request: cfg.focus_indicator,
459 visible: cfg.visible,
460 is_always_on_top: false,
461 taskbar_visible: true,
462 movable: cfg.movable,
463 pending_frames: VecDeque::new(),
464 rendered_frame_id: FrameId::INVALID,
465 cursor_pos: DipPoint::zero(),
466 touch_pos: vec![],
467 cursor_device: DeviceId::INVALID,
468 cursor_over: false,
469 clear_color: None,
470 focused: None,
471 modal_dialog_active: Arc::new(AtomicBool::new(false)),
472 render_mode,
473 access: Some(access),
474 ime_area: cfg.ime_area,
475 #[cfg(windows)]
476 has_shutdown_warn: false,
477 cursor: None,
478 cursor_img: None,
479
480 #[cfg(any(
481 target_os = "linux",
482 target_os = "dragonfly",
483 target_os = "freebsd",
484 target_os = "netbsd",
485 target_os = "openbsd"
486 ))]
487 xlib_maximize: false,
488 };
489
490 if !cfg.default_position && win.state.state == WindowState::Normal {
491 win.set_inner_position(win.state.restore_rect.origin);
492 }
493
494 if cfg.always_on_top {
495 win.set_always_on_top(true);
496 }
497
498 win.cursor = cfg.cursor;
499 win.cursor_img = cfg_cursor_image;
500 win.update_cursor();
501
502 win.set_taskbar_visible(cfg.taskbar_visible);
503
504 win.set_enabled_buttons(cfg.enabled_buttons);
505
506 if !cfg.system_shutdown_warn.is_empty() {
507 win.set_system_shutdown_warn(cfg.system_shutdown_warn);
508 }
509
510 if win.ime_area.is_some() {
511 win.window.set_ime_allowed(true);
512 }
513
514 match win.state.state {
516 WindowState::Normal | WindowState::Minimized => {}
517 WindowState::Maximized => win.window.set_maximized(true),
518 WindowState::Fullscreen => win.window.set_fullscreen(Some(Fullscreen::Borderless(None))),
519 WindowState::Exclusive => win.window.set_fullscreen(Some(if let Some(mode) = win.video_mode() {
520 Fullscreen::Exclusive(mode)
521 } else {
522 Fullscreen::Borderless(None)
523 })),
524 }
525
526 win.state.global_position = win.window.inner_position().unwrap_or_default().to_px();
527 let monitor_offset = if let Some(m) = win.window.current_monitor() {
528 m.position().to_px().to_vector()
529 } else {
530 PxVector::zero()
531 };
532
533 if win.state.state == WindowState::Normal && cfg.default_position {
534 win.state.restore_rect.origin = (win.state.global_position - monitor_offset).to_dip(win.scale_factor());
536 }
537
538 win
539 }
540
541 pub fn id(&self) -> WindowId {
542 self.id
543 }
544
545 pub fn monitor(&self) -> Option<winit::monitor::MonitorHandle> {
546 self.window.current_monitor()
547 }
548
549 pub fn window_id(&self) -> winit::window::WindowId {
550 self.window.id()
551 }
552
553 pub fn frame_id(&self) -> FrameId {
555 self.rendered_frame_id
556 }
557
558 pub fn set_title(&self, title: Txt) {
559 self.window.set_title(&title);
560 }
561
562 pub fn modal_dialog_active(&self) -> bool {
566 self.modal_dialog_active.load(Ordering::Relaxed)
567 }
568
569 pub fn cursor_moved(&mut self, pos: DipPoint, device: DeviceId) -> bool {
571 if !self.cursor_over {
572 return false;
574 }
575
576 let moved = self.cursor_pos != pos || self.cursor_device != device;
577
578 if moved {
579 self.cursor_pos = pos;
580 self.cursor_device = device;
581 }
582
583 moved
584 }
585
586 pub fn touch_moved(&mut self, pos: DipPoint, device: DeviceId, touch: u64) -> bool {
588 if let Some(p) = self.touch_pos.iter_mut().find(|p| p.0 == (device, touch)) {
589 let moved = p.1 != pos;
590 p.1 = pos;
591 moved
592 } else {
593 self.touch_pos.push(((device, touch), pos));
594 true
595 }
596 }
597
598 pub fn touch_end(&mut self, device: DeviceId, touch: u64) {
600 if let Some(i) = self.touch_pos.iter().position(|p| p.0 == (device, touch)) {
601 self.touch_pos.swap_remove(i);
602 }
603 }
604
605 #[cfg(windows)]
606 fn windows_is_foreground(&self) -> bool {
607 let foreground = unsafe { windows_sys::Win32::UI::WindowsAndMessaging::GetForegroundWindow() };
608 foreground == crate::util::winit_to_hwnd(&self.window)
609 }
610
611 pub fn is_focused(&self) -> bool {
612 self.focused.unwrap_or(false)
613 }
614
615 pub fn focused_changed(&mut self, focused: &mut bool) -> bool {
619 #[cfg(windows)]
620 if self.focused.is_none() {
621 *focused = self.windows_is_foreground();
622 }
623
624 let focused = Some(*focused);
625
626 let changed = self.focused != focused;
627 if changed {
628 self.focused = focused;
629 }
630 changed
631 }
632
633 pub fn last_cursor_pos(&self) -> (DipPoint, DeviceId) {
635 (self.cursor_pos, self.cursor_device)
636 }
637
638 pub fn cursor_entered(&mut self) -> bool {
640 let changed = !self.cursor_over;
641 self.cursor_over = true;
642 changed
643 }
644
645 pub fn cursor_left(&mut self) -> bool {
647 let changed = self.cursor_over;
648 self.cursor_over = false;
649 changed
650 }
651
652 pub fn set_visible(&mut self, visible: bool) {
653 if self.kiosk && !self.visible {
654 tracing::error!("window in `kiosk` mode cannot be hidden");
655 }
656
657 if !self.waiting_first_frame {
658 let _s = tracing::trace_span!("set_visible", %visible).entered();
659
660 self.visible = visible;
661
662 if visible {
663 if self.state.state != WindowState::Minimized {
664 self.window.set_minimized(false);
665 }
666
667 self.window.set_visible(true);
668 self.apply_state(self.state.clone(), true);
669 } else {
670 if self.state.state != WindowState::Minimized {
671 self.window.set_minimized(true);
674 }
675
676 self.window.set_visible(false);
677 }
678 }
679 }
680
681 pub fn set_always_on_top(&mut self, always_on_top: bool) {
682 self.window.set_window_level(if always_on_top {
683 winit::window::WindowLevel::AlwaysOnTop
684 } else {
685 winit::window::WindowLevel::Normal
686 });
687 self.is_always_on_top = always_on_top;
688 }
689
690 pub fn set_movable(&mut self, movable: bool) {
691 self.movable = movable;
692 }
693
694 pub fn set_resizable(&mut self, resizable: bool) {
695 self.window.set_resizable(resizable)
696 }
697
698 #[cfg(windows)]
699 pub fn bring_to_top(&mut self) {
700 use windows_sys::Win32::UI::WindowsAndMessaging::*;
701
702 if !self.is_always_on_top {
703 let hwnd = crate::util::winit_to_hwnd(&self.window);
704
705 unsafe {
706 let _ = SetWindowPos(
707 hwnd as _,
708 HWND_TOP,
709 0,
710 0,
711 0,
712 0,
713 SWP_NOACTIVATE | SWP_NOMOVE | SWP_NOSIZE | SWP_SHOWWINDOW,
714 );
715 }
716 }
717 }
718
719 #[cfg(not(windows))]
720 pub fn bring_to_top(&mut self) {
721 if !self.is_always_on_top {
722 self.set_always_on_top(true);
723 self.set_always_on_top(false);
724 }
725 }
726
727 pub fn moved(&mut self) -> Option<(PxPoint, DipPoint)> {
729 if !self.visible {
730 return None;
731 }
732
733 let new_pos = match self.window.inner_position() {
734 Ok(p) => p.to_px(),
735 Err(e) => {
736 tracing::error!("cannot get inner_position, {e}");
737 PxPoint::zero()
738 }
739 };
740 if self.prev_pos != new_pos {
741 self.prev_pos = new_pos;
742
743 let monitor_offset = if let Some(m) = self.window.current_monitor() {
744 m.position().to_px().to_vector()
745 } else {
746 PxVector::zero()
747 };
748
749 Some((new_pos, (new_pos - monitor_offset).to_dip(self.scale_factor())))
750 } else {
751 None
752 }
753 }
754
755 pub fn resized(&mut self) -> Option<DipSize> {
757 if !self.visible {
758 return None;
759 }
760
761 #[cfg(any(
762 target_os = "linux",
763 target_os = "dragonfly",
764 target_os = "freebsd",
765 target_os = "netbsd",
766 target_os = "openbsd"
767 ))]
768 if std::mem::take(&mut self.xlib_maximize) {
769 self.window.set_maximized(true);
772 return None;
773 }
774
775 let new_size = self.window.inner_size().to_px().to_dip(self.scale_factor());
776 if self.prev_size != new_size {
777 #[cfg(windows)]
778 if matches!(self.state.state, WindowState::Maximized | WindowState::Fullscreen)
779 && self.window.current_monitor() != self.window.primary_monitor()
780 {
781 match self.state.state {
785 WindowState::Maximized => {
786 self.window.set_maximized(false);
787 self.window.set_maximized(true);
788 }
789 WindowState::Fullscreen => {
790 self.window.set_fullscreen(None);
791 self.window.set_fullscreen(Some(Fullscreen::Borderless(None)));
792 }
793 _ => unreachable!(),
794 }
795
796 let new_size = self.window.inner_size().to_px().to_dip(self.scale_factor());
797 return if self.prev_size != new_size {
798 self.prev_size = new_size;
799 self.resized = true;
800
801 Some(new_size)
802 } else {
803 None
804 };
805 }
806
807 self.prev_size = new_size;
808 self.resized = true;
809
810 Some(new_size)
811 } else {
812 None
813 }
814 }
815
816 pub fn monitor_change(&mut self) -> Option<MonitorHandle> {
818 let handle = self.window.current_monitor();
819 if self.prev_monitor != handle {
820 self.prev_monitor.clone_from(&handle);
821 handle
822 } else {
823 None
824 }
825 }
826
827 #[cfg(windows)]
828 fn windows_set_restore(&self) {
829 use windows_sys::Win32::Graphics::Gdi::{GetMonitorInfoW, MONITORINFO, MONITORINFOEXW};
830 use windows_sys::Win32::{
831 Foundation::{POINT, RECT},
832 UI::WindowsAndMessaging::*,
833 };
834 use winit::platform::windows::MonitorHandleExtWindows;
835
836 if let Some(monitor) = self.window.current_monitor() {
837 let hwnd = crate::util::winit_to_hwnd(&self.window) as _;
838 let mut placement = WINDOWPLACEMENT {
839 length: mem::size_of::<WINDOWPLACEMENT>() as _,
840 flags: 0,
841 showCmd: 0,
842 ptMinPosition: POINT { x: 0, y: 0 },
843 ptMaxPosition: POINT { x: 0, y: 0 },
844 rcNormalPosition: RECT {
845 left: 0,
846 top: 0,
847 right: 0,
848 bottom: 0,
849 },
850 };
851 if unsafe { GetWindowPlacement(hwnd, &mut placement) } != 0 {
852 let scale_factor = self.scale_factor();
853 let mut left_top = self.state.restore_rect.origin.to_px(scale_factor);
854
855 let hmonitor = monitor.hmonitor() as _;
857 let mut monitor_info = MONITORINFOEXW {
858 monitorInfo: MONITORINFO {
859 cbSize: mem::size_of::<MONITORINFOEXW>() as _,
860 rcMonitor: RECT {
861 left: 0,
862 top: 0,
863 right: 0,
864 bottom: 0,
865 },
866 rcWork: RECT {
867 left: 0,
868 top: 0,
869 right: 0,
870 bottom: 0,
871 },
872 dwFlags: 0,
873 },
874 szDevice: [0; 32],
875 };
876 if unsafe { GetMonitorInfoW(hmonitor, &mut monitor_info as *mut MONITORINFOEXW as *mut MONITORINFO) } != 0 {
877 left_top.x.0 += monitor_info.monitorInfo.rcWork.left;
878 left_top.y.0 += monitor_info.monitorInfo.rcWork.top;
879 }
880
881 let outer_offset =
883 self.window.outer_position().unwrap_or_default().to_px() - self.window.inner_position().unwrap_or_default().to_px();
884 let size_offset = self.window.outer_size().to_px() - self.window.inner_size().to_px();
885
886 left_top += outer_offset;
887 let bottom_right = left_top + self.state.restore_rect.size.to_px(scale_factor) + size_offset;
888
889 placement.rcNormalPosition.top = left_top.y.0;
890 placement.rcNormalPosition.left = left_top.x.0;
891 placement.rcNormalPosition.bottom = bottom_right.y.0;
892 placement.rcNormalPosition.right = bottom_right.x.0;
893
894 let _ = unsafe { SetWindowPlacement(hwnd, &placement) };
895 }
896 }
897 }
898
899 pub fn set_icon(&mut self, icon: Option<Icon>) {
900 self.window.set_window_icon(icon);
901 }
902
903 pub fn set_cursor(&mut self, icon: Option<CursorIcon>) {
905 self.cursor = icon;
906 self.update_cursor();
907 }
908
909 pub fn set_cursor_image(&mut self, img: Option<CustomCursor>) {
911 self.cursor_img = img;
912 self.update_cursor();
913 }
914
915 fn update_cursor(&self) {
916 match (&self.cursor_img, self.cursor) {
917 (Some(i), _) => {
918 self.window.set_cursor(i.clone());
919 self.window.set_cursor_visible(true);
920 }
921 (None, Some(i)) => {
922 self.window.set_cursor(i.to_winit());
923 self.window.set_cursor_visible(true);
924 }
925 (None, None) => {
926 self.window.set_cursor_visible(false);
927 self.window.set_cursor(CursorIcon::Default.to_winit());
928 }
929 }
930 }
931
932 pub fn set_focus_request(&mut self, request: Option<FocusIndicator>) {
934 if self.waiting_first_frame {
935 self.init_focus_request = request;
936 } else {
937 self.window.request_user_attention(request.map(|r| match r {
938 FocusIndicator::Critical => winit::window::UserAttentionType::Critical,
939 FocusIndicator::Info => winit::window::UserAttentionType::Informational,
940 }));
941 }
942 }
943
944 #[cfg(not(windows))]
946 pub fn focus(&mut self) -> zng_view_api::FocusResult {
947 if self.waiting_first_frame {
948 self.steal_init_focus = true;
949 } else if !self.modal_dialog_active() {
950 self.window.focus_window();
951 }
952 if self.window.has_focus() {
953 zng_view_api::FocusResult::AlreadyFocused
954 } else {
955 zng_view_api::FocusResult::Requested
956 }
957 }
958
959 #[cfg(windows)]
963 #[must_use]
964 pub fn focus(&mut self) -> (zng_view_api::FocusResult, bool) {
965 let skip_ralt = if self.waiting_first_frame {
966 self.steal_init_focus = true;
967 false
968 } else if !self.modal_dialog_active() && !self.windows_is_foreground() {
969 self.window.focus_window();
971 self.windows_is_foreground()
972 } else {
973 false
974 };
975 let r = if self.window.has_focus() {
976 zng_view_api::FocusResult::AlreadyFocused
977 } else {
978 zng_view_api::FocusResult::Requested
979 };
980 (r, skip_ralt)
981 }
982
983 fn is_maximized(&self) -> bool {
985 #[cfg(windows)]
986 {
987 let hwnd = crate::util::winit_to_hwnd(&self.window);
988 return unsafe { windows_sys::Win32::UI::WindowsAndMessaging::IsZoomed(hwnd as _) } != 0;
990 }
991
992 #[allow(unreachable_code)]
993 {
994 self.window.is_maximized()
996 }
997 }
998
999 fn is_minimized(&self) -> bool {
1001 let size = self.window.inner_size();
1002 if size.width == 0 || size.height == 0 {
1003 return true;
1004 }
1005
1006 #[cfg(windows)]
1007 {
1008 let hwnd = crate::util::winit_to_hwnd(&self.window);
1009 return unsafe { windows_sys::Win32::UI::WindowsAndMessaging::IsIconic(hwnd as _) } != 0;
1011 }
1012
1013 #[allow(unreachable_code)]
1014 false
1015 }
1016
1017 fn probe_state(&self) -> WindowStateAll {
1018 let mut state = self.state.clone();
1019
1020 state.global_position = match self.window.inner_position() {
1021 Ok(p) => p.to_px(),
1022 Err(e) => {
1023 tracing::error!("cannot get inner_position, {e}");
1024 PxPoint::zero()
1025 }
1026 };
1027
1028 if self.is_minimized() {
1029 state.state = WindowState::Minimized;
1030 } else if let Some(h) = self.window.fullscreen() {
1031 state.state = match h {
1032 Fullscreen::Exclusive(_) => WindowState::Exclusive,
1033 Fullscreen::Borderless(_) => WindowState::Fullscreen,
1034 };
1035 } else if self.is_maximized() {
1036 state.state = WindowState::Maximized;
1037 } else {
1038 state.state = WindowState::Normal;
1039
1040 let scale_factor = self.scale_factor();
1041
1042 let monitor_offset = if let Some(monitor) = self.window.current_monitor() {
1043 monitor.position().to_px().to_vector()
1044 } else {
1045 PxVector::zero()
1046 };
1047
1048 state.restore_rect = DipRect::new(
1049 (state.global_position - monitor_offset).to_dip(scale_factor),
1050 self.window.inner_size().to_px().to_dip(scale_factor),
1051 );
1052 }
1053
1054 state
1055 }
1056
1057 pub fn state_change(&mut self) -> Option<WindowStateAll> {
1059 if !self.visible {
1060 return None;
1061 }
1062
1063 let mut new_state = self.probe_state();
1064
1065 if self.state.state == WindowState::Minimized && self.state.restore_state == WindowState::Fullscreen {
1066 self.window.set_fullscreen(Some(Fullscreen::Borderless(None)));
1067 } else if new_state.state == WindowState::Normal && self.state.state != WindowState::Normal {
1068 new_state.restore_rect = self.state.restore_rect;
1069
1070 self.set_inner_position(new_state.restore_rect.origin);
1071 let new_size = new_state.restore_rect.size.to_winit();
1072 if let Some(immediate_new_size) = self.window.request_inner_size(new_size) {
1073 if immediate_new_size == new_size.to_physical(self.window.scale_factor()) {
1074 tracing::debug!("immediate resize may not have notified, new size: {immediate_new_size:?}");
1077 }
1078 }
1079
1080 self.window.set_min_inner_size(Some(new_state.min_size.to_winit()));
1081 self.window.set_max_inner_size(Some(new_state.max_size.to_winit()));
1082 }
1083
1084 new_state.set_restore_state_from(self.state.state);
1085
1086 if new_state != self.state {
1087 self.state = new_state.clone();
1088 Some(new_state)
1089 } else {
1090 None
1091 }
1092 }
1093
1094 fn video_mode(&self) -> Option<GVideoMode> {
1095 let mode = &self.video_mode;
1096 self.window.current_monitor().and_then(|m| {
1097 let mut candidate: Option<GVideoMode> = None;
1098 for m in m.video_modes() {
1099 if m.size().width <= mode.size.width.0 as u32
1101 && m.size().height <= mode.size.height.0 as u32
1102 && m.bit_depth() <= mode.bit_depth
1103 && m.refresh_rate_millihertz() <= mode.refresh_rate
1104 {
1105 if let Some(c) = &candidate {
1107 if m.size().width >= c.size().width
1108 && m.size().height >= c.size().height
1109 && m.bit_depth() >= c.bit_depth()
1110 && m.refresh_rate_millihertz() >= c.refresh_rate_millihertz()
1111 {
1112 candidate = Some(m);
1113 }
1114 } else {
1115 candidate = Some(m);
1116 }
1117 }
1118 }
1119 candidate
1120 })
1121 }
1122
1123 pub fn set_video_mode(&mut self, mode: VideoMode) {
1124 self.video_mode = mode;
1125 if let WindowState::Exclusive = self.state.state {
1126 self.window.set_fullscreen(None);
1127
1128 if let Some(mode) = self.video_mode() {
1129 self.window.set_fullscreen(Some(Fullscreen::Exclusive(mode)));
1130 } else {
1131 self.window.set_fullscreen(Some(Fullscreen::Borderless(None)));
1132 }
1133 }
1134 }
1135
1136 #[cfg(not(windows))]
1137 pub fn set_taskbar_visible(&mut self, visible: bool) {
1138 if visible != self.taskbar_visible {
1139 return;
1140 }
1141 self.taskbar_visible = visible;
1142 tracing::warn!("`set_taskbar_visible` not implemented for {}", std::env::consts::OS);
1143 }
1144
1145 #[cfg(windows)]
1146 pub fn set_taskbar_visible(&mut self, visible: bool) {
1147 if visible == self.taskbar_visible {
1148 return;
1149 }
1150 self.taskbar_visible = visible;
1151
1152 use windows_sys::Win32::System::Com::*;
1153
1154 use crate::util::taskbar_com;
1155
1156 unsafe {
1159 let mut taskbar_list2: *mut taskbar_com::ITaskbarList2 = std::ptr::null_mut();
1160 match CoCreateInstance(
1161 &taskbar_com::CLSID_TaskbarList,
1162 std::ptr::null_mut(),
1163 CLSCTX_ALL,
1164 &taskbar_com::IID_ITaskbarList2,
1165 &mut taskbar_list2 as *mut _ as *mut _,
1166 ) {
1167 0 => {
1168 let result = if visible {
1169 let add_tab = (*(*taskbar_list2).lpVtbl).parent.AddTab;
1170 add_tab(taskbar_list2.cast(), crate::util::winit_to_hwnd(&self.window) as _)
1171 } else {
1172 let delete_tab = (*(*taskbar_list2).lpVtbl).parent.DeleteTab;
1173 delete_tab(taskbar_list2.cast(), crate::util::winit_to_hwnd(&self.window) as _)
1174 };
1175 if result != 0 {
1176 let mtd_name = if visible { "AddTab" } else { "DeleteTab" };
1177 tracing::error!(
1178 target: "window",
1179 "cannot set `taskbar_visible`, `ITaskbarList::{mtd_name}` failed, error: 0x{result:x}",
1180 )
1181 }
1182
1183 let release = (*(*taskbar_list2).lpVtbl).parent.parent.Release;
1184 let result = release(taskbar_list2.cast());
1185 if result != 0 {
1186 tracing::error!(
1187 target: "window",
1188 "failed to release `taskbar_list`, error: 0x{result:x}"
1189 )
1190 }
1191 }
1192 error => {
1193 tracing::error!(
1194 target: "window",
1195 "cannot set `taskbar_visible`, failed to create instance of `ITaskbarList`, error: 0x{error:x}",
1196 )
1197 }
1198 }
1199 }
1200 }
1201
1202 pub fn state(&self) -> WindowStateAll {
1204 self.state.clone()
1205 }
1206
1207 fn set_inner_position(&self, pos: DipPoint) {
1208 let monitor_offset = if let Some(m) = self.window.current_monitor() {
1209 m.position().to_px().to_vector()
1210 } else {
1211 PxVector::zero()
1212 };
1213
1214 let outer_pos = self.window.outer_position().unwrap_or_default();
1215 let inner_pos = self.window.inner_position().unwrap_or_default();
1216 let inner_offset = PxVector::new(Px(outer_pos.x - inner_pos.x), Px(outer_pos.y - inner_pos.y));
1217 let pos = pos.to_px(self.scale_factor()) + monitor_offset + inner_offset;
1218 self.window.set_outer_position(pos.to_winit());
1219 }
1220
1221 pub fn set_state(&mut self, new_state: WindowStateAll) -> bool {
1225 if self.state == new_state {
1226 return false;
1227 }
1228
1229 if !self.visible {
1230 self.state = new_state;
1232 return true;
1233 }
1234
1235 self.apply_state(new_state, false);
1236
1237 true
1238 }
1239
1240 pub fn drag_move(&self) {
1242 if let Err(e) = self.window.drag_window() {
1243 tracing::error!("failed to drag_move, {e}");
1244 }
1245 }
1246
1247 pub fn drag_resize(&self, direction: ResizeDirection) {
1249 if let Err(e) = self.window.drag_resize_window(direction.to_winit()) {
1250 tracing::error!("failed to drag_resize, {e}");
1251 }
1252 }
1253
1254 pub fn set_enabled_buttons(&self, buttons: WindowButton) {
1256 self.window.set_enabled_buttons(buttons.to_winit());
1257 }
1258
1259 pub fn open_title_bar_context_menu(&self, pos: DipPoint) {
1261 self.window.show_window_menu(pos.to_winit())
1262 }
1263
1264 fn apply_state(&mut self, new_state: WindowStateAll, force: bool) {
1265 if self.state.chrome_visible != new_state.chrome_visible {
1266 self.window.set_decorations(new_state.chrome_visible);
1267 }
1268
1269 if self.state.state != new_state.state || force {
1270 match self.state.state {
1272 WindowState::Normal => {}
1273 WindowState::Minimized => self.window.set_minimized(false),
1274 WindowState::Maximized => {
1275 if !new_state.state.is_fullscreen() && new_state.state != WindowState::Minimized {
1276 self.window.set_maximized(false);
1277 }
1278 }
1279 WindowState::Fullscreen | WindowState::Exclusive => self.window.set_fullscreen(None),
1280 }
1281
1282 match new_state.state {
1284 WindowState::Normal => {}
1285 WindowState::Minimized => self.window.set_minimized(true),
1286 WindowState::Maximized => self.window.set_maximized(true),
1287 WindowState::Fullscreen => {
1288 self.window.set_fullscreen(Some(Fullscreen::Borderless(None)));
1289 }
1290 WindowState::Exclusive => {
1291 if let Some(mode) = self.video_mode() {
1292 self.window.set_fullscreen(Some(Fullscreen::Exclusive(mode)));
1293 } else {
1294 self.window.set_fullscreen(Some(Fullscreen::Borderless(None)));
1295 }
1296 }
1297 }
1298 }
1299
1300 self.state = new_state;
1301
1302 if self.state.state == WindowState::Normal {
1303 let _ = self.window.request_inner_size(self.state.restore_rect.size.to_winit());
1304
1305 let outer_offset = match (self.window.outer_position(), self.window.inner_position()) {
1306 (Ok(o), Ok(i)) => (i.x - o.x, i.y - o.y),
1307 _ => (0, 0),
1308 };
1309 let mut origin = self
1310 .state
1311 .restore_rect
1312 .origin
1313 .to_winit()
1314 .to_physical::<i32>(self.window.scale_factor());
1315 origin.x -= outer_offset.0;
1316 origin.y -= outer_offset.1;
1317 self.window.set_outer_position(origin);
1318
1319 self.window.set_min_inner_size(Some(self.state.min_size.to_winit()));
1320 self.window.set_max_inner_size(Some(self.state.max_size.to_winit()));
1321
1322 #[cfg(windows)]
1332 if self.is_minimized() {
1333 self.windows_set_restore();
1334
1335 self.window.set_minimized(true);
1336 self.window.set_minimized(false);
1337 }
1338 }
1339
1340 #[cfg(windows)]
1344 if !matches!(self.state.state, WindowState::Normal | WindowState::Fullscreen) {
1345 self.windows_set_restore();
1346 }
1347 }
1348
1349 pub fn use_image(&mut self, image: &Image) -> ImageTextureId {
1350 self.image_use.new_use(image, self.document_id, &mut self.api)
1351 }
1352
1353 pub fn update_image(&mut self, texture_id: ImageTextureId, image: &Image) {
1354 self.image_use.update_use(texture_id, image, self.document_id, &mut self.api);
1355 }
1356
1357 pub fn delete_image(&mut self, texture_id: ImageTextureId) {
1358 self.image_use.delete(texture_id, self.document_id, &mut self.api);
1359 }
1360
1361 pub fn add_font_face(&mut self, font: Vec<u8>, index: u32) -> FontFaceId {
1362 #[cfg(target_os = "macos")]
1363 let index = {
1364 if index != 0 {
1365 tracing::error!("webrender does not support font index on macOS, ignoring `{index}` will use `0`");
1366 }
1367 0
1368 };
1369 let key = self.api.generate_font_key();
1370 let mut txn = webrender::Transaction::new();
1371 txn.add_raw_font(key, font, index);
1372 self.api.send_transaction(self.document_id, txn);
1373 FontFaceId::from_raw(key.1)
1374 }
1375
1376 pub fn delete_font_face(&mut self, font_face_id: FontFaceId) {
1377 let mut txn = webrender::Transaction::new();
1378 txn.delete_font(FontKey(self.api.get_namespace_id(), font_face_id.get()));
1379 self.api.send_transaction(self.document_id, txn);
1380 }
1381
1382 pub fn add_font(
1383 &mut self,
1384 font_face_id: FontFaceId,
1385 glyph_size: Px,
1386 options: FontOptions,
1387 variations: Vec<(FontVariationName, f32)>,
1388 ) -> FontId {
1389 let key = self.api.generate_font_instance_key();
1390 let mut txn = webrender::Transaction::new();
1391 txn.add_font_instance(
1392 key,
1393 FontKey(self.api.get_namespace_id(), font_face_id.get()),
1394 glyph_size.to_wr().get(),
1395 options.to_wr(),
1396 None,
1397 variations
1398 .into_iter()
1399 .map(|(n, v)| FontVariation {
1400 tag: u32::from_be_bytes(n),
1401 value: v,
1402 })
1403 .collect(),
1404 );
1405 self.api.send_transaction(self.document_id, txn);
1406 FontId::from_raw(key.1)
1407 }
1408
1409 pub fn delete_font(&mut self, font_id: FontId) {
1410 let mut txn = webrender::Transaction::new();
1411 txn.delete_font_instance(FontInstanceKey(self.api.get_namespace_id(), font_id.get()));
1412 self.api.send_transaction(self.document_id, txn);
1413 }
1414
1415 pub fn set_capture_mode(&mut self, enabled: bool) {
1416 self.capture_mode = enabled;
1417 }
1418
1419 pub fn render(&mut self, frame: FrameRequest) {
1423 let _scope = tracing::trace_span!("render", ?frame.id).entered();
1424
1425 self.renderer.as_mut().unwrap().set_clear_color(frame.clear_color.to_wr());
1426
1427 let mut txn = Transaction::new();
1428 txn.set_root_pipeline(self.pipeline_id);
1429 self.push_resize(&mut txn);
1430 txn.generate_frame(frame.id.get(), frame_render_reasons(&frame));
1431
1432 let display_list = display_list_to_webrender(
1433 frame.display_list,
1434 &mut DisplayListExtAdapter {
1435 frame_id: frame.id,
1436 extensions: &mut self.renderer_exts,
1437 transaction: &mut txn,
1438 renderer: self.renderer.as_mut().unwrap(),
1439 api: &mut self.api,
1440 external_images: &mut self.external_images,
1441 },
1442 &mut self.display_list_cache,
1443 );
1444
1445 txn.reset_dynamic_properties();
1446 txn.append_dynamic_properties(DynamicProperties {
1447 transforms: vec![],
1448 floats: vec![],
1449 colors: vec![],
1450 });
1451
1452 self.renderer.as_mut().unwrap().set_clear_color(frame.clear_color.to_wr());
1453 self.clear_color = Some(frame.clear_color);
1454
1455 txn.set_display_list(webrender::api::Epoch(frame.id.epoch()), (self.pipeline_id, display_list));
1456
1457 let frame_scope =
1458 tracing::trace_span!("<frame>", ?frame.id, capture = ?frame.capture, from_update = false, thread = "<webrender>").entered();
1459
1460 self.pending_frames.push_back((frame.id, frame.capture, Some(frame_scope)));
1461
1462 self.api.send_transaction(self.document_id, txn);
1463 }
1464
1465 pub fn render_update(&mut self, frame: FrameUpdateRequest) {
1467 let _scope = tracing::trace_span!("render_update", ?frame.id).entered();
1468
1469 let render_reasons = frame_update_render_reasons(&frame);
1470
1471 if let Some(color) = frame.clear_color {
1472 self.clear_color = Some(color);
1473 self.renderer.as_mut().unwrap().set_clear_color(color.to_wr());
1474 }
1475
1476 let resized = self.resized;
1477
1478 let mut txn = Transaction::new();
1479 txn.set_root_pipeline(self.pipeline_id);
1480 self.push_resize(&mut txn);
1481 txn.generate_frame(self.frame_id().get(), render_reasons);
1482
1483 let frame_scope = match self.display_list_cache.update(
1484 &mut DisplayListExtAdapter {
1485 frame_id: self.frame_id(),
1486 extensions: &mut self.renderer_exts,
1487 transaction: &mut txn,
1488 renderer: self.renderer.as_mut().unwrap(),
1489 api: &mut self.api,
1490 external_images: &mut self.external_images,
1491 },
1492 frame.transforms,
1493 frame.floats,
1494 frame.colors,
1495 frame.extensions,
1496 resized,
1497 ) {
1498 Ok(p) => {
1499 if let Some(p) = p {
1500 txn.append_dynamic_properties(p);
1501 }
1502
1503 tracing::trace_span!("<frame-update>", ?frame.id, capture = ?frame.capture, thread = "<webrender>")
1504 }
1505 Err(d) => {
1506 txn.reset_dynamic_properties();
1507 txn.append_dynamic_properties(DynamicProperties {
1508 transforms: vec![],
1509 floats: vec![],
1510 colors: vec![],
1511 });
1512
1513 txn.set_display_list(webrender::api::Epoch(frame.id.epoch()), (self.pipeline_id, d));
1514
1515 tracing::trace_span!("<frame>", ?frame.id, capture = ?frame.capture, from_update = true, thread = "<webrender>")
1516 }
1517 };
1518
1519 self.pending_frames
1520 .push_back((frame.id, frame.capture, Some(frame_scope.entered())));
1521
1522 self.api.send_transaction(self.document_id, txn);
1523 }
1524
1525 #[must_use = "events must be generated from the result"]
1527 pub fn on_frame_ready(&mut self, msg: FrameReadyMsg, images: &mut ImageCache) -> FrameReadyResult {
1528 let (frame_id, capture, _) = self
1529 .pending_frames
1530 .pop_front()
1531 .unwrap_or((self.rendered_frame_id, FrameCapture::None, None));
1532 self.rendered_frame_id = frame_id;
1533
1534 let first_frame = self.waiting_first_frame;
1535
1536 let mut ext_args = FrameReadyArgs {
1537 frame_id,
1538 redraw: msg.composite_needed || self.waiting_first_frame,
1539 };
1540 for (_, ext) in &mut self.renderer_exts {
1541 ext.frame_ready(&mut ext_args);
1542 ext_args.redraw |= msg.composite_needed || self.waiting_first_frame;
1543 }
1544
1545 if self.waiting_first_frame {
1546 let _s = tracing::trace_span!("first-draw").entered();
1547 debug_assert!(msg.composite_needed);
1548
1549 self.waiting_first_frame = false;
1550 let s = self.window.inner_size();
1551 self.context.make_current();
1552 self.context.resize(s);
1553 self.redraw();
1554 if self.kiosk {
1555 self.window.request_redraw();
1556 } else if self.visible {
1557 self.set_visible(true);
1558
1559 #[cfg(any(
1562 target_os = "linux",
1563 target_os = "dragonfly",
1564 target_os = "freebsd",
1565 target_os = "netbsd",
1566 target_os = "openbsd"
1567 ))]
1568 if let (raw_window_handle::RawWindowHandle::Xlib(_), WindowState::Maximized) = (
1569 raw_window_handle::HasWindowHandle::window_handle(&self.window).unwrap().as_raw(),
1570 self.state.state,
1571 ) {
1572 self.xlib_maximize = self.window.request_inner_size(self.window.inner_size()).is_none();
1573 }
1574
1575 if mem::take(&mut self.steal_init_focus) {
1576 self.window.focus_window();
1577 }
1578 if let Some(r) = self.init_focus_request.take() {
1579 self.set_focus_request(Some(r));
1580 }
1581 }
1582 } else if ext_args.redraw || msg.composite_needed {
1583 self.window.request_redraw();
1584 }
1585
1586 let scale_factor = self.scale_factor();
1587
1588 let capture = match capture {
1589 FrameCapture::None => None,
1590 FrameCapture::Full => Some(None),
1591 FrameCapture::Mask(m) => Some(Some(m)),
1592 };
1593 let image = if let Some(mask) = capture {
1594 let _s = tracing::trace_span!("capture_image").entered();
1595 if ext_args.redraw || msg.composite_needed {
1596 self.redraw();
1597 }
1598 Some(images.frame_image_data(
1599 &**self.context.gl(),
1600 PxRect::from_size(self.window.inner_size().to_px()),
1601 scale_factor,
1602 mask,
1603 ))
1604 } else {
1605 None
1606 };
1607
1608 FrameReadyResult {
1609 frame_id,
1610 image,
1611 first_frame,
1612 }
1613 }
1614
1615 pub fn redraw(&mut self) {
1616 let span = tracing::trace_span!("redraw", stats = tracing::field::Empty).entered();
1617
1618 self.context.make_current();
1619
1620 let scale_factor = self.scale_factor();
1621 let size = self.window.inner_size().to_px();
1622
1623 let renderer = self.renderer.as_mut().unwrap();
1624 renderer.update();
1625
1626 let r = renderer.render(size.to_wr_device(), 0).unwrap();
1627 span.record("stats", tracing::field::debug(&r.stats));
1628
1629 for (_, ext) in &mut self.renderer_exts {
1630 ext.redraw(&mut RedrawArgs {
1631 scale_factor,
1632 size,
1633 context: &mut self.context,
1634 });
1635 }
1636
1637 let _ = renderer.flush_pipeline_info();
1638
1639 self.window.pre_present_notify();
1640 self.context.swap_buffers();
1641 }
1642
1643 pub fn is_rendering_frame(&self) -> bool {
1644 !self.pending_frames.is_empty()
1645 }
1646
1647 fn push_resize(&mut self, txn: &mut Transaction) {
1648 if self.resized {
1649 self.resized = false;
1650
1651 self.context.make_current();
1652 let size = self.window.inner_size();
1653 self.context.resize(size);
1654 txn.set_document_view(PxRect::from_size(size.to_px()).to_wr_device());
1655 }
1656 }
1657
1658 pub fn frame_image(&mut self, images: &mut ImageCache, mask: Option<ImageMaskMode>) -> ImageId {
1659 let scale_factor = self.scale_factor();
1660 if !self.context.is_software() {
1661 self.redraw(); }
1663 images.frame_image(
1664 &**self.context.gl(),
1665 PxRect::from_size(self.window.inner_size().to_px()),
1666 self.id,
1667 self.rendered_frame_id,
1668 scale_factor,
1669 mask,
1670 )
1671 }
1672
1673 pub fn frame_image_rect(&mut self, images: &mut ImageCache, rect: PxRect, mask: Option<ImageMaskMode>) -> ImageId {
1674 let scale_factor = self.scale_factor();
1675 let rect = PxRect::from_size(self.window.inner_size().to_px())
1676 .intersection(&rect)
1677 .unwrap_or_default();
1678 if !self.context.is_software() {
1679 self.redraw(); }
1681 images.frame_image(&**self.context.gl(), rect, self.id, self.rendered_frame_id, scale_factor, mask)
1682 }
1683
1684 pub fn inner_position(&self) -> (PxPoint, DipPoint) {
1686 let global_pos = self.window.inner_position().unwrap_or_default().to_px();
1687 let monitor_offset = if let Some(m) = self.window.current_monitor() {
1688 m.position().to_px().to_vector()
1689 } else {
1690 PxVector::zero()
1691 };
1692
1693 (global_pos, (global_pos - monitor_offset).to_dip(self.scale_factor()))
1694 }
1695
1696 pub fn size(&self) -> DipSize {
1697 self.window.inner_size().to_logical(self.window.scale_factor()).to_dip()
1698 }
1699
1700 pub fn safe_padding(&self) -> DipSideOffsets {
1701 #[cfg(target_os = "android")]
1702 match self.try_get_insets() {
1703 Ok(s) => s,
1704 Err(e) => {
1705 tracing::error!("cannot get insets, {e}");
1706 DipSideOffsets::zero()
1707 }
1708 }
1709
1710 #[cfg(not(target_os = "android"))]
1711 {
1712 DipSideOffsets::zero()
1713 }
1714 }
1715 #[cfg(target_os = "android")]
1716 fn try_get_insets(&self) -> jni::errors::Result<DipSideOffsets> {
1717 let ctx = ndk_context::android_context();
1718 let vm = unsafe { jni::JavaVM::from_raw(ctx.vm() as _) }?;
1719
1720 let activity = unsafe { jni::objects::JObject::from_raw(ctx.context() as _) };
1721 let mut env = vm.attach_current_thread()?;
1722
1723 let jni_window = env.call_method(&activity, "getWindow", "()Landroid/view/Window;", &[])?.l()?;
1724
1725 let view = env.call_method(jni_window, "getDecorView", "()Landroid/view/View;", &[])?.l()?;
1726
1727 let insets = env
1728 .call_method(view, "getRootWindowInsets", "()Landroid/view/WindowInsets;", &[])?
1729 .l()?;
1730 let cutout = env
1731 .call_method(insets, "getDisplayCutout", "()Landroid/view/DisplayCutout;", &[])?
1732 .l()?;
1733 let top = env.call_method(&cutout, "getSafeInsetTop", "()I", &[])?.i()? as i32;
1734 let right = env.call_method(&cutout, "getSafeInsetRight", "()I", &[])?.i()? as i32;
1735 let bottom = env.call_method(&cutout, "getSafeInsetBottom", "()I", &[])?.i()? as i32;
1736 let left = env.call_method(&cutout, "getSafeInsetLeft", "()I", &[])?.i()? as i32;
1737
1738 let offsets = zng_unit::PxSideOffsets::new(Px(top), Px(right), Px(bottom), Px(left));
1739
1740 Ok(offsets.to_dip(self.scale_factor()))
1741 }
1742
1743 pub fn scale_factor(&self) -> Factor {
1744 Factor(self.window.scale_factor() as f32)
1745 }
1746
1747 pub fn render_mode(&self) -> RenderMode {
1749 self.render_mode
1750 }
1751
1752 pub fn window_extension(&mut self, extension_id: ApiExtensionId, request: ApiExtensionPayload) -> ApiExtensionPayload {
1754 for (key, ext) in &mut self.window_exts {
1755 if *key == extension_id {
1756 return ext.command(&mut WindowCommandArgs {
1757 window: &self.window,
1758 context: &mut self.context,
1759 request,
1760 });
1761 }
1762 }
1763 ApiExtensionPayload::unknown_extension(extension_id)
1764 }
1765
1766 pub fn render_extension(&mut self, extension_id: ApiExtensionId, request: ApiExtensionPayload) -> ApiExtensionPayload {
1768 for (key, ext) in &mut self.renderer_exts {
1769 if *key == extension_id {
1770 let mut redraw = false;
1771 let r = ext.command(&mut RendererCommandArgs {
1772 renderer: self.renderer.as_mut().unwrap(),
1773 api: &mut self.api,
1774 request,
1775 window: Some(&self.window),
1776 context: &mut self.context,
1777 redraw: &mut redraw,
1778 });
1779 if redraw {
1780 self.window.request_redraw();
1781 }
1782
1783 return r;
1784 }
1785 }
1786 ApiExtensionPayload::unknown_extension(extension_id)
1787 }
1788
1789 #[cfg(not(target_os = "android"))]
1790 fn enter_dialog(&self, id: dlg_api::DialogId, event_sender: &AppEventSender) -> bool {
1791 let already_open = self.modal_dialog_active.swap(true, Ordering::Acquire);
1792 if already_open {
1793 let _ = event_sender.send(AppEvent::Notify(Event::MsgDialogResponse(
1794 id,
1795 dlg_api::MsgDialogResponse::Error(Txt::from_static("dialog already open")),
1796 )));
1797 }
1798 already_open
1799 }
1800
1801 #[cfg(target_os = "android")]
1802 pub(crate) fn message_dialog(&self, dialog: dlg_api::MsgDialog, id: dlg_api::DialogId, event_sender: AppEventSender) {
1803 let _ = dialog;
1804 let _ = event_sender.send(AppEvent::Notify(Event::MsgDialogResponse(
1805 id,
1806 dlg_api::MsgDialogResponse::Error(Txt::from_static("native dialogs not implemented for android")),
1807 )));
1808 }
1809
1810 #[cfg(not(target_os = "android"))]
1812 pub(crate) fn message_dialog(&self, dialog: dlg_api::MsgDialog, id: dlg_api::DialogId, event_sender: AppEventSender) {
1813 if self.enter_dialog(id, &event_sender) {
1814 return;
1815 }
1816
1817 let dlg = rfd::AsyncMessageDialog::new()
1818 .set_level(match dialog.icon {
1819 dlg_api::MsgDialogIcon::Info => rfd::MessageLevel::Info,
1820 dlg_api::MsgDialogIcon::Warn => rfd::MessageLevel::Warning,
1821 dlg_api::MsgDialogIcon::Error => rfd::MessageLevel::Error,
1822 })
1823 .set_buttons(match dialog.buttons {
1824 dlg_api::MsgDialogButtons::Ok => rfd::MessageButtons::Ok,
1825 dlg_api::MsgDialogButtons::OkCancel => rfd::MessageButtons::OkCancel,
1826 dlg_api::MsgDialogButtons::YesNo => rfd::MessageButtons::YesNo,
1827 })
1828 .set_title(dialog.title.as_str())
1829 .set_description(dialog.message.as_str())
1830 .set_parent(&self.window);
1831
1832 let modal_dialog_active = self.modal_dialog_active.clone();
1833 Self::run_dialog(async move {
1834 let r = dlg.show().await;
1835
1836 let r = match dialog.buttons {
1837 dlg_api::MsgDialogButtons::Ok => dlg_api::MsgDialogResponse::Ok,
1838 dlg_api::MsgDialogButtons::OkCancel => match r {
1839 rfd::MessageDialogResult::Yes => dlg_api::MsgDialogResponse::Ok,
1840 rfd::MessageDialogResult::No => dlg_api::MsgDialogResponse::Cancel,
1841 rfd::MessageDialogResult::Ok => dlg_api::MsgDialogResponse::Ok,
1842 rfd::MessageDialogResult::Cancel => dlg_api::MsgDialogResponse::Cancel,
1843 rfd::MessageDialogResult::Custom(_) => dlg_api::MsgDialogResponse::Cancel,
1844 },
1845 dlg_api::MsgDialogButtons::YesNo => match r {
1846 rfd::MessageDialogResult::Yes => dlg_api::MsgDialogResponse::Yes,
1847 rfd::MessageDialogResult::No => dlg_api::MsgDialogResponse::No,
1848 rfd::MessageDialogResult::Ok => dlg_api::MsgDialogResponse::Yes,
1849 rfd::MessageDialogResult::Cancel => dlg_api::MsgDialogResponse::No,
1850 rfd::MessageDialogResult::Custom(_) => dlg_api::MsgDialogResponse::No,
1851 },
1852 };
1853 modal_dialog_active.store(false, Ordering::Release);
1854 let _ = event_sender.send(AppEvent::Notify(Event::MsgDialogResponse(id, r)));
1855 });
1856 }
1857
1858 #[cfg(target_os = "android")]
1859 pub(crate) fn file_dialog(&self, dialog: dlg_api::FileDialog, id: dlg_api::DialogId, event_sender: AppEventSender) {
1860 let _ = dialog;
1861 let _ = event_sender.send(AppEvent::Notify(Event::MsgDialogResponse(
1862 id,
1863 dlg_api::MsgDialogResponse::Error(Txt::from_static("native dialogs not implemented for android")),
1864 )));
1865 }
1866
1867 #[cfg(not(target_os = "android"))]
1869 pub(crate) fn file_dialog(&self, dialog: dlg_api::FileDialog, id: dlg_api::DialogId, event_sender: AppEventSender) {
1870 if self.enter_dialog(id, &event_sender) {
1871 return;
1872 }
1873
1874 let mut dlg = rfd::AsyncFileDialog::new()
1875 .set_title(dialog.title.as_str())
1876 .set_directory(&dialog.starting_dir)
1877 .set_file_name(dialog.starting_name.as_str())
1878 .set_parent(&self.window);
1879 for (name, patterns) in dialog.iter_filters() {
1880 dlg = dlg.add_filter(
1881 name,
1882 &patterns
1883 .map(|s| {
1884 let s = s.trim_start_matches(['*', '.']);
1885 if s.is_empty() { "*" } else { s }
1886 })
1887 .collect::<Vec<_>>(),
1888 );
1889 }
1890
1891 let modal_dialog_active = self.modal_dialog_active.clone();
1892 Self::run_dialog(async move {
1893 let selection: Vec<_> = match dialog.kind {
1894 dlg_api::FileDialogKind::OpenFile => dlg.pick_file().await.into_iter().map(Into::into).collect(),
1895 dlg_api::FileDialogKind::OpenFiles => dlg.pick_files().await.into_iter().flatten().map(Into::into).collect(),
1896 dlg_api::FileDialogKind::SelectFolder => dlg.pick_folder().await.into_iter().map(Into::into).collect(),
1897 dlg_api::FileDialogKind::SelectFolders => dlg.pick_folders().await.into_iter().flatten().map(Into::into).collect(),
1898 dlg_api::FileDialogKind::SaveFile => dlg.save_file().await.into_iter().map(Into::into).collect(),
1899 };
1900
1901 let r = if selection.is_empty() {
1902 dlg_api::FileDialogResponse::Cancel
1903 } else {
1904 dlg_api::FileDialogResponse::Selected(selection)
1905 };
1906
1907 modal_dialog_active.store(false, Ordering::Release);
1908 let _ = event_sender.send(AppEvent::Notify(Event::FileDialogResponse(id, r)));
1909 });
1910 }
1911 #[cfg(not(target_os = "android"))]
1913 fn run_dialog(run: impl Future + Send + 'static) {
1914 let mut task = Box::pin(run);
1915 std::thread::spawn(move || {
1916 struct ThreadWaker(std::thread::Thread);
1917 impl std::task::Wake for ThreadWaker {
1918 fn wake(self: std::sync::Arc<Self>) {
1919 self.0.unpark();
1920 }
1921 }
1922 let waker = Arc::new(ThreadWaker(std::thread::current())).into();
1923 let mut cx = std::task::Context::from_waker(&waker);
1924 loop {
1925 match task.as_mut().poll(&mut cx) {
1926 std::task::Poll::Ready(_) => return,
1927 std::task::Poll::Pending => std::thread::park(),
1928 }
1929 }
1930 });
1931 }
1932
1933 pub fn on_window_event(&mut self, event: &winit::event::WindowEvent) {
1935 if let Some(a) = &mut self.access {
1936 a.process_event(&self.window, event);
1937 }
1938 for (_, ext) in &mut self.window_exts {
1939 ext.event(&mut extensions::WindowEventArgs {
1940 window: &self.window,
1941 context: &mut self.context,
1942 event,
1943 });
1944 }
1945 }
1946
1947 pub fn access_update(&mut self, update: zng_view_api::access::AccessTreeUpdate, event_sender: &AppEventSender) {
1949 if let Some(a) = &mut self.access {
1950 let mut a = std::panic::AssertUnwindSafe(a);
1952 let panic = crate::util::catch_suppress(move || {
1953 a.update_if_active(|| crate::util::access_tree_update_to_kit(update));
1954 });
1955 if let Err(p) = panic {
1956 self.access = None;
1957
1958 let _ = event_sender.send(AppEvent::Notify(Event::RecoveredFromComponentPanic {
1959 component: Txt::from_static("accesskit_winit::Adapter::update_if_active"),
1960 recover: Txt::from_static("accessibility disabled for this window instance"),
1961 panic: p.to_txt(),
1962 }));
1963 }
1964 }
1965 }
1966
1967 pub(crate) fn on_low_memory(&mut self) {
1968 self.api.notify_memory_pressure();
1969
1970 for (_, ext) in &mut self.renderer_exts {
1971 ext.low_memory();
1972 }
1973 }
1974
1975 pub(crate) fn set_ime_area(&mut self, area: Option<DipRect>) {
1976 if let Some(a) = area {
1977 if self.ime_area != Some(a) {
1978 if self.ime_area.is_none() {
1979 self.window.set_ime_allowed(true);
1980
1981 #[cfg(target_os = "android")]
1982 self.set_mobile_keyboard_vis(true);
1983 }
1984
1985 self.ime_area = Some(a);
1986 self.window.set_ime_cursor_area(a.origin.to_winit(), a.size.to_winit());
1987 }
1988 } else if self.ime_area.is_some() {
1989 self.window.set_ime_allowed(false);
1990 self.ime_area = None;
1991
1992 #[cfg(target_os = "android")]
1993 self.set_mobile_keyboard_vis(false);
1994 }
1995 }
1996 #[cfg(target_os = "android")]
1997 fn set_mobile_keyboard_vis(&self, visible: bool) {
1998 if let Err(e) = self.try_show_hide_soft_keyboard(visible) {
2008 tracing::error!("cannot {} mobile keyboard, {e}", if visible { "show" } else { "hide" });
2009 }
2010 }
2011 #[cfg(target_os = "android")]
2012 fn try_show_hide_soft_keyboard(&self, show: bool) -> jni::errors::Result<()> {
2013 use jni::objects::JValue;
2014
2015 let ctx = ndk_context::android_context();
2016 let vm = unsafe { jni::JavaVM::from_raw(ctx.vm() as _) }?;
2017
2018 let activity = unsafe { jni::objects::JObject::from_raw(ctx.context() as _) };
2019 let mut env = vm.attach_current_thread()?;
2020
2021 let class_ctx = env.find_class("android/content/Context")?;
2022 let ims = env.get_static_field(class_ctx, "INPUT_METHOD_SERVICE", "Ljava/lang/String;")?;
2023
2024 let im_manager = env
2025 .call_method(
2026 &activity,
2027 "getSystemService",
2028 "(Ljava/lang/String;)Ljava/lang/Object;",
2029 &[ims.borrow()],
2030 )?
2031 .l()?;
2032
2033 let jni_window = env.call_method(&activity, "getWindow", "()Landroid/view/Window;", &[])?.l()?;
2034
2035 let view = env.call_method(jni_window, "getDecorView", "()Landroid/view/View;", &[])?.l()?;
2036
2037 if show {
2038 env.call_method(&view, "requestFocus", "()Z", &[])?;
2039
2040 env.call_method(
2041 im_manager,
2042 "showSoftInput",
2043 "(Landroid/view/View;I)Z",
2044 &[JValue::Object(&view), 0i32.into()],
2045 )?;
2046 } else {
2047 let window_token = env.call_method(view, "getWindowToken", "()Landroid/os/IBinder;", &[])?.l()?;
2048 let jvalue_window_token = jni::objects::JValueGen::Object(&window_token);
2049
2050 env.call_method(
2051 im_manager,
2052 "hideSoftInputFromWindow",
2053 "(Landroid/os/IBinder;I)Z",
2054 &[jvalue_window_token, 0i32.into()],
2055 )?;
2056 }
2057
2058 Ok(())
2059 }
2060
2061 #[cfg(windows)]
2062 pub(crate) fn set_system_shutdown_warn(&mut self, reason: Txt) {
2063 if !reason.is_empty() {
2064 self.has_shutdown_warn = true;
2065 let hwnd = crate::util::winit_to_hwnd(&self.window);
2066 let reason = windows::core::HSTRING::from(reason.as_str());
2067 let created = unsafe { windows_sys::Win32::System::Shutdown::ShutdownBlockReasonCreate(hwnd, reason.as_ptr()) } != 0;
2069 if !created {
2070 let error = unsafe { windows_sys::Win32::Foundation::GetLastError() };
2071 tracing::error!("failed to set system shutdown warn ({error:#X}), requested warn reason was: {reason}");
2072 }
2073 } else if mem::take(&mut self.has_shutdown_warn) {
2074 let hwnd = crate::util::winit_to_hwnd(&self.window);
2075 let destroyed = unsafe { windows_sys::Win32::System::Shutdown::ShutdownBlockReasonDestroy(hwnd) } != 0;
2077 if !destroyed {
2078 let error = unsafe { windows_sys::Win32::Foundation::GetLastError() };
2079 tracing::error!("failed to unset system shutdown warn ({error:#X})");
2080 }
2081 }
2082 }
2083
2084 #[cfg(not(windows))]
2085 pub(crate) fn set_system_shutdown_warn(&mut self, reason: Txt) {
2086 if !reason.is_empty() {
2087 tracing::warn!("system shutdown warn not implemented on {}", std::env::consts::OS);
2088 }
2089 }
2090
2091 pub(crate) fn drag_drop_cursor_pos(&self) -> Option<DipPoint> {
2092 #[cfg(windows)]
2093 {
2094 let mut pt = windows::Win32::Foundation::POINT::default();
2095 if unsafe { windows::Win32::UI::WindowsAndMessaging::GetCursorPos(&mut pt) }.is_ok() {
2097 let cursor_pos = PxPoint::new(Px(pt.x), Px(pt.y));
2098 let win_pos = self.window.inner_position().unwrap_or_default().to_px();
2099 let pos = cursor_pos - win_pos.to_vector();
2100 if pos.x >= Px(0) && pos.y >= Px(0) {
2101 let size = self.window.inner_size().to_px();
2102 if pos.x <= size.width && pos.y <= size.height {
2103 return Some(pos.to_dip(self.scale_factor()));
2104 }
2105 }
2106 }
2107 }
2108 None
2109 }
2110}
2111impl Drop for Window {
2112 fn drop(&mut self) {
2113 self.set_system_shutdown_warn(Txt::from(""));
2114
2115 self.api.stop_render_backend();
2116 self.api.shut_down(true);
2117
2118 self.context.make_current();
2120 self.renderer.take().unwrap().deinit();
2121
2122 for (_, ext) in &mut self.renderer_exts {
2123 ext.renderer_deinited(&mut RendererDeinitedArgs {
2124 document_id: self.document_id,
2125 pipeline_id: self.pipeline_id,
2126 context: &mut self.context,
2127 window: Some(&self.window),
2128 })
2129 }
2130 for (_, ext) in &mut self.window_exts {
2131 ext.window_deinited(&mut WindowDeinitedArgs {
2132 window: &self.window,
2133 context: &mut self.context,
2134 });
2135 }
2136 }
2137}
2138
2139pub(crate) struct FrameReadyResult {
2140 pub frame_id: FrameId,
2141 pub image: Option<ImageLoadedData>,
2142 pub first_frame: bool,
2143}
2144
2145struct AccessActivateHandler {
2146 id: WindowId,
2147 event_sender: AppEventSender,
2148}
2149impl accesskit::ActivationHandler for AccessActivateHandler {
2150 fn request_initial_tree(&mut self) -> Option<accesskit::TreeUpdate> {
2151 let _ = self.event_sender.send(AppEvent::Notify(Event::AccessInit { window: self.id }));
2152 None
2153 }
2154}
2155
2156struct AccessDeactivateHandler {
2157 id: WindowId,
2158 event_sender: AppEventSender,
2159}
2160
2161impl accesskit::DeactivationHandler for AccessDeactivateHandler {
2162 fn deactivate_accessibility(&mut self) {
2163 let _ = self.event_sender.send(AppEvent::Notify(Event::AccessDeinit { window: self.id }));
2164 }
2165}
2166
2167struct AccessActionSender {
2168 id: WindowId,
2169 event_sender: AppEventSender,
2170}
2171impl accesskit::ActionHandler for AccessActionSender {
2172 fn do_action(&mut self, request: accesskit::ActionRequest) {
2173 if let Some(ev) = crate::util::accesskit_to_event(self.id, request) {
2174 let _ = self.event_sender.send(AppEvent::Notify(ev));
2175 }
2176 }
2177}