Skip to content

Commit

Permalink
feat(core): expose option to set dialog type, closes #4183 (#4187)
Browse files Browse the repository at this point in the history
  • Loading branch information
lucasfernog authored May 21, 2022
1 parent d99c5d5 commit f46175d
Show file tree
Hide file tree
Showing 6 changed files with 268 additions and 59 deletions.
6 changes: 6 additions & 0 deletions .changes/dialog-type.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
---
"tauri": patch
"api": patch
---

Expose option to set the dialog type.
2 changes: 1 addition & 1 deletion core/tauri/scripts/bundle.js

Large diffs are not rendered by default.

135 changes: 133 additions & 2 deletions core/tauri/src/api/dialog.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand All @@ -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.
Expand Down Expand Up @@ -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,
Expand Down Expand Up @@ -314,6 +421,7 @@ pub mod blocking {
title,
message,
buttons,
MessageDialogKind::Info,
move |response| {
tx.send(response).unwrap();
},
Expand All @@ -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.
Expand Down Expand Up @@ -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,
Expand All @@ -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.
Expand Down Expand Up @@ -483,6 +611,7 @@ mod nonblocking {
title,
message,
rfd::MessageButtons::OkCancel,
MessageDialogKind::Info,
f,
)
}
Expand Down Expand Up @@ -511,6 +640,7 @@ mod nonblocking {
title,
message,
rfd::MessageButtons::Ok,
MessageDialogKind::Info,
|_| {},
)
}
Expand All @@ -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();
Expand All @@ -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"))]
{
Expand Down
125 changes: 87 additions & 38 deletions core/tauri/src/endpoints/dialog.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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")]
Expand Down Expand Up @@ -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)]
Expand All @@ -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>,
},
}

Expand Down Expand Up @@ -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))]
Expand Down
Loading

0 comments on commit f46175d

Please sign in to comment.