diff --git a/examples/test/src/app.rs b/examples/test/src/app.rs index 693516b..77c685a 100644 --- a/examples/test/src/app.rs +++ b/examples/test/src/app.rs @@ -23,7 +23,7 @@ pub async fn get_version() -> anyhow::Result<()> { pub async fn get_tauri_version() -> anyhow::Result<()> { let version = app::get_tauri_version().await?; - + ensure!(version.major == 1); ensure!(version.minor == 2); ensure!(version.patch == 0); diff --git a/examples/test/src/dialog.rs b/examples/test/src/dialog.rs index c852a77..364a3b9 100644 --- a/examples/test/src/dialog.rs +++ b/examples/test/src/dialog.rs @@ -1,10 +1,10 @@ use anyhow::ensure; -use tauri_sys::dialog::{FileDialogBuilder, MessageDialogBuilder, MessageDialogType}; +use tauri_sys::dialog::{FileDialogBuilder, MessageDialogBuilder, MessageDialogKind}; pub async fn ask() -> anyhow::Result<()> { let mut builder = MessageDialogBuilder::new(); builder.set_title("Tauri"); - builder.set_type(MessageDialogType::Warning); + builder.set_type(MessageDialogKind::Warning); let works = builder .ask("Does this work? \n Click Yes to mark this test as passing") @@ -18,7 +18,7 @@ pub async fn ask() -> anyhow::Result<()> { pub async fn confirm() -> anyhow::Result<()> { let mut builder = MessageDialogBuilder::new(); builder.set_title("Tauri"); - builder.set_type(MessageDialogType::Warning); + builder.set_type(MessageDialogKind::Warning); let works = builder .confirm("Does this work? \n Click Ok to mark this test as passing") @@ -32,7 +32,7 @@ pub async fn confirm() -> anyhow::Result<()> { pub async fn message() -> anyhow::Result<()> { let mut builder = MessageDialogBuilder::new(); builder.set_title("Tauri"); - builder.set_type(MessageDialogType::Warning); + builder.set_type(MessageDialogKind::Warning); builder.message("This is a message just for you!").await?; @@ -94,4 +94,4 @@ pub async fn save() -> anyhow::Result<()> { ensure!(file.is_some()); Ok(()) -} \ No newline at end of file +} diff --git a/examples/test/src/notification.rs b/examples/test/src/notification.rs index 1ce9dfd..556611e 100644 --- a/examples/test/src/notification.rs +++ b/examples/test/src/notification.rs @@ -19,10 +19,10 @@ pub async fn request_permission() -> anyhow::Result<()> { pub async fn show_notification() -> anyhow::Result<()> { let mut n = notification::Notification::default(); - n.set_title("TAURI"); - n.set_body("Tauri is awesome!"); - + n.set_title("TAURI"); + n.set_body("Tauri is awesome!"); + n.show()?; Ok(()) -} \ No newline at end of file +} diff --git a/examples/test/src/os.rs b/examples/test/src/os.rs index 9e1ddf8..a464380 100644 --- a/examples/test/src/os.rs +++ b/examples/test/src/os.rs @@ -38,4 +38,4 @@ pub async fn version() -> anyhow::Result<()> { log::debug!("{:?}", version); Ok(()) -} \ No newline at end of file +} diff --git a/src/app.rs b/src/app.rs index e2464e2..2c91804 100644 --- a/src/app.rs +++ b/src/app.rs @@ -47,7 +47,7 @@ pub async fn get_tauri_version() -> crate::Result { Ok(serde_wasm_bindgen::from_value(js_val)?) } -/// Shows the application on macOS. This function does not automatically focuses any app window. +/// Shows the application on macOS. This function does not automatically focus the apps windows. /// /// # Example /// diff --git a/src/dialog.rs b/src/dialog.rs index 8074d5c..b0b2560 100644 --- a/src/dialog.rs +++ b/src/dialog.rs @@ -1,13 +1,16 @@ use serde::Serialize; use std::path::{Path, PathBuf}; -#[derive(Debug, Serialize)] +#[derive(Debug, Clone, Copy, Hash, Serialize)] struct DialogFilter<'a> { extensions: &'a [&'a str], name: &'a str, } -#[derive(Debug, Default, Serialize)] +/// The file dialog builder. +/// +/// Constructs file picker dialogs that can select single/multiple files or directories. +#[derive(Debug, Default, Clone, Hash, Serialize)] #[serde(rename = "camelCase")] pub struct FileDialogBuilder<'a> { default_path: Option<&'a Path>, @@ -203,31 +206,24 @@ impl<'a> FileDialogBuilder<'a> { } } -#[derive(Debug, Default)] -pub enum MessageDialogType { +/// Types of message, ask and confirm dialogs. +#[derive(Debug, Default, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, Serialize)] +pub enum MessageDialogKind { #[default] + #[serde(rename = "info")] Info, + #[serde(rename = "warning")] Warning, + #[serde(rename = "error")] Error, } -impl Serialize for MessageDialogType { - fn serialize(&self, serializer: S) -> Result - where - S: serde::Serializer, - { - match self { - MessageDialogType::Info => serializer.serialize_str("info"), - MessageDialogType::Warning => serializer.serialize_str("warning"), - MessageDialogType::Error => serializer.serialize_str("error"), - } - } -} - -#[derive(Debug, Default, Serialize)] +/// A builder for message dialogs. +#[derive(Debug, Default, Clone, Copy, Hash, Serialize)] pub struct MessageDialogBuilder<'a> { title: Option<&'a str>, - r#type: MessageDialogType, + #[serde(rename = "type")] + kind: MessageDialogKind, } impl<'a> MessageDialogBuilder<'a> { @@ -256,19 +252,19 @@ impl<'a> MessageDialogBuilder<'a> { /// # Example /// /// ```rust - /// use tauri_sys::dialog::{MessageDialogBuilder,MessageDialogType}; + /// use tauri_sys::dialog::{MessageDialogBuilder,MessageDialogKind}; /// /// # fn main() -> Result<(), Box> { - /// let _builder = MessageDialogBuilder::new().set_type(MessageDialogType::Error); + /// let _builder = MessageDialogBuilder::new().set_kind(MessageDialogKind::Error); /// # Ok(()) /// # } /// ``` - pub fn set_type(&mut self, r#type: MessageDialogType) { - self.r#type = r#type; + pub fn set_kind(&mut self, r#kind: MessageDialogKind) { + self.kind = r#kind; } /// Shows a message dialog with an `Ok` button. - /// + /// /// # Example /// /// ```rust,no_run @@ -284,7 +280,7 @@ impl<'a> MessageDialogBuilder<'a> { } /// Shows a question dialog with `Yes` and `No` buttons. - /// + /// /// # Example /// /// ```rust,no_run @@ -302,7 +298,7 @@ impl<'a> MessageDialogBuilder<'a> { } /// Shows a question dialog with `Ok` and `Cancel` buttons. - /// + /// /// # Example /// /// ```rust,no_run @@ -320,271 +316,6 @@ impl<'a> MessageDialogBuilder<'a> { } } -// //! User interaction with the file system using dialog boxes. -// //! -// //! # Example -// //! -// //! ```rust,no_run -// //! use tauri_api::dialog::open; -// //! -// //! let path = open(None).await; -// //! ``` -// use serde::Serialize; -// use std::path::PathBuf; - -// /// Extension filter for the file dialog. -// /// -// /// # Example -// /// -// /// ```rust,no_run -// /// let filter = DialogFilter { -// /// extension: vec![".jpg", ".jpeg", ".png", ".bmp"], -// /// name: "images", -// /// }; -// /// ``` -// #[derive(Serialize)] -// pub struct DialogFilter { -// /// Extensions to filter, without a `.` prefix. -// pub extensions: Vec, - -// /// Filter name -// pub name: String, -// } - -// /// Types of a [`message`] dialog. -// #[derive(Serialize)] -// pub enum MessageDialogType { -// Error, -// Info, -// Warning, -// } - -// /// Options for the [`message`] dialog. -// #[derive(Serialize)] -// pub struct MessageDialogOptions { -// /// The title of the dialog. Defaults to the app name. -// pub title: Option, - -// /// The type of the dialog. Defaults to MessageDialogType::Info. -// #[serde(rename(serialize = "type"))] -// pub kind: MessageDialogType, -// } - -// impl MessageDialogOptions { -// /// Creates a new `MessageDialogOptions` with sensible default values. -// pub fn new() -> Self { -// Self { -// title: None, -// kind: MessageDialogType::Info, -// } -// } -// } - -// /// Options for an [`open`] dialog. -// #[derive(Serialize)] -// pub struct OpenDialogOptions { -// /// Initial directory or file path. -// #[serde(rename(serialize = "defaultPath"))] -// pub default_path: Option, - -// /// Whether the dialog is a directory selection or not. -// pub directory: bool, - -// /// The filters of the dialog. -// pub filters: Vec, - -// /// Whether the dialog allows multiple selection or not. -// pub multiple: bool, - -// /// If `directory` is `true`, indicatees that it will be read recursivley later. -// /// Defines whether subdirectories will be allowed on the scope or not. -// pub recursive: bool, - -// /// The title of the dialog window. -// pub title: Option, -// } - -// impl OpenDialogOptions { -// /// Creates a new `OpenDialogOptions` with sensible default values. -// pub fn new() -> Self { -// Self { -// default_path: None, -// directory: false, -// filters: Vec::new(), -// multiple: false, -// recursive: false, -// title: None, -// } -// } -// } - -// /// Options for the save dialog. -// #[derive(Serialize)] -// pub struct SaveDialogOptions { -// /// Initial directory of the file path. -// /// If it's not a directory path, the dialog interface will change to that folder. -// /// If it's not an existing directory, the file name will be set to the dialog's -// /// file name input and the dialog will be set to the parent folder. -// #[serde(rename(serialize = "defaultPath"))] -// pub default_path: Option, - -// /// The filters of the dialog. -// pub filters: Vec, - -// /// The title of the dialog window. -// pub title: Option, -// } - -// impl SaveDialogOptions { -// /// Creates a new `SaveDialogOptions` with sensible default values. -// pub fn new() -> Self { -// Self { -// default_path: None, -// filters: Vec::new(), -// title: None, -// } -// } -// } - -// /// Show a question dialog with `Yes` and `No` buttons. -// /// -// /// # Example -// /// -// /// ```rust,no_run -// /// use tauri_api::dialog::{ask, MessageDialogOptions}; -// /// -// /// let yes = ask("Are you sure?", None).await; -// /// ``` -// /// @param message Message to display. -// /// @param options Dialog options. -// /// @returns Whether the user selected `Yes` or `No`. -// #[inline(always)] -// pub async fn ask(message: &str, options: Option) -> crate::Result { -// let js_val = inner::ask(message, serde_wasm_bindgen::to_value(&options)?).await?; - -// Ok(serde_wasm_bindgen::from_value(js_val)?) -// } - -// /// Shows a question dialog with `Ok` and `Cancel` buttons. -// /// -// /// # Example -// /// -// /// ```rust,no_run -// /// use tauri_api::dialog::{confirm, MessageDialogOptions}; -// /// -// /// let confirmed = confirm("Are you sure?", None).await; -// /// ``` -// /// @returns Whether the user selelced `Ok` or `Cancel`. -// pub async fn confirm(message: &str, options: Option) -> crate::Result { -// let js_val = inner::confirm(message, serde_wasm_bindgen::to_value(&options)?).await?; - -// Ok(serde_wasm_bindgen::from_value(js_val)?) -// } - -// /// Shows a message dialog with an `Ok` button. -// /// -// /// # Example -// /// -// /// ```rust,no_run -// /// use tauri_api::dialog::{message, MessageDialogOptions}; -// /// -// /// message("Tauri is awesome", None).await; -// /// ``` -// /// @param message Message to display. -// /// @param options Dialog options. -// /// @returns Promise resolved when user closes the dialog. -// pub async fn message(message: &str, options: Option) -> crate::Result<()> { -// Ok(inner::message(message, serde_wasm_bindgen::to_value(&options)?).await?) -// } - -// /// Opens a file/directory selection dialog for a single file. -// /// `multiple` field of [`options`](OpenDialogOptions) must be `false`, if provided. -// /// -// /// The selected paths are added to the filesystem and asset protocol allowlist scopes. -// /// When security is mroe important than the ease of use of this API, -// /// prefer writing a dedicated command instead. -// /// -// /// Note that the allowlist scope change is not persisited, -// /// so the values are cleared when the applicaiton is restarted. -// /// You can save it to the filessytem using the [tauri-plugin-persisted-scope](https://github.com/tauri-apps/tauri-plugin-persisted-scope). -// /// -// /// # Example -// /// -// /// ```rust,no_run -// /// use tauri_api::dialog::{open, OpenDialogOptions}; -// /// -// /// let file = open(None).await; -// /// -// /// let mut opts = OpenDialogOptions::new(); -// /// opts.directory = true; -// /// let dir = open(Some(opts)).await; -// /// ``` -// /// @param options Dialog options. -// /// @returns List of file paths, or `None` if user cancelled the dialog. -// pub async fn open(options: Option) -> crate::Result> { -// let file = inner::open(serde_wasm_bindgen::to_value(&options)?).await?; - -// Ok(serde_wasm_bindgen::from_value(file)?) -// } - -// /// Opens a file/directory selection dialog for multiple files. -// /// `multiple` field of [`options`](OpenDialogOptions) must be `true`, if provided. -// /// -// /// The selected paths are added to the filesystem and asset protocol allowlist scopes. -// /// When security is mroe important than the ease of use of this API, -// /// prefer writing a dedicated command instead. -// /// -// /// Note that the allowlist scope change is not persisited, -// /// so the values are cleared when the applicaiton is restarted. -// /// You can save it to the filessytem using the [tauri-plugin-persisted-scope](https://github.com/tauri-apps/tauri-plugin-persisted-scope). -// /// -// /// # Example -// /// -// /// ```rust,no_run -// /// use tauri_api::dialog::{open, OpenDialogOptions}; -// /// -// /// let files = open_multiple(None).await; -// /// -// /// let mut opts = OpenDialogOptions::new(); -// /// opts.multiple = true; -// /// opts.directory = true; -// /// let dirs = open(Some(opts)).await; -// /// ``` -// /// @param options Dialog options. -// /// @returns List of file paths, or `None` if user cancelled the dialog. -// pub async fn open_multiple( -// options: Option, -// ) -> crate::Result>> { -// let files = inner::open_multiple(serde_wasm_bindgen::to_value(&options)?).await?; - -// Ok(serde_wasm_bindgen::from_value(files)?) -// } - -// /// Opens a file/directory save dialog. -// /// -// /// The selected paths are added to the filesystem and asset protocol allowlist scopes. -// /// When security is mroe important than the ease of use of this API, -// /// prefer writing a dedicated command instead. -// /// -// /// Note that the allowlist scope change is not persisited, -// /// so the values are cleared when the applicaiton is restarted. -// /// You can save it to the filessytem using the [tauri-plugin-persisted-scope](https://github.com/tauri-apps/tauri-plugin-persisted-scope). -// /// -// /// # Example -// /// -// /// ```rust,no_run -// /// use tauri_api::dialog::{save, SaveDialogOptions}; -// /// -// /// let file = save(None).await; -// /// ``` -// /// @param options Dialog options. -// /// @returns File path, or `None` if user cancelled the dialog. -// pub async fn save(options: Option) -> crate::Result> { -// let path = inner::save(serde_wasm_bindgen::to_value(&options)?).await?; - -// Ok(serde_wasm_bindgen::from_value(path)?) -// } - mod inner { use wasm_bindgen::{prelude::wasm_bindgen, JsValue}; diff --git a/src/event.rs b/src/event.rs index 48db53b..f9cff2a 100644 --- a/src/event.rs +++ b/src/event.rs @@ -2,7 +2,7 @@ use serde::{de::DeserializeOwned, Deserialize, Serialize}; use std::fmt::Debug; use wasm_bindgen::{prelude::Closure, JsValue}; -#[derive(Deserialize)] +#[derive(Debug, Clone, Hash, Deserialize)] pub struct Event { /// Event name pub event: String, @@ -14,17 +14,6 @@ pub struct Event { pub window_label: String, } -impl Debug for Event { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - f.debug_struct("Event") - .field("event", &self.event) - .field("id", &self.id) - .field("payload", &self.payload) - .field("window_label", &self.window_label) - .finish() - } -} - /// Emits an event to the backend. /// /// # Example @@ -56,9 +45,10 @@ pub async fn emit(event: &str, payload: &T) -> crate::Result<()> { /// /// ```rust,no_run /// use tauri_api::event::{emit, listen}; +/// use web_sys::console; /// /// const unlisten = listen::("error", |event| { -/// println!("Got error in window {}, payload: {}", event.window_label, event.payload); +/// console::log_1(&format!("Got error in window {}, payload: {}", event.window_label, event.payload).into()); /// }).await; /// /// // you need to call unlisten if your handler goes out of scope e.g. the component is unmounted @@ -96,6 +86,7 @@ where /// ```rust,no_run /// use tauri_api::event::once; /// use serde::Deserialize; +/// use web_sys::console; /// /// #[derive(Deserialize)] /// interface LoadedPayload { @@ -103,7 +94,7 @@ where /// token: String /// } /// const unlisten = once::("loaded", |event| { -/// println!("App is loaded, loggedIn: {}, token: {}", event.payload.logged_in, event.payload.token); +/// console::log_1!(&format!("App is loaded, loggedIn: {}, token: {}", event.payload.logged_in, event.payload.token).into()); /// }).await; /// /// // you need to call unlisten if your handler goes out of scope e.g. the component is unmounted diff --git a/src/lib.rs b/src/lib.rs index caa58fd..353cc22 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -10,6 +10,12 @@ pub mod dialog; pub mod event; #[cfg(feature = "mocks")] pub mod mocks; +#[cfg(feature = "notification")] +pub mod notification; +#[cfg(feature = "os")] +pub mod os; +#[cfg(feature = "path")] +pub mod path; #[cfg(feature = "process")] pub mod process; #[cfg(feature = "tauri")] @@ -18,12 +24,6 @@ pub mod tauri; pub mod updater; #[cfg(feature = "window")] pub mod window; -#[cfg(feature = "notification")] -pub mod notification; -#[cfg(feature = "os")] -pub mod os; -#[cfg(feature = "path")] -pub mod path; #[derive(Debug, thiserror::Error)] pub enum Error { @@ -31,12 +31,14 @@ pub enum Error { Serde(String), #[error("Unknown Theme \"{0}\". Expected one of \"light\",\"dark\"")] UnknownTheme(String), - #[error("Invalid Url {0}")] - InvalidUrl(#[from] url::ParseError), - #[error("Invalid Version {0}")] - InvalidVersion(#[from] semver::Error), #[error("{0}")] Other(String), + #[cfg(feature = "tauri")] + #[error("Invalid Url {0}")] + InvalidUrl(#[from] url::ParseError), + #[cfg(feature = "app")] + #[error("Invalid Version {0}")] + InvalidVersion(#[from] semver::Error), } impl From for Error { diff --git a/src/notification.rs b/src/notification.rs index b65f958..b930e38 100644 --- a/src/notification.rs +++ b/src/notification.rs @@ -1,5 +1,17 @@ use serde::{Deserialize, Serialize}; +/// Checks if the permission to send notifications is granted. +/// +/// # Example +/// +/// ```rust,no_run +/// use tauri_sys::notification; +/// +/// # async fn main() -> Result<(), Box> { +/// let is_granted = notification::is_permission_granted().await?; +/// # Ok(()) +/// # } +/// ``` #[inline(always)] pub async fn is_permission_granted() -> crate::Result { let raw = inner::isPermissionGranted().await?; @@ -7,6 +19,18 @@ pub async fn is_permission_granted() -> crate::Result { Ok(serde_wasm_bindgen::from_value(raw)?) } +/// Requests the permission to send notifications. +/// +/// # Example +/// +/// ```rust,no_run +/// use tauri_sys::notification; +/// +/// # async fn main() -> Result<(), Box> { +/// let perm = notification::request_permission().await?; +/// # Ok(()) +/// # } +/// ``` #[inline(always)] pub async fn request_permission() -> crate::Result { let raw = inner::requestPermission().await?; @@ -14,6 +38,7 @@ pub async fn request_permission() -> crate::Result { Ok(serde_wasm_bindgen::from_value(raw)?) } +/// Possible permission values. #[derive(Debug, Deserialize, Default, Clone, Copy, PartialEq, Eq)] pub enum Permission { #[default] @@ -25,34 +50,55 @@ pub enum Permission { Denied, } +/// The desktop notification definition. +/// +/// Allows you to construct a Notification data and send it. #[derive(Debug, Default, Serialize)] pub struct Notification<'a> { body: Option<&'a str>, title: Option<&'a str>, - icon: Option<&'a str> -} + icon: Option<&'a str>, +} impl<'a> Notification<'a> { pub fn new() -> Self { Self::default() } + /// Sets the notification title. pub fn set_title(&mut self, title: &'a str) { self.title = Some(title); } + /// Sets the notification body. pub fn set_body(&mut self, body: &'a str) { self.body = Some(body); } + /// Sets the notification icon. pub fn set_icon(&mut self, icon: &'a str) { self.icon = Some(icon); } + /// Shows the notification. + /// + /// # Example + /// + /// ```rust,no_run + /// use tauri_sys::notification::Notification; + /// + /// # fn main() -> Result<(), Box> { + /// Notification::new() + /// .set_title("Tauri") + /// .set_body("Tauri is awesome!") + /// .show()?; + /// # Ok(()) + /// # } + /// ``` #[inline(always)] pub fn show(&self) -> crate::Result<()> { inner::sendNotification(serde_wasm_bindgen::to_value(&self)?)?; - + Ok(()) } } diff --git a/src/os.rs b/src/os.rs index ef6c1ef..2cb0a84 100644 --- a/src/os.rs +++ b/src/os.rs @@ -58,7 +58,7 @@ pub enum OsKind { #[serde(rename = "Darwin")] Darwin, #[serde(rename = "Windows_NT")] - WindowsNt, + WindowsNT, } /// Returns the operating system CPU architecture for which the tauri app was compiled. @@ -85,7 +85,7 @@ pub async fn tempdir() -> crate::Result { Ok(serde_wasm_bindgen::from_value(raw)?) } -/// Returns 'OsKind::Linux' on Linux, 'OsKind::Darwin' on macOS, and 'OsKind::WindowsNT' on Windows. +/// Returns [`OsKind::Linux`] on Linux, [`OsKind::Darwin`] on macOS, and [`OsKind::WindowsNT`] on Windows. #[inline(always)] pub async fn kind() -> crate::Result { let raw = inner::kind().await?; diff --git a/src/path.rs b/src/path.rs index 94e81df..9a4d9e3 100644 --- a/src/path.rs +++ b/src/path.rs @@ -2,13 +2,14 @@ use std::path::PathBuf; use wasm_bindgen::JsValue; /// Returns the path to the suggested directory for your app's config files. +/// /// Resolves to `${configDir}/${bundleIdentifier}`, where `bundleIdentifier` is the value [`tauri.bundle.identifier`](https://tauri.app/v1/api/config/#bundleconfig.identifier) is configured in `tauri.conf.json`. /// /// # Example /// /// ```rust,no_run /// use tauri_sys::path::app_config_dir; -/// +/// /// # async fn main() -> Result<(), Box> { /// let app_config_dir_path = app_config_dir().await?; /// # Ok(()) @@ -22,13 +23,14 @@ pub async fn app_config_dir() -> crate::Result { } /// Returns the path to the suggested directory for your app's data files. +/// /// Resolves to `${dataDir}/${bundleIdentifier}`, where `bundleIdentifier` is the value [`tauri.bundle.identifier`](https://tauri.app/v1/api/config/#bundleconfig.identifier) is configured in `tauri.conf.json`. /// /// # Example /// /// ```rust,no_run /// use tauri_sys::path::app_data_dir; -/// +/// /// # async fn main() -> Result<(), Box> { /// let app_data_dir_path = app_data_dir().await?; /// # Ok(()) @@ -42,13 +44,14 @@ pub async fn app_data_dir() -> crate::Result { } /// Returns the path to the suggested directory for your app's local data files. +/// /// Resolves to `${localDataDir}/${bundleIdentifier}`, where `bundleIdentifier` is the value [`tauri.bundle.identifier`](https://tauri.app/v1/api/config/#bundleconfig.identifier) is configured in `tauri.conf.json`. /// /// # Example /// /// ```rust,no_run /// use tauri_sys::path::app_local_data_dir; -/// +/// /// # async fn main() -> Result<(), Box> { /// let app_local_data_dir_path = app_local_data_dir().await?; /// # Ok(()) @@ -62,13 +65,14 @@ pub async fn app_local_data_dir() -> crate::Result { } /// Returns the path to the suggested directory for your app's cache files. +/// /// Resolves to `${cacheDir}/${bundleIdentifier}`, where `bundleIdentifier` is the value [`tauri.bundle.identifier`](https://tauri.app/v1/api/config/#bundleconfig.identifier) is configured in `tauri.conf.json`. /// /// # Example /// /// ```rust,no_run /// use tauri_sys::path::app_cache_dir; -/// +/// /// # async fn main() -> Result<(), Box> { /// let app_cache_dir_path = app_cache_dir().await?; /// # Ok(()) @@ -85,15 +89,15 @@ pub async fn app_cache_dir() -> crate::Result { /// /// #### Platform-specific /// -/// -///*Linux:** Resolves to [`xdg-user-dirs`](https://www.freedesktop.org/wiki/Software/xdg-user-dirs/)' `XDG_MUSIC_DIR`. -/// -///*macOS:** Resolves to `$HOME/Music`. -/// -///*Windows:** Resolves to `{FOLDERID_Music}`. +/// - **Linux:** Resolves to [`xdg-user-dirs`](https://www.freedesktop.org/wiki/Software/xdg-user-dirs/)' `XDG_MUSIC_DIR`. +/// - **macOS:** Resolves to `$HOME/Music`. +/// - **Windows:** Resolves to `{FOLDERID_Music}`. /// /// # Example /// /// ```rust,no_run /// use tauri_sys::path::audio_dir; -/// +/// /// # async fn main() -> Result<(), Box> { /// let audio_dir_path = audio_dir().await?; /// # Ok(()) @@ -110,15 +114,15 @@ pub async fn audio_dir() -> crate::Result { /// /// #### Platform-specific /// -/// -///*Linux:** Resolves to `$XDG_CACHE_HOME` or `$HOME/.cache`. -/// -///*macOS:** Resolves to `$HOME/Library/Caches`. -/// -///*Windows:** Resolves to `{FOLDERID_LocalAppData}`. +/// - **Linux:** Resolves to `$XDG_CACHE_HOME` or `$HOME/.cache`. +/// - **macOS:** Resolves to `$HOME/Library/Caches`. +/// - **Windows:** Resolves to `{FOLDERID_LocalAppData}`. /// /// # Example /// /// ```rust,no_run /// use tauri_sys::path::cache_dir; -/// +/// /// # async fn main() -> Result<(), Box> { /// let cache_dir_path = cache_dir().await?; /// # Ok(()) @@ -135,15 +139,15 @@ pub async fn cache_dir() -> crate::Result { /// /// #### Platform-specific /// -/// -///*Linux:** Resolves to `$XDG_CONFIG_HOME` or `$HOME/.config`. -/// -///*macOS:** Resolves to `$HOME/Library/Application Support`. -/// -///*Windows:** Resolves to `{FOLDERID_RoamingAppData}`. +/// - **Linux:** Resolves to `$XDG_CONFIG_HOME` or `$HOME/.config`. +/// - **macOS:** Resolves to `$HOME/Library/Application Support`. +/// - **Windows:** Resolves to `{FOLDERID_RoamingAppData}`. /// /// # Example /// /// ```rust,no_run /// use tauri_sys::path::config_dir; -/// +/// /// # async fn main() -> Result<(), Box> { /// let config_dir_path = config_dir().await?; /// # Ok(()) @@ -160,15 +164,15 @@ pub async fn config_dir() -> crate::Result { /// /// #### Platform-specific /// -/// -///*Linux:** Resolves to `$XDG_DATA_HOME` or `$HOME/.local/share`. -/// -///*macOS:** Resolves to `$HOME/Library/Application Support`. -/// -///*Windows:** Resolves to `{FOLDERID_RoamingAppData}`. +/// - **Linux:** Resolves to `$XDG_DATA_HOME` or `$HOME/.local/share`. +/// - **macOS:** Resolves to `$HOME/Library/Application Support`. +/// - **Windows:** Resolves to `{FOLDERID_RoamingAppData}`. /// /// # Example /// /// ```rust,no_run /// use tauri_sys::path::data_dir; -/// +/// /// # async fn main() -> Result<(), Box> { /// let data_dir_path = data_dir().await?; /// # Ok(()) @@ -185,15 +189,15 @@ pub async fn data_dir() -> crate::Result { /// /// #### Platform-specific /// -/// -///*Linux:** Resolves to [`xdg-user-dirs`](https://www.freedesktop.org/wiki/Software/xdg-user-dirs/)' `XDG_DESKTOP_DIR`. -/// -///*macOS:** Resolves to `$HOME/Desktop`. -/// -///*Windows:** Resolves to `{FOLDERID_Desktop}`. +/// - **Linux:** Resolves to [`xdg-user-dirs`](https://www.freedesktop.org/wiki/Software/xdg-user-dirs/)' `XDG_DESKTOP_DIR`. +/// - **macOS:** Resolves to `$HOME/Desktop`. +/// - **Windows:** Resolves to `{FOLDERID_Desktop}`. /// /// # Example /// /// ```rust,no_run /// use tauri_sys::path::desktop_dir; -/// +/// /// # async fn main() -> Result<(), Box> { /// let desktop_dir_path = desktop_dir().await?; /// # Ok(()) @@ -213,12 +217,12 @@ pub async fn desktop_dir() -> crate::Result { /// - **Linux:** Resolves to [`xdg-user-dirs`](https://www.freedesktop.org/wiki/Software/xdg-user-dirs/)' `XDG_DOCUMENTS_DIR`. /// - **macOS:** Resolves to `$HOME/Documents`. /// - **Windows:** Resolves to `{FOLDERID_Documents}`. -/// +/// /// # Example /// /// ```rust,no_run /// use tauri_sys::path::document_dir; -/// +/// /// # async fn main() -> Result<(), Box> { /// let document_dir_path = document_dir().await?; /// # Ok(()) @@ -243,7 +247,7 @@ pub async fn document_dir() -> crate::Result { /// /// ```rust,no_run /// use tauri_sys::path::download_dir; -/// +/// /// # async fn main() -> Result<(), Box> { /// let download_dir_path = download_dir().await?; /// # Ok(()) @@ -268,7 +272,7 @@ pub async fn download_dir() -> crate::Result { /// /// ```rust,no_run /// use tauri_sys::path::executable_dir; -/// +/// /// # async fn main() -> Result<(), Box> { /// let executable_dir_path = executable_dir().await?; /// # Ok(()) @@ -293,7 +297,7 @@ pub async fn executable_dir() -> crate::Result { /// /// ```rust,no_run /// use tauri_sys::path::font_dir; -/// +/// /// # async fn main() -> Result<(), Box> { /// let font_dir_path = font_dir().await?; /// # Ok(()) @@ -318,7 +322,7 @@ pub async fn font_dir() -> crate::Result { /// /// ```rust,no_run /// use tauri_sys::path::home_dir; -/// +/// /// # async fn main() -> Result<(), Box> { /// let home_dir_path = home_dir().await?; /// # Ok(()) @@ -343,7 +347,7 @@ pub async fn home_dir() -> crate::Result { /// /// ```rust,no_run /// use tauri_sys::path::local_data_dir; -/// +/// /// # async fn main() -> Result<(), Box> { /// let local_data_dir_path = local_data_dir().await?; /// # Ok(()) @@ -368,7 +372,7 @@ pub async fn local_data_dir() -> crate::Result { /// /// ```rust,no_run /// use tauri_sys::path::picture_dir; -/// +/// /// # async fn main() -> Result<(), Box> { /// let picture_dir_path = picture_dir().await?; /// # Ok(()) @@ -393,7 +397,7 @@ pub async fn picture_dir() -> crate::Result { /// /// ```rust,no_run /// use tauri_sys::path::public_dir; -/// +/// /// # async fn main() -> Result<(), Box> { /// let public_dir_path = public_dir().await?; /// # Ok(()) @@ -407,13 +411,14 @@ pub async fn public_dir() -> crate::Result { } /// Returns the path to the application's resource directory. -/// To resolve a resource path, see the [[resolveResource | `resolveResource API`]]. +/// +/// To resolve a resource path, see the [`resolve_resource`] function. /// /// # Example /// /// ```rust,no_run /// use tauri_sys::path::resource_dir; -/// +/// /// # async fn main() -> Result<(), Box> { /// let resource_dir_path = resource_dir().await?; /// # Ok(()) @@ -431,10 +436,10 @@ pub async fn resource_dir() -> crate::Result { /// @param resourcePath The path to the resource. /// Must follow the same syntax as defined in `tauri.conf.json > tauri > bundle > resources`, i.e. keeping subfolders and parent dir components (`../`). /// @returns The full path to the resource. -/// +/// /// ```rust,no_run /// use tauri_sys::path::resolve_resource; -/// +/// /// # async fn main() -> Result<(), Box> { /// let resource_path = resolve_resource("script.sh").await?; /// # Ok(()) @@ -451,15 +456,15 @@ pub async fn resolve_resource(resource_path: &str) -> crate::Result { /// /// #### Platform-specific /// -/// -///*Linux:** Resolves to `$XDG_RUNTIME_DIR`. -/// -///*macOS:** Not supported. -/// -///*Windows:** Not supported. +/// - **Linux:** Resolves to `$XDG_RUNTIME_DIR`. +/// - **macOS:** Not supported. +/// - **Windows:** Not supported. /// /// # Example /// /// ```rust,no_run /// use tauri_sys::path::runtime_dir; -/// +/// /// # async fn main() -> Result<(), Box> { /// let runtime_dir_path = runtime_dir().await?; /// # Ok(()) @@ -476,15 +481,15 @@ pub async fn runtime_dir() -> crate::Result { /// /// #### Platform-specific /// -/// -///*Linux:** Resolves to [`xdg-user-dirs`](https://www.freedesktop.org/wiki/Software/xdg-user-dirs/)' `XDG_TEMPLATES_DIR`. -/// -///*macOS:** Not supported. -/// -///*Windows:** Resolves to `{FOLDERID_Templates}`. +/// - **Linux:** Resolves to [`xdg-user-dirs`](https://www.freedesktop.org/wiki/Software/xdg-user-dirs/)' `XDG_TEMPLATES_DIR`. +/// - **macOS:** Not supported. +/// - **Windows:** Resolves to `{FOLDERID_Templates}`. /// /// # Example /// /// ```rust,no_run /// use tauri_sys::path::template_dir; -/// +/// /// # async fn main() -> Result<(), Box> { /// let template_dir_path = template_dir().await?; /// # Ok(()) @@ -501,15 +506,15 @@ pub async fn template_dir() -> crate::Result { /// /// #### Platform-specific /// -/// -///*Linux:** Resolves to [`xdg-user-dirs`](https://www.freedesktop.org/wiki/Software/xdg-user-dirs/)' `XDG_VIDEOS_DIR`. -/// -///*macOS:** Resolves to `$HOME/Movies`. -/// -///*Windows:** Resolves to `{FOLDERID_Videos}`. +/// - **Linux:** Resolves to [`xdg-user-dirs`](https://www.freedesktop.org/wiki/Software/xdg-user-dirs/)' `XDG_VIDEOS_DIR`. +/// - **macOS:** Resolves to `$HOME/Movies`. +/// - **Windows:** Resolves to `{FOLDERID_Videos}`. /// /// # Example /// /// ```rust,no_run /// use tauri_sys::path::video_dir; -/// +/// /// # async fn main() -> Result<(), Box> { /// let video_dir_path = video_dir().await?; /// # Ok(()) @@ -534,7 +539,7 @@ pub async fn video_dir() -> crate::Result { /// /// ```rust,no_run /// use tauri_sys::path::app_log_dir; -/// +/// /// # async fn main() -> Result<(), Box> { /// let app_log_dir_path = app_log_dir().await?; /// # Ok(()) @@ -553,10 +558,10 @@ pub async fn app_log_dir() -> crate::Result { /// /// ```rust,no_run /// use tauri_sys::path::{resolve, app_data_dir}; -/// +/// /// # async fn main() -> Result<(), Box> { /// let app_data_dir_path = app_data_dir().await?; -/// +/// /// let path = resolve([app_data_dir_path, "..", "users", "tauri", "avatar.png"]).await?; /// # Ok(()) /// # } @@ -575,10 +580,10 @@ pub async fn resolve(paths: impl IntoIterator) -> crate::Result Result<(), Box> { /// let app_data_dir_path = app_data_dir().await?; -/// +/// /// let path = normalize([app_data_dir_path, "..", "users", "tauri", "avatar.png"]).await?; /// # Ok(()) /// # } @@ -596,10 +601,10 @@ pub async fn normalize(path: &str) -> crate::Result { /// /// ```rust,no_run /// use tauri_sys::path::{join, app_data_dir}; -/// +/// /// # async fn main() -> Result<(), Box> { /// let app_data_dir_path = app_data_dir().await?; -/// +/// /// let path = join([app_data_dir_path, "..", "users", "tauri", "avatar.png"]).await?; /// # Ok(()) /// # } @@ -618,10 +623,10 @@ pub async fn join(paths: impl IntoIterator) -> crate::Result Result<(), Box> { /// let app_data_dir_path = app_data_dir().await?; -/// +/// /// let dir = dirname(app_data_dir_path).await?; /// # Ok(()) /// # } @@ -639,7 +644,7 @@ pub async fn dirname(path: &str) -> crate::Result { /// /// ```rust,no_run /// use tauri_sys::path::{extname, resolve_resource}; -/// +/// /// # async fn main() -> Result<(), Box> { /// let resource_path = await resolve_resource("app.conf").await?; /// let ext = extname(resource_path).await?; @@ -657,12 +662,12 @@ pub async fn extname(path: &str) -> crate::Result { /// Returns the last portion of a `path`. Trailing directory separators are ignored. /// /// @param ext An optional file extension to be removed from the returned path. -/// +/// /// # Example /// /// ```rust,no_run /// use tauri_sys::path::{basename, resolve_resource}; -/// +/// /// # async fn main() -> Result<(), Box> { /// let resource_path = await resolve_resource("app.conf").await?; /// let ext = basename(resource_path).await?; @@ -687,7 +692,7 @@ pub async fn basename(path: &str, ext: Option<&str>) -> crate::Result { /// /// ```rust,no_run /// use tauri_sys::path::is_absolute; -/// +/// /// # async fn main() -> Result<(), Box> { /// assert!(is_absolute("/home/tauri").await?); /// # Ok(()) diff --git a/src/process.rs b/src/process.rs index a038eb5..4b3b41f 100644 --- a/src/process.rs +++ b/src/process.rs @@ -1,9 +1,11 @@ +/// Exits immediately with the given `exit_code`. #[inline(always)] -pub async fn exit(exit_code: u32) -> ! { +pub async fn exit(exit_code: i32) -> ! { inner::exit(exit_code).await; unreachable!() } +/// Exits the current instance of the app then relaunches it. #[inline(always)] pub fn relaunch() { inner::relaunch(); @@ -14,7 +16,7 @@ mod inner { #[wasm_bindgen(module = "/src/process.js")] extern "C" { - pub async fn exit(exitCode: u32); + pub async fn exit(exitCode: i32); pub fn relaunch(); } } diff --git a/src/tauri.rs b/src/tauri.rs index ea3cf68..7ee8312 100644 --- a/src/tauri.rs +++ b/src/tauri.rs @@ -2,6 +2,7 @@ use serde::{de::DeserializeOwned, Serialize}; use url::Url; /// Convert a device file path to an URL that can be loaded by the webview. +/// /// Note that `asset:` and `https://asset.localhost` must be added to [`tauri.security.csp`](https://tauri.app/v1/api/config/#securityconfig.csp) in `tauri.conf.json`. /// Example CSP value: `"csp": "default-src 'self'; img-src 'self' asset: https://asset.localhost"` to use the asset protocol on image sources. /// @@ -68,6 +69,7 @@ pub async fn invoke(cmd: &str, args: &A) -> c } /// Transforms a callback function to a string identifier that can be passed to the backend. +/// /// The backend uses the identifier to `eval()` the callback. /// /// @return A unique identifier associated with the callback function. diff --git a/src/updater.rs b/src/updater.rs index 7be5bff..0631ed7 100644 --- a/src/updater.rs +++ b/src/updater.rs @@ -5,19 +5,19 @@ use wasm_bindgen::{prelude::Closure, JsValue}; pub struct UpdateManifest { pub body: String, pub date: String, - pub version: String + pub version: String, } #[derive(Deserialize, Debug, Clone)] pub struct UpdateResult { pub manifest: Option, - pub should_update: bool + pub should_update: bool, } #[derive(Deserialize)] struct UpdateStatusResult { error: Option, - status: UpdateStatus + status: UpdateStatus, } #[derive(Deserialize)] @@ -27,16 +27,16 @@ pub enum UpdateStatus { #[serde(rename = "DONE")] Done, #[serde(rename = "UPTODATE")] - UpToDate + UpToDate, } /// Checks if an update is available. -/// +/// /// # Example -/// +/// /// ```rust,no_run /// use tauri_sys::updater::check_update; -/// +/// /// # async fn main() -> Result<(), Box> { /// let update = check_update().await?; /// // now run installUpdate() if needed @@ -51,15 +51,15 @@ pub async fn check_update() -> crate::Result { } /// Install the update if there's one available. -/// +/// /// # Example -/// +/// /// ```rust,no_run /// use tauri_sys::updater::{check_update, install_update}; -/// +/// /// # async fn main() -> Result<(), Box> { /// let update = check_update().await?; -/// +/// /// if update.should_update { /// log::info("Installing update {:?}", update.manifest); /// install_update().await?; @@ -74,17 +74,17 @@ pub async fn install_update() -> crate::Result<()> { } /// Listen to an updater event. -/// +/// /// # Example -/// +/// /// ```rust,no_run /// use tauri_sys::updater::on_updater_event; -/// +/// /// # async fn main() -> Result<(), Box> { /// let unlisten = on_updater_event(|event| { /// log::debug!("Updater event {:?}", event); /// }).await?; -/// +/// /// // you need to call unlisten if your handler goes out of scope e.g. the component is unmounted /// unlisten(); /// # Ok(()) diff --git a/src/window.rs b/src/window.rs index 5d28453..2f5a8b7 100644 --- a/src/window.rs +++ b/src/window.rs @@ -1,24 +1,37 @@ -use crate::{event::Event, Error}; -use serde::{de::DeserializeOwned, Serialize}; +use crate::event::Event; +use serde::{de::DeserializeOwned, Deserialize, Serialize}; use std::fmt::Display; use wasm_bindgen::{prelude::Closure, JsCast, JsValue}; -#[derive(Debug, Clone, PartialEq)] +#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)] pub enum Theme { + #[serde(rename = "light")] Light, + #[serde(rename = "dark")] Dark, } -#[derive(Debug, Clone, PartialEq)] +#[derive(Debug, Default, Clone, PartialEq, Serialize)] pub enum TitleBarStyle { + #[default] + #[serde(rename = "visible")] Visible, + #[serde(rename = "transparent")] Transparent, + #[serde(rename = "overlay")] Overlay, } -#[derive(Debug, Clone, PartialEq)] +/// Attention type to request on a window. +#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] pub enum UserAttentionType { + /// #### Platform-specific + /// - macOS: Bounces the dock icon until the application is in focus. + /// - Windows: Flashes both the window and the taskbar button until the application is in focus. Critical = 1, + /// #### Platform-specific + /// - macOS: Bounces the dock icon once. + /// - Windows: Flashes the taskbar button until the application is in focus. Informational, } @@ -119,94 +132,299 @@ impl Display for CursorIcon { } } +#[derive(Debug, Default, Clone, Serialize)] +struct WebviewWindowOptions<'a> { + url: Option<&'a str>, + center: bool, + x: Option, + y: Option, + width: Option, + height: Option, + min_width: Option, + min_height: Option, + max_width: Option, + max_height: Option, + resizable: bool, + title: Option<&'a str>, + fullscreen: bool, + focus: bool, + transparent: bool, + maximized: bool, + visible: bool, + decorations: bool, + always_on_top: bool, + skip_taskbar: bool, + file_drop_enabled: bool, + theme: Option, + title_bar_style: Option, + hidden_title: bool, + accept_first_mouse: bool, + tabbing_identifier: Option<&'a str>, + user_agent: Option<&'a str>, +} + +#[derive(Debug, Default, Clone, Serialize)] +pub struct WebviewWindowBuilder<'a> { + label: &'a str, + inner: WebviewWindowOptions<'a>, +} + +impl<'a> WebviewWindowBuilder<'a> { + pub fn new(label: &'a str) -> Self { + Self { + label, + ..Default::default() + } + } + + /// Remote URL or local file path to open. + /// + /// - URL such as `https://github.com/tauri-apps` is opened directly on a Tauri window. + /// - data: URL such as `data:text/html,...` is only supported with the `window-data-url` Cargo feature for the `tauri` dependency. + /// - local file path or route such as `/path/to/page.html` or `/users` is appended to the application URL (the devServer URL on development, or `tauri://localhost/` and `https://tauri.localhost/` on production). + pub fn set_url(&mut self, url: &'a str) { + self.inner.url = Some(url); + } + + /// Show window in the center of the screen. + pub fn set_center(&mut self, center: bool) { + self.inner.center = center; + } + + /// The initial position. + pub fn set_position(&mut self, position: PhysicalPosition) { + self.inner.x = Some(position.x()); + self.inner.y = Some(position.y()); + } + + /// The initial size. + pub fn set_size(&mut self, size: PhysicalSize) { + self.inner.width = Some(size.width()); + self.inner.height = Some(size.height()); + } + + /// Minimum window size. + pub fn set_min_size(&mut self, min_size: PhysicalSize) { + self.inner.min_width = Some(min_size.width()); + self.inner.min_height = Some(min_size.height()); + } + + /// Maximum window size. + pub fn set_max_size(&mut self, max_size: PhysicalSize) { + self.inner.max_width = Some(max_size.width()); + self.inner.max_height = Some(max_size.height()); + } + + /// Whether the window is resizable or not. + pub fn set_resizable(&mut self, resizable: bool) { + self.inner.resizable = resizable; + } + + /// Window title. + pub fn set_title(&mut self, title: &'a str) { + self.inner.title = Some(title); + } + + /// Whether the window is in fullscreen mode or not. + pub fn set_fullscreen(&mut self, fullscreen: bool) { + self.inner.fullscreen = fullscreen; + } + + /// Whether the window will be initially focused or not. + pub fn set_focus(&mut self, focus: bool) { + self.inner.focus = focus; + } + + /// Whether the window is transparent or not. + /// + /// Note that on `macOS` this requires the `macos-private-api` feature flag, enabled under `tauri.conf.json > tauri > macOSPrivateApi`. + /// WARNING: Using private APIs on `macOS` prevents your application from being accepted to the `App Store`. + pub fn set_transparent(&mut self, transparent: bool) { + self.inner.transparent = transparent; + } + + /// Whether the window should be maximized upon creation or not. + pub fn set_maximized(&mut self, maximized: bool) { + self.inner.maximized = maximized; + } + + /// Whether the window should be immediately visible upon creation or not. + pub fn set_visible(&mut self, visible: bool) { + self.inner.visible = visible; + } + + /// Whether the window should have borders and bars or not. + pub fn set_decorations(&mut self, decorations: bool) { + self.inner.decorations = decorations; + } + + /// Whether the window should always be on top of other windows or not. + pub fn set_always_on_top(&mut self, always_on_top: bool) { + self.inner.always_on_top = always_on_top; + } + + /// Whether or not the window icon should be added to the taskbar. + pub fn set_skip_taskbar(&mut self, skip_taskbar: bool) { + self.inner.skip_taskbar = skip_taskbar; + } + + /// Whether the file drop is enabled or not on the webview. By default it is enabled. + /// + /// Disabling it is required to use drag and drop on the frontend on Windows. + pub fn set_file_drop_enabled(&mut self, file_drop_enabled: bool) { + self.inner.file_drop_enabled = file_drop_enabled; + } + + /// The initial window theme. Defaults to the system theme. + /// + /// Only implemented on Windows and macOS 10.14+. + pub fn set_theme(&mut self, theme: Theme) { + self.inner.theme = Some(theme); + } + + /// The style of the macOS title bar. + pub fn set_title_bar_style(&mut self, title_bar_style: TitleBarStyle) { + self.inner.title_bar_style = Some(title_bar_style); + } + + /// If `true`, sets the window title to be hidden on macOS. + pub fn set_hidden_title(&mut self, hidden_title: bool) { + self.inner.hidden_title = hidden_title; + } + + /// Whether clicking an inactive window also clicks through to the webview. + pub fn set_accept_first_mouse(&mut self, accept_first_mouse: bool) { + self.inner.accept_first_mouse = accept_first_mouse; + } + + /// Defines the window [tabbing identifier](https://developer.apple.com/documentation/appkit/nswindow/1644704-tabbingidentifier) on macOS. + /// + /// Windows with the same tabbing identifier will be grouped together. + /// If the tabbing identifier is not set, automatic tabbing will be disabled. + pub fn set_tabbing_identifier(&mut self, tabbing_identifier: &'a str) { + self.inner.tabbing_identifier = Some(tabbing_identifier); + } + + /// The user agent for the webview. + pub fn set_user_agent(&mut self, user_agent: &'a str) { + self.inner.user_agent = Some(user_agent); + } + + pub fn build(self) -> crate::Result { + let opts = serde_wasm_bindgen::to_value(&self.inner)?; + + Ok(WebviewWindow(inner::WebviewWindow::new(self.label, opts))) + } +} + +/// Create new webview windows and get a handle to existing ones. +/// +/// Windows are identified by a label a unique identifier that can be used to reference it later. It may only contain alphanumeric characters a-zA-Z plus the following special characters -, /, : and _. #[derive(Debug, Clone, PartialEq)] pub struct WebviewWindow(inner::WebviewWindow); impl WebviewWindow { - pub fn new(label: &str, options: ()) -> Self { - Self(inner::WebviewWindow::new(label, options)) - } - pub fn get_by_label(label: &str) -> Option { inner::WebviewWindow::getByLabel(label).map(Self) } + /// The label of this window. pub fn label(&self) -> String { self.0.label() } + /// Returns the scale factor that can be used to map logical pixels to physical pixels, and vice versa. pub async fn scale_factor(&self) -> crate::Result { let js_val = self.0.scaleFactor().await?; Ok(serde_wasm_bindgen::from_value(js_val)?) } + /// Returns the position of the top-left hand corner of the window’s client area relative to the top-left hand corner of the desktop. pub async fn inner_position(&self) -> crate::Result { Ok(PhysicalPosition( self.0.innerPosition().await?.unchecked_into(), )) } + /// Returns the position of the top-left hand corner of the window relative to the top-left hand corner of the desktop. pub async fn outer_position(&self) -> crate::Result { Ok(PhysicalPosition( self.0.outerPosition().await?.unchecked_into(), )) } + /// Returns the physical size of the window’s client area. + /// + /// The client area is the content of the window, excluding the title bar and borders. pub async fn inner_size(&self) -> crate::Result { Ok(PhysicalSize(self.0.innerSize().await?.unchecked_into())) } + /// Returns the physical size of the entire window. + /// + /// These dimensions include the title bar and borders. If you don’t want that (and you usually don’t), use inner_size instead. pub async fn outer_size(&self) -> crate::Result { Ok(PhysicalSize(self.0.outerSize().await?.unchecked_into())) } + /// Gets the window’s current fullscreen state. pub async fn is_fullscreen(&self) -> crate::Result { let js_val = self.0.isFullscreen().await?; Ok(serde_wasm_bindgen::from_value(js_val)?) } + /// Gets the window’s current maximized state. pub async fn is_maximized(&self) -> crate::Result { let js_val = self.0.isMaximized().await?; Ok(serde_wasm_bindgen::from_value(js_val)?) } + /// Gets the window’s current decoration state. pub async fn is_decorated(&self) -> crate::Result { let js_val = self.0.isDecorated().await?; Ok(serde_wasm_bindgen::from_value(js_val)?) } + /// Gets the window’s current resizable state. pub async fn is_resizable(&self) -> crate::Result { let js_val = self.0.isResizable().await?; Ok(serde_wasm_bindgen::from_value(js_val)?) } + /// Gets the window’s current visibility state. pub async fn is_visible(&self) -> crate::Result { let js_val = self.0.isVisible().await?; Ok(serde_wasm_bindgen::from_value(js_val)?) } + /// Returns the current window theme. + /// + /// #### Platform-specific + /// - macOS: Only supported on macOS 10.14+. pub async fn theme(&self) -> crate::Result { let js_val = self.0.theme().await?; - let str = serde_wasm_bindgen::from_value::(js_val)?; - - match str.as_str() { - "light" => Ok(Theme::Light), - "dark" => Ok(Theme::Dark), - _ => Err(Error::UnknownTheme(str)), - } + Ok(serde_wasm_bindgen::from_value(js_val)?) } + /// Centers the window. pub async fn center(&self) -> crate::Result<()> { Ok(self.0.center().await?) } + /// Requests user attention to the window, this has no effect if the application is already focused. How requesting for user attention manifests is platform dependent, see UserAttentionType for details. + /// + /// Providing None will unset the request for user attention. Unsetting the request for user attention might not be done automatically by the WM when the window receives input. + /// + /// #### Platform-specific + /// - macOS: None has no effect. + /// - Linux: Urgency levels have the same effect. pub async fn request_user_attention( &self, request_type: UserAttentionType, @@ -214,18 +432,27 @@ impl WebviewWindow { Ok(self.0.requestUserAttention(request_type as u32).await?) } + /// Opens the dialog to prints the contents of the webview. Currently only supported on macOS on wry. window.print() works on all platforms. + pub fn print(&self) -> crate::Result<()> { + todo!() + } + + /// Determines if this window should be resizable. pub async fn set_resizable(&self, resizable: bool) -> crate::Result<()> { Ok(self.0.setResizable(resizable).await?) } + /// Set this window’s title. pub async fn set_title(&self, title: impl AsRef) -> crate::Result<()> { Ok(self.0.setTitle(title.as_ref()).await?) } + /// Maximizes this window. pub async fn maximize(&self) -> crate::Result<()> { Ok(self.0.maximize().await?) } + /// Un-maximizes this window. pub async fn unmaximize(&self) -> crate::Result<()> { Ok(self.0.unmaximize().await?) } @@ -234,36 +461,44 @@ impl WebviewWindow { Ok(self.0.toggleMaximize().await?) } + /// Minimizes this window. pub async fn minimize(&self) -> crate::Result<()> { Ok(self.0.minimize().await?) } + /// Un-minimizes this window. pub async fn unminimize(&self) -> crate::Result<()> { Ok(self.0.unminimize().await?) } + /// Show this window. pub async fn show(&self) -> crate::Result<()> { Ok(self.0.show().await?) } + /// Hide this window. pub async fn hide(&self) -> crate::Result<()> { Ok(self.0.hide().await?) } + /// Closes this window. pub async fn close(&self) -> crate::Result<()> { Ok(self.0.close().await?) } + /// Determines if this window should be [decorated](https://en.wikipedia.org/wiki/Window_(computing)#Window_decoration). pub async fn set_decorations(&self, decorations: bool) -> crate::Result<()> { Ok(self.0.setDecorations(decorations).await?) } + /// Determines if this window should always be on top of other windows. pub async fn set_always_on_top(&self, always_on_top: bool) -> crate::Result<()> { Ok(self.0.setAlwaysOnTop(always_on_top).await?) } - pub async fn set_size(&self, size: Size) -> crate::Result<()> { - match size { + /// Resizes this window. + pub async fn set_size(&self, size: impl Into) -> crate::Result<()> { + match size.into() { Size::Physical(size) => self.0.setSizePhysical(size.0).await?, Size::Logical(size) => self.0.setSizeLogical(size.0).await?, } @@ -271,8 +506,9 @@ impl WebviewWindow { Ok(()) } - pub async fn set_min_size(&self, size: Option) -> crate::Result<()> { - match size { + /// Sets this window’s minimum size. + pub async fn set_min_size(&self, size: Option>) -> crate::Result<()> { + match size.map(Into::into) { None => self.0.setMinSizePhysical(None).await?, Some(Size::Physical(size)) => self.0.setMinSizePhysical(Some(size.0)).await?, Some(Size::Logical(size)) => self.0.setMinSizeLogical(Some(size.0)).await?, @@ -281,8 +517,9 @@ impl WebviewWindow { Ok(()) } - pub async fn set_max_size(&self, size: Option) -> crate::Result<()> { - match size { + /// Sets this window’s maximum size. + pub async fn set_max_size(&self, size: Option>) -> crate::Result<()> { + match size.map(Into::into) { None => self.0.setMaxSizePhysical(None).await?, Some(Size::Physical(size)) => self.0.setMaxSizePhysical(Some(size.0)).await?, Some(Size::Logical(size)) => self.0.setMaxSizeLogical(Some(size.0)).await?, @@ -291,8 +528,9 @@ impl WebviewWindow { Ok(()) } - pub async fn set_position(&self, position: Position) -> crate::Result<()> { - match position { + /// Sets this window’s position. + pub async fn set_position(&self, position: impl Into) -> crate::Result<()> { + match position.into() { Position::Physical(pos) => self.0.setPositionPhysical(pos.0).await?, Position::Logical(pos) => self.0.setPositionLogical(pos.0).await?, } @@ -300,34 +538,54 @@ impl WebviewWindow { Ok(()) } + /// Determines if this window should be fullscreen. pub async fn set_fullscreen(&self, fullscreen: bool) -> crate::Result<()> { Ok(self.0.setFullscreen(fullscreen).await?) } + /// Bring the window to front and focus. pub async fn set_focus(&self) -> crate::Result<()> { Ok(self.0.setFocus().await?) } + /// Sets this window’ icon. pub async fn set_icon(&self, icon: &[u8]) -> crate::Result<()> { Ok(self.0.setIcon(icon).await?) } + /// Whether to show the window icon in the task bar or not. pub async fn set_skip_taskbar(&self, skip: bool) -> crate::Result<()> { Ok(self.0.setSkipTaskbar(skip).await?) } + /// Grabs the cursor, preventing it from leaving the window. + /// + /// There’s no guarantee that the cursor will be hidden. You should hide it by yourself if you want so. + /// + /// #### Platform-specific + /// - Linux: Unsupported. + /// - macOS: This locks the cursor in a fixed location, which looks visually awkward. pub async fn set_cursor_grab(&self, grab: bool) -> crate::Result<()> { Ok(self.0.setCursorGrab(grab).await?) } + /// Modifies the cursor’s visibility. + /// + /// If false, this will hide the cursor. If true, this will show the cursor. + /// + /// #### Platform-specific + /// - Windows: The cursor is only hidden within the confines of the window. + /// - macOS: The cursor is hidden as long as the window has input focus, even if the cursor is outside of the window. pub async fn set_cursor_visible(&self, visible: bool) -> crate::Result<()> { Ok(self.0.setCursorVisible(visible).await?) } + /// Modifies the cursor icon of the window. pub async fn set_cursor_icon(&self, icon: CursorIcon) -> crate::Result<()> { Ok(self.0.setCursorIcon(&icon.to_string()).await?) } + /// Changes the position of the cursor in window coordinates. pub async fn set_cursor_position(&self, position: Position) -> crate::Result<()> { match position { Position::Physical(pos) => self.0.setCursorPositionPhysical(pos.0).await?, @@ -337,14 +595,17 @@ impl WebviewWindow { Ok(()) } + /// Ignores the window cursor events. pub async fn set_ignore_cursor_events(&self, ignore: bool) -> crate::Result<()> { Ok(self.0.setIgnoreCursorEvents(ignore).await?) } + /// Starts dragging the window. pub async fn start_dragging(&self) -> crate::Result<()> { Ok(self.0.startDragging().await?) } + /// Emits an event to the backend, tied to the webview window. #[inline(always)] pub async fn emit(&self, event: &str, payload: &T) -> crate::Result<()> { self.0 @@ -354,6 +615,7 @@ impl WebviewWindow { Ok(()) } + /// Listen to an event emitted by the backend that is tied to the webview window. #[inline(always)] pub async fn listen(&self, event: &str, mut handler: H) -> crate::Result where @@ -374,6 +636,7 @@ impl WebviewWindow { }) } + /// Listen to an one-off event emitted by the backend that is tied to the webview window. #[inline(always)] pub async fn once(&self, event: &str, mut handler: H) -> crate::Result where @@ -395,54 +658,89 @@ impl WebviewWindow { } } +/// A position represented in logical pixels. #[derive(Debug, Clone, PartialEq)] pub struct LogicalPosition(inner::LogicalPosition); impl LogicalPosition { - pub fn new(x: u32, y: u32) -> Self { + pub fn new(x: i32, y: i32) -> Self { Self(inner::LogicalPosition::new(x, y)) } - pub fn x(&self) -> u32 { + pub fn from_physical(physical: impl Into, scale_factor: f64) -> Self { + physical.into().to_logical(scale_factor) + } + + pub fn to_physical(self, scale_factor: f64) -> PhysicalPosition { + let x = self.x() as f64 * scale_factor; + let y = self.y() as f64 * scale_factor; + + PhysicalPosition::new(x as i32, y as i32) + } + + pub fn x(&self) -> i32 { self.0.x() } - pub fn set_x(&self, x: u32) { + pub fn set_x(&self, x: i32) { self.0.set_x(x) } - pub fn y(&self) -> u32 { + pub fn y(&self) -> i32 { self.0.y() } - pub fn set_y(&self, y: u32) { + pub fn set_y(&self, y: i32) { self.0.set_y(y) } } +impl From for Position { + fn from(pos: LogicalPosition) -> Self { + Position::Logical(pos) + } +} + +/// A position represented in physical pixels. #[derive(Debug, Clone, PartialEq)] pub struct PhysicalPosition(inner::PhysicalPosition); impl PhysicalPosition { - pub fn new(x: u32, y: u32) -> Self { + pub fn new(x: i32, y: i32) -> Self { Self(inner::PhysicalPosition::new(x, y)) } - pub fn to_logical(self, scale_factor: u32) -> LogicalPosition { - LogicalPosition(self.0.toLogical(scale_factor)) + #[inline] + pub fn from_logical(logical: impl Into, scale_factor: f64) -> Self { + logical.into().to_physical(scale_factor) } - pub fn x(&self) -> u32 { + #[inline] + pub fn to_logical(&self, scale_factor: f64) -> LogicalPosition { + let x = self.x() as f64 / scale_factor; + let y = self.y() as f64 / scale_factor; + + LogicalPosition::new(x as i32, y as i32) + } + + pub fn x(&self) -> i32 { self.0.x() } - pub fn set_x(&self, x: u32) { + pub fn set_x(&self, x: i32) { self.0.set_x(x) } - pub fn y(&self) -> u32 { + pub fn y(&self) -> i32 { self.0.y() } - pub fn set_y(&self, y: u32) { + pub fn set_y(&self, y: i32) { self.0.set_y(y) } } +impl From for Position { + fn from(pos: PhysicalPosition) -> Self { + Position::Physical(pos) + } +} + +/// A size represented in logical pixels. #[derive(Debug, Clone, PartialEq)] pub struct LogicalSize(inner::LogicalSize); @@ -465,6 +763,13 @@ impl LogicalSize { } } +impl From for Size { + fn from(size: LogicalSize) -> Self { + Size::Logical(size) + } +} + +/// A size represented in physical pixels. #[derive(Debug, Clone, PartialEq)] pub struct PhysicalSize(inner::PhysicalSize); @@ -491,28 +796,39 @@ impl PhysicalSize { } } +impl From for Size { + fn from(size: PhysicalSize) -> Self { + Size::Physical(size) + } +} + +/// Allows you to retrieve information about a given monitor. #[derive(Debug, Clone, PartialEq)] pub struct Monitor(JsValue); impl Monitor { + /// Human-readable name of the monitor pub fn name(&self) -> Option { let raw = js_sys::Reflect::get(&self.0, &JsValue::from_str("name")).unwrap(); raw.as_string() } + /// The monitor's resolution. pub fn size(&self) -> PhysicalSize { let raw = js_sys::Reflect::get(&self.0, &JsValue::from_str("size")).unwrap(); PhysicalSize(raw.unchecked_into()) } + /// The Top-left corner position of the monitor relative to the larger full screen area. pub fn position(&self) -> PhysicalPosition { let raw = js_sys::Reflect::get(&self.0, &JsValue::from_str("position")).unwrap(); PhysicalPosition(raw.unchecked_into()) } + /// The scale factor that can be used to map physical pixels to logical pixels. pub fn scale_factor(&self) -> u32 { let raw = js_sys::Reflect::get(&self.0, &JsValue::from_str("size")) .unwrap() @@ -523,19 +839,89 @@ impl Monitor { } } +/// Get an instance of [`WebviewWindow`] for the current webview window. +/// +/// # Example +/// +/// ```rust,no_run +/// use tauri_sys::window::current_window; +/// +/// # async fn main() -> Result<(), Box> { +/// let win = current_window().await?; +/// # Ok(()) +/// # } +/// ``` pub fn current_window() -> WebviewWindow { WebviewWindow(inner::getCurrent()) } +/// Gets a list of instances of [`WebviewWindow`] for all available webview windows. +/// +/// # Example +/// +/// ```rust,no_run +/// use tauri_sys::window::all_windows; +/// use web_sys::console; +/// +/// # async fn main() -> Result<(), Box> { +/// let windows = all_windows().await?; +/// +/// for win in windows { +/// console::log_1(&format!("{:?}", win).into()); +/// } +/// # Ok(()) +/// # } +/// ``` pub fn all_windows() -> Vec { inner::getAll().into_iter().map(WebviewWindow).collect() } -pub async fn current_monitor() -> Monitor { - Monitor(inner::currentMonitor().await) +/// Returns the monitor on which the window currently resides. +/// +/// Returns `None` if current monitor can't be detected. +/// +/// # Example +/// +/// ```rust,no_run +/// use tauri_sys::window::current_monitor; +/// +/// # async fn main() -> Result<(), Box> { +/// let monitor = current_monitor().await?; +/// # Ok(()) +/// # } +/// ``` +pub async fn current_monitor() -> crate::Result> { + let raw = inner::currentMonitor().await?; + + if raw.is_null() { + Ok(None) + } else { + Ok(Some(Monitor(raw))) + } } -pub async fn primary_monitor() -> Monitor { - Monitor(inner::primaryMonitor().await) + +/// Returns the primary monitor of the system. +/// +/// Returns `None` if it can't identify any monitor as a primary one. +/// +/// # Example +/// +/// ```rust,no_run +/// use tauri_sys::window::primary_monitor; +/// +/// # async fn main() -> Result<(), Box> { +/// let monitor = primary_monitor().await?; +/// # Ok(()) +/// # } +/// ``` +pub async fn primary_monitor() -> crate::Result> { + let raw = inner::primaryMonitor().await?; + + if raw.is_null() { + Ok(None) + } else { + Ok(Some(Monitor(raw))) + } } #[derive(Debug, Clone)] @@ -561,11 +947,30 @@ impl Iterator for AvailableMonitors { } } -pub async fn available_monitors() -> AvailableMonitors { - AvailableMonitors { +/// Returns the list of all the monitors available on the system. +/// +/// # Example +/// +/// ```rust,no_run +/// use tauri_sys::window::available_monitors; +/// use web_sys::console; +/// +/// # async fn main() -> Result<(), Box> { +/// let monitors = available_monitors().await?; +/// +/// for monitor in monitors { +/// console::log_1(&format!("{:?}", monitor).into()); +/// } +/// # Ok(()) +/// # } +/// ``` +pub async fn available_monitors() -> crate::Result { + let raw = inner::availableMonitors().await?; + + Ok(AvailableMonitors { idx: 0, - array: inner::availableMonitors().await.unchecked_into(), - } + array: raw.unchecked_into(), + }) } mod inner { @@ -579,15 +984,15 @@ mod inner { #[derive(Debug, Clone, PartialEq)] pub type LogicalPosition; #[wasm_bindgen(constructor)] - pub fn new(x: u32, y: u32) -> LogicalPosition; + pub fn new(x: i32, y: i32) -> LogicalPosition; #[wasm_bindgen(method, getter)] - pub fn x(this: &LogicalPosition) -> u32; + pub fn x(this: &LogicalPosition) -> i32; #[wasm_bindgen(method, setter)] - pub fn set_x(this: &LogicalPosition, x: u32); + pub fn set_x(this: &LogicalPosition, x: i32); #[wasm_bindgen(method, getter)] - pub fn y(this: &LogicalPosition) -> u32; + pub fn y(this: &LogicalPosition) -> i32; #[wasm_bindgen(method, setter)] - pub fn set_y(this: &LogicalPosition, y: u32); + pub fn set_y(this: &LogicalPosition, y: i32); } #[wasm_bindgen(module = "/src/window.js")] @@ -595,17 +1000,17 @@ mod inner { #[derive(Debug, Clone, PartialEq)] pub type PhysicalPosition; #[wasm_bindgen(constructor)] - pub fn new(x: u32, y: u32) -> PhysicalPosition; + pub fn new(x: i32, y: i32) -> PhysicalPosition; #[wasm_bindgen(method)] - pub fn toLogical(this: &PhysicalPosition, scaleFactor: u32) -> LogicalPosition; + pub fn toLogical(this: &PhysicalPosition, scaleFactor: i32) -> LogicalPosition; #[wasm_bindgen(method, getter)] - pub fn x(this: &PhysicalPosition) -> u32; + pub fn x(this: &PhysicalPosition) -> i32; #[wasm_bindgen(method, setter)] - pub fn set_x(this: &PhysicalPosition, x: u32); + pub fn set_x(this: &PhysicalPosition, x: i32); #[wasm_bindgen(method, getter)] - pub fn y(this: &PhysicalPosition) -> u32; + pub fn y(this: &PhysicalPosition) -> i32; #[wasm_bindgen(method, setter)] - pub fn set_y(this: &PhysicalPosition, y: u32); + pub fn set_y(this: &PhysicalPosition, y: i32); } #[wasm_bindgen(module = "/src/window.js")] @@ -809,7 +1214,7 @@ mod inner { #[derive(Debug, Clone, PartialEq)] pub type WebviewWindow; #[wasm_bindgen(constructor)] - pub fn new(label: &str, options: ()) -> WebviewWindow; + pub fn new(label: &str, options: JsValue) -> WebviewWindow; #[wasm_bindgen(static_method_of = WebviewWindow)] pub fn getByLabel(label: &str) -> Option; } @@ -818,8 +1223,11 @@ mod inner { extern "C" { pub fn getCurrent() -> WebviewWindow; pub fn getAll() -> Vec; - pub async fn currentMonitor() -> JsValue; - pub async fn primaryMonitor() -> JsValue; - pub async fn availableMonitors() -> JsValue; + #[wasm_bindgen(catch)] + pub async fn currentMonitor() -> Result; + #[wasm_bindgen(catch)] + pub async fn primaryMonitor() -> Result; + #[wasm_bindgen(catch)] + pub async fn availableMonitors() -> Result; } }