Skip to main content

winio_ui_windows_common/
msgbox.rs

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}