1use std::ptr::{null, null_mut};
2
3use futures_util::FutureExt;
4use widestring::U16CString;
5use windows::core::HRESULT;
6use windows_sys::Win32::{
7 Foundation::HWND,
8 UI::{
9 Controls::{
10 TASKDIALOG_BUTTON, TASKDIALOGCONFIG, TASKDIALOGCONFIG_0, TASKDIALOGCONFIG_1,
11 TD_ERROR_ICON, TD_INFORMATION_ICON, TD_WARNING_ICON, TDF_ALLOW_DIALOG_CANCELLATION,
12 TDF_SIZE_TO_CONTENT, TaskDialogIndirect,
13 },
14 WindowsAndMessaging::{IDCANCEL, IDCLOSE, IDNO, IDOK, IDRETRY, IDYES},
15 },
16};
17use winio_handle::AsWindow;
18use winio_primitive::{MessageBoxButton, MessageBoxResponse, MessageBoxStyle};
19
20use crate::{Error, Result, darkmode::TASK_DIALOG_CALLBACK};
21
22fn msgbox(
23 parent: Option<HWND>,
24 msg: U16CString,
25 title: U16CString,
26 instr: U16CString,
27 style: MessageBoxStyle,
28 btns: MessageBoxButton,
29 cbtns: Vec<CustomButton>,
30) -> Result<impl Future<Output = Result<MessageBoxResponse>> + 'static> {
31 const CUSTOM_RESULT_OFFSET: usize = 15;
32
33 let parent_handle = parent.map(|p| p as isize).unwrap_or_default();
34 let task = crate::spawn_blocking(move || {
35 let cbtn_ptrs = cbtns
36 .iter()
37 .map(|b| TASKDIALOG_BUTTON {
38 nButtonID: ((b.result as i32) << CUSTOM_RESULT_OFFSET),
39 pszButtonText: b.text.as_ptr(),
40 })
41 .collect::<Vec<_>>();
42 let config = TASKDIALOGCONFIG {
43 cbSize: std::mem::size_of::<TASKDIALOGCONFIG>() as _,
44 hwndParent: parent_handle as _,
45 hInstance: null_mut(),
46 dwFlags: TDF_ALLOW_DIALOG_CANCELLATION | TDF_SIZE_TO_CONTENT,
47 dwCommonButtons: btns.bits(),
48 pszWindowTitle: title.as_ptr(),
49 Anonymous1: TASKDIALOGCONFIG_0 {
50 pszMainIcon: match style {
51 MessageBoxStyle::None => null_mut(),
52 MessageBoxStyle::Info => TD_INFORMATION_ICON,
53 MessageBoxStyle::Warning => TD_WARNING_ICON,
54 MessageBoxStyle::Error => TD_ERROR_ICON,
55 },
56 },
57 pszMainInstruction: instr.as_ptr(),
58 pszContent: msg.as_ptr(),
59 cButtons: cbtn_ptrs.len() as _,
60 pButtons: if cbtn_ptrs.is_empty() {
61 null()
62 } else {
63 cbtn_ptrs.as_ptr()
64 },
65 nDefaultButton: 0,
66 cRadioButtons: 0,
67 pRadioButtons: null(),
68 nDefaultRadioButton: 0,
69 pszVerificationText: null(),
70 pszExpandedInformation: null(),
71 pszExpandedControlText: null(),
72 pszCollapsedControlText: null(),
73 Anonymous2: TASKDIALOGCONFIG_1 {
74 hFooterIcon: null_mut(),
75 },
76 pszFooter: null(),
77 pfCallback: TASK_DIALOG_CALLBACK,
78 lpCallbackData: 0,
79 cxWidth: 0,
80 };
81
82 let mut result = 0;
83 let res = unsafe { TaskDialogIndirect(&config, &mut result, null_mut(), null_mut()) };
84 (res, result)
85 });
86
87 Ok(task.map(|(res, result)| {
88 if res >= 0 {
89 let res = match result {
90 IDCANCEL => MessageBoxResponse::Cancel,
91 IDNO => MessageBoxResponse::No,
92 IDOK => MessageBoxResponse::Ok,
93 IDRETRY => MessageBoxResponse::Retry,
94 IDYES => MessageBoxResponse::Yes,
95 IDCLOSE => MessageBoxResponse::Close,
96 _ => MessageBoxResponse::Custom((result >> CUSTOM_RESULT_OFFSET) as _),
97 };
98 Ok(res)
99 } else {
100 Err(Error::from_hresult(HRESULT(res)))
101 }
102 }))
103}
104
105#[derive(Debug, Clone)]
106pub struct MessageBox {
107 msg: U16CString,
108 title: U16CString,
109 instr: U16CString,
110 style: MessageBoxStyle,
111 btns: MessageBoxButton,
112 cbtns: Vec<CustomButton>,
113}
114
115impl Default for MessageBox {
116 fn default() -> Self {
117 Self::new()
118 }
119}
120
121impl MessageBox {
122 pub fn new() -> Self {
123 Self {
124 msg: U16CString::new(),
125 title: U16CString::new(),
126 instr: U16CString::new(),
127 style: MessageBoxStyle::None,
128 btns: MessageBoxButton::empty(),
129 cbtns: vec![],
130 }
131 }
132
133 pub fn show(
134 self,
135 parent: Option<impl AsWindow>,
136 ) -> Result<impl Future<Output = Result<MessageBoxResponse>> + 'static> {
137 let parent = parent.and_then(|p| p.as_window().handle().ok());
138 msgbox(
139 parent, self.msg, self.title, self.instr, self.style, self.btns, self.cbtns,
140 )
141 }
142
143 pub fn message(&mut self, msg: &str) {
144 self.msg = U16CString::from_str_truncate(msg);
145 }
146
147 pub fn title(&mut self, title: &str) {
148 self.title = U16CString::from_str_truncate(title);
149 }
150
151 pub fn instruction(&mut self, instr: &str) {
152 self.instr = U16CString::from_str_truncate(instr);
153 }
154
155 pub fn style(&mut self, style: MessageBoxStyle) {
156 self.style = style;
157 }
158
159 pub fn buttons(&mut self, btns: MessageBoxButton) {
160 self.btns = btns;
161 }
162
163 pub fn custom_button(&mut self, btn: CustomButton) {
164 self.cbtns.push(btn);
165 }
166
167 pub fn custom_buttons(&mut self, btn: impl IntoIterator<Item = CustomButton>) {
168 self.cbtns.extend(btn);
169 }
170}
171
172#[derive(Debug, PartialEq, Eq, Clone)]
173pub struct CustomButton {
174 pub result: u16,
175 pub text: U16CString,
176}
177
178impl CustomButton {
179 pub fn new(result: u16, text: &str) -> Self {
180 Self {
181 result,
182 text: U16CString::from_str_truncate(text),
183 }
184 }
185}