mirror of
https://github.com/tauri-apps/tauri.git
synced 2026-04-01 10:01:07 +02:00
committed by
GitHub
parent
d99c5d583b
commit
f46175d5d4
6
.changes/dialog-type.md
Normal file
6
.changes/dialog-type.md
Normal file
@@ -0,0 +1,6 @@
|
||||
---
|
||||
"tauri": patch
|
||||
"api": patch
|
||||
---
|
||||
|
||||
Expose option to set the dialog type.
|
||||
File diff suppressed because one or more lines are too long
@@ -124,6 +124,95 @@ macro_rules! file_dialog_builder {
|
||||
};
|
||||
}
|
||||
|
||||
macro_rules! message_dialog_builder {
|
||||
() => {
|
||||
/// A builder for message dialogs.
|
||||
pub struct MessageDialogBuilder(rfd::MessageDialog);
|
||||
|
||||
impl MessageDialogBuilder {
|
||||
/// Creates a new message dialog builder.
|
||||
pub fn new(title: impl AsRef<str>, message: impl AsRef<str>) -> Self {
|
||||
let title = title.as_ref().to_string();
|
||||
let message = message.as_ref().to_string();
|
||||
Self(
|
||||
rfd::MessageDialog::new()
|
||||
.set_title(&title)
|
||||
.set_description(&message),
|
||||
)
|
||||
}
|
||||
|
||||
/// Set parent windows explicitly (optional)
|
||||
///
|
||||
/// ## Platform-specific
|
||||
///
|
||||
/// - **Linux:** Unsupported.
|
||||
pub fn parent<W: raw_window_handle::HasRawWindowHandle>(mut self, parent: &W) -> Self {
|
||||
self.0 = self.0.set_parent(parent);
|
||||
self
|
||||
}
|
||||
|
||||
/// Set the set of button that will be displayed on the dialog.
|
||||
pub fn buttons(mut self, buttons: MessageDialogButtons) -> Self {
|
||||
self.0 = self.0.set_buttons(buttons.into());
|
||||
self
|
||||
}
|
||||
|
||||
/// Set type of a dialog.
|
||||
///
|
||||
/// Depending on the system it can result in type specific icon to show up,
|
||||
/// the will inform user it message is a error, warning or just information.
|
||||
pub fn kind(mut self, kind: MessageDialogKind) -> Self {
|
||||
self.0 = self.0.set_level(kind.into());
|
||||
self
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
/// Options for action buttons on message dialogs.
|
||||
#[non_exhaustive]
|
||||
#[derive(Debug, Copy, Clone, PartialEq, Eq, Hash)]
|
||||
pub enum MessageDialogButtons {
|
||||
/// Ok button.
|
||||
Ok,
|
||||
/// Ok and Cancel buttons.
|
||||
OkCancel,
|
||||
/// Yes and No buttons.
|
||||
YesNo,
|
||||
}
|
||||
|
||||
impl From<MessageDialogButtons> for rfd::MessageButtons {
|
||||
fn from(kind: MessageDialogButtons) -> Self {
|
||||
match kind {
|
||||
MessageDialogButtons::Ok => Self::Ok,
|
||||
MessageDialogButtons::OkCancel => Self::OkCancel,
|
||||
MessageDialogButtons::YesNo => Self::YesNo,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Types of message, ask and confirm dialogs.
|
||||
#[non_exhaustive]
|
||||
#[derive(Debug, Copy, Clone, PartialEq, Eq, Hash)]
|
||||
pub enum MessageDialogKind {
|
||||
/// Information dialog.
|
||||
Info,
|
||||
/// Warning dialog.
|
||||
Warning,
|
||||
/// Error dialog.
|
||||
Error,
|
||||
}
|
||||
|
||||
impl From<MessageDialogKind> for rfd::MessageLevel {
|
||||
fn from(kind: MessageDialogKind) -> Self {
|
||||
match kind {
|
||||
MessageDialogKind::Info => Self::Info,
|
||||
MessageDialogKind::Warning => Self::Warning,
|
||||
MessageDialogKind::Error => Self::Error,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Blocking interfaces for the dialog APIs.
|
||||
///
|
||||
/// The blocking APIs will block the current thread to execute instead of relying on callback closures,
|
||||
@@ -132,11 +221,13 @@ macro_rules! file_dialog_builder {
|
||||
/// **NOTE:** You cannot block the main thread when executing the dialog APIs, so you must use the [`crate::api::dialog`] methods instead.
|
||||
/// Examples of main thread context are the [`crate::App::run`] closure and non-async commmands.
|
||||
pub mod blocking {
|
||||
use super::{MessageDialogButtons, MessageDialogKind};
|
||||
use crate::{Runtime, Window};
|
||||
use std::path::{Path, PathBuf};
|
||||
use std::sync::mpsc::sync_channel;
|
||||
|
||||
file_dialog_builder!();
|
||||
message_dialog_builder!();
|
||||
|
||||
impl FileDialogBuilder {
|
||||
/// Shows the dialog to select a single file.
|
||||
@@ -233,6 +324,22 @@ pub mod blocking {
|
||||
}
|
||||
}
|
||||
|
||||
impl MessageDialogBuilder {
|
||||
//// Shows a message dialog.
|
||||
///
|
||||
/// - In `Ok` dialog, it will return `true` when `OK` was pressed.
|
||||
/// - In `OkCancel` dialog, it will return `true` when `OK` was pressed.
|
||||
/// - In `YesNo` dialog, it will return `true` when `Yes` was pressed.
|
||||
pub fn show(self) -> bool {
|
||||
let (tx, rx) = sync_channel(1);
|
||||
let f = move |response| {
|
||||
tx.send(response).unwrap();
|
||||
};
|
||||
run_dialog!(self.0.show(), f);
|
||||
rx.recv().unwrap()
|
||||
}
|
||||
}
|
||||
|
||||
/// Displays a dialog with a message and an optional title with a "yes" and a "no" button and wait for it to be closed.
|
||||
///
|
||||
/// This is a blocking operation,
|
||||
@@ -314,6 +421,7 @@ pub mod blocking {
|
||||
title,
|
||||
message,
|
||||
buttons,
|
||||
MessageDialogKind::Info,
|
||||
move |response| {
|
||||
tx.send(response).unwrap();
|
||||
},
|
||||
@@ -323,10 +431,12 @@ pub mod blocking {
|
||||
}
|
||||
|
||||
mod nonblocking {
|
||||
use super::{MessageDialogButtons, MessageDialogKind};
|
||||
use crate::{Runtime, Window};
|
||||
use std::path::{Path, PathBuf};
|
||||
|
||||
file_dialog_builder!();
|
||||
message_dialog_builder!();
|
||||
|
||||
impl FileDialogBuilder {
|
||||
/// Shows the dialog to select a single file.
|
||||
@@ -431,6 +541,17 @@ mod nonblocking {
|
||||
}
|
||||
}
|
||||
|
||||
impl MessageDialogBuilder {
|
||||
/// Shows a message dialog:
|
||||
///
|
||||
/// - In `Ok` dialog, it will call the closure with `true` when `OK` was pressed
|
||||
/// - In `OkCancel` dialog, it will call the closure with `true` when `OK` was pressed
|
||||
/// - In `YesNo` dialog, it will call the closure with `true` when `Yes` was pressed
|
||||
pub fn show<F: FnOnce(bool) + Send + 'static>(self, f: F) {
|
||||
run_dialog!(self.0.show(), f);
|
||||
}
|
||||
}
|
||||
|
||||
/// Displays a non-blocking dialog with a message and an optional title with a "yes" and a "no" button.
|
||||
///
|
||||
/// This is not a blocking operation,
|
||||
@@ -453,7 +574,14 @@ mod nonblocking {
|
||||
message: impl AsRef<str>,
|
||||
f: F,
|
||||
) {
|
||||
run_message_dialog(parent_window, title, message, rfd::MessageButtons::YesNo, f)
|
||||
run_message_dialog(
|
||||
parent_window,
|
||||
title,
|
||||
message,
|
||||
rfd::MessageButtons::YesNo,
|
||||
MessageDialogKind::Info,
|
||||
f,
|
||||
)
|
||||
}
|
||||
|
||||
/// Displays a non-blocking dialog with a message and an optional title with an "ok" and a "cancel" button.
|
||||
@@ -483,6 +611,7 @@ mod nonblocking {
|
||||
title,
|
||||
message,
|
||||
rfd::MessageButtons::OkCancel,
|
||||
MessageDialogKind::Info,
|
||||
f,
|
||||
)
|
||||
}
|
||||
@@ -511,6 +640,7 @@ mod nonblocking {
|
||||
title,
|
||||
message,
|
||||
rfd::MessageButtons::Ok,
|
||||
MessageDialogKind::Info,
|
||||
|_| {},
|
||||
)
|
||||
}
|
||||
@@ -521,6 +651,7 @@ mod nonblocking {
|
||||
title: impl AsRef<str>,
|
||||
message: impl AsRef<str>,
|
||||
buttons: rfd::MessageButtons,
|
||||
level: MessageDialogKind,
|
||||
f: F,
|
||||
) {
|
||||
let title = title.as_ref().to_string();
|
||||
@@ -530,7 +661,7 @@ mod nonblocking {
|
||||
.set_title(&title)
|
||||
.set_description(&message)
|
||||
.set_buttons(buttons)
|
||||
.set_level(rfd::MessageLevel::Info);
|
||||
.set_level(level.into());
|
||||
|
||||
#[cfg(any(windows, target_os = "macos"))]
|
||||
{
|
||||
|
||||
@@ -8,11 +8,38 @@ use super::{InvokeContext, InvokeResponse};
|
||||
use crate::Runtime;
|
||||
#[cfg(any(dialog_open, dialog_save))]
|
||||
use crate::{api::dialog::blocking::FileDialogBuilder, Manager, Scopes};
|
||||
use serde::Deserialize;
|
||||
use serde::{Deserialize, Deserializer};
|
||||
use tauri_macros::{command_enum, module_command_handler, CommandModule};
|
||||
|
||||
use std::path::PathBuf;
|
||||
|
||||
#[cfg(any(dialog_message, dialog_ask, dialog_confirm))]
|
||||
macro_rules! message_dialog {
|
||||
($fn_name: ident, $allowlist: ident, $buttons: expr) => {
|
||||
#[module_command_handler($allowlist)]
|
||||
fn $fn_name<R: Runtime>(
|
||||
context: InvokeContext<R>,
|
||||
title: Option<String>,
|
||||
message: String,
|
||||
level: Option<MessageDialogType>,
|
||||
) -> super::Result<bool> {
|
||||
let mut builder = crate::api::dialog::blocking::MessageDialogBuilder::new(
|
||||
title.unwrap_or_else(|| context.window.app_handle.package_info().name.clone()),
|
||||
message,
|
||||
)
|
||||
.buttons($buttons);
|
||||
#[cfg(any(windows, target_os = "macos"))]
|
||||
{
|
||||
builder = builder.parent(&context.window);
|
||||
}
|
||||
if let Some(level) = level {
|
||||
builder = builder.kind(level.into());
|
||||
}
|
||||
Ok(builder.show())
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
#[allow(dead_code)]
|
||||
#[derive(Debug, Clone, Deserialize)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
@@ -57,6 +84,44 @@ pub struct SaveDialogOptions {
|
||||
pub default_path: Option<PathBuf>,
|
||||
}
|
||||
|
||||
/// Types of message, ask and confirm dialogs.
|
||||
#[non_exhaustive]
|
||||
#[derive(Debug, Copy, Clone, PartialEq, Eq, Hash)]
|
||||
pub enum MessageDialogType {
|
||||
/// Information dialog.
|
||||
Info,
|
||||
/// Warning dialog.
|
||||
Warning,
|
||||
/// Error dialog.
|
||||
Error,
|
||||
}
|
||||
|
||||
impl<'de> Deserialize<'de> for MessageDialogType {
|
||||
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
|
||||
where
|
||||
D: Deserializer<'de>,
|
||||
{
|
||||
let s = String::deserialize(deserializer)?;
|
||||
Ok(match s.to_lowercase().as_str() {
|
||||
"info" => MessageDialogType::Info,
|
||||
"warning" => MessageDialogType::Warning,
|
||||
"error" => MessageDialogType::Error,
|
||||
_ => MessageDialogType::Info,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(any(dialog_message, dialog_ask, dialog_confirm))]
|
||||
impl From<MessageDialogType> for crate::api::dialog::MessageDialogKind {
|
||||
fn from(kind: MessageDialogType) -> Self {
|
||||
match kind {
|
||||
MessageDialogType::Info => Self::Info,
|
||||
MessageDialogType::Warning => Self::Warning,
|
||||
MessageDialogType::Error => Self::Error,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// The API descriptor.
|
||||
#[command_enum]
|
||||
#[derive(Deserialize, CommandModule)]
|
||||
@@ -73,16 +138,22 @@ pub enum Cmd {
|
||||
MessageDialog {
|
||||
title: Option<String>,
|
||||
message: String,
|
||||
#[serde(rename = "type")]
|
||||
level: Option<MessageDialogType>,
|
||||
},
|
||||
#[cmd(dialog_ask, "dialog > ask")]
|
||||
AskDialog {
|
||||
title: Option<String>,
|
||||
message: String,
|
||||
#[serde(rename = "type")]
|
||||
level: Option<MessageDialogType>,
|
||||
},
|
||||
#[cmd(dialog_confirm, "dialog > confirm")]
|
||||
ConfirmDialog {
|
||||
title: Option<String>,
|
||||
message: String,
|
||||
#[serde(rename = "type")]
|
||||
level: Option<MessageDialogType>,
|
||||
},
|
||||
}
|
||||
|
||||
@@ -170,45 +241,23 @@ impl Cmd {
|
||||
Ok(path)
|
||||
}
|
||||
|
||||
#[module_command_handler(dialog_message)]
|
||||
fn message_dialog<R: Runtime>(
|
||||
context: InvokeContext<R>,
|
||||
title: Option<String>,
|
||||
message: String,
|
||||
) -> super::Result<()> {
|
||||
crate::api::dialog::blocking::message(
|
||||
Some(&context.window),
|
||||
title.unwrap_or_else(|| context.window.app_handle.package_info().name.clone()),
|
||||
message,
|
||||
);
|
||||
Ok(())
|
||||
}
|
||||
message_dialog!(
|
||||
message_dialog,
|
||||
dialog_message,
|
||||
crate::api::dialog::MessageDialogButtons::Ok
|
||||
);
|
||||
|
||||
#[module_command_handler(dialog_ask)]
|
||||
fn ask_dialog<R: Runtime>(
|
||||
context: InvokeContext<R>,
|
||||
title: Option<String>,
|
||||
message: String,
|
||||
) -> super::Result<bool> {
|
||||
Ok(crate::api::dialog::blocking::ask(
|
||||
Some(&context.window),
|
||||
title.unwrap_or_else(|| context.window.app_handle.package_info().name.clone()),
|
||||
message,
|
||||
))
|
||||
}
|
||||
message_dialog!(
|
||||
ask_dialog,
|
||||
dialog_ask,
|
||||
crate::api::dialog::MessageDialogButtons::YesNo
|
||||
);
|
||||
|
||||
#[module_command_handler(dialog_confirm)]
|
||||
fn confirm_dialog<R: Runtime>(
|
||||
context: InvokeContext<R>,
|
||||
title: Option<String>,
|
||||
message: String,
|
||||
) -> super::Result<bool> {
|
||||
Ok(crate::api::dialog::blocking::confirm(
|
||||
Some(&context.window),
|
||||
title.unwrap_or_else(|| context.window.app_handle.package_info().name.clone()),
|
||||
message,
|
||||
))
|
||||
}
|
||||
message_dialog!(
|
||||
confirm_dialog,
|
||||
dialog_confirm,
|
||||
crate::api::dialog::MessageDialogButtons::OkCancel
|
||||
);
|
||||
}
|
||||
|
||||
#[cfg(any(dialog_open, dialog_save))]
|
||||
|
||||
14
examples/api/src-tauri/Cargo.lock
generated
14
examples/api/src-tauri/Cargo.lock
generated
@@ -3112,7 +3112,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "tauri"
|
||||
version = "1.0.0-rc.10"
|
||||
version = "1.0.0-rc.11"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"attohttpc",
|
||||
@@ -3173,7 +3173,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "tauri-build"
|
||||
version = "1.0.0-rc.8"
|
||||
version = "1.0.0-rc.9"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"cargo_toml",
|
||||
@@ -3186,7 +3186,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "tauri-codegen"
|
||||
version = "1.0.0-rc.6"
|
||||
version = "1.0.0-rc.7"
|
||||
dependencies = [
|
||||
"base64",
|
||||
"brotli",
|
||||
@@ -3206,7 +3206,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "tauri-macros"
|
||||
version = "1.0.0-rc.6"
|
||||
version = "1.0.0-rc.7"
|
||||
dependencies = [
|
||||
"heck 0.4.0",
|
||||
"proc-macro2",
|
||||
@@ -3218,7 +3218,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "tauri-runtime"
|
||||
version = "0.5.0"
|
||||
version = "0.5.1"
|
||||
dependencies = [
|
||||
"gtk",
|
||||
"http",
|
||||
@@ -3235,7 +3235,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "tauri-runtime-wry"
|
||||
version = "0.5.1"
|
||||
version = "0.5.2"
|
||||
dependencies = [
|
||||
"cocoa",
|
||||
"gtk",
|
||||
@@ -3252,7 +3252,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "tauri-utils"
|
||||
version = "1.0.0-rc.6"
|
||||
version = "1.0.0-rc.7"
|
||||
dependencies = [
|
||||
"aes-gcm",
|
||||
"brotli",
|
||||
|
||||
@@ -74,6 +74,13 @@ interface SaveDialogOptions {
|
||||
defaultPath?: string
|
||||
}
|
||||
|
||||
interface MessageDialogOptions {
|
||||
/** The title of the dialog. Defaults to the app name. */
|
||||
title?: string
|
||||
/** The type of the dialog. Defaults to `info`. */
|
||||
type?: 'info' | 'warning' | 'error'
|
||||
}
|
||||
|
||||
/**
|
||||
* Open a file/directory selection dialog.
|
||||
*
|
||||
@@ -132,16 +139,22 @@ async function save(options: SaveDialogOptions = {}): Promise<string> {
|
||||
* Shows a message dialog with an `Ok` button.
|
||||
*
|
||||
* @param {string} message The message to show.
|
||||
* @param {string|MessageDialogOptions|undefined} options The dialog's options. If a string, it represents the dialog title.
|
||||
*
|
||||
* @return {Promise<void>} A promise indicating the success or failure of the operation.
|
||||
*/
|
||||
async function message(message: string, title?: string): Promise<void> {
|
||||
async function message(
|
||||
message: string,
|
||||
options?: string | MessageDialogOptions
|
||||
): Promise<void> {
|
||||
const opts = typeof options === 'string' ? { title: options } : options
|
||||
return invokeTauriCommand({
|
||||
__tauriModule: 'Dialog',
|
||||
message: {
|
||||
cmd: 'messageDialog',
|
||||
title,
|
||||
message
|
||||
message,
|
||||
title: opts?.title,
|
||||
type: opts?.type
|
||||
}
|
||||
})
|
||||
}
|
||||
@@ -150,17 +163,22 @@ async function message(message: string, title?: string): Promise<void> {
|
||||
* Shows a question dialog with `Yes` and `No` buttons.
|
||||
*
|
||||
* @param {string} message The message to show.
|
||||
* @param {string|undefined} title The dialog's title. Defaults to the application name.
|
||||
* @param {string|MessageDialogOptions|undefined} options The dialog's options. If a string, it represents the dialog title.
|
||||
*
|
||||
* @return {Promise<void>} A promise resolving to a boolean indicating whether `Yes` was clicked or not.
|
||||
*/
|
||||
async function ask(message: string, title?: string): Promise<boolean> {
|
||||
async function ask(
|
||||
message: string,
|
||||
options?: string | MessageDialogOptions
|
||||
): Promise<boolean> {
|
||||
const opts = typeof options === 'string' ? { title: options } : options
|
||||
return invokeTauriCommand({
|
||||
__tauriModule: 'Dialog',
|
||||
message: {
|
||||
cmd: 'askDialog',
|
||||
title,
|
||||
message
|
||||
message,
|
||||
title: opts?.title,
|
||||
type: opts?.type
|
||||
}
|
||||
})
|
||||
}
|
||||
@@ -169,17 +187,22 @@ async function ask(message: string, title?: string): Promise<boolean> {
|
||||
* Shows a question dialog with `Ok` and `Cancel` buttons.
|
||||
*
|
||||
* @param {string} message The message to show.
|
||||
* @param {string|undefined} title The dialog's title. Defaults to the application name.
|
||||
* @param {string|MessageDialogOptions|undefined} options The dialog's options. If a string, it represents the dialog title.
|
||||
*
|
||||
* @return {Promise<void>} A promise resolving to a boolean indicating whether `Ok` was clicked or not.
|
||||
*/
|
||||
async function confirm(message: string, title?: string): Promise<boolean> {
|
||||
async function confirm(
|
||||
message: string,
|
||||
options?: string | MessageDialogOptions
|
||||
): Promise<boolean> {
|
||||
const opts = typeof options === 'string' ? { title: options } : options
|
||||
return invokeTauriCommand({
|
||||
__tauriModule: 'Dialog',
|
||||
message: {
|
||||
cmd: 'confirmDialog',
|
||||
title,
|
||||
message
|
||||
message,
|
||||
title: opts?.title,
|
||||
type: opts?.type
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user