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