mirror of
https://github.com/tauri-apps/plugins-workspace.git
synced 2026-04-27 11:56:05 +02:00
2302c2db1c
* Fix `ask`'s button texts being ok and cancel * Update change file
236 lines
7.7 KiB
Rust
236 lines
7.7 KiB
Rust
// Copyright 2019-2023 Tauri Programme within The Commons Conservancy
|
|
// SPDX-License-Identifier: Apache-2.0
|
|
// SPDX-License-Identifier: MIT
|
|
|
|
//! Use native message and file open/save dialogs.
|
|
//!
|
|
//! This module exposes non-blocking APIs on its root, relying on callback closures
|
|
//! to give results back. This is particularly useful when running dialogs from the main thread.
|
|
//! When using on asynchronous contexts such as async commands, the [`blocking`] APIs are recommended.
|
|
|
|
use raw_window_handle::{HasDisplayHandle, HasWindowHandle, RawDisplayHandle, RawWindowHandle};
|
|
use rfd::{AsyncFileDialog, AsyncMessageDialog};
|
|
use serde::de::DeserializeOwned;
|
|
use tauri::{plugin::PluginApi, AppHandle, Runtime};
|
|
|
|
use crate::{models::*, FileDialogBuilder, FilePath, MessageDialogBuilder, OK};
|
|
|
|
pub fn init<R: Runtime, C: DeserializeOwned>(
|
|
app: &AppHandle<R>,
|
|
_api: PluginApi<R, C>,
|
|
) -> crate::Result<Dialog<R>> {
|
|
Ok(Dialog(app.clone()))
|
|
}
|
|
|
|
/// Access to the dialog APIs.
|
|
#[derive(Debug)]
|
|
pub struct Dialog<R: Runtime>(AppHandle<R>);
|
|
|
|
impl<R: Runtime> Clone for Dialog<R> {
|
|
fn clone(&self) -> Self {
|
|
Self(self.0.clone())
|
|
}
|
|
}
|
|
|
|
impl<R: Runtime> Dialog<R> {
|
|
pub(crate) fn app_handle(&self) -> &AppHandle<R> {
|
|
&self.0
|
|
}
|
|
}
|
|
|
|
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,
|
|
}
|
|
}
|
|
}
|
|
|
|
#[derive(Debug)]
|
|
pub(crate) struct WindowHandle {
|
|
window_handle: RawWindowHandle,
|
|
display_handle: RawDisplayHandle,
|
|
}
|
|
|
|
impl WindowHandle {
|
|
pub(crate) fn new(window_handle: RawWindowHandle, display_handle: RawDisplayHandle) -> Self {
|
|
Self {
|
|
window_handle,
|
|
display_handle,
|
|
}
|
|
}
|
|
}
|
|
|
|
impl HasWindowHandle for WindowHandle {
|
|
fn window_handle(
|
|
&self,
|
|
) -> Result<raw_window_handle::WindowHandle<'_>, raw_window_handle::HandleError> {
|
|
Ok(unsafe { raw_window_handle::WindowHandle::borrow_raw(self.window_handle) })
|
|
}
|
|
}
|
|
|
|
impl HasDisplayHandle for WindowHandle {
|
|
fn display_handle(
|
|
&self,
|
|
) -> Result<raw_window_handle::DisplayHandle<'_>, raw_window_handle::HandleError> {
|
|
Ok(unsafe { raw_window_handle::DisplayHandle::borrow_raw(self.display_handle) })
|
|
}
|
|
}
|
|
|
|
impl<R: Runtime> From<FileDialogBuilder<R>> for AsyncFileDialog {
|
|
fn from(d: FileDialogBuilder<R>) -> Self {
|
|
let mut builder = AsyncFileDialog::new();
|
|
|
|
if let Some(title) = d.title {
|
|
builder = builder.set_title(title);
|
|
}
|
|
if let Some(starting_directory) = d.starting_directory {
|
|
builder = builder.set_directory(starting_directory);
|
|
}
|
|
if let Some(file_name) = d.file_name {
|
|
builder = builder.set_file_name(file_name);
|
|
}
|
|
for filter in d.filters {
|
|
let v: Vec<&str> = filter.extensions.iter().map(|x| &**x).collect();
|
|
builder = builder.add_filter(&filter.name, &v);
|
|
}
|
|
#[cfg(desktop)]
|
|
if let Some(parent) = d.parent {
|
|
builder = builder.set_parent(&parent);
|
|
}
|
|
|
|
builder = builder.set_can_create_directories(d.can_create_directories.unwrap_or(true));
|
|
|
|
builder
|
|
}
|
|
}
|
|
|
|
impl From<MessageDialogButtons> for rfd::MessageButtons {
|
|
fn from(value: MessageDialogButtons) -> Self {
|
|
match value {
|
|
MessageDialogButtons::Ok => Self::Ok,
|
|
MessageDialogButtons::OkCancel => Self::OkCancel,
|
|
MessageDialogButtons::YesNo => Self::YesNo,
|
|
MessageDialogButtons::OkCustom(ok) => Self::OkCustom(ok),
|
|
MessageDialogButtons::OkCancelCustom(ok, cancel) => Self::OkCancelCustom(ok, cancel),
|
|
}
|
|
}
|
|
}
|
|
|
|
impl<R: Runtime> From<MessageDialogBuilder<R>> for AsyncMessageDialog {
|
|
fn from(d: MessageDialogBuilder<R>) -> Self {
|
|
let mut dialog = AsyncMessageDialog::new()
|
|
.set_title(&d.title)
|
|
.set_description(&d.message)
|
|
.set_level(d.kind.into())
|
|
.set_buttons(d.buttons.into());
|
|
|
|
if let Some(parent) = d.parent {
|
|
dialog = dialog.set_parent(&parent);
|
|
}
|
|
|
|
dialog
|
|
}
|
|
}
|
|
|
|
pub fn pick_file<R: Runtime, F: FnOnce(Option<FilePath>) + Send + 'static>(
|
|
dialog: FileDialogBuilder<R>,
|
|
f: F,
|
|
) {
|
|
let f = |path: Option<rfd::FileHandle>| f(path.map(|p| p.path().to_path_buf().into()));
|
|
let handle = dialog.dialog.app_handle().to_owned();
|
|
let _ = handle.run_on_main_thread(move || {
|
|
let dialog = AsyncFileDialog::from(dialog).pick_file();
|
|
std::thread::spawn(move || f(tauri::async_runtime::block_on(dialog)));
|
|
});
|
|
}
|
|
|
|
pub fn pick_files<R: Runtime, F: FnOnce(Option<Vec<FilePath>>) + Send + 'static>(
|
|
dialog: FileDialogBuilder<R>,
|
|
f: F,
|
|
) {
|
|
let f = |paths: Option<Vec<rfd::FileHandle>>| {
|
|
f(paths.map(|list| {
|
|
list.into_iter()
|
|
.map(|p| p.path().to_path_buf().into())
|
|
.collect()
|
|
}))
|
|
};
|
|
let handle = dialog.dialog.app_handle().to_owned();
|
|
let _ = handle.run_on_main_thread(move || {
|
|
let dialog = AsyncFileDialog::from(dialog).pick_files();
|
|
std::thread::spawn(move || f(tauri::async_runtime::block_on(dialog)));
|
|
});
|
|
}
|
|
|
|
pub fn pick_folder<R: Runtime, F: FnOnce(Option<FilePath>) + Send + 'static>(
|
|
dialog: FileDialogBuilder<R>,
|
|
f: F,
|
|
) {
|
|
let f = |path: Option<rfd::FileHandle>| f(path.map(|p| p.path().to_path_buf().into()));
|
|
let handle = dialog.dialog.app_handle().to_owned();
|
|
let _ = handle.run_on_main_thread(move || {
|
|
let dialog = AsyncFileDialog::from(dialog).pick_folder();
|
|
std::thread::spawn(move || f(tauri::async_runtime::block_on(dialog)));
|
|
});
|
|
}
|
|
|
|
pub fn pick_folders<R: Runtime, F: FnOnce(Option<Vec<FilePath>>) + Send + 'static>(
|
|
dialog: FileDialogBuilder<R>,
|
|
f: F,
|
|
) {
|
|
let f = |paths: Option<Vec<rfd::FileHandle>>| {
|
|
f(paths.map(|list| {
|
|
list.into_iter()
|
|
.map(|p| p.path().to_path_buf().into())
|
|
.collect()
|
|
}))
|
|
};
|
|
let handle = dialog.dialog.app_handle().to_owned();
|
|
let _ = handle.run_on_main_thread(move || {
|
|
let dialog = AsyncFileDialog::from(dialog).pick_folders();
|
|
std::thread::spawn(move || f(tauri::async_runtime::block_on(dialog)));
|
|
});
|
|
}
|
|
|
|
pub fn save_file<R: Runtime, F: FnOnce(Option<FilePath>) + Send + 'static>(
|
|
dialog: FileDialogBuilder<R>,
|
|
f: F,
|
|
) {
|
|
let f = |path: Option<rfd::FileHandle>| f(path.map(|p| p.path().to_path_buf().into()));
|
|
let handle = dialog.dialog.app_handle().to_owned();
|
|
let _ = handle.run_on_main_thread(move || {
|
|
let dialog = AsyncFileDialog::from(dialog).save_file();
|
|
std::thread::spawn(move || f(tauri::async_runtime::block_on(dialog)));
|
|
});
|
|
}
|
|
|
|
/// Shows a message dialog
|
|
pub fn show_message_dialog<R: Runtime, F: FnOnce(bool) + Send + 'static>(
|
|
dialog: MessageDialogBuilder<R>,
|
|
f: F,
|
|
) {
|
|
use rfd::MessageDialogResult;
|
|
|
|
let ok_label = match &dialog.buttons {
|
|
MessageDialogButtons::OkCustom(ok) => Some(ok.clone()),
|
|
MessageDialogButtons::OkCancelCustom(ok, _) => Some(ok.clone()),
|
|
_ => None,
|
|
};
|
|
let f = move |res| {
|
|
f(match res {
|
|
MessageDialogResult::Ok | MessageDialogResult::Yes => true,
|
|
MessageDialogResult::Custom(s) => ok_label.map_or(s == OK, |ok_label| ok_label == s),
|
|
_ => false,
|
|
});
|
|
};
|
|
|
|
let handle = dialog.dialog.app_handle().to_owned();
|
|
let _ = handle.run_on_main_thread(move || {
|
|
let dialog = AsyncMessageDialog::from(dialog).show();
|
|
std::thread::spawn(move || f(tauri::async_runtime::block_on(dialog)));
|
|
});
|
|
}
|