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::PxDensity2d;
21use zng_layout::unit::{DipPoint, DipRect, DipSideOffsets, DipSize, Factor, Px, PxPoint, PxRect, PxSize};
22use zng_task::SignalOnce;
23use zng_txt::Txt;
24use zng_var::ResponderVar;
25use zng_view_api::{
26    self, DeviceEventsFilter, DragDropId, Event, FocusResult, ViewProcessGen,
27    api_extension::{ApiExtensionId, ApiExtensionName, ApiExtensionPayload, ApiExtensionRecvError, ApiExtensions},
28    dialog::{FileDialog, FileDialogResponse, MsgDialog, MsgDialogResponse},
29    drag_drop::{DragDropData, DragDropEffect, DragDropError},
30    font::{FontOptions, IpcFontBytes},
31    image::{ImageMaskMode, ImageRequest, ImageTextureId},
32    ipc::{IpcBytes, IpcBytesReceiver, ViewChannelError},
33    window::{
34        CursorIcon, FocusIndicator, FrameRequest, FrameUpdateRequest, HeadlessOpenData, HeadlessRequest, RenderMode, ResizeDirection,
35        VideoMode, WindowButton, WindowRequest, WindowStateAll,
36    },
37};
38
39pub(crate) use zng_view_api::{
40    Controller, raw_input::InputDeviceId as ApiDeviceId, window::MonitorId as ApiMonitorId, window::WindowId as ApiWindowId,
41};
42use zng_view_api::{
43    clipboard::{ClipboardData, ClipboardError, ClipboardType},
44    font::{FontFaceId, FontId, FontVariationName},
45    image::{ImageId, ImageLoadedData},
46};
47
48use self::raw_device_events::InputDeviceId;
49
50use super::{APP, AppId};
51
52/// Connection to the running view-process for the context app.
53#[expect(non_camel_case_types)]
54pub struct VIEW_PROCESS;
55struct ViewProcessService {
56    process: zng_view_api::Controller,
57    input_device_ids: HashMap<ApiDeviceId, InputDeviceId>,
58    monitor_ids: HashMap<ApiMonitorId, MonitorId>,
59
60    data_generation: ViewProcessGen,
61
62    extensions: ApiExtensions,
63
64    loading_images: Vec<sync::Weak<RwLock<ViewImageData>>>,
65    frame_images: Vec<sync::Weak<RwLock<ViewImageData>>>,
66    encoding_images: Vec<EncodeRequest>,
67
68    pending_frames: usize,
69
70    message_dialogs: Vec<(zng_view_api::dialog::DialogId, ResponderVar<MsgDialogResponse>)>,
71    file_dialogs: Vec<(zng_view_api::dialog::DialogId, ResponderVar<FileDialogResponse>)>,
72
73    ping_count: u16,
74}
75app_local! {
76    static VIEW_PROCESS_SV: Option<ViewProcessService> = None;
77}
78impl VIEW_PROCESS {
79    /// If the `VIEW_PROCESS` can be used, this is only true in app threads for apps with render, all other
80    /// methods will panic if called when this is not true.
81    pub fn is_available(&self) -> bool {
82        APP.is_running() && VIEW_PROCESS_SV.read().is_some()
83    }
84
85    fn read(&self) -> MappedRwLockReadGuard<'_, ViewProcessService> {
86        VIEW_PROCESS_SV.read_map(|e| e.as_ref().expect("VIEW_PROCESS not available"))
87    }
88
89    fn write(&self) -> MappedRwLockWriteGuard<'_, ViewProcessService> {
90        VIEW_PROCESS_SV.write_map(|e| e.as_mut().expect("VIEW_PROCESS not available"))
91    }
92
93    fn try_write(&self) -> Result<MappedRwLockWriteGuard<'_, ViewProcessService>> {
94        let vp = VIEW_PROCESS_SV.write();
95        if let Some(w) = &*vp
96            && w.process.is_connected()
97        {
98            return Ok(MappedRwLockWriteGuard::map(vp, |w| w.as_mut().unwrap()));
99        }
100        Err(ViewChannelError::Disconnected)
101    }
102
103    fn check_app(&self, id: AppId) {
104        let actual = APP.id();
105        if Some(id) != actual {
106            panic!("cannot use view handle from app `{id:?}` in app `{actual:?}`");
107        }
108    }
109
110    fn handle_write(&self, id: AppId) -> MappedRwLockWriteGuard<'_, ViewProcessService> {
111        self.check_app(id);
112        self.write()
113    }
114
115    /// View-process running, connected and ready.
116    pub fn is_connected(&self) -> bool {
117        self.read().process.is_connected()
118    }
119
120    /// If is running in headless renderer mode.
121    pub fn is_headless_with_render(&self) -> bool {
122        self.read().process.headless()
123    }
124
125    /// If is running both view and app in the same process.
126    pub fn is_same_process(&self) -> bool {
127        self.read().process.same_process()
128    }
129
130    /// Gets the current view-process generation.
131    pub fn generation(&self) -> ViewProcessGen {
132        self.read().process.generation()
133    }
134
135    /// Enable/disable global device events.
136    ///
137    /// This filter affects device events not targeted at windows, such as mouse move outside windows or
138    /// key presses when the app has no focused window.
139    pub fn set_device_events_filter(&self, filter: DeviceEventsFilter) -> Result<()> {
140        self.write().process.set_device_events_filter(filter)
141    }
142
143    /// Sends a request to open a window and associate it with the `window_id`.
144    ///
145    /// A [`RAW_WINDOW_OPEN_EVENT`] or [`RAW_WINDOW_OR_HEADLESS_OPEN_ERROR_EVENT`] will be received in response to this request.
146    ///
147    /// [`RAW_WINDOW_OPEN_EVENT`]: crate::view_process::raw_events::RAW_WINDOW_OPEN_EVENT
148    /// [`RAW_WINDOW_OR_HEADLESS_OPEN_ERROR_EVENT`]: crate::view_process::raw_events::RAW_WINDOW_OR_HEADLESS_OPEN_ERROR_EVENT
149    pub fn open_window(&self, config: WindowRequest) -> Result<()> {
150        let _s = tracing::debug_span!("VIEW_PROCESS.open_window").entered();
151        self.write().process.open_window(config)
152    }
153
154    /// Sends a request to open a headless renderer and associate it with the `window_id`.
155    ///
156    /// Note that no actual window is created, only the renderer, the use of window-ids to identify
157    /// this renderer is only for convenience.
158    ///
159    /// A [`RAW_HEADLESS_OPEN_EVENT`] or [`RAW_WINDOW_OR_HEADLESS_OPEN_ERROR_EVENT`] will be received in response to this request.
160    ///
161    /// [`RAW_HEADLESS_OPEN_EVENT`]: crate::view_process::raw_events::RAW_HEADLESS_OPEN_EVENT
162    /// [`RAW_WINDOW_OR_HEADLESS_OPEN_ERROR_EVENT`]: crate::view_process::raw_events::RAW_WINDOW_OR_HEADLESS_OPEN_ERROR_EVENT
163    pub fn open_headless(&self, config: HeadlessRequest) -> Result<()> {
164        let _s = tracing::debug_span!("VIEW_PROCESS.open_headless").entered();
165        self.write().process.open_headless(config)
166    }
167
168    /// Send an image for decoding.
169    ///
170    /// This function returns immediately, the [`ViewImage`] will update when
171    /// [`Event::ImageMetadataLoaded`], [`Event::ImageLoaded`] and [`Event::ImageLoadError`] events are received.
172    ///
173    /// [`Event::ImageMetadataLoaded`]: zng_view_api::Event::ImageMetadataLoaded
174    /// [`Event::ImageLoaded`]: zng_view_api::Event::ImageLoaded
175    /// [`Event::ImageLoadError`]: zng_view_api::Event::ImageLoadError
176    pub fn add_image(&self, request: ImageRequest<IpcBytes>) -> Result<ViewImage> {
177        let mut app = self.write();
178        let id = app.process.add_image(request)?;
179        let img = ViewImage(Arc::new(RwLock::new(ViewImageData {
180            id: Some(id),
181            app_id: APP.id(),
182            generation: app.process.generation(),
183            size: PxSize::zero(),
184            partial_size: PxSize::zero(),
185            density: None,
186            is_opaque: false,
187            partial_pixels: None,
188            pixels: None,
189            is_mask: false,
190            done_signal: SignalOnce::new(),
191        })));
192        app.loading_images.push(Arc::downgrade(&img.0));
193        Ok(img)
194    }
195
196    /// Starts sending an image for *progressive* decoding.
197    ///
198    /// This function returns immediately, the [`ViewImage`] will update when
199    /// [`Event::ImageMetadataLoaded`], [`Event::ImagePartiallyLoaded`],
200    /// [`Event::ImageLoaded`] and [`Event::ImageLoadError`] events are received.
201    ///
202    /// [`Event::ImageMetadataLoaded`]: zng_view_api::Event::ImageMetadataLoaded
203    /// [`Event::ImageLoaded`]: zng_view_api::Event::ImageLoaded
204    /// [`Event::ImageLoadError`]: zng_view_api::Event::ImageLoadError
205    /// [`Event::ImagePartiallyLoaded`]: zng_view_api::Event::ImagePartiallyLoaded
206    pub fn add_image_pro(&self, request: ImageRequest<IpcBytesReceiver>) -> Result<ViewImage> {
207        let mut app = self.write();
208        let id = app.process.add_image_pro(request)?;
209        let img = ViewImage(Arc::new(RwLock::new(ViewImageData {
210            id: Some(id),
211            app_id: APP.id(),
212            generation: app.process.generation(),
213            size: PxSize::zero(),
214            partial_size: PxSize::zero(),
215            density: None,
216            is_opaque: false,
217            partial_pixels: None,
218            pixels: None,
219            is_mask: false,
220            done_signal: SignalOnce::new(),
221        })));
222        app.loading_images.push(Arc::downgrade(&img.0));
223        Ok(img)
224    }
225
226    /// View-process clipboard methods.
227    pub fn clipboard(&self) -> Result<&ViewClipboard> {
228        if VIEW_PROCESS.is_connected() {
229            Ok(&ViewClipboard {})
230        } else {
231            Err(ViewChannelError::Disconnected)
232        }
233    }
234
235    /// Returns a list of image decoders supported by the view-process backend.
236    ///
237    /// Each text is the lower-case file extension, without the dot.
238    pub fn image_decoders(&self) -> Result<Vec<Txt>> {
239        self.write().process.image_decoders()
240    }
241
242    /// Returns a list of image encoders supported by the view-process backend.
243    ///
244    /// Each text is the lower-case file extension, without the dot.
245    pub fn image_encoders(&self) -> Result<Vec<Txt>> {
246        self.write().process.image_encoders()
247    }
248
249    /// Number of frame send that have not finished rendering.
250    ///
251    /// This is the sum of pending frames for all renderers.
252    pub fn pending_frames(&self) -> usize {
253        self.write().pending_frames
254    }
255
256    /// Reopen the view-process, causing another [`Event::Inited`].
257    ///
258    /// [`Event::Inited`]: zng_view_api::Event::Inited
259    pub fn respawn(&self) {
260        self.write().process.respawn()
261    }
262
263    /// Gets the ID for the `extension_name` in the current view-process.
264    ///
265    /// The ID can change for every view-process instance, you must subscribe to the
266    /// [`VIEW_PROCESS_INITED_EVENT`] to refresh the ID. The view-process can respawn
267    /// at any time in case of error.
268    pub fn extension_id(&self, extension_name: impl Into<ApiExtensionName>) -> Result<Option<ApiExtensionId>> {
269        let me = self.read();
270        if me.process.is_connected() {
271            Ok(me.extensions.id(&extension_name.into()))
272        } else {
273            Err(ViewChannelError::Disconnected)
274        }
275    }
276
277    /// Licenses that may be required to be displayed in the app about screen.
278    ///
279    /// This is specially important for prebuilt view users, as the tools that scrap licenses
280    /// may not find the prebuilt dependencies.
281    pub fn third_party_licenses(&self) -> Result<Vec<crate::third_party::LicenseUsed>> {
282        self.write().process.third_party_licenses()
283    }
284
285    /// Call an extension with custom encoded payload.
286    pub fn app_extension_raw(&self, extension_id: ApiExtensionId, extension_request: ApiExtensionPayload) -> Result<ApiExtensionPayload> {
287        self.write().process.app_extension(extension_id, extension_request)
288    }
289
290    /// Call an extension with payload `request`.
291    pub fn app_extension<I, O>(&self, extension_id: ApiExtensionId, request: &I) -> Result<std::result::Result<O, ApiExtensionRecvError>>
292    where
293        I: serde::Serialize,
294        O: serde::de::DeserializeOwned,
295    {
296        let payload = ApiExtensionPayload::serialize(&request).unwrap();
297        let response = self.write().process.app_extension(extension_id, payload)?;
298        Ok(response.deserialize::<O>())
299    }
300
301    /// Handle an [`Event::Disconnected`].
302    ///
303    /// The process will exit if the view-process was killed by the user.
304    ///
305    /// [`Event::Disconnected`]: zng_view_api::Event::Disconnected
306    pub fn handle_disconnect(&self, vp_gen: ViewProcessGen) {
307        self.write().process.handle_disconnect(vp_gen)
308    }
309
310    /// Spawn the View Process.
311    pub(super) fn start<F>(&self, view_process_exe: PathBuf, view_process_env: HashMap<Txt, Txt>, headless: bool, on_event: F)
312    where
313        F: FnMut(Event) + Send + 'static,
314    {
315        let _s = tracing::debug_span!("VIEW_PROCESS.start").entered();
316
317        let process = zng_view_api::Controller::start(view_process_exe, view_process_env, headless, on_event);
318        *VIEW_PROCESS_SV.write() = Some(ViewProcessService {
319            data_generation: process.generation(),
320            process,
321            input_device_ids: HashMap::default(),
322            monitor_ids: HashMap::default(),
323            loading_images: vec![],
324            encoding_images: vec![],
325            frame_images: vec![],
326            pending_frames: 0,
327            message_dialogs: vec![],
328            file_dialogs: vec![],
329            extensions: ApiExtensions::default(),
330            ping_count: 0,
331        });
332    }
333
334    pub(crate) fn on_window_opened(&self, window_id: WindowId, data: zng_view_api::window::WindowOpenData) -> (ViewWindow, WindowOpenData) {
335        let mut app = self.write();
336        let _ = app.check_generation();
337
338        let win = ViewWindow(Arc::new(ViewWindowData {
339            app_id: APP.id().unwrap(),
340            id: ApiWindowId::from_raw(window_id.get()),
341            generation: app.data_generation,
342        }));
343        drop(app);
344
345        let data = WindowOpenData::new(data, |id| self.monitor_id(id));
346
347        (win, data)
348    }
349    /// Translate input device ID, generates a device id if it was unknown.
350    pub(super) fn input_device_id(&self, id: ApiDeviceId) -> InputDeviceId {
351        *self.write().input_device_ids.entry(id).or_insert_with(InputDeviceId::new_unique)
352    }
353
354    /// Translate `MonId` to `MonitorId`, generates a monitor id if it was unknown.
355    pub(super) fn monitor_id(&self, id: ApiMonitorId) -> MonitorId {
356        *self.write().monitor_ids.entry(id).or_insert_with(MonitorId::new_unique)
357    }
358
359    /// Handle an [`Event::Inited`].
360    ///
361    /// The view-process becomes "connected" only after this call.
362    ///
363    /// [`Event::Inited`]: zng_view_api::Event::Inited
364    pub(super) fn handle_inited(&self, vp_gen: ViewProcessGen, extensions: ApiExtensions) {
365        let mut me = self.write();
366        me.extensions = extensions;
367        me.process.handle_inited(vp_gen);
368    }
369
370    pub(super) fn handle_suspended(&self) {
371        self.write().process.handle_suspended();
372    }
373
374    pub(crate) fn on_headless_opened(
375        &self,
376        id: WindowId,
377        data: zng_view_api::window::HeadlessOpenData,
378    ) -> (ViewHeadless, HeadlessOpenData) {
379        let mut app = self.write();
380        let _ = app.check_generation();
381
382        let surf = ViewHeadless(Arc::new(ViewWindowData {
383            app_id: APP.id().unwrap(),
384            id: ApiWindowId::from_raw(id.get()),
385            generation: app.data_generation,
386        }));
387
388        (surf, data)
389    }
390
391    fn loading_image_index(&self, id: ImageId) -> Option<usize> {
392        let mut app = self.write();
393
394        // cleanup
395        app.loading_images.retain(|i| i.strong_count() > 0);
396
397        app.loading_images.iter().position(|i| i.upgrade().unwrap().read().id == Some(id))
398    }
399
400    pub(super) fn on_image_metadata_loaded(
401        &self,
402        id: ImageId,
403        size: PxSize,
404        density: Option<PxDensity2d>,
405        is_mask: bool,
406    ) -> Option<ViewImage> {
407        if let Some(i) = self.loading_image_index(id) {
408            let img = self.read().loading_images[i].upgrade().unwrap();
409            {
410                let mut img = img.write();
411                img.size = size;
412                img.density = density;
413                img.is_mask = is_mask;
414            }
415            Some(ViewImage(img))
416        } else {
417            None
418        }
419    }
420
421    pub(super) fn on_image_partially_loaded(
422        &self,
423        id: ImageId,
424        partial_size: PxSize,
425        density: Option<PxDensity2d>,
426        is_opaque: bool,
427        is_mask: bool,
428        partial_pixels: IpcBytes,
429    ) -> Option<ViewImage> {
430        if let Some(i) = self.loading_image_index(id) {
431            let img = self.read().loading_images[i].upgrade().unwrap();
432            {
433                let mut img = img.write();
434                img.partial_size = partial_size;
435                img.density = density;
436                img.is_opaque = is_opaque;
437                img.partial_pixels = Some(partial_pixels);
438                img.is_mask = is_mask;
439            }
440            Some(ViewImage(img))
441        } else {
442            None
443        }
444    }
445
446    pub(super) fn on_image_loaded(&self, data: ImageLoadedData) -> Option<ViewImage> {
447        if let Some(i) = self.loading_image_index(data.id) {
448            let img = self.write().loading_images.swap_remove(i).upgrade().unwrap();
449            {
450                let mut img = img.write();
451                img.size = data.size;
452                img.partial_size = data.size;
453                img.density = data.density;
454                img.is_opaque = data.is_opaque;
455                img.pixels = Some(Ok(data.pixels));
456                img.partial_pixels = None;
457                img.is_mask = data.is_mask;
458                img.done_signal.set();
459            }
460            Some(ViewImage(img))
461        } else {
462            None
463        }
464    }
465
466    pub(super) fn on_image_error(&self, id: ImageId, error: Txt) -> Option<ViewImage> {
467        if let Some(i) = self.loading_image_index(id) {
468            let img = self.write().loading_images.swap_remove(i).upgrade().unwrap();
469            {
470                let mut img = img.write();
471                img.pixels = Some(Err(error));
472                img.done_signal.set();
473            }
474            Some(ViewImage(img))
475        } else {
476            None
477        }
478    }
479
480    pub(crate) fn on_frame_rendered(&self, _id: WindowId) {
481        let mut vp = self.write();
482        vp.pending_frames = vp.pending_frames.saturating_sub(1);
483    }
484
485    pub(crate) fn on_frame_image(&self, data: ImageLoadedData) -> ViewImage {
486        ViewImage(Arc::new(RwLock::new(ViewImageData {
487            app_id: APP.id(),
488            id: Some(data.id),
489            generation: self.generation(),
490            size: data.size,
491            partial_size: data.size,
492            density: data.density,
493            is_opaque: data.is_opaque,
494            partial_pixels: None,
495            pixels: Some(Ok(data.pixels)),
496            is_mask: data.is_mask,
497            done_signal: SignalOnce::new_set(),
498        })))
499    }
500
501    pub(super) fn on_frame_image_ready(&self, id: ImageId) -> Option<ViewImage> {
502        let mut app = self.write();
503
504        // cleanup
505        app.frame_images.retain(|i| i.strong_count() > 0);
506
507        let i = app.frame_images.iter().position(|i| i.upgrade().unwrap().read().id == Some(id));
508
509        i.map(|i| ViewImage(app.frame_images.swap_remove(i).upgrade().unwrap()))
510    }
511
512    pub(super) fn on_image_encoded(&self, id: ImageId, format: Txt, data: IpcBytes) {
513        self.on_image_encode_result(id, format, Ok(data));
514    }
515    pub(super) fn on_image_encode_error(&self, id: ImageId, format: Txt, error: Txt) {
516        self.on_image_encode_result(id, format, Err(EncodeError::Encode(error)));
517    }
518    fn on_image_encode_result(&self, id: ImageId, format: Txt, result: std::result::Result<IpcBytes, EncodeError>) {
519        let mut app = self.write();
520        app.encoding_images.retain(move |r| {
521            let done = r.image_id == id && r.format == format;
522            if done {
523                for sender in &r.listeners {
524                    let _ = sender.send(result.clone());
525                }
526            }
527            !done
528        })
529    }
530
531    pub(crate) fn on_message_dlg_response(&self, id: zng_view_api::dialog::DialogId, response: MsgDialogResponse) {
532        let mut app = self.write();
533        if let Some(i) = app.message_dialogs.iter().position(|(i, _)| *i == id) {
534            let (_, r) = app.message_dialogs.swap_remove(i);
535            r.respond(response);
536        }
537    }
538
539    pub(crate) fn on_file_dlg_response(&self, id: zng_view_api::dialog::DialogId, response: FileDialogResponse) {
540        let mut app = self.write();
541        if let Some(i) = app.file_dialogs.iter().position(|(i, _)| *i == id) {
542            let (_, r) = app.file_dialogs.swap_remove(i);
543            r.respond(response);
544        }
545    }
546
547    pub(super) fn on_respawned(&self, _gen: ViewProcessGen) {
548        let mut app = self.write();
549        app.pending_frames = 0;
550        for (_, r) in app.message_dialogs.drain(..) {
551            r.respond(MsgDialogResponse::Error(Txt::from_static("respawn")));
552        }
553    }
554
555    pub(crate) fn exit(&self) {
556        *VIEW_PROCESS_SV.write() = None;
557    }
558
559    pub(crate) fn ping(&self) {
560        let mut app = self.write();
561        let count = app.ping_count.wrapping_add(1);
562        if let Ok(c) = app.process.ping(count)
563            && c != count
564        {
565            tracing::error!("incorrect ping response, expected {count}, was {c}");
566        }
567        app.ping_count = count;
568    }
569
570    pub(crate) fn on_pong(&self, count: u16) {
571        let expected = self.read().ping_count;
572        if expected != count {
573            // this could indicates a severe slowdown in the event pump
574            tracing::warn!("unexpected pong event, expected {expected}, was {count}");
575        }
576    }
577}
578impl ViewProcessService {
579    #[must_use = "if `true` all current WinId, DevId and MonId are invalid"]
580    fn check_generation(&mut self) -> bool {
581        let vp_gen = self.process.generation();
582        let invalid = vp_gen != self.data_generation;
583        if invalid {
584            self.data_generation = vp_gen;
585            self.input_device_ids.clear();
586            self.monitor_ids.clear();
587        }
588        invalid
589    }
590}
591
592event_args! {
593    /// Arguments for the [`VIEW_PROCESS_INITED_EVENT`].
594    pub struct ViewProcessInitedArgs {
595        /// View-process generation.
596        pub generation: ViewProcessGen,
597
598        /// If this is not the first time a view-process was inited. If `true`
599        /// all resources created in a previous generation must be rebuilt.
600        ///
601        /// This can happen after a view-process crash or app suspension.
602        pub is_respawn: bool,
603
604        /// API extensions implemented by the view-process.
605        ///
606        /// The extension IDs will stay valid for the duration of the view-process.
607        pub extensions: ApiExtensions,
608
609        ..
610
611        /// Broadcast to all widgets.
612        fn delivery_list(&self, list: &mut UpdateDeliveryList) {
613            list.search_all()
614        }
615    }
616
617    /// Arguments for the [`VIEW_PROCESS_SUSPENDED_EVENT`].
618    pub struct ViewProcessSuspendedArgs {
619
620        ..
621
622        /// Broadcast to all widgets.
623        fn delivery_list(&self, list: &mut UpdateDeliveryList) {
624            list.search_all()
625        }
626    }
627}
628
629event! {
630    /// View-Process finished initializing and is now connected and ready.
631    pub static VIEW_PROCESS_INITED_EVENT: ViewProcessInitedArgs;
632    /// View-Process suspended, all resources dropped.
633    ///
634    /// The view-process will only be available if the app resumes. On resume [`VIEW_PROCESS_INITED_EVENT`]
635    /// notify a view-process respawn.
636    pub static VIEW_PROCESS_SUSPENDED_EVENT: ViewProcessSuspendedArgs;
637}
638
639/// Information about a successfully opened window.
640#[derive(Debug, Clone)]
641#[non_exhaustive]
642pub struct WindowOpenData {
643    /// Window complete state.
644    pub state: WindowStateAll,
645
646    /// Monitor that contains the window.
647    pub monitor: Option<MonitorId>,
648
649    /// Final top-left offset of the window (excluding outer chrome).
650    ///
651    /// The values are the global position and the position in the monitor.
652    pub position: (PxPoint, DipPoint),
653    /// Final dimensions of the client area of the window (excluding outer chrome).
654    pub size: DipSize,
655
656    /// Final scale factor.
657    pub scale_factor: Factor,
658
659    /// Actual render mode, can be different from the requested mode if it is not available.
660    pub render_mode: RenderMode,
661
662    /// Padding that must be applied to the window content so that it stays clear of screen obstructions
663    /// such as a camera notch cutout.
664    ///
665    /// Note that the *unsafe* area must still be rendered as it may be partially visible, just don't place nay
666    /// interactive or important content outside of this padding.
667    pub safe_padding: DipSideOffsets,
668}
669impl WindowOpenData {
670    pub(crate) fn new(data: zng_view_api::window::WindowOpenData, map_monitor: impl FnOnce(ApiMonitorId) -> MonitorId) -> Self {
671        WindowOpenData {
672            state: data.state,
673            monitor: data.monitor.map(map_monitor),
674            position: data.position,
675            size: data.size,
676            scale_factor: data.scale_factor,
677            render_mode: data.render_mode,
678            safe_padding: data.safe_padding,
679        }
680    }
681}
682
683/// Handle to a window open in the view-process.
684///
685/// The window is closed when all clones of the handle are dropped.
686#[derive(Debug, Clone)]
687#[must_use = "the window is closed when all clones of the handle are dropped"]
688pub struct ViewWindow(Arc<ViewWindowData>);
689impl PartialEq for ViewWindow {
690    fn eq(&self, other: &Self) -> bool {
691        Arc::ptr_eq(&self.0, &other.0)
692    }
693}
694impl Eq for ViewWindow {}
695
696impl ViewWindow {
697    /// Returns the view-process generation on which the window was open.
698    pub fn generation(&self) -> ViewProcessGen {
699        self.0.generation
700    }
701
702    /// Set the window title.
703    pub fn set_title(&self, title: Txt) -> Result<()> {
704        self.0.call(|id, p| p.set_title(id, title))
705    }
706
707    /// Set the window visibility.
708    pub fn set_visible(&self, visible: bool) -> Result<()> {
709        self.0.call(|id, p| p.set_visible(id, visible))
710    }
711
712    /// Set if the window is "top-most".
713    pub fn set_always_on_top(&self, always_on_top: bool) -> Result<()> {
714        self.0.call(|id, p| p.set_always_on_top(id, always_on_top))
715    }
716
717    /// Set if the user can drag-move the window.
718    pub fn set_movable(&self, movable: bool) -> Result<()> {
719        self.0.call(|id, p| p.set_movable(id, movable))
720    }
721
722    /// Set if the user can resize the window.
723    pub fn set_resizable(&self, resizable: bool) -> Result<()> {
724        self.0.call(|id, p| p.set_resizable(id, resizable))
725    }
726
727    /// Set the window icon.
728    pub fn set_icon(&self, icon: Option<&ViewImage>) -> Result<()> {
729        self.0.call(|id, p| {
730            if let Some(icon) = icon {
731                let icon = icon.0.read();
732                if p.generation() == icon.generation {
733                    p.set_icon(id, icon.id)
734                } else {
735                    Err(ViewChannelError::Disconnected)
736                }
737            } else {
738                p.set_icon(id, None)
739            }
740        })
741    }
742
743    /// Set the window cursor icon and visibility.
744    pub fn set_cursor(&self, cursor: Option<CursorIcon>) -> Result<()> {
745        self.0.call(|id, p| p.set_cursor(id, cursor))
746    }
747
748    /// Set the window cursor to a custom image.
749    ///
750    /// Falls back to cursor icon if set to `None`.
751    ///
752    /// The `hotspot` value is an exact point in the image that is the mouse position. This value is only used if
753    /// the image format does not contain a hotspot.
754    pub fn set_cursor_image(&self, cursor: Option<&ViewImage>, hotspot: PxPoint) -> Result<()> {
755        self.0.call(|id, p| {
756            if let Some(cur) = cursor {
757                let cur = cur.0.read();
758                if p.generation() == cur.generation {
759                    p.set_cursor_image(id, cur.id.map(|img| zng_view_api::window::CursorImage::new(img, hotspot)))
760                } else {
761                    Err(ViewChannelError::Disconnected)
762                }
763            } else {
764                p.set_cursor_image(id, None)
765            }
766        })
767    }
768
769    /// Set the window icon visibility in the taskbar.
770    pub fn set_taskbar_visible(&self, visible: bool) -> Result<()> {
771        self.0.call(|id, p| p.set_taskbar_visible(id, visible))
772    }
773
774    /// Bring the window the z top.
775    pub fn bring_to_top(&self) -> Result<()> {
776        self.0.call(|id, p| p.bring_to_top(id))
777    }
778
779    /// Set the window state.
780    pub fn set_state(&self, state: WindowStateAll) -> Result<()> {
781        self.0.call(|id, p| p.set_state(id, state))
782    }
783
784    /// Set video mode used in exclusive fullscreen.
785    pub fn set_video_mode(&self, mode: VideoMode) -> Result<()> {
786        self.0.call(|id, p| p.set_video_mode(id, mode))
787    }
788
789    /// Set enabled window chrome buttons.
790    pub fn set_enabled_buttons(&self, buttons: WindowButton) -> Result<()> {
791        self.0.call(|id, p| p.set_enabled_buttons(id, buttons))
792    }
793
794    /// Reference the window renderer.
795    pub fn renderer(&self) -> ViewRenderer {
796        ViewRenderer(Arc::downgrade(&self.0))
797    }
798
799    /// Sets if the headed window is in *capture-mode*. If `true` the resources used to capture
800    /// a screenshot may be kept in memory to be reused in the next screenshot capture.
801    ///
802    /// Note that capture must still be requested in each frame request.
803    pub fn set_capture_mode(&self, enabled: bool) -> Result<()> {
804        self.0.call(|id, p| p.set_capture_mode(id, enabled))
805    }
806
807    /// Brings the window to the front and sets input focus.
808    ///
809    /// This request can steal focus from other apps disrupting the user, be careful with it.
810    pub fn focus(&self) -> Result<FocusResult> {
811        self.0.call(|id, p| p.focus(id))
812    }
813
814    /// Sets the user attention request indicator, the indicator is cleared when the window is focused or
815    /// if canceled by setting to `None`.
816    pub fn set_focus_indicator(&self, indicator: Option<FocusIndicator>) -> Result<()> {
817        self.0.call(|id, p| p.set_focus_indicator(id, indicator))
818    }
819
820    /// Moves the window with the left mouse button until the button is released.
821    ///
822    /// There's no guarantee that this will work unless the left mouse button was pressed immediately before this function is called.
823    pub fn drag_move(&self) -> Result<()> {
824        self.0.call(|id, p| p.drag_move(id))
825    }
826
827    /// Resizes the window with the left mouse button until the button is released.
828    ///
829    /// There's no guarantee that this will work unless the left mouse button was pressed immediately before this function is called.
830    pub fn drag_resize(&self, direction: ResizeDirection) -> Result<()> {
831        self.0.call(|id, p| p.drag_resize(id, direction))
832    }
833
834    /// Start a drag and drop operation, if the window is pressed.
835    ///
836    /// A [`RAW_APP_DRAG_ENDED_EVENT`] will be received when the operation finishes.
837    ///
838    /// [`RAW_APP_DRAG_ENDED_EVENT`]: raw_events::RAW_APP_DRAG_ENDED_EVENT
839    pub fn start_drag_drop(
840        &self,
841        data: Vec<DragDropData>,
842        allowed_effects: DragDropEffect,
843    ) -> Result<std::result::Result<DragDropId, DragDropError>> {
844        self.0.call(|id, p| p.start_drag_drop(id, data, allowed_effects))
845    }
846
847    /// Notify the drag source of what effect was applied for a received drag&drop.
848    pub fn drag_dropped(&self, drop_id: DragDropId, applied: DragDropEffect) -> Result<()> {
849        self.0.call(|id, p| p.drag_dropped(id, drop_id, applied))
850    }
851
852    /// Open system title bar context menu.
853    pub fn open_title_bar_context_menu(&self, position: DipPoint) -> Result<()> {
854        self.0.call(|id, p| p.open_title_bar_context_menu(id, position))
855    }
856
857    /// Shows a native message dialog for the window.
858    ///
859    /// The window is not interactive while the dialog is visible and the dialog may be modal in the view-process.
860    /// In the app-process this is always async, and the response var will update once when the user responds.
861    pub fn message_dialog(&self, dlg: MsgDialog, responder: ResponderVar<MsgDialogResponse>) -> Result<()> {
862        let dlg_id = self.0.call(|id, p| p.message_dialog(id, dlg))?;
863        VIEW_PROCESS.handle_write(self.0.app_id).message_dialogs.push((dlg_id, responder));
864        Ok(())
865    }
866
867    /// Shows a native file/folder dialog for the window.
868    ///
869    /// The window is not interactive while the dialog is visible and the dialog may be modal in the view-process.
870    /// In the app-process this is always async, and the response var will update once when the user responds.
871    pub fn file_dialog(&self, dlg: FileDialog, responder: ResponderVar<FileDialogResponse>) -> Result<()> {
872        let dlg_id = self.0.call(|id, p| p.file_dialog(id, dlg))?;
873        VIEW_PROCESS.handle_write(self.0.app_id).file_dialogs.push((dlg_id, responder));
874        Ok(())
875    }
876
877    /// Update the window's accessibility info tree.
878    pub fn access_update(&self, update: zng_view_api::access::AccessTreeUpdate) -> Result<()> {
879        self.0.call(|id, p| p.access_update(id, update))
880    }
881
882    /// Enable or disable IME by setting a cursor area.
883    ///
884    /// In mobile platforms also shows the software keyboard for `Some(_)` and hides it for `None`.
885    pub fn set_ime_area(&self, area: Option<DipRect>) -> Result<()> {
886        self.0.call(|id, p| p.set_ime_area(id, area))
887    }
888
889    /// Attempt to set a system wide shutdown warning associated with the window.
890    ///
891    /// Operating systems that support this show the `reason` in a warning for the user, it must be a short text
892    /// that identifies the critical operation that cannot be cancelled.
893    ///
894    /// Note that there is no guarantee that the view-process or operating system will actually set a block, there
895    /// is no error result because operating systems can silently ignore block requests at any moment, even after
896    /// an initial successful block.
897    ///
898    /// Set to an empty text to remove the warning.
899    pub fn set_system_shutdown_warn(&self, reason: Txt) -> Result<()> {
900        self.0.call(move |id, p| p.set_system_shutdown_warn(id, reason))
901    }
902
903    /// Drop `self`.
904    pub fn close(self) {
905        drop(self)
906    }
907
908    /// Call a window extension with custom encoded payload.
909    pub fn window_extension_raw(&self, extension_id: ApiExtensionId, request: ApiExtensionPayload) -> Result<ApiExtensionPayload> {
910        self.0.call(|id, p| p.window_extension(id, extension_id, request))
911    }
912
913    /// Call a window extension with serialized payload.
914    pub fn window_extension<I, O>(&self, extension_id: ApiExtensionId, request: &I) -> Result<std::result::Result<O, ApiExtensionRecvError>>
915    where
916        I: serde::Serialize,
917        O: serde::de::DeserializeOwned,
918    {
919        let r = self.window_extension_raw(extension_id, ApiExtensionPayload::serialize(&request).unwrap())?;
920        Ok(r.deserialize())
921    }
922}
923
924/// View window or headless surface.
925#[derive(Clone, Debug)]
926pub enum ViewWindowOrHeadless {
927    /// Headed window view.
928    Window(ViewWindow),
929    /// Headless surface view.
930    Headless(ViewHeadless),
931}
932impl ViewWindowOrHeadless {
933    /// Reference the window or surface renderer.
934    pub fn renderer(&self) -> ViewRenderer {
935        match self {
936            ViewWindowOrHeadless::Window(w) => w.renderer(),
937            ViewWindowOrHeadless::Headless(h) => h.renderer(),
938        }
939    }
940
941    /// Call a window extension with custom encoded payload.
942    pub fn window_extension_raw(&self, extension_id: ApiExtensionId, request: ApiExtensionPayload) -> Result<ApiExtensionPayload> {
943        match self {
944            ViewWindowOrHeadless::Window(w) => w.window_extension_raw(extension_id, request),
945            ViewWindowOrHeadless::Headless(h) => h.window_extension_raw(extension_id, request),
946        }
947    }
948
949    /// Call a window extension with serialized payload.
950    pub fn window_extension<I, O>(&self, extension_id: ApiExtensionId, request: &I) -> Result<std::result::Result<O, ApiExtensionRecvError>>
951    where
952        I: serde::Serialize,
953        O: serde::de::DeserializeOwned,
954    {
955        match self {
956            ViewWindowOrHeadless::Window(w) => w.window_extension(extension_id, request),
957            ViewWindowOrHeadless::Headless(h) => h.window_extension(extension_id, request),
958        }
959    }
960}
961impl From<ViewWindow> for ViewWindowOrHeadless {
962    fn from(w: ViewWindow) -> Self {
963        ViewWindowOrHeadless::Window(w)
964    }
965}
966impl From<ViewHeadless> for ViewWindowOrHeadless {
967    fn from(w: ViewHeadless) -> Self {
968        ViewWindowOrHeadless::Headless(w)
969    }
970}
971
972#[derive(Debug)]
973struct ViewWindowData {
974    app_id: AppId,
975    id: ApiWindowId,
976    generation: ViewProcessGen,
977}
978impl ViewWindowData {
979    fn call<R>(&self, f: impl FnOnce(ApiWindowId, &mut Controller) -> Result<R>) -> Result<R> {
980        let mut app = VIEW_PROCESS.handle_write(self.app_id);
981        if app.check_generation() {
982            Err(ViewChannelError::Disconnected)
983        } else {
984            f(self.id, &mut app.process)
985        }
986    }
987}
988impl Drop for ViewWindowData {
989    fn drop(&mut self) {
990        if VIEW_PROCESS.is_available() {
991            let mut app = VIEW_PROCESS.handle_write(self.app_id);
992            if self.generation == app.process.generation() {
993                let _ = app.process.close(self.id);
994            }
995        }
996    }
997}
998type Result<T> = std::result::Result<T, ViewChannelError>;
999
1000/// Handle to a headless surface/document open in the View Process.
1001///
1002/// The view is disposed when all clones of the handle are dropped.
1003#[derive(Clone, Debug)]
1004#[must_use = "the view is disposed when all clones of the handle are dropped"]
1005pub struct ViewHeadless(Arc<ViewWindowData>);
1006impl PartialEq for ViewHeadless {
1007    fn eq(&self, other: &Self) -> bool {
1008        Arc::ptr_eq(&self.0, &other.0)
1009    }
1010}
1011impl Eq for ViewHeadless {}
1012impl ViewHeadless {
1013    /// Resize the headless surface.
1014    pub fn set_size(&self, size: DipSize, scale_factor: Factor) -> Result<()> {
1015        self.0.call(|id, p| p.set_headless_size(id, size, scale_factor))
1016    }
1017
1018    /// Reference the window renderer.
1019    pub fn renderer(&self) -> ViewRenderer {
1020        ViewRenderer(Arc::downgrade(&self.0))
1021    }
1022
1023    /// Call a window extension with custom encoded payload.
1024    pub fn window_extension_raw(&self, extension_id: ApiExtensionId, request: ApiExtensionPayload) -> Result<ApiExtensionPayload> {
1025        self.0.call(|id, p| p.window_extension(id, extension_id, request))
1026    }
1027
1028    /// Call a window extension with serialized payload.
1029    pub fn window_extension<I, O>(&self, extension_id: ApiExtensionId, request: &I) -> Result<std::result::Result<O, ApiExtensionRecvError>>
1030    where
1031        I: serde::Serialize,
1032        O: serde::de::DeserializeOwned,
1033    {
1034        let r = self.window_extension_raw(extension_id, ApiExtensionPayload::serialize(&request).unwrap())?;
1035        Ok(r.deserialize())
1036    }
1037}
1038
1039/// Weak handle to a window or view.
1040///
1041/// This is only a weak reference, every method returns [`ViewChannelError::Disconnected`] if the
1042/// window is closed or view is disposed.
1043#[derive(Clone, Debug)]
1044pub struct ViewRenderer(sync::Weak<ViewWindowData>);
1045impl PartialEq for ViewRenderer {
1046    fn eq(&self, other: &Self) -> bool {
1047        if let Some(s) = self.0.upgrade()
1048            && let Some(o) = other.0.upgrade()
1049        {
1050            Arc::ptr_eq(&s, &o)
1051        } else {
1052            false
1053        }
1054    }
1055}
1056impl Eq for ViewRenderer {}
1057
1058impl ViewRenderer {
1059    fn call<R>(&self, f: impl FnOnce(ApiWindowId, &mut Controller) -> Result<R>) -> Result<R> {
1060        if let Some(c) = self.0.upgrade() {
1061            c.call(f)
1062        } else {
1063            Err(ViewChannelError::Disconnected)
1064        }
1065    }
1066
1067    /// Returns the view-process generation on which the renderer was created.
1068    pub fn generation(&self) -> Result<ViewProcessGen> {
1069        self.0.upgrade().map(|c| c.generation).ok_or(ViewChannelError::Disconnected)
1070    }
1071
1072    /// Use an image resource in the window renderer.
1073    ///
1074    /// Returns the image texture ID.
1075    pub fn use_image(&self, image: &ViewImage) -> Result<ImageTextureId> {
1076        self.call(|id, p| {
1077            let image = image.0.read();
1078            if p.generation() == image.generation {
1079                p.use_image(id, image.id.unwrap_or(ImageId::INVALID))
1080            } else {
1081                Err(ViewChannelError::Disconnected)
1082            }
1083        })
1084    }
1085
1086    /// Replace the image resource in the window renderer.
1087    pub fn update_image_use(&mut self, tex_id: ImageTextureId, image: &ViewImage) -> Result<()> {
1088        self.call(|id, p| {
1089            let image = image.0.read();
1090            if p.generation() == image.generation {
1091                p.update_image_use(id, tex_id, image.id.unwrap_or(ImageId::INVALID))
1092            } else {
1093                Err(ViewChannelError::Disconnected)
1094            }
1095        })
1096    }
1097
1098    /// Delete the image resource in the window renderer.
1099    pub fn delete_image_use(&mut self, tex_id: ImageTextureId) -> Result<()> {
1100        self.call(|id, p| p.delete_image_use(id, tex_id))
1101    }
1102
1103    /// Add a raw font resource to the window renderer.
1104    ///
1105    /// Returns the new font face ID, unique for this renderer.
1106    pub fn add_font_face(&self, bytes: IpcFontBytes, index: u32) -> Result<FontFaceId> {
1107        self.call(|id, p| p.add_font_face(id, bytes, index))
1108    }
1109
1110    /// Delete the font resource in the window renderer.
1111    pub fn delete_font_face(&self, font_face_id: FontFaceId) -> Result<()> {
1112        self.call(|id, p| p.delete_font_face(id, font_face_id))
1113    }
1114
1115    /// Add a sized font to the window renderer.
1116    ///
1117    /// Returns the new font ID, unique for this renderer.
1118    pub fn add_font(
1119        &self,
1120        font_face_id: FontFaceId,
1121        glyph_size: Px,
1122        options: FontOptions,
1123        variations: Vec<(FontVariationName, f32)>,
1124    ) -> Result<FontId> {
1125        self.call(|id, p| p.add_font(id, font_face_id, glyph_size, options, variations))
1126    }
1127
1128    /// Delete the sized font.
1129    pub fn delete_font(&self, font_id: FontId) -> Result<()> {
1130        self.call(|id, p| p.delete_font(id, font_id))
1131    }
1132
1133    /// Create a new image resource from the current rendered frame.
1134    pub fn frame_image(&self, mask: Option<ImageMaskMode>) -> Result<ViewImage> {
1135        if let Some(c) = self.0.upgrade() {
1136            let id = c.call(|id, p| p.frame_image(id, mask))?;
1137            Ok(Self::add_frame_image(c.app_id, id))
1138        } else {
1139            Err(ViewChannelError::Disconnected)
1140        }
1141    }
1142
1143    /// Create a new image resource from a selection of the current rendered frame.
1144    pub fn frame_image_rect(&self, rect: PxRect, mask: Option<ImageMaskMode>) -> Result<ViewImage> {
1145        if let Some(c) = self.0.upgrade() {
1146            let id = c.call(|id, p| p.frame_image_rect(id, rect, mask))?;
1147            Ok(Self::add_frame_image(c.app_id, id))
1148        } else {
1149            Err(ViewChannelError::Disconnected)
1150        }
1151    }
1152
1153    fn add_frame_image(app_id: AppId, id: ImageId) -> ViewImage {
1154        if id == ImageId::INVALID {
1155            ViewImage::dummy(None)
1156        } else {
1157            let mut app = VIEW_PROCESS.handle_write(app_id);
1158            let img = ViewImage(Arc::new(RwLock::new(ViewImageData {
1159                app_id: Some(app_id),
1160                id: Some(id),
1161                generation: app.process.generation(),
1162                size: PxSize::zero(),
1163                partial_size: PxSize::zero(),
1164                density: None,
1165                is_opaque: false,
1166                partial_pixels: None,
1167                pixels: None,
1168                is_mask: false,
1169                done_signal: SignalOnce::new(),
1170            })));
1171
1172            app.loading_images.push(Arc::downgrade(&img.0));
1173            app.frame_images.push(Arc::downgrade(&img.0));
1174
1175            img
1176        }
1177    }
1178
1179    /// Render a new frame.
1180    pub fn render(&self, frame: FrameRequest) -> Result<()> {
1181        let _s = tracing::debug_span!("ViewRenderer.render").entered();
1182
1183        if let Some(w) = self.0.upgrade() {
1184            w.call(|id, p| p.render(id, frame))?;
1185            VIEW_PROCESS.handle_write(w.app_id).pending_frames += 1;
1186            Ok(())
1187        } else {
1188            Err(ViewChannelError::Disconnected)
1189        }
1190    }
1191
1192    /// Update the current frame and re-render it.
1193    pub fn render_update(&self, frame: FrameUpdateRequest) -> Result<()> {
1194        let _s = tracing::debug_span!("ViewRenderer.render_update").entered();
1195
1196        if let Some(w) = self.0.upgrade() {
1197            w.call(|id, p| p.render_update(id, frame))?;
1198            VIEW_PROCESS.handle_write(w.app_id).pending_frames += 1;
1199            Ok(())
1200        } else {
1201            Err(ViewChannelError::Disconnected)
1202        }
1203    }
1204
1205    /// Call a render extension with custom encoded payload.
1206    pub fn render_extension_raw(&self, extension_id: ApiExtensionId, request: ApiExtensionPayload) -> Result<ApiExtensionPayload> {
1207        if let Some(w) = self.0.upgrade() {
1208            w.call(|id, p| p.render_extension(id, extension_id, request))
1209        } else {
1210            Err(ViewChannelError::Disconnected)
1211        }
1212    }
1213
1214    /// Call a render extension with serialized payload.
1215    pub fn render_extension<I, O>(&self, extension_id: ApiExtensionId, request: &I) -> Result<std::result::Result<O, ApiExtensionRecvError>>
1216    where
1217        I: serde::Serialize,
1218        O: serde::de::DeserializeOwned,
1219    {
1220        let r = self.render_extension_raw(extension_id, ApiExtensionPayload::serialize(&request).unwrap())?;
1221        Ok(r.deserialize())
1222    }
1223}
1224
1225/// Handle to an image loading or loaded in the View Process.
1226///
1227/// The image is disposed when all clones of the handle are dropped.
1228#[must_use = "the image is disposed when all clones of the handle are dropped"]
1229#[derive(Clone)]
1230pub struct ViewImage(Arc<RwLock<ViewImageData>>);
1231impl PartialEq for ViewImage {
1232    fn eq(&self, other: &Self) -> bool {
1233        Arc::ptr_eq(&self.0, &other.0)
1234    }
1235}
1236impl Eq for ViewImage {}
1237impl std::hash::Hash for ViewImage {
1238    fn hash<H: std::hash::Hasher>(&self, state: &mut H) {
1239        let ptr = Arc::as_ptr(&self.0) as usize;
1240        ptr.hash(state)
1241    }
1242}
1243impl fmt::Debug for ViewImage {
1244    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
1245        f.debug_struct("ViewImage")
1246            .field("loaded", &self.is_loaded())
1247            .field("error", &self.error())
1248            .field("size", &self.size())
1249            .field("density", &self.density())
1250            .field("is_opaque", &self.is_opaque())
1251            .field("is_mask", &self.is_mask())
1252            .field("generation", &self.generation())
1253            .finish_non_exhaustive()
1254    }
1255}
1256
1257struct ViewImageData {
1258    app_id: Option<AppId>,
1259    id: Option<ImageId>,
1260    generation: ViewProcessGen,
1261
1262    size: PxSize,
1263    partial_size: PxSize,
1264    density: Option<PxDensity2d>,
1265    is_opaque: bool,
1266
1267    partial_pixels: Option<IpcBytes>,
1268    pixels: Option<std::result::Result<IpcBytes, Txt>>,
1269    is_mask: bool,
1270
1271    done_signal: SignalOnce,
1272}
1273impl Drop for ViewImageData {
1274    fn drop(&mut self) {
1275        if let Some(id) = self.id {
1276            let app_id = self.app_id.unwrap();
1277            if let Some(app) = APP.id() {
1278                if app_id != app {
1279                    tracing::error!("image from app `{:?}` dropped in app `{:?}`", app_id, app);
1280                }
1281
1282                if VIEW_PROCESS.is_available() && VIEW_PROCESS.generation() == self.generation {
1283                    let _ = VIEW_PROCESS.write().process.forget_image(id);
1284                }
1285            }
1286        }
1287    }
1288}
1289
1290impl ViewImage {
1291    /// Image id.
1292    pub fn id(&self) -> Option<ImageId> {
1293        self.0.read().id
1294    }
1295
1296    /// If the image does not actually exists in the view-process.
1297    pub fn is_dummy(&self) -> bool {
1298        self.0.read().id.is_none()
1299    }
1300
1301    /// Returns `true` if the image has successfully decoded.
1302    pub fn is_loaded(&self) -> bool {
1303        self.0.read().pixels.as_ref().map(|r| r.is_ok()).unwrap_or(false)
1304    }
1305
1306    /// Returns `true` if the image is progressively decoding and has partially decoded.
1307    pub fn is_partially_loaded(&self) -> bool {
1308        self.0.read().partial_pixels.is_some()
1309    }
1310
1311    /// if [`error`] is `Some`.
1312    ///
1313    /// [`error`]: Self::error
1314    pub fn is_error(&self) -> bool {
1315        self.0.read().pixels.as_ref().map(|r| r.is_err()).unwrap_or(false)
1316    }
1317
1318    /// Returns the load error if one happened.
1319    pub fn error(&self) -> Option<Txt> {
1320        self.0.read().pixels.as_ref().and_then(|s| s.as_ref().err().cloned())
1321    }
1322
1323    /// Returns the pixel size, or zero if is not loaded or error.
1324    pub fn size(&self) -> PxSize {
1325        self.0.read().size
1326    }
1327
1328    /// Actual size of the current pixels.
1329    ///
1330    /// Can be different from [`size`] if the image is progressively decoding.
1331    ///
1332    /// [`size`]: Self::size
1333    pub fn partial_size(&self) -> PxSize {
1334        self.0.read().partial_size
1335    }
1336
1337    /// Returns the pixel density metadata associated with the image, or `None` if not loaded or error or no
1338    /// metadata provided by decoder.
1339    pub fn density(&self) -> Option<PxDensity2d> {
1340        self.0.read().density
1341    }
1342
1343    /// Returns if the image is fully opaque.
1344    pub fn is_opaque(&self) -> bool {
1345        self.0.read().is_opaque
1346    }
1347
1348    /// Returns if the image is a single channel mask (A8).
1349    pub fn is_mask(&self) -> bool {
1350        self.0.read().is_mask
1351    }
1352
1353    /// Copy the partially decoded pixels if the image is progressively decoding
1354    /// and has not finished decoding.
1355    ///
1356    /// Format is BGRA8 for normal images or A8 if [`is_mask`].
1357    ///
1358    /// [`is_mask`]: Self::is_mask
1359    pub fn partial_pixels(&self) -> Option<Vec<u8>> {
1360        self.0.read().partial_pixels.as_ref().map(|r| r[..].to_vec())
1361    }
1362
1363    /// Reference the decoded pixels of image.
1364    ///
1365    /// Returns `None` until the image is fully loaded. Use [`partial_pixels`] to copy
1366    /// partially decoded bytes.
1367    ///
1368    /// Format is pre-multiplied BGRA8 for normal images or A8 if [`is_mask`].
1369    ///
1370    /// [`is_mask`]: Self::is_mask
1371    ///
1372    /// [`partial_pixels`]: Self::partial_pixels
1373    pub fn pixels(&self) -> Option<IpcBytes> {
1374        self.0.read().pixels.as_ref().and_then(|r| r.as_ref().ok()).cloned()
1375    }
1376
1377    /// Returns the app that owns the view-process that is handling this image.
1378    pub fn app_id(&self) -> Option<AppId> {
1379        self.0.read().app_id
1380    }
1381
1382    /// Returns the view-process generation on which the image is loaded.
1383    pub fn generation(&self) -> ViewProcessGen {
1384        self.0.read().generation
1385    }
1386
1387    /// Creates a [`WeakViewImage`].
1388    pub fn downgrade(&self) -> WeakViewImage {
1389        WeakViewImage(Arc::downgrade(&self.0))
1390    }
1391
1392    /// Create a dummy image in the loaded or error state.
1393    pub fn dummy(error: Option<Txt>) -> Self {
1394        ViewImage(Arc::new(RwLock::new(ViewImageData {
1395            app_id: None,
1396            id: None,
1397            generation: ViewProcessGen::INVALID,
1398            size: PxSize::zero(),
1399            partial_size: PxSize::zero(),
1400            density: None,
1401            is_opaque: true,
1402            partial_pixels: None,
1403            pixels: if let Some(e) = error {
1404                Some(Err(e))
1405            } else {
1406                Some(Ok(IpcBytes::from_slice(&[])))
1407            },
1408            is_mask: false,
1409            done_signal: SignalOnce::new_set(),
1410        })))
1411    }
1412
1413    /// Returns a future that awaits until this image is loaded or encountered an error.
1414    pub fn awaiter(&self) -> SignalOnce {
1415        self.0.read().done_signal.clone()
1416    }
1417
1418    /// Tries to encode the image to the format.
1419    ///
1420    /// The `format` must be one of the [`image_encoders`] supported by the view-process backend.
1421    ///
1422    /// [`image_encoders`]: VIEW_PROCESS::image_encoders
1423    pub async fn encode(&self, format: Txt) -> std::result::Result<IpcBytes, EncodeError> {
1424        self.awaiter().await;
1425
1426        if let Some(e) = self.error() {
1427            return Err(EncodeError::Encode(e));
1428        }
1429
1430        let receiver = {
1431            let img = self.0.read();
1432            if let Some(id) = img.id {
1433                let mut app = VIEW_PROCESS.handle_write(img.app_id.unwrap());
1434
1435                app.process.encode_image(id, format.clone())?;
1436
1437                let (sender, receiver) = flume::bounded(1);
1438                if let Some(entry) = app.encoding_images.iter_mut().find(|r| r.image_id == id && r.format == format) {
1439                    entry.listeners.push(sender);
1440                } else {
1441                    app.encoding_images.push(EncodeRequest {
1442                        image_id: id,
1443                        format,
1444                        listeners: vec![sender],
1445                    });
1446                }
1447                receiver
1448            } else {
1449                return Err(EncodeError::Dummy);
1450            }
1451        };
1452
1453        receiver.recv_async().await?
1454    }
1455}
1456
1457/// Error returned by [`ViewImage::encode`].
1458#[derive(Debug, Clone, PartialEq, Eq)]
1459#[non_exhaustive]
1460pub enum EncodeError {
1461    /// Encode error.
1462    Encode(Txt),
1463    /// Attempted to encode dummy image.
1464    ///
1465    /// In a headless-app without renderer all images are dummy because there is no
1466    /// view-process backend running.
1467    Dummy,
1468    /// The View-Process disconnected or has not finished initializing yet, try again after [`VIEW_PROCESS_INITED_EVENT`].
1469    Disconnected,
1470}
1471impl From<Txt> for EncodeError {
1472    fn from(e: Txt) -> Self {
1473        EncodeError::Encode(e)
1474    }
1475}
1476impl From<ViewChannelError> for EncodeError {
1477    fn from(_: ViewChannelError) -> Self {
1478        EncodeError::Disconnected
1479    }
1480}
1481impl From<flume::RecvError> for EncodeError {
1482    fn from(_: flume::RecvError) -> Self {
1483        EncodeError::Disconnected
1484    }
1485}
1486impl fmt::Display for EncodeError {
1487    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
1488        match self {
1489            EncodeError::Encode(e) => write!(f, "{e}"),
1490            EncodeError::Dummy => write!(f, "cannot encode dummy image"),
1491            EncodeError::Disconnected => write!(f, "{}", ViewChannelError::Disconnected),
1492        }
1493    }
1494}
1495impl std::error::Error for EncodeError {}
1496
1497/// Connection to an image loading or loaded in the View Process.
1498///
1499/// The image is removed from the View Process cache when all clones of [`ViewImage`] drops, but
1500/// if there is another image pointer holding the image, this weak pointer can be upgraded back
1501/// to a strong connection to the image.
1502#[derive(Clone)]
1503pub struct WeakViewImage(sync::Weak<RwLock<ViewImageData>>);
1504impl WeakViewImage {
1505    /// Attempt to upgrade the weak pointer to the image to a full image.
1506    ///
1507    /// Returns `Some` if the is at least another [`ViewImage`] holding the image alive.
1508    pub fn upgrade(&self) -> Option<ViewImage> {
1509        self.0.upgrade().map(ViewImage)
1510    }
1511}
1512
1513struct EncodeRequest {
1514    image_id: ImageId,
1515    format: Txt,
1516    listeners: Vec<flume::Sender<std::result::Result<IpcBytes, EncodeError>>>,
1517}
1518
1519type ClipboardResult<T> = std::result::Result<T, ClipboardError>;
1520
1521/// View-process clipboard methods.
1522#[non_exhaustive]
1523pub struct ViewClipboard {}
1524impl ViewClipboard {
1525    /// Read [`ClipboardType::Text`].
1526    ///
1527    /// [`ClipboardType::Text`]: zng_view_api::clipboard::ClipboardType::Text
1528    pub fn read_text(&self) -> Result<ClipboardResult<Txt>> {
1529        match VIEW_PROCESS.try_write()?.process.read_clipboard(ClipboardType::Text)? {
1530            Ok(ClipboardData::Text(t)) => Ok(Ok(t)),
1531            Err(e) => Ok(Err(e)),
1532            _ => Ok(Err(ClipboardError::Other(Txt::from_static("view-process returned incorrect type")))),
1533        }
1534    }
1535
1536    /// Write [`ClipboardType::Text`].
1537    ///
1538    /// [`ClipboardType::Text`]: zng_view_api::clipboard::ClipboardType::Text
1539    pub fn write_text(&self, txt: Txt) -> Result<ClipboardResult<()>> {
1540        VIEW_PROCESS.try_write()?.process.write_clipboard(ClipboardData::Text(txt))
1541    }
1542
1543    /// Read [`ClipboardType::Image`].
1544    ///
1545    /// [`ClipboardType::Image`]: zng_view_api::clipboard::ClipboardType::Image
1546    pub fn read_image(&self) -> Result<ClipboardResult<ViewImage>> {
1547        let mut app = VIEW_PROCESS.try_write()?;
1548        match app.process.read_clipboard(ClipboardType::Image)? {
1549            Ok(ClipboardData::Image(id)) => {
1550                if id == ImageId::INVALID {
1551                    Ok(Err(ClipboardError::Other(Txt::from_static("view-process returned invalid image"))))
1552                } else {
1553                    let img = ViewImage(Arc::new(RwLock::new(ViewImageData {
1554                        id: Some(id),
1555                        app_id: APP.id(),
1556                        generation: app.process.generation(),
1557                        size: PxSize::zero(),
1558                        partial_size: PxSize::zero(),
1559                        density: None,
1560                        is_opaque: false,
1561                        partial_pixels: None,
1562                        pixels: None,
1563                        is_mask: false,
1564                        done_signal: SignalOnce::new(),
1565                    })));
1566                    app.loading_images.push(Arc::downgrade(&img.0));
1567                    Ok(Ok(img))
1568                }
1569            }
1570            Err(e) => Ok(Err(e)),
1571            _ => Ok(Err(ClipboardError::Other(Txt::from_static("view-process returned incorrect type")))),
1572        }
1573    }
1574
1575    /// Write [`ClipboardType::Image`].
1576    ///
1577    /// [`ClipboardType::Image`]: zng_view_api::clipboard::ClipboardType::Image
1578    pub fn write_image(&self, img: &ViewImage) -> Result<ClipboardResult<()>> {
1579        if img.is_loaded()
1580            && let Some(id) = img.id()
1581        {
1582            return VIEW_PROCESS.try_write()?.process.write_clipboard(ClipboardData::Image(id));
1583        }
1584        Ok(Err(ClipboardError::Other(Txt::from_static("image not loaded"))))
1585    }
1586
1587    /// Read [`ClipboardType::FileList`].
1588    ///
1589    /// [`ClipboardType::FileList`]: zng_view_api::clipboard::ClipboardType::FileList
1590    pub fn read_file_list(&self) -> Result<ClipboardResult<Vec<PathBuf>>> {
1591        match VIEW_PROCESS.try_write()?.process.read_clipboard(ClipboardType::FileList)? {
1592            Ok(ClipboardData::FileList(f)) => Ok(Ok(f)),
1593            Err(e) => Ok(Err(e)),
1594            _ => Ok(Err(ClipboardError::Other(Txt::from_static("view-process returned incorrect type")))),
1595        }
1596    }
1597
1598    /// Write [`ClipboardType::FileList`].
1599    ///
1600    /// [`ClipboardType::FileList`]: zng_view_api::clipboard::ClipboardType::FileList
1601    pub fn write_file_list(&self, list: Vec<PathBuf>) -> Result<ClipboardResult<()>> {
1602        VIEW_PROCESS.try_write()?.process.write_clipboard(ClipboardData::FileList(list))
1603    }
1604
1605    /// Read [`ClipboardType::Extension`].
1606    ///
1607    /// [`ClipboardType::Extension`]: zng_view_api::clipboard::ClipboardType::Extension
1608    pub fn read_extension(&self, data_type: Txt) -> Result<ClipboardResult<IpcBytes>> {
1609        match VIEW_PROCESS
1610            .try_write()?
1611            .process
1612            .read_clipboard(ClipboardType::Extension(data_type.clone()))?
1613        {
1614            Ok(ClipboardData::Extension { data_type: rt, data }) if rt == data_type => Ok(Ok(data)),
1615            Err(e) => Ok(Err(e)),
1616            _ => Ok(Err(ClipboardError::Other(Txt::from_static("view-process returned incorrect type")))),
1617        }
1618    }
1619
1620    /// Write [`ClipboardType::Extension`].
1621    ///
1622    /// [`ClipboardType::Extension`]: zng_view_api::clipboard::ClipboardType::Extension
1623    pub fn write_extension(&self, data_type: Txt, data: IpcBytes) -> Result<ClipboardResult<()>> {
1624        VIEW_PROCESS
1625            .try_write()?
1626            .process
1627            .write_clipboard(ClipboardData::Extension { data_type, data })
1628    }
1629}