sdl3/
messagebox.rs

1use std::error;
2use std::ffi::{CString, NulError};
3use std::fmt;
4use std::os::raw::{c_char, c_int};
5use std::ptr;
6
7use crate::get_error;
8use crate::video::Window;
9use crate::Error;
10
11use crate::sys;
12
13bitflags! {
14    #[derive(Debug, Copy, Clone)]
15    pub struct MessageBoxFlag: u32 {
16        const ERROR =
17            sys::messagebox::SDL_MESSAGEBOX_ERROR.0;
18        const WARNING =
19            sys::messagebox::SDL_MESSAGEBOX_WARNING.0;
20        const INFORMATION =
21            sys::messagebox::SDL_MESSAGEBOX_INFORMATION.0;
22    }
23}
24
25impl From<MessageBoxFlag> for sys::messagebox::SDL_MessageBoxFlags {
26    fn from(flags: MessageBoxFlag) -> Self {
27        sys::messagebox::SDL_MessageBoxFlags(flags.bits() as sys::stdinc::Uint32)
28    }
29}
30
31impl From<sys::messagebox::SDL_MessageBoxFlags> for MessageBoxFlag {
32    fn from(flags: sys::messagebox::SDL_MessageBoxFlags) -> Self {
33        MessageBoxFlag::from_bits_truncate(flags.0)
34    }
35}
36
37bitflags! {
38    #[derive(Debug, Copy, Clone)]
39    pub struct MessageBoxButtonFlag: u32 {
40        const ESCAPEKEY_DEFAULT =
41            sys::messagebox::SDL_MESSAGEBOX_BUTTON_ESCAPEKEY_DEFAULT.0;
42        const RETURNKEY_DEFAULT =
43            sys::messagebox::SDL_MESSAGEBOX_BUTTON_RETURNKEY_DEFAULT.0;
44        const NOTHING = 0;
45    }
46}
47
48impl From<MessageBoxButtonFlag> for sys::messagebox::SDL_MessageBoxButtonFlags {
49    fn from(flags: MessageBoxButtonFlag) -> Self {
50        sys::messagebox::SDL_MessageBoxButtonFlags(flags.bits() as sys::stdinc::Uint32)
51    }
52}
53
54impl From<sys::messagebox::SDL_MessageBoxButtonFlags> for MessageBoxButtonFlag {
55    fn from(flags: sys::messagebox::SDL_MessageBoxButtonFlags) -> Self {
56        MessageBoxButtonFlag::from_bits_truncate(flags.0)
57    }
58}
59
60#[derive(Debug)]
61pub struct MessageBoxColorScheme {
62    pub background: (u8, u8, u8),
63    pub text: (u8, u8, u8),
64    pub button_border: (u8, u8, u8),
65    pub button_background: (u8, u8, u8),
66    pub button_selected: (u8, u8, u8),
67}
68
69impl From<MessageBoxColorScheme> for sys::messagebox::SDL_MessageBoxColorScheme {
70    fn from(val: MessageBoxColorScheme) -> Self {
71        sys::messagebox::SDL_MessageBoxColorScheme { colors: val.into() }
72    }
73}
74
75impl From<sys::messagebox::SDL_MessageBoxColorScheme> for MessageBoxColorScheme {
76    fn from(prim: sys::messagebox::SDL_MessageBoxColorScheme) -> MessageBoxColorScheme {
77        prim.colors.into()
78    }
79}
80
81/// `button_id` is the integer that will be returned
82/// by `show_message_box`. It is not sed by SDL2,
83/// and should only be used to know which button has been triggered
84#[derive(Debug)]
85pub struct ButtonData<'a> {
86    pub flags: MessageBoxButtonFlag,
87    pub button_id: i32,
88    pub text: &'a str,
89}
90
91#[derive(Debug)]
92pub enum ClickedButton<'a> {
93    CloseButton,
94    CustomButton(&'a ButtonData<'a>),
95}
96
97impl From<MessageBoxColorScheme> for [sys::messagebox::SDL_MessageBoxColor; 5] {
98    fn from(scheme: MessageBoxColorScheme) -> [sys::messagebox::SDL_MessageBoxColor; 5] {
99        fn to_message_box_color(t: (u8, u8, u8)) -> sys::messagebox::SDL_MessageBoxColor {
100            sys::messagebox::SDL_MessageBoxColor {
101                r: t.0,
102                g: t.1,
103                b: t.2,
104            }
105        }
106        [
107            to_message_box_color(scheme.background),
108            to_message_box_color(scheme.text),
109            to_message_box_color(scheme.button_border),
110            to_message_box_color(scheme.button_background),
111            to_message_box_color(scheme.button_selected),
112        ]
113    }
114}
115
116impl From<[sys::messagebox::SDL_MessageBoxColor; 5]> for MessageBoxColorScheme {
117    fn from(val: [sys::messagebox::SDL_MessageBoxColor; 5]) -> Self {
118        fn from_message_box_color(
119            prim_color: sys::messagebox::SDL_MessageBoxColor,
120        ) -> (u8, u8, u8) {
121            (prim_color.r, prim_color.g, prim_color.b)
122        }
123        MessageBoxColorScheme {
124            background: from_message_box_color(val[0]),
125            text: from_message_box_color(val[1]),
126            button_border: from_message_box_color(val[2]),
127            button_background: from_message_box_color(val[3]),
128            button_selected: from_message_box_color(val[4]),
129        }
130    }
131}
132
133#[derive(Debug, Clone)]
134pub enum ShowMessageError {
135    InvalidTitle(NulError),
136    InvalidMessage(NulError),
137    /// Second argument of the tuple (i32) corresponds to the
138    /// first button_id having an error
139    InvalidButton(NulError, i32),
140    SdlError(Error),
141}
142
143impl fmt::Display for ShowMessageError {
144    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
145        use self::ShowMessageError::*;
146
147        match *self {
148            InvalidTitle(ref e) => write!(f, "Invalid title: {e}"),
149            InvalidMessage(ref e) => write!(f, "Invalid message: {e}"),
150            InvalidButton(ref e, value) => write!(f, "Invalid button ({value}): {e}"),
151            SdlError(ref e) => write!(f, "SDL error: {e}"),
152        }
153    }
154}
155
156impl error::Error for ShowMessageError {
157    fn description(&self) -> &str {
158        use self::ShowMessageError::*;
159
160        match *self {
161            InvalidTitle(_) => "invalid title",
162            InvalidMessage(_) => "invalid message",
163            InvalidButton(..) => "invalid button",
164            SdlError(ref e) => &e.0,
165        }
166    }
167}
168
169/// Show a simple message box, meant to be informative only.
170///
171/// There is no way to know if the user clicked "Ok" or closed the message box,
172/// If you want to retrieve which button was clicked and customize a bit more
173/// your message box, use `show_message_box` instead.
174#[doc(alias = "SDL_ShowSimpleMessageBox")]
175pub fn show_simple_message_box<'a, W>(
176    flags: MessageBoxFlag,
177    title: &str,
178    message: &str,
179    window: W,
180) -> Result<(), ShowMessageError>
181where
182    W: Into<Option<&'a Window>>,
183{
184    use self::ShowMessageError::*;
185    let result = unsafe {
186        let title = match CString::new(title) {
187            Ok(s) => s,
188            Err(err) => return Err(InvalidTitle(err)),
189        };
190        let message = match CString::new(message) {
191            Ok(s) => s,
192            Err(err) => return Err(InvalidMessage(err)),
193        };
194        let sys_flags: sys::messagebox::SDL_MessageBoxFlags = flags.into();
195        sys::messagebox::SDL_ShowSimpleMessageBox(
196            sys_flags,
197            title.as_ptr() as *const c_char,
198            message.as_ptr() as *const c_char,
199            window.into().map_or(ptr::null_mut(), |win| win.raw()),
200        )
201    };
202
203    if result {
204        Ok(())
205    } else {
206        Err(SdlError(get_error()))
207    }
208}
209
210/// Show a customizable message box.
211///
212/// An array of buttons is required for it to work. The array can be empty,
213/// but it will have no button beside the close button.
214///
215/// On success, it will return either the button clicked or the close button.
216/// Note that the variant of the `ClickedButton` enum will also be returned if the message box
217/// has been forcefully closed (Alt-F4, ...)
218///
219#[doc(alias = "SDL_ShowMessageBox")]
220pub fn show_message_box<'a, 'b, W, M>(
221    flags: MessageBoxFlag,
222    buttons: &'a [ButtonData],
223    title: &str,
224    message: &str,
225    window: W,
226    scheme: M,
227) -> Result<ClickedButton<'a>, ShowMessageError>
228where
229    W: Into<Option<&'b Window>>,
230    M: Into<Option<MessageBoxColorScheme>>,
231{
232    let window = window.into();
233    let scheme = scheme.into();
234
235    use self::ShowMessageError::*;
236    let mut button_id: c_int = 0;
237    let title = match CString::new(title) {
238        Ok(s) => s,
239        Err(err) => return Err(InvalidTitle(err)),
240    };
241    let message = match CString::new(message) {
242        Ok(s) => s,
243        Err(err) => return Err(InvalidMessage(err)),
244    };
245    let button_texts: Result<Vec<_>, (_, i32)> = buttons
246        .iter()
247        .map(|b| CString::new(b.text).map_err(|e| (e, b.button_id)))
248        .collect(); // Create CString for every button; and catch any CString Error
249    let button_texts = match button_texts {
250        Ok(b) => b,
251        Err(e) => return Err(InvalidButton(e.0, e.1)),
252    };
253    let raw_buttons: Vec<sys::messagebox::SDL_MessageBoxButtonData> = buttons
254        .iter()
255        .zip(button_texts.iter())
256        .map(|(b, b_text)| sys::messagebox::SDL_MessageBoxButtonData {
257            flags: b.flags.into(),
258            buttonID: b.button_id as c_int,
259            text: b_text.as_ptr(),
260        })
261        .collect();
262    let result = unsafe {
263        let msg_box_data = sys::messagebox::SDL_MessageBoxData {
264            flags: flags.into(),
265            window: window.map_or(ptr::null_mut(), |win| win.raw()),
266            title: title.as_ptr() as *const c_char,
267            message: message.as_ptr() as *const c_char,
268            numbuttons: raw_buttons.len() as c_int,
269            buttons: raw_buttons.as_ptr(),
270            colorScheme: if let Some(scheme) = scheme {
271                &sys::messagebox::SDL_MessageBoxColorScheme {
272                    colors: From::from(scheme),
273                } as *const _
274            } else {
275                ptr::null()
276            },
277        };
278        sys::messagebox::SDL_ShowMessageBox(&msg_box_data as *const _, &mut button_id as &mut _)
279    };
280    if result {
281        match button_id {
282            -1 => Ok(ClickedButton::CloseButton),
283            id => {
284                let button = buttons.iter().find(|b| b.button_id == id);
285                Ok(ClickedButton::CustomButton(button.unwrap()))
286            }
287        }
288    } else {
289        Err(SdlError(get_error()))
290    }
291}