zng_app/
view_process.rs

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