zng_view_api/
lib.rs

1#![doc(html_favicon_url = "https://raw.githubusercontent.com/zng-ui/zng/main/examples/image/res/zng-logo-icon.png")]
2#![doc(html_logo_url = "https://raw.githubusercontent.com/zng-ui/zng/main/examples/image/res/zng-logo.png")]
3//!
4//! The View Process API.
5//!
6//! Zng isolates all render and windowing related code to a different process (the view-process), this crate
7//! provides the API that must be implemented to create a view-process backend, plus the [`Controller`] that
8//! can be used from an app-process to spawn and communicate with a view-process.
9//!
10//! # VERSION
11//!
12//! The [`VERSION`] of this crate must match exactly in both *App-Process* and *View-Process*, otherwise a runtime
13//! panic error is generated.
14//!
15//! # Same Process Patch
16//!
17//! Dynamically loaded same process implementers must propagate a [`StaticPatch`], otherwise the view will not connect.
18//!
19//! # Crate
20//!
21#![doc = include_str!(concat!("../", std::env!("CARGO_PKG_README")))]
22#![warn(missing_docs)]
23#![warn(unused_extern_crates)]
24
25use drag_drop::{DragDropData, DragDropEffect, DragDropError};
26#[cfg(ipc)]
27use serde::{Deserialize, Serialize};
28
29/// The *App Process* and *View Process* must be build using the same exact version and this is
30/// validated during run-time, causing a panic if the versions don't match.
31pub const VERSION: &str = env!("CARGO_PKG_VERSION");
32
33pub mod access;
34pub mod api_extension;
35pub mod clipboard;
36pub mod config;
37pub mod dialog;
38pub mod display_list;
39pub mod drag_drop;
40pub mod font;
41pub mod image;
42pub mod ipc;
43pub mod keyboard;
44pub mod mouse;
45pub mod touch;
46pub mod window;
47
48mod types;
49pub use types::*;
50
51mod app_process;
52pub use app_process::*;
53
54mod view_process;
55pub use view_process::*;
56use zng_txt::Txt;
57
58use std::fmt;
59
60use api_extension::{ApiExtensionId, ApiExtensionPayload};
61use clipboard::{ClipboardData, ClipboardError};
62use dialog::DialogId;
63use font::{FontFaceId, FontId, FontOptions, FontVariationName};
64use image::{ImageId, ImageMaskMode, ImageRequest, ImageTextureId};
65use ipc::{IpcBytes, IpcBytesReceiver};
66use window::WindowId;
67use zng_unit::{DipPoint, DipRect, DipSize, Factor, Px, PxRect};
68
69/// Packaged API request.
70#[derive(Debug)]
71#[cfg_attr(ipc, derive(Serialize, Deserialize))]
72pub struct Request(RequestData);
73impl Request {
74    /// Returns `true` if the request can only be made after the *init* event.
75    pub fn must_be_online(&self) -> bool {
76        !matches!(&self.0, RequestData::init { .. })
77    }
78
79    /// Returns `true` if the request represents a new frame or frame update for the window with the same wait ID.
80    pub fn is_frame(&self, window_id: WindowId, wait_id: Option<window::FrameWaitId>) -> bool {
81        match &self.0 {
82            RequestData::render { id, frame } if *id == window_id && frame.wait_id == wait_id => true,
83            RequestData::render_update { id, frame } if *id == window_id && frame.wait_id == wait_id => true,
84            _ => false,
85        }
86    }
87
88    /// Returns `true` if the request affects position or size of the window.
89    pub fn affects_window_rect(&self, window_id: WindowId) -> bool {
90        matches!(
91            &self.0,
92            RequestData::set_state { id, .. }
93            if *id == window_id
94        )
95    }
96
97    /// Returns `true` if this request will receive a response. Only [`Api`] methods
98    /// that have a return value send back a response.
99    pub fn expect_response(&self) -> bool {
100        self.0.expect_response()
101    }
102}
103
104/// Packaged API response.
105#[derive(Debug)]
106#[cfg_attr(ipc, derive(Serialize, Deserialize))]
107pub struct Response(ResponseData);
108impl Response {
109    /// If this response must be send back to the app process. Only [`Api`] methods
110    /// that have a return value send back a response.
111    pub fn must_be_send(&self) -> bool {
112        self.0.must_be_send()
113    }
114}
115
116macro_rules! TypeOrNil {
117    ($T:ty) => {
118        $T
119    };
120    () => {
121        ()
122    };
123}
124
125macro_rules! type_is_some {
126    (if $T:ty { $($t_true:tt)* } else { $($t_false:tt)* }) => {
127        $($t_true)*
128    };
129    (if { $($t_true:tt)* } else { $($t_false:tt)* }) => {
130        $($t_false)*
131    };
132}
133
134/// Declares the internal `Request` and `Response` enums, public methods in `Controller` and the public trait `ViewApp`, in the
135/// controller it packs and sends the request and receives and unpacks the response. In the view it implements
136/// the method.
137macro_rules! declare_api {
138    (
139        $(
140            $(#[$meta:meta])*
141            $vis:vis fn $method:ident(
142                &mut $self:ident
143                $(, $input:ident : $RequestType:ty)* $(,)?
144            ) $(-> $ResponseType:ty)?;
145        )*
146    ) => {
147        #[cfg_attr(ipc, derive(Serialize, Deserialize))]
148        #[allow(non_camel_case_types)]
149        #[allow(clippy::large_enum_variant)]
150        #[repr(u32)]
151        enum RequestData {
152            $(
153                $(#[$meta])*
154                $method { $($input: $RequestType),* },
155            )*
156        }
157        impl RequestData {
158            #[allow(unused_doc_comments)]
159            pub fn expect_response(&self) -> bool {
160                match self {
161                    $(
162                        $(#[$meta])*
163                        Self::$method { .. } => type_is_some! {
164                            if $($ResponseType)? {
165                                true
166                            } else {
167                                false
168                            }
169                        },
170                    )*
171                }
172            }
173        }
174        impl fmt::Debug for RequestData {
175            fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
176                #[allow(unused_doc_comments)]
177                if f.alternate() {
178                    match self {
179                        $(
180                            $(#[$meta])*
181                            RequestData::$method { $($input),* } => write!(f, "{}{:#?}", stringify!($method), ($($input),*)),
182                        )+
183                    }
184                } else {
185                    match self {
186                        $(
187                            $(#[$meta])*
188                            RequestData::$method { .. } => write!(f, "{}(..)", stringify!($method)),
189                        )+
190                    }
191                }
192            }
193        }
194
195        #[derive(Debug)]
196        #[cfg_attr(ipc, derive(Serialize, Deserialize))]
197        #[allow(non_camel_case_types)]
198        #[repr(u32)]
199        enum ResponseData {
200            $(
201                $(#[$meta])*
202                $method(TypeOrNil![$($ResponseType)?]),
203            )*
204        }
205        impl ResponseData {
206            #[allow(unused_doc_comments)]
207            pub fn must_be_send(&self) -> bool {
208                match self {
209                    $(
210                        $(#[$meta])*
211                        Self::$method(_) => type_is_some! {
212                            if $($ResponseType)? {
213                                true
214                            } else {
215                                false
216                            }
217                        },
218                    )*
219                }
220            }
221        }
222
223        #[allow(unused_parens)]
224        impl Controller {
225            $(
226                $(#[$meta])*
227                #[allow(clippy::too_many_arguments)]
228                $vis fn $method(&mut self $(, $input: $RequestType)*) -> VpResult<TypeOrNil![$($ResponseType)?]> {
229                    let req = Request(RequestData::$method { $($input),* });
230                    type_is_some! {
231                        if $($ResponseType)? {
232                            match self.talk(req)?.0 {
233                                ResponseData::$method(r) => Ok(r),
234                                r => panic!("view-process did not respond correctly for `{}`, {r:?}", stringify!($method))
235                            }
236                        } else {
237                            self.command(req)
238                        }
239                    }
240                }
241            )*
242        }
243
244        /// The view-process API.
245        pub trait Api {
246            /// Already implemented, matches a request, calls the corresponding method and packages the response.
247            fn respond(&mut self, request: Request) -> Response {
248                match request.0 {
249                    $(
250                        #[allow(unused_doc_comments)]
251                        $(#[$meta])* // for the cfg
252                        RequestData::$method { $($input),* } => {
253                            let r = self.$method($($input),*);
254                            Response(ResponseData::$method(r))
255                        }
256                    )*
257                }
258            }
259
260            $(
261                $(#[$meta])*
262                #[allow(clippy::too_many_arguments)]
263                fn $method(&mut self, $($input: $RequestType),*) $(-> $ResponseType)?;
264            )*
265        }
266    };
267}
268declare_api! {
269    /// Called once on init.
270    ///
271    /// Sends an [`Event::Inited`] once the view is completely online.
272    /// Other methods may only be called after this event.
273    fn init(&mut self, vp_gen: ViewProcessGen, is_respawn: bool, device_events: bool, headless: bool);
274
275    /// Called once after exit, if running in a managed external process it will be killed after this call.
276    fn exit(&mut self);
277
278    /// Open a window.
279    ///
280    /// Sends an [`Event::WindowOpened`] once the window, context and renderer have finished initializing or a
281    /// [`Event::WindowOrHeadlessOpenError`] if it failed.
282    pub fn open_window(&mut self, request: window::WindowRequest);
283
284    /// Open a headless surface.
285    ///
286    /// This is a real renderer but not connected to any window, you can requests pixels to get the
287    /// rendered frames.
288    ///
289    /// Sends an [`Event::HeadlessOpened`] once the context and renderer have finished initializing or a
290    /// [`Event::WindowOrHeadlessOpenError`] if it failed.
291    pub fn open_headless(&mut self, request: window::HeadlessRequest);
292
293    /// Close the window or headless surface.
294    ///
295    /// All documents associated with the window or surface are also closed.
296    pub fn close(&mut self, id: WindowId);
297
298    /// Set window title.
299    pub fn set_title(&mut self, id: WindowId, title: Txt);
300
301    /// Set window visible.
302    pub fn set_visible(&mut self, id: WindowId, visible: bool);
303
304    /// Set if the window is "top-most".
305    pub fn set_always_on_top(&mut self, id: WindowId, always_on_top: bool);
306
307    /// Set if the user can drag-move the window when it is in `Normal` mode.
308    pub fn set_movable(&mut self, id: WindowId, movable: bool);
309
310    /// Set if the user can resize the window when it is in `Normal` mode.
311    pub fn set_resizable(&mut self, id: WindowId, resizable: bool);
312
313    /// Set the window taskbar icon visibility.
314    pub fn set_taskbar_visible(&mut self, id: WindowId, visible: bool);
315
316    /// Bring the window to the Z top, without focusing it.
317    pub fn bring_to_top(&mut self, id: WindowId);
318
319    /// Set the window state, position, size.
320    pub fn set_state(&mut self, id: WindowId, state: window::WindowStateAll);
321
322    /// Set the headless surface or document area size (viewport size).
323    pub fn set_headless_size(&mut self, id: WindowId, size: DipSize, scale_factor: Factor);
324
325    /// Set the window icon, the icon image must be loaded.
326    pub fn set_icon(&mut self, id: WindowId, icon: Option<ImageId>);
327
328    /// Set the window cursor icon and visibility.
329    pub fn set_cursor(&mut self, id: WindowId, cursor: Option<window::CursorIcon>);
330
331    /// Set the window cursor to a custom image.
332    ///
333    /// Falls back to cursor icon if not supported or if set to `None`.
334    pub fn set_cursor_image(&mut self, id: WindowId, cursor: Option<window::CursorImage>);
335
336    /// Sets the user attention request indicator, the indicator is cleared when the window is focused or
337    /// if canceled by setting to `None`.
338    pub fn set_focus_indicator(&mut self, id: WindowId, indicator: Option<window::FocusIndicator>);
339
340    /// Set enabled window chrome buttons.
341    pub fn set_enabled_buttons(&mut self, id: WindowId, buttons: window::WindowButton);
342
343    /// Brings the window to the front and sets input focus.
344    ///
345    /// Sends an [`Event::FocusChanged`] if the window is focused, the request can be ignored by the window manager, or if the
346    /// window is not visible, minimized or already focused.
347    ///
348    /// This request can steal focus from other apps disrupting the user, be careful with it.
349    pub fn focus(&mut self, id: WindowId) -> FocusResult;
350
351    /// Moves the window with the left mouse button until the button is released.
352    ///
353    /// There's no guarantee that this will work unless the left mouse button was pressed immediately before this function is called.
354    pub fn drag_move(&mut self, id: WindowId);
355
356    /// Resizes the window with the left mouse button until the button is released.
357    ///
358    /// There's no guarantee that this will work unless the left mouse button was pressed immediately before this function is called.
359    pub fn drag_resize(&mut self, id: WindowId, direction: window::ResizeDirection);
360
361    /// Open the system title bar context menu.
362    pub fn open_title_bar_context_menu(&mut self, id: WindowId, position: DipPoint);
363
364    /// Cache an image resource.
365    ///
366    /// The image is decoded asynchronously, the events [`Event::ImageMetadataLoaded`], [`Event::ImageLoaded`]
367    /// or [`Event::ImageLoadError`] will be send when the image is ready for use or failed.
368    ///
369    /// The [`ImageRequest::data`] handle must contain the full image data already, it will be dropped after the image finishes decoding.
370    ///
371    /// Images are shared between renderers, to use an image in a window you must first call [`use_image`]
372    /// this will register the image data with the renderer.
373    ///
374    /// [`use_image`]: Api::use_image
375    pub fn add_image(&mut self, request: ImageRequest<IpcBytes>) -> ImageId;
376
377    /// Cache an image from data that has not fully loaded.
378    ///
379    /// If the view-process implementation supports **progressive decoding** it will start decoding the image
380    /// as more data is received, otherwise it will collect all data first and then [`add_image`]. Each
381    /// [`ImageRequest::`data`] package is the continuation of the previous call, send an empty package to indicate finish.
382    ///
383    /// The events [`Event::ImageMetadataLoaded`], [`Event::ImageLoaded`] or [`Event::ImageLoadError`] will
384    /// be send while decoding.
385    ///
386    /// [`add_image`]: Api::add_image
387    pub fn add_image_pro(&mut self, request: ImageRequest<IpcBytesReceiver>) -> ImageId;
388
389    /// Remove an image from cache.
390    ///
391    /// Note that if the image is in use in a renderer it will remain in memory until [`delete_image_use`] is
392    /// called or the renderer is deinited by closing the window.
393    ///
394    /// [`delete_image_use`]: Api::delete_image_use
395    pub fn forget_image(&mut self, id: ImageId);
396
397    /// Add an image resource to the window renderer.
398    ///
399    /// Returns the new image texture ID. If the `image_id` is not loaded returns the [`INVALID`] image ID.
400    ///
401    /// [`INVALID`]: ImageTextureId::INVALID
402    pub fn use_image(&mut self, id: WindowId, image_id: ImageId) -> ImageTextureId;
403
404    /// Replace the image resource in the window renderer.
405    ///
406    /// The [`ImageTextureId`] will be associated with the new [`ImageId`].
407    pub fn update_image_use(&mut self, id: WindowId, texture_id: ImageTextureId, image_id: ImageId);
408
409    /// Delete the image resource in the window renderer.
410    pub fn delete_image_use(&mut self, id: WindowId, texture_id: ImageTextureId);
411
412    /// Returns a list of image decoders supported by this implementation.
413    ///
414    /// Each string is the lower-case file extension.
415    pub fn image_decoders(&mut self) -> Vec<Txt>;
416
417    /// Returns a list of image encoders supported by this implementation.
418    ///
419    /// Each string is the lower-case file extension.
420    pub fn image_encoders(&mut self) -> Vec<Txt>;
421
422    /// Encode the image into the `format`.
423    ///
424    /// The format must be one of the values returned by [`image_encoders`].
425    ///
426    /// Returns immediately. The encoded data will be send as the event
427    /// [`Event::ImageEncoded`] or [`Event::ImageEncodeError`].
428    ///
429    /// [`image_encoders`]: Api::image_encoders
430    pub fn encode_image(&mut self, id: ImageId, format: Txt);
431
432    /// Add a raw font resource to the window renderer.
433    ///
434    /// Returns the new font key.
435    pub fn add_font_face(&mut self, id: WindowId, bytes: IpcBytes, index: u32) -> FontFaceId;
436
437    /// Delete the font resource in the window renderer.
438    pub fn delete_font_face(&mut self, id: WindowId, font_face_id: FontFaceId);
439
440    /// Add a sized font to the window renderer.
441    ///
442    /// Returns the new fond ID.
443    pub fn add_font(
444        &mut self,
445        id: WindowId,
446        font_face_id: FontFaceId,
447        glyph_size: Px,
448        options: FontOptions,
449        variations: Vec<(FontVariationName, f32)>,
450    ) -> FontId;
451
452    /// Delete a font instance.
453    pub fn delete_font(&mut self, id: WindowId, font_id: FontId);
454
455    /// Sets if the headed window is in *capture-mode*. If `true` the resources used to capture
456    /// a screenshot may be kept in memory to be reused in the next screenshot capture.
457    ///
458    /// Note that capture must still be requested in each frame request.
459    pub fn set_capture_mode(&mut self, id: WindowId, enable: bool);
460
461    /// Create a new image resource from the current rendered frame.
462    ///
463    /// If `mask` is set captures an A8 mask, otherwise captures a full BGRA8 image.
464    ///
465    /// Returns immediately if an [`Event::FrameImageReady`] will be send when the image is ready.
466    /// Returns `0` if the window is not found.
467    pub fn frame_image(&mut self, id: WindowId, mask: Option<ImageMaskMode>) -> ImageId;
468
469    /// Create a new image from a selection of the current rendered frame.
470    ///
471    /// If `mask` is set captures an A8 mask, otherwise captures a full BGRA8 image.
472    ///
473    /// Returns immediately if an [`Event::FrameImageReady`] will be send when the image is ready.
474    /// Returns `0` if the window is not found.
475    pub fn frame_image_rect(&mut self, id: WindowId, rect: PxRect, mask: Option<ImageMaskMode>) -> ImageId;
476
477    /// Set the video mode used when the window is in exclusive fullscreen.
478    pub fn set_video_mode(&mut self, id: WindowId, mode: window::VideoMode);
479
480    /// Render a new frame.
481    pub fn render(&mut self, id: WindowId, frame: window::FrameRequest);
482
483    /// Update the current frame and re-render it.
484    pub fn render_update(&mut self, id: WindowId, frame: window::FrameUpdateRequest);
485
486    /// Update the window's accessibility info tree.
487    pub fn access_update(&mut self, id: WindowId, update: access::AccessTreeUpdate);
488
489    /// Shows a native message dialog for the window.
490    ///
491    /// Returns an ID that identifies the response event.
492    pub fn message_dialog(&mut self, id: WindowId, dialog: dialog::MsgDialog) -> DialogId;
493
494    /// Shows a native file/folder picker for the window.
495    ///
496    /// Returns the ID that identifies the response event.
497    pub fn file_dialog(&mut self, id: WindowId, dialog: dialog::FileDialog) -> DialogId;
498
499    /// Get the clipboard content that matches the `data_type`.
500    pub fn read_clipboard(&mut self, data_type: clipboard::ClipboardType) -> Result<ClipboardData, ClipboardError>;
501
502    /// Set the clipboard content.
503    pub fn write_clipboard(&mut self, data: ClipboardData) -> Result<(), ClipboardError>;
504
505    /// Start a drag and drop operation, if the window is pressed.
506    pub fn start_drag_drop(
507        &mut self,
508        id: WindowId,
509        data: Vec<DragDropData>,
510        allowed_effects: DragDropEffect,
511    ) -> Result<DragDropId, DragDropError>;
512
513    /// Cancel a drag and drop operation.
514    pub fn cancel_drag_drop(&mut self, id: WindowId, drag_id: DragDropId);
515
516    /// Notify the drag source of what effect was applied for a received drag&drop.
517    pub fn drag_dropped(&mut self, id: WindowId, drop_id: DragDropId, applied: DragDropEffect);
518
519    /// Enable or disable IME by setting a cursor area.
520    ///
521    /// In mobile platforms also shows the software keyboard for `Some(_)` and hides it for `None`.
522    pub fn set_ime_area(&mut self, id: WindowId, area: Option<DipRect>);
523
524    /// Attempt to set a system wide shutdown warning associated with the window.
525    ///
526    /// Operating systems that support this show the `reason` in a warning for the user, it must be a short text
527    /// that identifies the critical operation that cannot be cancelled.
528    ///
529    /// Note that there is no guarantee that the view-process or operating system will actually set a block, there
530    /// is no error result because operating systems can silently ignore block requests at any moment, even after
531    /// an initial successful block.
532    ///
533    /// Set to an empty text to remove the warning.
534    pub fn set_system_shutdown_warn(&mut self, id: WindowId, reason: Txt);
535
536    /// Licenses that may be required to be displayed in the app about screen.
537    ///
538    /// This is specially important for prebuilt view users, as the tools that scrap licenses
539    /// may not find the prebuilt dependencies.
540    pub fn third_party_licenses(&mut self) -> Vec<zng_tp_licenses::LicenseUsed>;
541
542    /// Call the API extension.
543    ///
544    /// The `extension_id` is the index of an extension in the extensions list provided by the view-process on init.
545    /// The `extension_request` is any data required by the extension.
546    ///
547    /// Returns the extension response or [`ApiExtensionPayload::unknown_extension`] if the `extension_id` is
548    /// not on the list, or [`ApiExtensionPayload::invalid_request`] if the `extension_request` is not in a
549    /// format expected by the extension.
550    pub fn app_extension(&mut self, extension_id: ApiExtensionId, extension_request: ApiExtensionPayload) -> ApiExtensionPayload;
551
552    /// Call the API extension.
553    ///
554    /// This is similar to [`Api::app_extension`], but is targeting the instance of an extension associated
555    /// with the `id` window or headless surface.
556    pub fn window_extension(
557        &mut self,
558        id: WindowId,
559        extension_id: ApiExtensionId,
560        extension_request: ApiExtensionPayload,
561    ) -> ApiExtensionPayload;
562
563    /// Call the API extension.
564    ///
565    /// This is similar to [`Api::app_extension`], but is targeting the instance of an extension associated
566    /// with the `id` renderer.
567    pub fn render_extension(
568        &mut self,
569        id: WindowId,
570        extension_id: ApiExtensionId,
571        extension_request: ApiExtensionPayload,
572    ) -> ApiExtensionPayload;
573}
574
575pub(crate) type AnyResult<T> = std::result::Result<T, Box<dyn std::error::Error>>;