Skip to content

Commit e98c1af

Browse files
authored
feat(core): expose message dialog APIs, fix window.confirm, implement HasRawWindowHandle for Window, closes #2535 (#2700)
1 parent 0e4d12b commit e98c1af

File tree

9 files changed

+164
-89
lines changed

9 files changed

+164
-89
lines changed
Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
"api": patch
3+
---
4+
5+
Expose `ask`, `message` and `confirm` APIs on the dialog module.

.changes/raw-window-handle.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
"tauri": patch
3+
---
4+
5+
Implement `raw_window_handle::RawWindowHandle` for `tauri::Window` on `Windows` and `macOS`. The `tauri::api::dialog::window_parent` function was removed since now you can use the window directly.

.changes/window-confirm.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
"tauri": patch
3+
---
4+
5+
Show `Ok/Cancel` buttons instead of `Yes/No` when executing `window.confirm`.

core/tauri/scripts/bundle.js

Lines changed: 1 addition & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

core/tauri/scripts/core.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -347,7 +347,7 @@
347347
{
348348
__tauriModule: 'Dialog',
349349
message: {
350-
cmd: 'askDialog',
350+
cmd: 'confirmDialog',
351351
message: message
352352
}
353353
},

core/tauri/src/api/dialog.rs

Lines changed: 37 additions & 67 deletions
Original file line numberDiff line numberDiff line change
@@ -32,48 +32,6 @@ macro_rules! run_dialog {
3232
}};
3333
}
3434

35-
/// Window parent definition.
36-
#[cfg(any(windows, target_os = "macos"))]
37-
#[cfg_attr(doc_cfg, doc(cfg(any(windows, target_os = "macos"))))]
38-
pub struct WindowParent {
39-
#[cfg(windows)]
40-
hwnd: *mut std::ffi::c_void,
41-
#[cfg(target_os = "macos")]
42-
ns_window: *mut std::ffi::c_void,
43-
}
44-
45-
#[cfg(any(windows, target_os = "macos"))]
46-
unsafe impl raw_window_handle::HasRawWindowHandle for WindowParent {
47-
#[cfg(windows)]
48-
fn raw_window_handle(&self) -> raw_window_handle::RawWindowHandle {
49-
let mut handle = raw_window_handle::windows::WindowsHandle::empty();
50-
handle.hwnd = self.hwnd;
51-
raw_window_handle::RawWindowHandle::Windows(handle)
52-
}
53-
54-
#[cfg(target_os = "macos")]
55-
fn raw_window_handle(&self) -> raw_window_handle::RawWindowHandle {
56-
let mut handle = raw_window_handle::macos::MacOSHandle::empty();
57-
handle.ns_window = self.ns_window;
58-
raw_window_handle::RawWindowHandle::MacOS(handle)
59-
}
60-
}
61-
62-
#[cfg(any(windows, target_os = "macos"))]
63-
#[cfg_attr(doc_cfg, doc(cfg(any(windows, target_os = "macos"))))]
64-
#[doc(hidden)]
65-
pub fn window_parent<R: Runtime>(window: &Window<R>) -> crate::Result<WindowParent> {
66-
#[cfg(windows)]
67-
let w = WindowParent {
68-
hwnd: window.hwnd()?,
69-
};
70-
#[cfg(target_os = "macos")]
71-
let w = WindowParent {
72-
ns_window: window.ns_window()?,
73-
};
74-
Ok(w)
75-
}
76-
7735
/// The file dialog builder.
7836
///
7937
/// Constructs file picker dialogs that can select single/multiple files or directories.
@@ -141,25 +99,24 @@ pub fn ask<R: Runtime, F: FnOnce(bool) + Send + 'static>(
14199
message: impl AsRef<str>,
142100
f: F,
143101
) {
144-
let title = title.as_ref().to_string();
145-
let message = message.as_ref().to_string();
146-
#[allow(unused_mut)]
147-
let mut builder = rfd::MessageDialog::new()
148-
.set_title(&title)
149-
.set_description(&message)
150-
.set_buttons(rfd::MessageButtons::YesNo)
151-
.set_level(rfd::MessageLevel::Info);
152-
153-
#[cfg(any(windows, target_os = "macos"))]
154-
{
155-
if let Some(window) = parent_window {
156-
if let Ok(parent) = window_parent(window) {
157-
builder = builder.set_parent(&parent);
158-
}
159-
}
160-
}
102+
run_message_dialog(parent_window, title, message, rfd::MessageButtons::YesNo, f)
103+
}
161104

162-
run_dialog!(builder.show(), f)
105+
/// Displays a dialog with a message and an optional title with an "ok" and a "cancel" button.
106+
#[allow(unused_variables)]
107+
pub fn confirm<R: Runtime, F: FnOnce(bool) + Send + 'static>(
108+
parent_window: Option<&Window<R>>,
109+
title: impl AsRef<str>,
110+
message: impl AsRef<str>,
111+
f: F,
112+
) {
113+
run_message_dialog(
114+
parent_window,
115+
title,
116+
message,
117+
rfd::MessageButtons::OkCancel,
118+
f,
119+
)
163120
}
164121

