feat(core): expose option to set dialog type, closes #4183 (#4187)

This commit is contained in:
Lucas Fernandes Nogueira
2022-05-21 07:24:39 -07:00
committed by GitHub
parent d99c5d583b
commit f46175d5d4
6 changed files with 268 additions and 59 deletions

6
.changes/dialog-type.md Normal file
View 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

View File

@@ -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"))]
{

View File

@@ -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))]

View File

@@ -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",

View File

@@ -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
}
})
}