1use std::{
4 collections::HashMap,
5 fmt,
6 path::PathBuf,
7 sync::{self, Arc},
8};
9
10pub mod raw_device_events;
11pub mod raw_events;
12
13use crate::{
14 event::{event, event_args},
15 window::{MonitorId, WindowId},
16};
17
18use parking_lot::{MappedRwLockReadGuard, MappedRwLockWriteGuard, RwLock};
19use zng_app_context::app_local;
20use zng_layout::unit::{DipPoint, DipRect, DipSideOffsets, DipSize, Factor, Px, PxPoint, PxRect, PxSize};
21use zng_task::SignalOnce;
22use zng_txt::Txt;
23use zng_var::ResponderVar;
24use zng_view_api::{
25 self, DragDropId, Event, FocusResult, ViewProcessGen, ViewProcessOffline,
26 api_extension::{ApiExtensionId, ApiExtensionName, ApiExtensionPayload, ApiExtensionRecvError, ApiExtensions},
27 config::{AnimationsConfig, ChromeConfig, ColorsConfig, FontAntiAliasing, LocaleConfig, MultiClickConfig, TouchConfig},
28 dialog::{FileDialog, FileDialogResponse, MsgDialog, MsgDialogResponse},
29 drag_drop::{DragDropData, DragDropEffect, DragDropError},
30 font::FontOptions,
31 image::{ImageMaskMode, ImagePpi, ImageRequest, ImageTextureId},
32 ipc::{IpcBytes, IpcBytesReceiver},
33 window::{
34 CursorIcon, FocusIndicator, FrameRequest, FrameUpdateRequest, HeadlessOpenData, HeadlessRequest, MonitorInfo, RenderMode,
35 ResizeDirection, VideoMode, WindowButton, WindowRequest, WindowStateAll,
36 },
37};
38
39pub(crate) use zng_view_api::{Controller, DeviceId as ApiDeviceId, window::MonitorId as ApiMonitorId, window::WindowId as ApiWindowId};
40use zng_view_api::{
41 clipboard::{ClipboardData, ClipboardError, ClipboardType},
42 config::KeyRepeatConfig,
43 font::{FontFaceId, FontId, FontVariationName},
44 image::{ImageId, ImageLoadedData},
45};
46
47use self::raw_device_events::DeviceId;
48
49use super::{APP, AppId};
50
51#[expect(non_camel_case_types)]
53pub struct VIEW_PROCESS;
54struct ViewProcessService {
55 process: zng_view_api::Controller,
56 device_ids: HashMap<ApiDeviceId, DeviceId>,
57 monitor_ids: HashMap<ApiMonitorId, MonitorId>,
58
59 data_generation: ViewProcessGen,
60
61 extensions: ApiExtensions,
62
63 loading_images: Vec<sync::Weak<RwLock<ViewImageData>>>,
64 frame_images: Vec<sync::Weak<RwLock<ViewImageData>>>,
65 encoding_images: Vec<EncodeRequest>,
66
67 pending_frames: usize,
68
69 message_dialogs: Vec<(zng_view_api::dialog::DialogId, ResponderVar<MsgDialogResponse>)>,
70 file_dialogs: Vec<(zng_view_api::dialog::DialogId, ResponderVar<FileDialogResponse>)>,
71}
72app_local! {
73 static VIEW_PROCESS_SV: Option<ViewProcessService> = None;
74}
75impl VIEW_PROCESS {
76 pub fn is_available(&self) -> bool {
79 APP.is_running() && VIEW_PROCESS_SV.read().is_some()
80 }
81
82 fn read(&self) -> MappedRwLockReadGuard<ViewProcessService> {
83 VIEW_PROCESS_SV.read_map(|e| e.as_ref().expect("VIEW_PROCESS not available"))
84 }
85
86 fn write(&self) -> MappedRwLockWriteGuard<ViewProcessService> {
87 VIEW_PROCESS_SV.write_map(|e| e.as_mut().expect("VIEW_PROCESS not available"))
88 }
89
90 fn try_write(&self) -> Result<MappedRwLockWriteGuard<ViewProcessService>> {
91 let vp = VIEW_PROCESS_SV.write();
92 if let Some(w) = &*vp {
93 if w.process.online() {
94 return Ok(MappedRwLockWriteGuard::map(vp, |w| w.as_mut().unwrap()));
95 }
96 }
97 Err(ViewProcessOffline)
98 }
99
100 fn check_app(&self, id: AppId) {
101 let actual = APP.id();
102 if Some(id) != actual {
103 panic!("cannot use view handle from app `{id:?}` in app `{actual:?}`");
104 }
105 }
106
107 fn handle_write(&self, id: AppId) -> MappedRwLockWriteGuard<ViewProcessService> {
108 self.check_app(id);
109 self.write()
110 }
111
112 pub fn is_online(&self) -> bool {
114 self.read().process.online()
115 }
116
117 pub fn is_headless_with_render(&self) -> bool {
119 self.read().process.headless()
120 }
121
122 pub fn is_same_process(&self) -> bool {
124 self.read().process.same_process()
125 }
126
127 pub fn generation(&self) -> ViewProcessGen {
129 self.read().process.generation()
130 }
131
132 pub fn open_window(&self, config: WindowRequest) -> Result<()> {
139 let _s = tracing::debug_span!("VIEW_PROCESS.open_window").entered();
140 self.write().process.open_window(config)
141 }
142
143 pub fn open_headless(&self, config: HeadlessRequest) -> Result<()> {
153 let _s = tracing::debug_span!("VIEW_PROCESS.open_headless").entered();
154 self.write().process.open_headless(config)
155 }
156
157 pub fn add_image(&self, request: ImageRequest<IpcBytes>) -> Result<ViewImage> {
166 let mut app = self.write();
167 let id = app.process.add_image(request)?;
168 let img = ViewImage(Arc::new(RwLock::new(ViewImageData {
169 id: Some(id),
170 app_id: APP.id(),
171 generation: app.process.generation(),
172 size: PxSize::zero(),
173 partial_size: PxSize::zero(),
174 ppi: None,
175 is_opaque: false,
176 partial_pixels: None,
177 pixels: None,
178 is_mask: false,
179 done_signal: SignalOnce::new(),
180 })));
181 app.loading_images.push(Arc::downgrade(&img.0));
182 Ok(img)
183 }
184
185 pub fn add_image_pro(&self, request: ImageRequest<IpcBytesReceiver>) -> Result<ViewImage> {
196 let mut app = self.write();
197 let id = app.process.add_image_pro(request)?;
198 let img = ViewImage(Arc::new(RwLock::new(ViewImageData {
199 id: Some(id),
200 app_id: APP.id(),
201 generation: app.process.generation(),
202 size: PxSize::zero(),
203 partial_size: PxSize::zero(),
204 ppi: None,
205 is_opaque: false,
206 partial_pixels: None,
207 pixels: None,
208 is_mask: false,
209 done_signal: SignalOnce::new(),
210 })));
211 app.loading_images.push(Arc::downgrade(&img.0));
212 Ok(img)
213 }
214
215 pub fn clipboard(&self) -> Result<&ViewClipboard> {
217 if VIEW_PROCESS.is_online() {
218 Ok(&ViewClipboard {})
219 } else {
220 Err(ViewProcessOffline)
221 }
222 }
223
224 pub fn image_decoders(&self) -> Result<Vec<Txt>> {
228 self.write().process.image_decoders()
229 }
230
231 pub fn image_encoders(&self) -> Result<Vec<Txt>> {
235 self.write().process.image_encoders()
236 }
237
238 pub fn pending_frames(&self) -> usize {
242 self.write().pending_frames
243 }
244
245 pub fn respawn(&self) {
249 self.write().process.respawn()
250 }
251
252 pub fn extension_id(&self, extension_name: impl Into<ApiExtensionName>) -> Result<Option<ApiExtensionId>> {
258 let me = self.read();
259 if me.process.online() {
260 Ok(me.extensions.id(&extension_name.into()))
261 } else {
262 Err(ViewProcessOffline)
263 }
264 }
265
266 pub fn third_party_licenses(&self) -> Result<Vec<crate::third_party::LicenseUsed>> {
271 self.write().process.third_party_licenses()
272 }
273
274 pub fn app_extension_raw(&self, extension_id: ApiExtensionId, extension_request: ApiExtensionPayload) -> Result<ApiExtensionPayload> {
276 self.write().process.app_extension(extension_id, extension_request)
277 }
278
279 pub fn app_extension<I, O>(&self, extension_id: ApiExtensionId, request: &I) -> Result<std::result::Result<O, ApiExtensionRecvError>>
281 where
282 I: serde::Serialize,
283 O: serde::de::DeserializeOwned,
284 {
285 let payload = ApiExtensionPayload::serialize(&request).unwrap();
286 let response = self.write().process.app_extension(extension_id, payload)?;
287 Ok(response.deserialize::<O>())
288 }
289
290 pub fn handle_disconnect(&self, vp_gen: ViewProcessGen) {
296 self.write().process.handle_disconnect(vp_gen)
297 }
298
299 pub(super) fn start<F>(
301 &self,
302 view_process_exe: PathBuf,
303 view_process_env: HashMap<Txt, Txt>,
304 device_events: bool,
305 headless: bool,
306 on_event: F,
307 ) where
308 F: FnMut(Event) + Send + 'static,
309 {
310 let _s = tracing::debug_span!("VIEW_PROCESS.start").entered();
311
312 let process = zng_view_api::Controller::start(view_process_exe, view_process_env, device_events, headless, on_event);
313 *VIEW_PROCESS_SV.write() = Some(ViewProcessService {
314 data_generation: process.generation(),
315 process,
316 device_ids: HashMap::default(),
317 monitor_ids: HashMap::default(),
318 loading_images: vec![],
319 encoding_images: vec![],
320 frame_images: vec![],
321 pending_frames: 0,
322 message_dialogs: vec![],
323 file_dialogs: vec![],
324 extensions: ApiExtensions::default(),
325 });
326 }
327
328 pub(crate) fn on_window_opened(&self, window_id: WindowId, data: zng_view_api::window::WindowOpenData) -> (ViewWindow, WindowOpenData) {
329 let mut app = self.write();
330 let _ = app.check_generation();
331
332 let win = ViewWindow(Arc::new(ViewWindowData {
333 app_id: APP.id().unwrap(),
334 id: ApiWindowId::from_raw(window_id.get()),
335 generation: app.data_generation,
336 }));
337 drop(app);
338
339 let data = WindowOpenData::new(data, |id| self.monitor_id(id));
340
341 (win, data)
342 }
343 pub(super) fn device_id(&self, id: ApiDeviceId) -> DeviceId {
345 *self.write().device_ids.entry(id).or_insert_with(DeviceId::new_unique)
346 }
347
348 pub(super) fn monitor_id(&self, id: ApiMonitorId) -> MonitorId {
350 *self.write().monitor_ids.entry(id).or_insert_with(MonitorId::new_unique)
351 }
352
353 pub(super) fn handle_inited(&self, vp_gen: ViewProcessGen, extensions: ApiExtensions) {
359 let mut me = self.write();
360 me.extensions = extensions;
361 me.process.handle_inited(vp_gen);
362 }
363
364 pub(super) fn handle_suspended(&self) {
365 self.write().process.handle_suspended();
366 }
367
368 pub(crate) fn on_headless_opened(
369 &self,
370 id: WindowId,
371 data: zng_view_api::window::HeadlessOpenData,
372 ) -> (ViewHeadless, HeadlessOpenData) {
373 let mut app = self.write();
374 let _ = app.check_generation();
375
376 let surf = ViewHeadless(Arc::new(ViewWindowData {
377 app_id: APP.id().unwrap(),
378 id: ApiWindowId::from_raw(id.get()),
379 generation: app.data_generation,
380 }));
381
382 (surf, data)
383 }
384
385 fn loading_image_index(&self, id: ImageId) -> Option<usize> {
386 let mut app = self.write();
387
388 app.loading_images.retain(|i| i.strong_count() > 0);
390
391 app.loading_images.iter().position(|i| i.upgrade().unwrap().read().id == Some(id))
392 }
393
394 pub(super) fn on_image_metadata_loaded(&self, id: ImageId, size: PxSize, ppi: Option<ImagePpi>, is_mask: bool) -> Option<ViewImage> {
395 if let Some(i) = self.loading_image_index(id) {
396 let img = self.read().loading_images[i].upgrade().unwrap();
397 {
398 let mut img = img.write();
399 img.size = size;
400 img.ppi = ppi;
401 img.is_mask = is_mask;
402 }
403 Some(ViewImage(img))
404 } else {
405 None
406 }
407 }
408
409 pub(super) fn on_image_partially_loaded(
410 &self,
411 id: ImageId,
412 partial_size: PxSize,
413 ppi: Option<ImagePpi>,
414 is_opaque: bool,
415 is_mask: bool,
416 partial_pixels: IpcBytes,
417 ) -> Option<ViewImage> {
418 if let Some(i) = self.loading_image_index(id) {
419 let img = self.read().loading_images[i].upgrade().unwrap();
420 {
421 let mut img = img.write();
422 img.partial_size = partial_size;
423 img.ppi = ppi;
424 img.is_opaque = is_opaque;
425 img.partial_pixels = Some(partial_pixels);
426 img.is_mask = is_mask;
427 }
428 Some(ViewImage(img))
429 } else {
430 None
431 }
432 }
433
434 pub(super) fn on_image_loaded(&self, data: ImageLoadedData) -> Option<ViewImage> {
435 if let Some(i) = self.loading_image_index(data.id) {
436 let img = self.write().loading_images.swap_remove(i).upgrade().unwrap();
437 {
438 let mut img = img.write();
439 img.size = data.size;
440 img.partial_size = data.size;
441 img.ppi = data.ppi;
442 img.is_opaque = data.is_opaque;
443 img.pixels = Some(Ok(data.pixels));
444 img.partial_pixels = None;
445 img.is_mask = data.is_mask;
446 img.done_signal.set();
447 }
448 Some(ViewImage(img))
449 } else {
450 None
451 }
452 }
453
454 pub(super) fn on_image_error(&self, id: ImageId, error: Txt) -> Option<ViewImage> {
455 if let Some(i) = self.loading_image_index(id) {
456 let img = self.write().loading_images.swap_remove(i).upgrade().unwrap();
457 {
458 let mut img = img.write();
459 img.pixels = Some(Err(error));
460 img.done_signal.set();
461 }
462 Some(ViewImage(img))
463 } else {
464 None
465 }
466 }
467
468 pub(crate) fn on_frame_rendered(&self, _id: WindowId) {
469 let mut vp = self.write();
470 vp.pending_frames = vp.pending_frames.saturating_sub(1);
471 }
472
473 pub(crate) fn on_frame_image(&self, data: ImageLoadedData) -> ViewImage {
474 ViewImage(Arc::new(RwLock::new(ViewImageData {
475 app_id: APP.id(),
476 id: Some(data.id),
477 generation: self.generation(),
478 size: data.size,
479 partial_size: data.size,
480 ppi: data.ppi,
481 is_opaque: data.is_opaque,
482 partial_pixels: None,
483 pixels: Some(Ok(data.pixels)),
484 is_mask: data.is_mask,
485 done_signal: SignalOnce::new_set(),
486 })))
487 }
488
489 pub(super) fn on_frame_image_ready(&self, id: ImageId) -> Option<ViewImage> {
490 let mut app = self.write();
491
492 app.frame_images.retain(|i| i.strong_count() > 0);
494
495 let i = app.frame_images.iter().position(|i| i.upgrade().unwrap().read().id == Some(id));
496
497 i.map(|i| ViewImage(app.frame_images.swap_remove(i).upgrade().unwrap()))
498 }
499
500 pub(super) fn on_image_encoded(&self, id: ImageId, format: Txt, data: IpcBytes) {
501 self.on_image_encode_result(id, format, Ok(data));
502 }
503 pub(super) fn on_image_encode_error(&self, id: ImageId, format: Txt, error: Txt) {
504 self.on_image_encode_result(id, format, Err(EncodeError::Encode(error)));
505 }
506 fn on_image_encode_result(&self, id: ImageId, format: Txt, result: std::result::Result<IpcBytes, EncodeError>) {
507 let mut app = self.write();
508 app.encoding_images.retain(move |r| {
509 let done = r.image_id == id && r.format == format;
510 if done {
511 for sender in &r.listeners {
512 let _ = sender.send(result.clone());
513 }
514 }
515 !done
516 })
517 }
518
519 pub(crate) fn on_message_dlg_response(&self, id: zng_view_api::dialog::DialogId, response: MsgDialogResponse) {
520 let mut app = self.write();
521 if let Some(i) = app.message_dialogs.iter().position(|(i, _)| *i == id) {
522 let (_, r) = app.message_dialogs.swap_remove(i);
523 r.respond(response);
524 }
525 }
526
527 pub(crate) fn on_file_dlg_response(&self, id: zng_view_api::dialog::DialogId, response: FileDialogResponse) {
528 let mut app = self.write();
529 if let Some(i) = app.file_dialogs.iter().position(|(i, _)| *i == id) {
530 let (_, r) = app.file_dialogs.swap_remove(i);
531 r.respond(response);
532 }
533 }
534
535 pub(super) fn on_respawned(&self, _gen: ViewProcessGen) {
536 let mut app = self.write();
537 app.pending_frames = 0;
538 for (_, r) in app.message_dialogs.drain(..) {
539 r.respond(MsgDialogResponse::Error(Txt::from_static("respawn")));
540 }
541 }
542
543 pub(crate) fn exit(&self) {
544 *VIEW_PROCESS_SV.write() = None;
545 }
546}
547impl ViewProcessService {
548 #[must_use = "if `true` all current WinId, DevId and MonId are invalid"]
549 fn check_generation(&mut self) -> bool {
550 let vp_gen = self.process.generation();
551 let invalid = vp_gen != self.data_generation;
552 if invalid {
553 self.data_generation = vp_gen;
554 self.device_ids.clear();
555 self.monitor_ids.clear();
556 }
557 invalid
558 }
559}
560
561event_args! {
562 pub struct ViewProcessInitedArgs {
564 pub generation: ViewProcessGen,
566
567 pub is_respawn: bool,
572
573 pub available_monitors: Vec<(MonitorId, MonitorInfo)>,
575
576 pub multi_click_config: MultiClickConfig,
578
579 pub key_repeat_config: KeyRepeatConfig,
581
582 pub touch_config: TouchConfig,
584
585 pub font_aa: FontAntiAliasing,
587
588 pub animations_config: AnimationsConfig,
590
591 pub locale_config: LocaleConfig,
593
594 pub colors_config: ColorsConfig,
596
597 pub chrome_config: ChromeConfig,
599
600 pub extensions: ApiExtensions,
604
605 ..
606
607 fn delivery_list(&self, list: &mut UpdateDeliveryList) {
609 list.search_all()
610 }
611 }
612
613 pub struct ViewProcessSuspendedArgs {
615
616 ..
617
618 fn delivery_list(&self, list: &mut UpdateDeliveryList) {
620 list.search_all()
621 }
622 }
623}
624
625event! {
626 pub static VIEW_PROCESS_INITED_EVENT: ViewProcessInitedArgs;
628 pub static VIEW_PROCESS_SUSPENDED_EVENT: ViewProcessSuspendedArgs;
633}
634
635#[derive(Debug, Clone)]
637pub struct WindowOpenData {
638 pub state: WindowStateAll,
640
641 pub monitor: Option<MonitorId>,
643
644 pub position: (PxPoint, DipPoint),
648 pub size: DipSize,
650
651 pub scale_factor: Factor,
653
654 pub render_mode: RenderMode,
656
657 pub safe_padding: DipSideOffsets,
663}
664impl WindowOpenData {
665 pub(crate) fn new(data: zng_view_api::window::WindowOpenData, map_monitor: impl FnOnce(ApiMonitorId) -> MonitorId) -> Self {
666 WindowOpenData {
667 state: data.state,
668 monitor: data.monitor.map(map_monitor),
669 position: data.position,
670 size: data.size,
671 scale_factor: data.scale_factor,
672 render_mode: data.render_mode,
673 safe_padding: data.safe_padding,
674 }
675 }
676}
677
678#[derive(Debug, Clone)]
682#[must_use = "the window is closed when all clones of the handle are dropped"]
683pub struct ViewWindow(Arc<ViewWindowData>);
684impl PartialEq for ViewWindow {
685 fn eq(&self, other: &Self) -> bool {
686 Arc::ptr_eq(&self.0, &other.0)
687 }
688}
689impl Eq for ViewWindow {}
690
691impl ViewWindow {
692 pub fn generation(&self) -> ViewProcessGen {
694 self.0.generation
695 }
696
697 pub fn set_title(&self, title: Txt) -> Result<()> {
699 self.0.call(|id, p| p.set_title(id, title))
700 }
701
702 pub fn set_visible(&self, visible: bool) -> Result<()> {
704 self.0.call(|id, p| p.set_visible(id, visible))
705 }
706
707 pub fn set_always_on_top(&self, always_on_top: bool) -> Result<()> {
709 self.0.call(|id, p| p.set_always_on_top(id, always_on_top))
710 }
711
712 pub fn set_movable(&self, movable: bool) -> Result<()> {
714 self.0.call(|id, p| p.set_movable(id, movable))
715 }
716
717 pub fn set_resizable(&self, resizable: bool) -> Result<()> {
719 self.0.call(|id, p| p.set_resizable(id, resizable))
720 }
721
722 pub fn set_icon(&self, icon: Option<&ViewImage>) -> Result<()> {
724 self.0.call(|id, p| {
725 if let Some(icon) = icon {
726 let icon = icon.0.read();
727 if p.generation() == icon.generation {
728 p.set_icon(id, icon.id)
729 } else {
730 Err(ViewProcessOffline)
731 }
732 } else {
733 p.set_icon(id, None)
734 }
735 })
736 }
737
738 pub fn set_cursor(&self, cursor: Option<CursorIcon>) -> Result<()> {
740 self.0.call(|id, p| p.set_cursor(id, cursor))
741 }
742
743 pub fn set_cursor_image(&self, cursor: Option<&ViewImage>, hotspot: PxPoint) -> Result<()> {
750 self.0.call(|id, p| {
751 if let Some(cur) = cursor {
752 let cur = cur.0.read();
753 if p.generation() == cur.generation {
754 p.set_cursor_image(id, cur.id.map(|img| zng_view_api::window::CursorImage { img, hotspot }))
755 } else {
756 Err(ViewProcessOffline)
757 }
758 } else {
759 p.set_cursor_image(id, None)
760 }
761 })
762 }
763
764 pub fn set_taskbar_visible(&self, visible: bool) -> Result<()> {
766 self.0.call(|id, p| p.set_taskbar_visible(id, visible))
767 }
768
769 pub fn bring_to_top(&self) -> Result<()> {
771 self.0.call(|id, p| p.bring_to_top(id))
772 }
773
774 pub fn set_state(&self, state: WindowStateAll) -> Result<()> {
776 self.0.call(|id, p| p.set_state(id, state))
777 }
778
779 pub fn set_video_mode(&self, mode: VideoMode) -> Result<()> {
781 self.0.call(|id, p| p.set_video_mode(id, mode))
782 }
783
784 pub fn set_enabled_buttons(&self, buttons: WindowButton) -> Result<()> {
786 self.0.call(|id, p| p.set_enabled_buttons(id, buttons))
787 }
788
789 pub fn renderer(&self) -> ViewRenderer {
791 ViewRenderer(Arc::downgrade(&self.0))
792 }
793
794 pub fn set_capture_mode(&self, enabled: bool) -> Result<()> {
799 self.0.call(|id, p| p.set_capture_mode(id, enabled))
800 }
801
802 pub fn focus(&self) -> Result<FocusResult> {
806 self.0.call(|id, p| p.focus(id))
807 }
808
809 pub fn set_focus_indicator(&self, indicator: Option<FocusIndicator>) -> Result<()> {
812 self.0.call(|id, p| p.set_focus_indicator(id, indicator))
813 }
814
815 pub fn drag_move(&self) -> Result<()> {
819 self.0.call(|id, p| p.drag_move(id))
820 }
821
822 pub fn drag_resize(&self, direction: ResizeDirection) -> Result<()> {
826 self.0.call(|id, p| p.drag_resize(id, direction))
827 }
828
829 pub fn start_drag_drop(
835 &self,
836 data: Vec<DragDropData>,
837 allowed_effects: DragDropEffect,
838 ) -> Result<std::result::Result<DragDropId, DragDropError>> {
839 self.0.call(|id, p| p.start_drag_drop(id, data, allowed_effects))
840 }
841
842 pub fn drag_dropped(&self, drop_id: DragDropId, applied: DragDropEffect) -> Result<()> {
844 self.0.call(|id, p| p.drag_dropped(id, drop_id, applied))
845 }
846
847 pub fn open_title_bar_context_menu(&self, position: DipPoint) -> Result<()> {
849 self.0.call(|id, p| p.open_title_bar_context_menu(id, position))
850 }
851
852 pub fn message_dialog(&self, dlg: MsgDialog, responder: ResponderVar<MsgDialogResponse>) -> Result<()> {
857 let dlg_id = self.0.call(|id, p| p.message_dialog(id, dlg))?;
858 VIEW_PROCESS.handle_write(self.0.app_id).message_dialogs.push((dlg_id, responder));
859 Ok(())
860 }
861
862 pub fn file_dialog(&self, dlg: FileDialog, responder: ResponderVar<FileDialogResponse>) -> Result<()> {
867 let dlg_id = self.0.call(|id, p| p.file_dialog(id, dlg))?;
868 VIEW_PROCESS.handle_write(self.0.app_id).file_dialogs.push((dlg_id, responder));
869 Ok(())
870 }
871
872 pub fn access_update(&self, update: zng_view_api::access::AccessTreeUpdate) -> Result<()> {
874 self.0.call(|id, p| p.access_update(id, update))
875 }
876
877 pub fn set_ime_area(&self, area: Option<DipRect>) -> Result<()> {
881 self.0.call(|id, p| p.set_ime_area(id, area))
882 }
883
884 pub fn set_system_shutdown_warn(&self, reason: Txt) -> Result<()> {
895 self.0.call(move |id, p| p.set_system_shutdown_warn(id, reason))
896 }
897
898 pub fn close(self) {
900 drop(self)
901 }
902
903 pub fn window_extension_raw(&self, extension_id: ApiExtensionId, request: ApiExtensionPayload) -> Result<ApiExtensionPayload> {
905 self.0.call(|id, p| p.window_extension(id, extension_id, request))
906 }
907
908 pub fn window_extension<I, O>(&self, extension_id: ApiExtensionId, request: &I) -> Result<std::result::Result<O, ApiExtensionRecvError>>
910 where
911 I: serde::Serialize,
912 O: serde::de::DeserializeOwned,
913 {
914 let r = self.window_extension_raw(extension_id, ApiExtensionPayload::serialize(&request).unwrap())?;
915 Ok(r.deserialize())
916 }
917}
918
919#[derive(Clone, Debug)]
921pub enum ViewWindowOrHeadless {
922 Window(ViewWindow),
924 Headless(ViewHeadless),
926}
927impl ViewWindowOrHeadless {
928 pub fn renderer(&self) -> ViewRenderer {
930 match self {
931 ViewWindowOrHeadless::Window(w) => w.renderer(),
932 ViewWindowOrHeadless::Headless(h) => h.renderer(),
933 }
934 }
935
936 pub fn window_extension_raw(&self, extension_id: ApiExtensionId, request: ApiExtensionPayload) -> Result<ApiExtensionPayload> {
938 match self {
939 ViewWindowOrHeadless::Window(w) => w.window_extension_raw(extension_id, request),
940 ViewWindowOrHeadless::Headless(h) => h.window_extension_raw(extension_id, request),
941 }
942 }
943
944 pub fn window_extension<I, O>(&self, extension_id: ApiExtensionId, request: &I) -> Result<std::result::Result<O, ApiExtensionRecvError>>
946 where
947 I: serde::Serialize,
948 O: serde::de::DeserializeOwned,
949 {
950 match self {
951 ViewWindowOrHeadless::Window(w) => w.window_extension(extension_id, request),
952 ViewWindowOrHeadless::Headless(h) => h.window_extension(extension_id, request),
953 }
954 }
955}
956impl From<ViewWindow> for ViewWindowOrHeadless {
957 fn from(w: ViewWindow) -> Self {
958 ViewWindowOrHeadless::Window(w)
959 }
960}
961impl From<ViewHeadless> for ViewWindowOrHeadless {
962 fn from(w: ViewHeadless) -> Self {
963 ViewWindowOrHeadless::Headless(w)
964 }
965}
966
967#[derive(Debug)]
968struct ViewWindowData {
969 app_id: AppId,
970 id: ApiWindowId,
971 generation: ViewProcessGen,
972}
973impl ViewWindowData {
974 fn call<R>(&self, f: impl FnOnce(ApiWindowId, &mut Controller) -> Result<R>) -> Result<R> {
975 let mut app = VIEW_PROCESS.handle_write(self.app_id);
976 if app.check_generation() {
977 Err(ViewProcessOffline)
978 } else {
979 f(self.id, &mut app.process)
980 }
981 }
982}
983impl Drop for ViewWindowData {
984 fn drop(&mut self) {
985 if VIEW_PROCESS.is_available() {
986 let mut app = VIEW_PROCESS.handle_write(self.app_id);
987 if self.generation == app.process.generation() {
988 let _ = app.process.close(self.id);
989 }
990 }
991 }
992}
993type Result<T> = std::result::Result<T, ViewProcessOffline>;
994
995#[derive(Clone, Debug)]
999#[must_use = "the view is disposed when all clones of the handle are dropped"]
1000pub struct ViewHeadless(Arc<ViewWindowData>);
1001impl PartialEq for ViewHeadless {
1002 fn eq(&self, other: &Self) -> bool {
1003 Arc::ptr_eq(&self.0, &other.0)
1004 }
1005}
1006impl Eq for ViewHeadless {}
1007impl ViewHeadless {
1008 pub fn set_size(&self, size: DipSize, scale_factor: Factor) -> Result<()> {
1010 self.0.call(|id, p| p.set_headless_size(id, size, scale_factor))
1011 }
1012
1013 pub fn renderer(&self) -> ViewRenderer {
1015 ViewRenderer(Arc::downgrade(&self.0))
1016 }
1017
1018 pub fn window_extension_raw(&self, extension_id: ApiExtensionId, request: ApiExtensionPayload) -> Result<ApiExtensionPayload> {
1020 self.0.call(|id, p| p.window_extension(id, extension_id, request))
1021 }
1022
1023 pub fn window_extension<I, O>(&self, extension_id: ApiExtensionId, request: &I) -> Result<std::result::Result<O, ApiExtensionRecvError>>
1025 where
1026 I: serde::Serialize,
1027 O: serde::de::DeserializeOwned,
1028 {
1029 let r = self.window_extension_raw(extension_id, ApiExtensionPayload::serialize(&request).unwrap())?;
1030 Ok(r.deserialize())
1031 }
1032}
1033
1034#[derive(Clone, Debug)]
1041pub struct ViewRenderer(sync::Weak<ViewWindowData>);
1042impl PartialEq for ViewRenderer {
1043 fn eq(&self, other: &Self) -> bool {
1044 if let (Some(s), Some(o)) = (self.0.upgrade(), other.0.upgrade()) {
1045 Arc::ptr_eq(&s, &o)
1046 } else {
1047 false
1048 }
1049 }
1050}
1051impl Eq for ViewRenderer {}
1052
1053impl ViewRenderer {
1054 fn call<R>(&self, f: impl FnOnce(ApiWindowId, &mut Controller) -> Result<R>) -> Result<R> {
1055 if let Some(c) = self.0.upgrade() {
1056 c.call(f)
1057 } else {
1058 Err(ViewProcessOffline)
1059 }
1060 }
1061
1062 pub fn generation(&self) -> Result<ViewProcessGen> {
1064 self.0.upgrade().map(|c| c.generation).ok_or(ViewProcessOffline)
1065 }
1066
1067 pub fn use_image(&self, image: &ViewImage) -> Result<ImageTextureId> {
1071 self.call(|id, p| {
1072 let image = image.0.read();
1073 if p.generation() == image.generation {
1074 p.use_image(id, image.id.unwrap_or(ImageId::INVALID))
1075 } else {
1076 Err(ViewProcessOffline)
1077 }
1078 })
1079 }
1080
1081 pub fn update_image_use(&mut self, tex_id: ImageTextureId, image: &ViewImage) -> Result<()> {
1083 self.call(|id, p| {
1084 let image = image.0.read();
1085 if p.generation() == image.generation {
1086 p.update_image_use(id, tex_id, image.id.unwrap_or(ImageId::INVALID))
1087 } else {
1088 Err(ViewProcessOffline)
1089 }
1090 })
1091 }
1092
1093 pub fn delete_image_use(&mut self, tex_id: ImageTextureId) -> Result<()> {
1095 self.call(|id, p| p.delete_image_use(id, tex_id))
1096 }
1097
1098 pub fn add_font_face(&self, bytes: Vec<u8>, index: u32) -> Result<FontFaceId> {
1102 self.call(|id, p| p.add_font_face(id, IpcBytes::from_vec(bytes), index))
1103 }
1104
1105 pub fn delete_font_face(&self, font_face_id: FontFaceId) -> Result<()> {
1107 self.call(|id, p| p.delete_font_face(id, font_face_id))
1108 }
1109
1110 pub fn add_font(
1114 &self,
1115 font_face_id: FontFaceId,
1116 glyph_size: Px,
1117 options: FontOptions,
1118 variations: Vec<(FontVariationName, f32)>,
1119 ) -> Result<FontId> {
1120 self.call(|id, p| p.add_font(id, font_face_id, glyph_size, options, variations))
1121 }
1122
1123 pub fn delete_font(&self, font_id: FontId) -> Result<()> {
1125 self.call(|id, p| p.delete_font(id, font_id))
1126 }
1127
1128 pub fn frame_image(&self, mask: Option<ImageMaskMode>) -> Result<ViewImage> {
1130 if let Some(c) = self.0.upgrade() {
1131 let id = c.call(|id, p| p.frame_image(id, mask))?;
1132 Ok(Self::add_frame_image(c.app_id, id))
1133 } else {
1134 Err(ViewProcessOffline)
1135 }
1136 }
1137
1138 pub fn frame_image_rect(&self, rect: PxRect, mask: Option<ImageMaskMode>) -> Result<ViewImage> {
1140 if let Some(c) = self.0.upgrade() {
1141 let id = c.call(|id, p| p.frame_image_rect(id, rect, mask))?;
1142 Ok(Self::add_frame_image(c.app_id, id))
1143 } else {
1144 Err(ViewProcessOffline)
1145 }
1146 }
1147
1148 fn add_frame_image(app_id: AppId, id: ImageId) -> ViewImage {
1149 if id == ImageId::INVALID {
1150 ViewImage::dummy(None)
1151 } else {
1152 let mut app = VIEW_PROCESS.handle_write(app_id);
1153 let img = ViewImage(Arc::new(RwLock::new(ViewImageData {
1154 app_id: Some(app_id),
1155 id: Some(id),
1156 generation: app.process.generation(),
1157 size: PxSize::zero(),
1158 partial_size: PxSize::zero(),
1159 ppi: None,
1160 is_opaque: false,
1161 partial_pixels: None,
1162 pixels: None,
1163 is_mask: false,
1164 done_signal: SignalOnce::new(),
1165 })));
1166
1167 app.loading_images.push(Arc::downgrade(&img.0));
1168 app.frame_images.push(Arc::downgrade(&img.0));
1169
1170 img
1171 }
1172 }
1173
1174 pub fn render(&self, frame: FrameRequest) -> Result<()> {
1176 let _s = tracing::debug_span!("ViewRenderer.render").entered();
1177
1178 if let Some(w) = self.0.upgrade() {
1179 w.call(|id, p| p.render(id, frame))?;
1180 VIEW_PROCESS.handle_write(w.app_id).pending_frames += 1;
1181 Ok(())
1182 } else {
1183 Err(ViewProcessOffline)
1184 }
1185 }
1186
1187 pub fn render_update(&self, frame: FrameUpdateRequest) -> Result<()> {
1189 let _s = tracing::debug_span!("ViewRenderer.render_update").entered();
1190
1191 if let Some(w) = self.0.upgrade() {
1192 w.call(|id, p| p.render_update(id, frame))?;
1193 VIEW_PROCESS.handle_write(w.app_id).pending_frames += 1;
1194 Ok(())
1195 } else {
1196 Err(ViewProcessOffline)
1197 }
1198 }
1199
1200 pub fn render_extension_raw(&self, extension_id: ApiExtensionId, request: ApiExtensionPayload) -> Result<ApiExtensionPayload> {
1202 if let Some(w) = self.0.upgrade() {
1203 w.call(|id, p| p.render_extension(id, extension_id, request))
1204 } else {
1205 Err(ViewProcessOffline)
1206 }
1207 }
1208
1209 pub fn render_extension<I, O>(&self, extension_id: ApiExtensionId, request: &I) -> Result<std::result::Result<O, ApiExtensionRecvError>>
1211 where
1212 I: serde::Serialize,
1213 O: serde::de::DeserializeOwned,
1214 {
1215 let r = self.render_extension_raw(extension_id, ApiExtensionPayload::serialize(&request).unwrap())?;
1216 Ok(r.deserialize())
1217 }
1218}
1219
1220#[must_use = "the image is disposed when all clones of the handle are dropped"]
1224#[derive(Clone)]
1225pub struct ViewImage(Arc<RwLock<ViewImageData>>);
1226impl PartialEq for ViewImage {
1227 fn eq(&self, other: &Self) -> bool {
1228 Arc::ptr_eq(&self.0, &other.0)
1229 }
1230}
1231impl Eq for ViewImage {}
1232impl std::hash::Hash for ViewImage {
1233 fn hash<H: std::hash::Hasher>(&self, state: &mut H) {
1234 let ptr = Arc::as_ptr(&self.0) as usize;
1235 ptr.hash(state)
1236 }
1237}
1238impl fmt::Debug for ViewImage {
1239 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
1240 f.debug_struct("ViewImage")
1241 .field("loaded", &self.is_loaded())
1242 .field("error", &self.error())
1243 .field("size", &self.size())
1244 .field("dpi", &self.ppi())
1245 .field("is_opaque", &self.is_opaque())
1246 .field("is_mask", &self.is_mask())
1247 .field("generation", &self.generation())
1248 .finish_non_exhaustive()
1249 }
1250}
1251
1252struct ViewImageData {
1253 app_id: Option<AppId>,
1254 id: Option<ImageId>,
1255 generation: ViewProcessGen,
1256
1257 size: PxSize,
1258 partial_size: PxSize,
1259 ppi: Option<ImagePpi>,
1260 is_opaque: bool,
1261
1262 partial_pixels: Option<IpcBytes>,
1263 pixels: Option<std::result::Result<IpcBytes, Txt>>,
1264 is_mask: bool,
1265
1266 done_signal: SignalOnce,
1267}
1268impl Drop for ViewImageData {
1269 fn drop(&mut self) {
1270 if let Some(id) = self.id {
1271 let app_id = self.app_id.unwrap();
1272 if let Some(app) = APP.id() {
1273 if app_id != app {
1274 tracing::error!("image from app `{:?}` dropped in app `{:?}`", app_id, app);
1275 }
1276
1277 if VIEW_PROCESS.is_available() && VIEW_PROCESS.generation() == self.generation {
1278 let _ = VIEW_PROCESS.write().process.forget_image(id);
1279 }
1280 }
1281 }
1282 }
1283}
1284
1285impl ViewImage {
1286 pub fn id(&self) -> Option<ImageId> {
1288 self.0.read().id
1289 }
1290
1291 pub fn is_dummy(&self) -> bool {
1293 self.0.read().id.is_none()
1294 }
1295
1296 pub fn is_loaded(&self) -> bool {
1298 self.0.read().pixels.as_ref().map(|r| r.is_ok()).unwrap_or(false)
1299 }
1300
1301 pub fn is_partially_loaded(&self) -> bool {
1303 self.0.read().partial_pixels.is_some()
1304 }
1305
1306 pub fn is_error(&self) -> bool {
1310 self.0.read().pixels.as_ref().map(|r| r.is_err()).unwrap_or(false)
1311 }
1312
1313 pub fn error(&self) -> Option<Txt> {
1315 self.0.read().pixels.as_ref().and_then(|s| s.as_ref().err().cloned())
1316 }
1317
1318 pub fn size(&self) -> PxSize {
1320 self.0.read().size
1321 }
1322
1323 pub fn partial_size(&self) -> PxSize {
1329 self.0.read().partial_size
1330 }
1331
1332 pub fn ppi(&self) -> Option<ImagePpi> {
1335 self.0.read().ppi
1336 }
1337
1338 pub fn is_opaque(&self) -> bool {
1340 self.0.read().is_opaque
1341 }
1342
1343 pub fn is_mask(&self) -> bool {
1345 self.0.read().is_mask
1346 }
1347
1348 pub fn partial_pixels(&self) -> Option<Vec<u8>> {
1355 self.0.read().partial_pixels.as_ref().map(|r| r[..].to_vec())
1356 }
1357
1358 pub fn pixels(&self) -> Option<IpcBytes> {
1369 self.0.read().pixels.as_ref().and_then(|r| r.as_ref().ok()).cloned()
1370 }
1371
1372 pub fn app_id(&self) -> Option<AppId> {
1374 self.0.read().app_id
1375 }
1376
1377 pub fn generation(&self) -> ViewProcessGen {
1379 self.0.read().generation
1380 }
1381
1382 pub fn downgrade(&self) -> WeakViewImage {
1384 WeakViewImage(Arc::downgrade(&self.0))
1385 }
1386
1387 pub fn dummy(error: Option<Txt>) -> Self {
1389 ViewImage(Arc::new(RwLock::new(ViewImageData {
1390 app_id: None,
1391 id: None,
1392 generation: ViewProcessGen::INVALID,
1393 size: PxSize::zero(),
1394 partial_size: PxSize::zero(),
1395 ppi: None,
1396 is_opaque: true,
1397 partial_pixels: None,
1398 pixels: if let Some(e) = error {
1399 Some(Err(e))
1400 } else {
1401 Some(Ok(IpcBytes::from_slice(&[])))
1402 },
1403 is_mask: false,
1404 done_signal: SignalOnce::new_set(),
1405 })))
1406 }
1407
1408 pub fn awaiter(&self) -> SignalOnce {
1410 self.0.read().done_signal.clone()
1411 }
1412
1413 pub async fn encode(&self, format: Txt) -> std::result::Result<IpcBytes, EncodeError> {
1419 self.awaiter().await;
1420
1421 if let Some(e) = self.error() {
1422 return Err(EncodeError::Encode(e));
1423 }
1424
1425 let receiver = {
1426 let img = self.0.read();
1427 if let Some(id) = img.id {
1428 let mut app = VIEW_PROCESS.handle_write(img.app_id.unwrap());
1429
1430 app.process.encode_image(id, format.clone())?;
1431
1432 let (sender, receiver) = flume::bounded(1);
1433 if let Some(entry) = app.encoding_images.iter_mut().find(|r| r.image_id == id && r.format == format) {
1434 entry.listeners.push(sender);
1435 } else {
1436 app.encoding_images.push(EncodeRequest {
1437 image_id: id,
1438 format,
1439 listeners: vec![sender],
1440 });
1441 }
1442 receiver
1443 } else {
1444 return Err(EncodeError::Dummy);
1445 }
1446 };
1447
1448 receiver.recv_async().await?
1449 }
1450}
1451
1452#[derive(Debug, Clone, PartialEq, Eq)]
1454pub enum EncodeError {
1455 Encode(Txt),
1457 Dummy,
1462 ViewProcessOffline,
1464}
1465impl From<Txt> for EncodeError {
1466 fn from(e: Txt) -> Self {
1467 EncodeError::Encode(e)
1468 }
1469}
1470impl From<ViewProcessOffline> for EncodeError {
1471 fn from(_: ViewProcessOffline) -> Self {
1472 EncodeError::ViewProcessOffline
1473 }
1474}
1475impl From<flume::RecvError> for EncodeError {
1476 fn from(_: flume::RecvError) -> Self {
1477 EncodeError::ViewProcessOffline
1478 }
1479}
1480impl fmt::Display for EncodeError {
1481 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
1482 match self {
1483 EncodeError::Encode(e) => write!(f, "{e}"),
1484 EncodeError::Dummy => write!(f, "cannot encode dummy image"),
1485 EncodeError::ViewProcessOffline => write!(f, "{ViewProcessOffline}"),
1486 }
1487 }
1488}
1489impl std::error::Error for EncodeError {}
1490
1491#[derive(Clone)]
1497pub struct WeakViewImage(sync::Weak<RwLock<ViewImageData>>);
1498impl WeakViewImage {
1499 pub fn upgrade(&self) -> Option<ViewImage> {
1503 self.0.upgrade().map(ViewImage)
1504 }
1505}
1506
1507struct EncodeRequest {
1508 image_id: ImageId,
1509 format: Txt,
1510 listeners: Vec<flume::Sender<std::result::Result<IpcBytes, EncodeError>>>,
1511}
1512
1513type ClipboardResult<T> = std::result::Result<T, ClipboardError>;
1514
1515pub struct ViewClipboard {}
1517impl ViewClipboard {
1518 pub fn read_text(&self) -> Result<ClipboardResult<Txt>> {
1522 match VIEW_PROCESS.try_write()?.process.read_clipboard(ClipboardType::Text)? {
1523 Ok(ClipboardData::Text(t)) => Ok(Ok(t)),
1524 Err(e) => Ok(Err(e)),
1525 _ => Ok(Err(ClipboardError::Other(Txt::from_static("view-process returned incorrect type")))),
1526 }
1527 }
1528
1529 pub fn write_text(&self, txt: Txt) -> Result<ClipboardResult<()>> {
1533 VIEW_PROCESS.try_write()?.process.write_clipboard(ClipboardData::Text(txt))
1534 }
1535
1536 pub fn read_image(&self) -> Result<ClipboardResult<ViewImage>> {
1540 let mut app = VIEW_PROCESS.try_write()?;
1541 match app.process.read_clipboard(ClipboardType::Image)? {
1542 Ok(ClipboardData::Image(id)) => {
1543 if id == ImageId::INVALID {
1544 Ok(Err(ClipboardError::Other(Txt::from_static("view-process returned invalid image"))))
1545 } else {
1546 let img = ViewImage(Arc::new(RwLock::new(ViewImageData {
1547 id: Some(id),
1548 app_id: APP.id(),
1549 generation: app.process.generation(),
1550 size: PxSize::zero(),
1551 partial_size: PxSize::zero(),
1552 ppi: None,
1553 is_opaque: false,
1554 partial_pixels: None,
1555 pixels: None,
1556 is_mask: false,
1557 done_signal: SignalOnce::new(),
1558 })));
1559 app.loading_images.push(Arc::downgrade(&img.0));
1560 Ok(Ok(img))
1561 }
1562 }
1563 Err(e) => Ok(Err(e)),
1564 _ => Ok(Err(ClipboardError::Other(Txt::from_static("view-process returned incorrect type")))),
1565 }
1566 }
1567
1568 pub fn write_image(&self, img: &ViewImage) -> Result<ClipboardResult<()>> {
1572 if img.is_loaded() {
1573 if let Some(id) = img.id() {
1574 return VIEW_PROCESS.try_write()?.process.write_clipboard(ClipboardData::Image(id));
1575 }
1576 }
1577 Ok(Err(ClipboardError::Other(Txt::from_static("image not loaded"))))
1578 }
1579
1580 pub fn read_file_list(&self) -> Result<ClipboardResult<Vec<PathBuf>>> {
1584 match VIEW_PROCESS.try_write()?.process.read_clipboard(ClipboardType::FileList)? {
1585 Ok(ClipboardData::FileList(f)) => Ok(Ok(f)),
1586 Err(e) => Ok(Err(e)),
1587 _ => Ok(Err(ClipboardError::Other(Txt::from_static("view-process returned incorrect type")))),
1588 }
1589 }
1590
1591 pub fn write_file_list(&self, list: Vec<PathBuf>) -> Result<ClipboardResult<()>> {
1595 VIEW_PROCESS.try_write()?.process.write_clipboard(ClipboardData::FileList(list))
1596 }
1597
1598 pub fn read_extension(&self, data_type: Txt) -> Result<ClipboardResult<IpcBytes>> {
1602 match VIEW_PROCESS
1603 .try_write()?
1604 .process
1605 .read_clipboard(ClipboardType::Extension(data_type.clone()))?
1606 {
1607 Ok(ClipboardData::Extension { data_type: rt, data }) if rt == data_type => Ok(Ok(data)),
1608 Err(e) => Ok(Err(e)),
1609 _ => Ok(Err(ClipboardError::Other(Txt::from_static("view-process returned incorrect type")))),
1610 }
1611 }
1612
1613 pub fn write_extension(&self, data_type: Txt, data: IpcBytes) -> Result<ClipboardResult<()>> {
1617 VIEW_PROCESS
1618 .try_write()?
1619 .process
1620 .write_clipboard(ClipboardData::Extension { data_type, data })
1621 }
1622}