165122
/// Displays a message dialog.
@@ -168,26 +125,39 @@ pub fn message<R: Runtime>(
168125
parent_window: Option<&Window<R>>,
169126
title: impl AsRef<str>,
170127
message: impl AsRef<str>,
128+
) {
129+
run_message_dialog(
130+
parent_window,
131+
title,
132+
message,
133+
rfd::MessageButtons::Ok,
134+
|_| {},
135+
)
136+
}
137+
138+
#[allow(unused_variables)]
139+
fn run_message_dialog<R: Runtime, F: FnOnce(bool) + Send + 'static>(
140+
parent_window: Option<&Window<R>>,
141+
title: impl AsRef<str>,
142+
message: impl AsRef<str>,
143+
buttons: rfd::MessageButtons,
144+
f: F,
171145
) {
172146
let title = title.as_ref().to_string();
173147
let message = message.as_ref().to_string();
174-
let cb = |_| {};
175-
176148
#[allow(unused_mut)]
177149
let mut builder = rfd::MessageDialog::new()
178150
.set_title(&title)
179151
.set_description(&message)
180-
.set_buttons(rfd::MessageButtons::Ok)
152+
.set_buttons(buttons)
181153
.set_level(rfd::MessageLevel::Info);
182154

183155
#[cfg(any(windows, target_os = "macos"))]
184156
{
185157
if let Some(window) = parent_window {
186-
if let Ok(parent) = window_parent(window) {
187-
builder = builder.set_parent(&parent);
188-
}
158+
builder = builder.set_parent(window);
189159
}
190160
}
191161

192-
run_dialog!(builder.show(), cb)
162+
run_dialog!(builder.show(), f)
193163
}

core/tauri/src/endpoints/dialog.rs

Lines changed: 32 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@ use super::InvokeResponse;
66
#[cfg(any(dialog_open, dialog_save))]
77
use crate::api::dialog::FileDialogBuilder;
88
use crate::{
9-
api::dialog::{ask as ask_dialog, message as message_dialog},
9+
api::dialog::{ask as ask_dialog, confirm as confirm_dialog, message as message_dialog},
1010
runtime::Runtime,
1111
Window,
1212
};
@@ -70,6 +70,10 @@ pub enum Cmd {
7070
title: Option<String>,
7171
message: String,
7272
},
73+
ConfirmDialog {
74+
title: Option<String>,
75+
message: String,
76+
},
7377
}
7478

