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