zng_app/
view_process.rs

1//! View process connection and other types.
2
3use 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/// Connection to the running view-process for the context app.
52#[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    /// If the `VIEW_PROCESS` can be used, this is only true in app threads for apps with render, all other
77    /// methods will panic if called when this is not true.
78    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    /// View-process connected and ready.
113    pub fn is_online(&self) -> bool {
114        self.read().process.online()
115    }
116
117    /// If is running in headless renderer mode.
118    pub fn is_headless_with_render(&self) -> bool {
119        self.read().process.headless()
120    }
121
122    /// If is running both view and app in the same process.
123    pub fn is_same_process(&self) -> bool {
124        self.read().process.same_process()
125    }
126
127    /// Gets the current view-process generation.
128    pub fn generation(&self) -> ViewProcessGen {
129        self.read().process.generation()
130    }
131
132    /// Sends a request to open a window and associate it with the `window_id`.
133    ///
134    /// A [`RAW_WINDOW_OPEN_EVENT`] or [`RAW_WINDOW_OR_HEADLESS_OPEN_ERROR_EVENT`] will be received in response to this request.
135    ///
136    /// [`RAW_WINDOW_OPEN_EVENT`]: crate::view_process::raw_events::RAW_WINDOW_OPEN_EVENT
137    /// [`RAW_WINDOW_OR_HEADLESS_OPEN_ERROR_EVENT`]: crate::view_process::raw_events::RAW_WINDOW_OR_HEADLESS_OPEN_ERROR_EVENT
138    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    /// Sends a request to open a headless renderer and associate it with the `window_id`.
144    ///
145    /// Note that no actual window is created, only the renderer, the use of window-ids to identify
146    /// this renderer is only for convenience.
147    ///
148    /// A [`RAW_HEADLESS_OPEN_EVENT`] or [`RAW_WINDOW_OR_HEADLESS_OPEN_ERROR_EVENT`] will be received in response to this request.
149    ///
150    /// [`RAW_HEADLESS_OPEN_EVENT`]: crate::view_process::raw_events::RAW_HEADLESS_OPEN_EVENT
151    /// [`RAW_WINDOW_OR_HEADLESS_OPEN_ERROR_EVENT`]: crate::view_process::raw_events::RAW_WINDOW_OR_HEADLESS_OPEN_ERROR_EVENT
152    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    /// Send an image for decoding.
158    ///
159    /// This function returns immediately, the [`ViewImage`] will update when
160    /// [`Event::ImageMetadataLoaded`], [`Event::ImageLoaded`] and [`Event::ImageLoadError`] events are received.
161    ///
162    /// [`Event::ImageMetadataLoaded`]: zng_view_api::Event::ImageMetadataLoaded
163    /// [`Event::ImageLoaded`]: zng_view_api::Event::ImageLoaded
164    /// [`Event::ImageLoadError`]: zng_view_api::Event::ImageLoadError
165    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    /// Starts sending an image for *progressive* decoding.
186    ///
187    /// This function returns immediately, the [`ViewImage`] will update when
188    /// [`Event::ImageMetadataLoaded`], [`Event::ImagePartiallyLoaded`],
189    /// [`Event::ImageLoaded`] and [`Event::ImageLoadError`] events are received.
190    ///
191    /// [`Event::ImageMetadataLoaded`]: zng_view_api::Event::ImageMetadataLoaded
192    /// [`Event::ImageLoaded`]: zng_view_api::Event::ImageLoaded
193    /// [`Event::ImageLoadError`]: zng_view_api::Event::ImageLoadError
194    /// [`Event::ImagePartiallyLoaded`]: zng_view_api::Event::ImagePartiallyLoaded
195    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    /// View-process clipboard methods.
216    pub fn clipboard(&self) -> Result<&ViewClipboard> {
217        if VIEW_PROCESS.is_online() {
218            Ok(&ViewClipboard {})
219        } else {
220            Err(ViewProcessOffline)
221        }
222    }
223
224    /// Returns a list of image decoders supported by the view-process backend.
225    ///
226    /// Each string is the lower-case file extension.
227    pub fn image_decoders(&self) -> Result<Vec<Txt>> {
228        self.write().process.image_decoders()
229    }
230
231    /// Returns a list of image encoders supported by the view-process backend.
232    ///
233    /// Each string is the lower-case file extension.
234    pub fn image_encoders(&self) -> Result<Vec<Txt>> {
235        self.write().process.image_encoders()
236    }
237
238    /// Number of frame send that have not finished rendering.
239    ///
240    /// This is the sum of pending frames for all renderers.
241    pub fn pending_frames(&self) -> usize {
242        self.write().pending_frames
243    }
244
245    /// Reopen the view-process, causing another [`Event::Inited`].
246    ///
247    /// [`Event::Inited`]: zng_view_api::Event::Inited
248    pub fn respawn(&self) {
249        self.write().process.respawn()
250    }
251
252    /// Gets the ID for the `extension_name` in the current view-process.
253    ///
254    /// The ID can change for every view-process instance, you must subscribe to the
255    /// [`VIEW_PROCESS_INITED_EVENT`] to refresh the ID. The view-process can respawn
256    /// at any time in case of error.
257    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    /// Licenses that may be required to be displayed in the app about screen.
267    ///
268    /// This is specially important for prebuilt view users, as the tools that scrap licenses
269    /// may not find the prebuilt dependencies.
270    pub fn third_party_licenses(&self) -> Result<Vec<crate::third_party::LicenseUsed>> {
271        self.write().process.third_party_licenses()
272    }
273
274    /// Call an extension with custom encoded payload.
275    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    /// Call an extension with payload `request`.
280    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    /// Handle an [`Event::Disconnected`].
291    ///
292    /// The process will exit if the view-process was killed by the user.
293    ///
294    /// [`Event::Disconnected`]: zng_view_api::Event::Disconnected
295    pub fn handle_disconnect(&self, vp_gen: ViewProcessGen) {
296        self.write().process.handle_disconnect(vp_gen)
297    }
298
299    /// Spawn the View Process.
300    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    /// Translate `DevId` to `DeviceId`, generates a device id if it was unknown.
344    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    /// Translate `MonId` to `MonitorId`, generates a monitor id if it was unknown.
349    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    /// Handle an [`Event::Inited`].
354    ///
355    /// The view-process becomes online only after this call.
356    ///
357    /// [`Event::Inited`]: zng_view_api::Event::Inited
358    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        // cleanup
389        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        // cleanup
493        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    /// Arguments for the [`VIEW_PROCESS_INITED_EVENT`].
563    pub struct ViewProcessInitedArgs {
564        /// View-process generation.
565        pub generation: ViewProcessGen,
566
567        /// If this is not the first time a view-process was inited. If `true`
568        /// all resources created in a previous generation must be rebuilt.
569        ///
570        /// This can happen after a view-process crash or app suspension.
571        pub is_respawn: bool,
572
573        /// Monitors list.
574        pub available_monitors: Vec<(MonitorId, MonitorInfo)>,
575
576        /// System multi-click config.
577        pub multi_click_config: MultiClickConfig,
578
579        /// System keyboard pressed repeat config.
580        pub key_repeat_config: KeyRepeatConfig,
581
582        /// System touch config.
583        pub touch_config: TouchConfig,
584
585        /// System font font-aliasing config.
586        pub font_aa: FontAntiAliasing,
587
588        /// System animations config.
589        pub animations_config: AnimationsConfig,
590
591        /// System locale config.
592        pub locale_config: LocaleConfig,
593
594        /// System preferred color scheme and accent color.
595        pub colors_config: ColorsConfig,
596
597        /// System window chrome preferences.
598        pub chrome_config: ChromeConfig,
599
600        /// API extensions implemented by the view-process.
601        ///
602        /// The extension IDs will stay valid for the duration of the view-process.
603        pub extensions: ApiExtensions,
604
605        ..
606
607        /// Broadcast to all widgets.
608        fn delivery_list(&self, list: &mut UpdateDeliveryList) {
609            list.search_all()
610        }
611    }
612
613    /// Arguments for the [`VIEW_PROCESS_SUSPENDED_EVENT`].
614    pub struct ViewProcessSuspendedArgs {
615
616        ..
617
618        /// Broadcast to all widgets.
619        fn delivery_list(&self, list: &mut UpdateDeliveryList) {
620            list.search_all()
621        }
622    }
623}
624
625event! {
626    /// View-Process finished initializing and is now online.
627    pub static VIEW_PROCESS_INITED_EVENT: ViewProcessInitedArgs;
628    /// View-Process suspended, all resources dropped.
629    ///
630    /// The view-process will only be available if the app resumes. On resume [`VIEW_PROCESS_INITED_EVENT`]
631    /// notify a view-process respawn.
632    pub static VIEW_PROCESS_SUSPENDED_EVENT: ViewProcessSuspendedArgs;
633}
634
635/// Information about a successfully opened window.
636#[derive(Debug, Clone)]
637pub struct WindowOpenData {
638    /// Window complete state.
639    pub state: WindowStateAll,
640
641    /// Monitor that contains the window.
642    pub monitor: Option<MonitorId>,
643
644    /// Final top-left offset of the window (excluding outer chrome).
645    ///
646    /// The values are the global position and the position in the monitor.
647    pub position: (PxPoint, DipPoint),
648    /// Final dimensions of the client area of the window (excluding outer chrome).
649    pub size: DipSize,
650
651    /// Final scale factor.
652    pub scale_factor: Factor,
653
654    /// Actual render mode, can be different from the requested mode if it is not available.
655    pub render_mode: RenderMode,
656
657    /// Padding that must be applied to the window content so that it stays clear of screen obstructions
658    /// such as a camera notch cutout.
659    ///
660    /// Note that the *unsafe* area must still be rendered as it may be partially visible, just don't place nay
661    /// interactive or important content outside of this padding.
662    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/// Handle to a window open in the view-process.
679///
680/// The window is closed when all clones of the handle are dropped.
681#[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    /// Returns the view-process generation on which the window was open.
693    pub fn generation(&self) -> ViewProcessGen {
694        self.0.generation
695    }
696
697    /// Set the window title.
698    pub fn set_title(&self, title: Txt) -> Result<()> {
699        self.0.call(|id, p| p.set_title(id, title))
700    }
701
702    /// Set the window visibility.
703    pub fn set_visible(&self, visible: bool) -> Result<()> {
704        self.0.call(|id, p| p.set_visible(id, visible))
705    }
706
707    /// Set if the window is "top-most".
708    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    /// Set if the user can drag-move the window.
713    pub fn set_movable(&self, movable: bool) -> Result<()> {
714        self.0.call(|id, p| p.set_movable(id, movable))
715    }
716
717    /// Set if the user can resize the window.
718    pub fn set_resizable(&self, resizable: bool) -> Result<()> {
719        self.0.call(|id, p| p.set_resizable(id, resizable))
720    }
721
722    /// Set the window icon.
723    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    /// Set the window cursor icon and visibility.
739    pub fn set_cursor(&self, cursor: Option<CursorIcon>) -> Result<()> {
740        self.0.call(|id, p| p.set_cursor(id, cursor))
741    }
742
743    /// Set the window cursor to a custom image.
744    ///
745    /// Falls back to cursor icon if set to `None`.
746    ///
747    /// The `hotspot` value is an exact point in the image that is the mouse position. This value is only used if
748    /// the image format does not contain a hotspot.
749    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    /// Set the window icon visibility in the taskbar.
765    pub fn set_taskbar_visible(&self, visible: bool) -> Result<()> {
766        self.0.call(|id, p| p.set_taskbar_visible(id, visible))
767    }
768
769    /// Bring the window the z top.
770    pub fn bring_to_top(&self) -> Result<()> {
771        self.0.call(|id, p| p.bring_to_top(id))
772    }
773
774    /// Set the window state.
775    pub fn set_state(&self, state: WindowStateAll) -> Result<()> {
776        self.0.call(|id, p| p.set_state(id, state))
777    }
778
779    /// Set video mode used in exclusive fullscreen.
780    pub fn set_video_mode(&self, mode: VideoMode) -> Result<()> {
781        self.0.call(|id, p| p.set_video_mode(id, mode))
782    }
783
784    /// Set enabled window chrome buttons.
785    pub fn set_enabled_buttons(&self, buttons: WindowButton) -> Result<()> {
786        self.0.call(|id, p| p.set_enabled_buttons(id, buttons))
787    }
788
789    /// Reference the window renderer.
790    pub fn renderer(&self) -> ViewRenderer {
791        ViewRenderer(Arc::downgrade(&self.0))
792    }
793
794    /// Sets if the headed window is in *capture-mode*. If `true` the resources used to capture
795    /// a screenshot may be kept in memory to be reused in the next screenshot capture.
796    ///
797    /// Note that capture must still be requested in each frame request.
798    pub fn set_capture_mode(&self, enabled: bool) -> Result<()> {
799        self.0.call(|id, p| p.set_capture_mode(id, enabled))
800    }
801
802    /// Brings the window to the front and sets input focus.
803    ///
804    /// This request can steal focus from other apps disrupting the user, be careful with it.
805    pub fn focus(&self) -> Result<FocusResult> {
806        self.0.call(|id, p| p.focus(id))
807    }
808
809    /// Sets the user attention request indicator, the indicator is cleared when the window is focused or
810    /// if canceled by setting to `None`.
811    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    /// Moves the window with the left mouse button until the button is released.
816    ///
817    /// There's no guarantee that this will work unless the left mouse button was pressed immediately before this function is called.
818    pub fn drag_move(&self) -> Result<()> {
819        self.0.call(|id, p| p.drag_move(id))
820    }
821
822    /// Resizes the window with the left mouse button until the button is released.
823    ///
824    /// There's no guarantee that this will work unless the left mouse button was pressed immediately before this function is called.
825    pub fn drag_resize(&self, direction: ResizeDirection) -> Result<()> {
826        self.0.call(|id, p| p.drag_resize(id, direction))
827    }
828
829    /// Start a drag and drop operation, if the window is pressed.
830    ///
831    /// A [`RAW_APP_DRAG_ENDED_EVENT`] will be received when the operation finishes.
832    ///
833    /// [`RAW_APP_DRAG_ENDED_EVENT`]: raw_events::RAW_APP_DRAG_ENDED_EVENT
834    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    /// Notify the drag source of what effect was applied for a received drag&drop.
843    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    /// Open system title bar context menu.
848    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    /// Shows a native message dialog for the window.
853    ///
854    /// The window is not interactive while the dialog is visible and the dialog may be modal in the view-process.
855    /// In the app-process this is always async, and the response var will update once when the user responds.
856    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    /// Shows a native file/folder dialog for the window.
863    ///
864    /// The window is not interactive while the dialog is visible and the dialog may be modal in the view-process.
865    /// In the app-process this is always async, and the response var will update once when the user responds.
866    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    /// Update the window's accessibility info tree.
873    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    /// Enable or disable IME by setting a cursor area.
878    ///
879    /// In mobile platforms also shows the software keyboard for `Some(_)` and hides it for `None`.
880    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    /// Attempt to set a system wide shutdown warning associated with the window.
885    ///
886    /// Operating systems that support this show the `reason` in a warning for the user, it must be a short text
887    /// that identifies the critical operation that cannot be cancelled.
888    ///
889    /// Note that there is no guarantee that the view-process or operating system will actually set a block, there
890    /// is no error result because operating systems can silently ignore block requests at any moment, even after
891    /// an initial successful block.
892    ///
893    /// Set to an empty text to remove the warning.
894    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    /// Drop `self`.
899    pub fn close(self) {
900        drop(self)
901    }
902
903    /// Call a window extension with custom encoded payload.
904    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    /// Call a window extension with serialized payload.
909    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/// View window or headless surface.
920#[derive(Clone, Debug)]
921pub enum ViewWindowOrHeadless {
922    /// Headed window view.
923    Window(ViewWindow),
924    /// Headless surface view.
925    Headless(ViewHeadless),
926}
927impl ViewWindowOrHeadless {
928    /// Reference the window or surface renderer.
929    pub fn renderer(&self) -> ViewRenderer {
930        match self {
931            ViewWindowOrHeadless::Window(w) => w.renderer(),
932            ViewWindowOrHeadless::Headless(h) => h.renderer(),
933        }
934    }
935
936    /// Call a window extension with custom encoded payload.
937    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    /// Call a window extension with serialized payload.
945    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/// Handle to a headless surface/document open in the View Process.
996///
997/// The view is disposed when all clones of the handle are dropped.
998#[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    /// Resize the headless surface.
1009    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    /// Reference the window renderer.
1014    pub fn renderer(&self) -> ViewRenderer {
1015        ViewRenderer(Arc::downgrade(&self.0))
1016    }
1017
1018    /// Call a window extension with custom encoded payload.
1019    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    /// Call a window extension with serialized payload.
1024    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/// Weak handle to a window or view.
1035///
1036/// This is only a weak reference, every method returns [`ViewProcessOffline`] if the
1037/// window is closed or view is disposed.
1038///
1039/// [`ViewProcessOffline`]: zng_view_api::ViewProcessOffline
1040#[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    /// Returns the view-process generation on which the renderer was created.
1063    pub fn generation(&self) -> Result<ViewProcessGen> {
1064        self.0.upgrade().map(|c| c.generation).ok_or(ViewProcessOffline)
1065    }
1066
1067    /// Use an image resource in the window renderer.
1068    ///
1069    /// Returns the image texture ID.
1070    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    /// Replace the image resource in the window renderer.
1082    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    /// Delete the image resource in the window renderer.
1094    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    /// Add a raw font resource to the window renderer.
1099    ///
1100    /// Returns the new font face ID, unique for this renderer.
1101    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    /// Delete the font resource in the window renderer.
1106    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    /// Add a sized font to the window renderer.
1111    ///
1112    /// Returns the new font ID, unique for this renderer.
1113    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    /// Delete the sized font.
1124    pub fn delete_font(&self, font_id: FontId) -> Result<()> {
1125        self.call(|id, p| p.delete_font(id, font_id))
1126    }
1127
1128    /// Create a new image resource from the current rendered frame.
1129    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    /// Create a new image resource from a selection of the current rendered frame.
1139    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    /// Render a new frame.
1175    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    /// Update the current frame and re-render it.
1188    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    /// Call a render extension with custom encoded payload.
1201    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    /// Call a render extension with serialized payload.
1210    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/// Handle to an image loading or loaded in the View Process.
1221///
1222/// The image is disposed when all clones of the handle are dropped.
1223#[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    /// Image id.
1287    pub fn id(&self) -> Option<ImageId> {
1288        self.0.read().id
1289    }
1290
1291    /// If the image does not actually exists in the view-process.
1292    pub fn is_dummy(&self) -> bool {
1293        self.0.read().id.is_none()
1294    }
1295
1296    /// Returns `true` if the image has successfully decoded.
1297    pub fn is_loaded(&self) -> bool {
1298        self.0.read().pixels.as_ref().map(|r| r.is_ok()).unwrap_or(false)
1299    }
1300
1301    /// Returns `true` if the image is progressively decoding and has partially decoded.
1302    pub fn is_partially_loaded(&self) -> bool {
1303        self.0.read().partial_pixels.is_some()
1304    }
1305
1306    /// if [`error`] is `Some`.
1307    ///
1308    /// [`error`]: Self::error
1309    pub fn is_error(&self) -> bool {
1310        self.0.read().pixels.as_ref().map(|r| r.is_err()).unwrap_or(false)
1311    }
1312
1313    /// Returns the load error if one happened.
1314    pub fn error(&self) -> Option<Txt> {
1315        self.0.read().pixels.as_ref().and_then(|s| s.as_ref().err().cloned())
1316    }
1317
1318    /// Returns the pixel size, or zero if is not loaded or error.
1319    pub fn size(&self) -> PxSize {
1320        self.0.read().size
1321    }
1322
1323    /// Actual size of the current pixels.
1324    ///
1325    /// Can be different from [`size`] if the image is progressively decoding.
1326    ///
1327    /// [`size`]: Self::size
1328    pub fn partial_size(&self) -> PxSize {
1329        self.0.read().partial_size
1330    }
1331
1332    /// Returns the "pixels-per-inch" metadata associated with the image, or `None` if not loaded or error or no
1333    /// metadata provided by decoder.
1334    pub fn ppi(&self) -> Option<ImagePpi> {
1335        self.0.read().ppi
1336    }
1337
1338    /// Returns if the image is fully opaque.
1339    pub fn is_opaque(&self) -> bool {
1340        self.0.read().is_opaque
1341    }
1342
1343    /// Returns if the image is a single channel mask (A8).
1344    pub fn is_mask(&self) -> bool {
1345        self.0.read().is_mask
1346    }
1347
1348    /// Copy the partially decoded pixels if the image is progressively decoding
1349    /// and has not finished decoding.
1350    ///
1351    /// Format is BGRA8 for normal images or A8 if [`is_mask`].
1352    ///
1353    /// [`is_mask`]: Self::is_mask
1354    pub fn partial_pixels(&self) -> Option<Vec<u8>> {
1355        self.0.read().partial_pixels.as_ref().map(|r| r[..].to_vec())
1356    }
1357
1358    /// Reference the decoded pixels of image.
1359    ///
1360    /// Returns `None` until the image is fully loaded. Use [`partial_pixels`] to copy
1361    /// partially decoded bytes.
1362    ///
1363    /// Format is pre-multiplied BGRA8 for normal images or A8 if [`is_mask`].
1364    ///
1365    /// [`is_mask`]: Self::is_mask
1366    ///
1367    /// [`partial_pixels`]: Self::partial_pixels
1368    pub fn pixels(&self) -> Option<IpcBytes> {
1369        self.0.read().pixels.as_ref().and_then(|r| r.as_ref().ok()).cloned()
1370    }
1371
1372    /// Returns the app that owns the view-process that is handling this image.
1373    pub fn app_id(&self) -> Option<AppId> {
1374        self.0.read().app_id
1375    }
1376
1377    /// Returns the view-process generation on which the image is loaded.
1378    pub fn generation(&self) -> ViewProcessGen {
1379        self.0.read().generation
1380    }
1381
1382    /// Creates a [`WeakViewImage`].
1383    pub fn downgrade(&self) -> WeakViewImage {
1384        WeakViewImage(Arc::downgrade(&self.0))
1385    }
1386
1387    /// Create a dummy image in the loaded or error state.
1388    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    /// Returns a future that awaits until this image is loaded or encountered an error.
1409    pub fn awaiter(&self) -> SignalOnce {
1410        self.0.read().done_signal.clone()
1411    }
1412
1413    /// Tries to encode the image to the format.
1414    ///
1415    /// The `format` must be one of the [`image_encoders`] supported by the view-process backend.
1416    ///
1417    /// [`image_encoders`]: VIEW_PROCESS::image_encoders
1418    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/// Error returned by [`ViewImage::encode`].
1453#[derive(Debug, Clone, PartialEq, Eq)]
1454pub enum EncodeError {
1455    /// Encode error.
1456    Encode(Txt),
1457    /// Attempted to encode dummy image.
1458    ///
1459    /// In a headless-app without renderer all images are dummy because there is no
1460    /// view-process backend running.
1461    Dummy,
1462    /// The View-Process disconnected or has not finished initializing yet, try again after [`VIEW_PROCESS_INITED_EVENT`].
1463    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/// Connection to an image loading or loaded in the View Process.
1492///
1493/// The image is removed from the View Process cache when all clones of [`ViewImage`] drops, but
1494/// if there is another image pointer holding the image, this weak pointer can be upgraded back
1495/// to a strong connection to the image.
1496#[derive(Clone)]
1497pub struct WeakViewImage(sync::Weak<RwLock<ViewImageData>>);
1498impl WeakViewImage {
1499    /// Attempt to upgrade the weak pointer to the image to a full image.
1500    ///
1501    /// Returns `Some` if the is at least another [`ViewImage`] holding the image alive.
1502    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
1515/// View-process clipboard methods.
1516pub struct ViewClipboard {}
1517impl ViewClipboard {
1518    /// Read [`ClipboardType::Text`].
1519    ///
1520    /// [`ClipboardType::Text`]: zng_view_api::clipboard::ClipboardType::Text
1521    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    /// Write [`ClipboardType::Text`].
1530    ///
1531    /// [`ClipboardType::Text`]: zng_view_api::clipboard::ClipboardType::Text
1532    pub fn write_text(&self, txt: Txt) -> Result<ClipboardResult<()>> {
1533        VIEW_PROCESS.try_write()?.process.write_clipboard(ClipboardData::Text(txt))
1534    }
1535
1536    /// Read [`ClipboardType::Image`].
1537    ///
1538    /// [`ClipboardType::Image`]: zng_view_api::clipboard::ClipboardType::Image
1539    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    /// Write [`ClipboardType::Image`].
1569    ///
1570    /// [`ClipboardType::Image`]: zng_view_api::clipboard::ClipboardType::Image
1571    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    /// Read [`ClipboardType::FileList`].
1581    ///
1582    /// [`ClipboardType::FileList`]: zng_view_api::clipboard::ClipboardType::FileList
1583    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    /// Write [`ClipboardType::FileList`].
1592    ///
1593    /// [`ClipboardType::FileList`]: zng_view_api::clipboard::ClipboardType::FileList
1594    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    /// Read [`ClipboardType::Extension`].
1599    ///
1600    /// [`ClipboardType::Extension`]: zng_view_api::clipboard::ClipboardType::Extension
1601    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    /// Write [`ClipboardType::Extension`].
1614    ///
1615    /// [`ClipboardType::Extension`]: zng_view_api::clipboard::ClipboardType::Extension
1616    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}