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