7579
impl Cmd {
@@ -88,25 +92,25 @@ impl Cmd {
8892

8993
Self::MessageDialog { message } => {
9094
let exe = std::env::current_exe()?;
91-
let app_name = exe
92-
.file_stem()
93-
.expect("failed to get binary filename")
94-
.to_string_lossy()
95-
.to_string();
96-
message_dialog(Some(&window), app_name, message);
95+
message_dialog(
96+
Some(&window),
97+
&window.app_handle.package_info().name,
98+
message,
99+
);
97100
Ok(().into())
98101
}
99102
Self::AskDialog { title, message } => {
100-
let exe = std::env::current_exe()?;
101103
let answer = ask(
102104
&window,
103-
title.unwrap_or_else(|| {
104-
exe
105-
.file_stem()
106-
.expect("failed to get binary filename")
107-
.to_string_lossy()
108-
.to_string()
109-
}),
105+
title.unwrap_or_else(|| window.app_handle.package_info().name.clone()),
106+
message,
107+
)?;
108+
Ok(answer)
109+
}
110+
Self::ConfirmDialog { title, message } => {
111+
let answer = confirm(
112+
&window,
113+
title.unwrap_or_else(|| window.app_handle.package_info().name.clone()),
110114
message,
111115
)?;
112116
Ok(answer)
@@ -143,7 +147,7 @@ pub fn open<R: Runtime>(
143147
let mut dialog_builder = FileDialogBuilder::new();
144148
#[cfg(any(windows, target_os = "macos"))]
145149
{
146-
dialog_builder = dialog_builder.set_parent(&crate::api::dialog::window_parent(window)?);
150+
dialog_builder = dialog_builder.set_parent(window);
147151
}
148152
if let Some(default_path) = options.default_path {
149153
if !default_path.exists() {
@@ -179,7 +183,7 @@ pub fn save<R: Runtime>(
179183
let mut dialog_builder = FileDialogBuilder::new();
180184
#[cfg(any(windows, target_os = "macos"))]
181185
{
182-
dialog_builder = dialog_builder.set_parent(&crate::api::dialog::window_parent(&window)?);
186+
dialog_builder = dialog_builder.set_parent(&window);
183187
}
184188
if let Some(default_path) = options.default_path {
185189
dialog_builder = set_default_path(dialog_builder, default_path);
@@ -203,3 +207,14 @@ pub fn ask<R: Runtime>(
203207
ask_dialog(Some(window), title, message, move |m| tx.send(m).unwrap());
204208
Ok(rx.recv().unwrap().into())
205209
}
210+
211+
/// Shows a dialog with a ok/cancel message.
212+
pub fn confirm<R: Runtime>(
213+
window: &Window<R>,
214+
title: String,
215+
message: String,
216+
) -> crate::Result<InvokeResponse> {
217+
let (tx, rx) = channel();
218+
confirm_dialog(Some(window), title, message, move |m| tx.send(m).unwrap());
219+
Ok(rx.recv().unwrap().into())
220+
}

core/tauri/src/window.rs

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -93,6 +93,26 @@ pub struct Window<R: Runtime> {
9393
pub(crate) app_handle: AppHandle<R>,
9494
}
9595

96+
#[cfg(any(windows, target_os = "macos"))]
97+
#[cfg_attr(doc_cfg, doc(cfg(any(windows, target_os = "macos"))))]
98+
unsafe impl<R: Runtime> raw_window_handle::HasRawWindowHandle for Window<R> {
99+
#[cfg(windows)]
100+
fn raw_window_handle(&self) -> raw_window_handle::RawWindowHandle {
101+
let mut handle = raw_window_handle::windows::WindowsHandle::empty();
102+
handle.hwnd = self.hwnd().expect("failed to get window `hwnd`");
103+
raw_window_handle::RawWindowHandle::Windows(handle)
104+
}
105+
106+
#[cfg(target_os = "macos")]
107+
fn raw_window_handle(&self) -> raw_window_handle::RawWindowHandle {
108+
let mut handle = raw_window_handle::macos::MacOSHandle::empty();
109+
handle.ns_window = self
110+
.ns_window()
111+
.expect("failed to get window's `ns_window`");
112+
raw_window_handle::RawWindowHandle::MacOS(handle)
113+
}
114+
}
115+
96116
impl<R: Runtime> Clone for Window<R> {
97117
fn clone(&self) -> Self {
98118
Self {

tooling/api/src/dialog.ts

Lines changed: 58 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -77,7 +77,7 @@ async function open(
7777
Object.freeze(options)
7878
}
7979

80-
return invokeTauriCommand<string | string[]>({
80+
return invokeTauriCommand({
8181
__tauriModule: 'Dialog',
8282
message: {
8383
cmd: 'openDialog',
@@ -96,7 +96,7 @@ async function save(options: SaveDialogOptions = {}): Promise<string> {
9696
Object.freeze(options)
9797
}
9898

99-
return invokeTauriCommand<string>({
99+
return invokeTauriCommand({
100100
__tauriModule: 'Dialog',
101101
message: {
102102
cmd: 'saveDialog',
@@ -105,6 +105,61 @@ async function save(options: SaveDialogOptions = {}): Promise<string> {
105105
})
106106
}
107107

108+
/**
109+
* Shows a message dialog with an `Ok` button.
110+
*
111+
* @param {string} message The message to show.
112+
*
113+
* @return {Promise<void>} A promise indicating the success or failure of the operation.
114+
*/
115+
async function message(message: string): Promise<void> {
116+
return invokeTauriCommand({
117+
__tauriModule: 'Dialog',
118+
message: {
119+
cmd: 'messageDialog',
120+
message
121+
}
122+
})
123+
}
124+
125+
/**
126+
* Shows a question dialog with `Yes` and `No` buttons.
127+
*
128+
* @param {string} message The message to show.
129+
* @param {string|undefined} title The dialog's title. Defaults to the application name.
130+
*
131+
* @return {Promise<void>} A promise resolving to a boolean indicating whether `Yes` was clicked or not.
132+
*/
133+
async function ask(message: string, title?: string): Promise<boolean> {
134+
return invokeTauriCommand({
135+
__tauriModule: 'Dialog',
136+
message: {
137+
cmd: 'askDialog',
138+
title,
139+
message
140+
}
141+
})
142+
}
143+
144+
/**
145+
* Shows a question dialog with `Ok` and `Cancel` buttons.
146+
*
147+
* @param {string} message The message to show.
148+
* @param {string|undefined} title The dialog's title. Defaults to the application name.
149+
*
150+
* @return {Promise<void>} A promise resolving to a boolean indicating whether `Ok` was clicked or not.
151+
*/
152+
async function confirm(message: string, title?: string): Promise<boolean> {
153+
return invokeTauriCommand({
154+
__tauriModule: 'Dialog',
155+
message: {
156+
cmd: 'confirmDialog',
157+
title,
158+
message
159+
}
160+
})
161+
}
162+
108163
export type { DialogFilter, OpenDialogOptions, SaveDialogOptions }
109164

110-
export { open, save }
165+
export { open, save, message, ask, confirm }

0 commit comments

Comments
 (0)