diff --git a/examples/message-box.rs b/examples/message-box.rs new file mode 100644 index 00000000..a2e5aceb --- /dev/null +++ b/examples/message-box.rs @@ -0,0 +1,72 @@ +extern crate sdl2; + +use sdl2::pixels::Color; +use sdl2::event::Event; +use sdl2::keyboard::Keycode; +use ::sdl2::messagebox::*; + +pub fn main() { + let sdl_context = sdl2::init().unwrap(); + let video_subsystem = sdl_context.video().unwrap(); + + let window = video_subsystem.window("rust-sdl2 demo: Video", 800, 600) + .position_centered() + .opengl() + .build() + .unwrap(); + + let mut renderer = window.renderer().build().unwrap(); + + renderer.set_draw_color(Color::RGB(255, 0, 0)); + renderer.clear(); + renderer.present(); + let mut event_pump = sdl_context.event_pump().unwrap(); + + 'running: loop { + for event in event_pump.poll_iter() { + match event { + Event::Quit {..} | Event::KeyDown { keycode: Some(Keycode::Escape), .. } => { + let res = + show_simple_message_box(MESSAGEBOX_ERROR, + "Some title", + "Some information inside the window", + renderer.window()); + match res { + Ok(_) => {} + Err(ShowMessageError::SdlError(string)) => { + println!("An error occured : {}",string); + } + Err(_) => println!("Unexpected error occured !"), + }; + break 'running + }, + _ => {} + } + } + // The rest of the game loop goes here... + } + let buttons : Vec<_> = vec![ + ButtonData { + flags:MESSAGEBOX_BUTTON_RETURNKEY_DEFAULT, + button_id:1, + text:"Ok" + }, + ButtonData { + flags:MESSAGEBOX_BUTTON_NOTHING, + button_id:2, + text:"No" + }, + ButtonData { + flags:MESSAGEBOX_BUTTON_ESCAPEKEY_DEFAULT, + button_id:3, + text:"Cancel" + }, + ]; + let res = show_message_box(MESSAGEBOX_WARNING, + buttons.as_slice(), + "Some warning", + "You forget to do something, do it anyway ?", + None, + None); + println!("{:?}",res); +} diff --git a/sdl2-sys/src/messagebox.rs b/sdl2-sys/src/messagebox.rs index 7c08c30b..eec24533 100644 --- a/sdl2-sys/src/messagebox.rs +++ b/sdl2-sys/src/messagebox.rs @@ -1,11 +1,77 @@ -use libc::{c_int, c_char, uint32_t}; +use std::os::raw::{c_int,c_char}; use video::SDL_Window; -pub type SDL_MessageBoxFlags = u32; -pub const SDL_MESSAGEBOX_ERROR : SDL_MessageBoxFlags = 0x00000010; -pub const SDL_MESSAGEBOX_WARNING : SDL_MessageBoxFlags = 0x00000020; -pub const SDL_MESSAGEBOX_INFORMATION : SDL_MessageBoxFlags = 0x00000040; +#[derive(Debug, Copy, Clone)] +#[repr(u32)] +pub enum SDL_MessageBoxFlags { + SDL_MESSAGEBOX_ERROR = 16, + SDL_MESSAGEBOX_WARNING = 32, + SDL_MESSAGEBOX_INFORMATION = 64, +} + +#[derive(Debug, Copy, Clone)] +#[repr(u32)] +pub enum SDL_MessageBoxButtonFlags { + SDL_MESSAGEBOX_BUTTON_RETURNKEY_DEFAULT = 1, + SDL_MESSAGEBOX_BUTTON_ESCAPEKEY_DEFAULT = 2, +} + +#[repr(C)] +#[derive(Debug, Copy, Clone)] +pub struct SDL_MessageBoxButtonData { + pub flags: u32, + pub buttonid: c_int, + pub text: *const c_char, +} +impl ::std::default::Default for SDL_MessageBoxButtonData { + fn default() -> Self { unsafe { ::std::mem::zeroed() } } +} + +#[repr(C)] +#[derive(Debug, Copy, Clone)] +pub struct SDL_MessageBoxColor { + pub r: u8, + pub g: u8, + pub b: u8, +} + +#[derive(Debug, Copy, Clone)] +#[repr(u32)] +pub enum SDL_MessageBoxColorType { + SDL_MESSAGEBOX_COLOR_BACKGROUND = 0, + SDL_MESSAGEBOX_COLOR_TEXT = 1, + SDL_MESSAGEBOX_COLOR_BUTTON_BORDER = 2, + SDL_MESSAGEBOX_COLOR_BUTTON_BACKGROUND = 3, + SDL_MESSAGEBOX_COLOR_BUTTON_SELECTED = 4, + SDL_MESSAGEBOX_COLOR_MAX = 5, +} + +#[repr(C)] +#[derive(Debug, Copy, Clone)] +pub struct SDL_MessageBoxColorScheme { + pub colors: [SDL_MessageBoxColor; 5usize], +} + +#[repr(C)] +#[derive(Debug, Copy, Clone)] +pub struct SDL_MessageBoxData { + pub flags: u32, + pub window: *mut SDL_Window, + pub title: *const c_char, + pub message: *const c_char, + pub numbuttons: c_int, + pub buttons: *const SDL_MessageBoxButtonData, + pub color_scheme: *const SDL_MessageBoxColorScheme, +} extern "C" { - pub fn SDL_ShowSimpleMessageBox(flags: uint32_t, title: *const c_char, message: *const c_char, window: *mut SDL_Window) -> c_int; + pub fn SDL_ShowMessageBox(messageboxdata: *const SDL_MessageBoxData, + buttonid: *mut c_int) + -> c_int; + + pub fn SDL_ShowSimpleMessageBox(flags: u32, + title: *const c_char, + message: *const c_char, + window: *mut SDL_Window) + -> c_int; } diff --git a/src/sdl2/messagebox.rs b/src/sdl2/messagebox.rs index cb3c3077..cb53bc5d 100644 --- a/src/sdl2/messagebox.rs +++ b/src/sdl2/messagebox.rs @@ -1,6 +1,6 @@ use std::ffi::{CString, NulError}; use std::ptr; -use libc::c_char; +use std::os::raw::{c_char,c_int}; use video::WindowRef; use get_error; @@ -9,18 +9,83 @@ use sys::messagebox as ll; bitflags! { pub flags MessageBoxFlag: u32 { - const MESSAGEBOX_ERROR = ::sys::messagebox::SDL_MESSAGEBOX_ERROR, - const MESSAGEBOX_WARNING = ::sys::messagebox::SDL_MESSAGEBOX_WARNING, - const MESSAGEBOX_INFORMATION = ::sys::messagebox::SDL_MESSAGEBOX_INFORMATION + const MESSAGEBOX_ERROR = + ::sys::messagebox::SDL_MessageBoxFlags::SDL_MESSAGEBOX_ERROR as u32, + const MESSAGEBOX_WARNING = + ::sys::messagebox::SDL_MessageBoxFlags::SDL_MESSAGEBOX_WARNING as u32, + const MESSAGEBOX_INFORMATION = + ::sys::messagebox::SDL_MessageBoxFlags::SDL_MESSAGEBOX_INFORMATION as u32 } } +bitflags! { + pub flags MessageBoxButtonFlag: u32 { + const MESSAGEBOX_BUTTON_ESCAPEKEY_DEFAULT = + ::sys::messagebox::SDL_MessageBoxButtonFlags::SDL_MESSAGEBOX_BUTTON_ESCAPEKEY_DEFAULT as u32, + const MESSAGEBOX_BUTTON_RETURNKEY_DEFAULT = + ::sys::messagebox::SDL_MessageBoxButtonFlags::SDL_MESSAGEBOX_BUTTON_RETURNKEY_DEFAULT as u32, + const MESSAGEBOX_BUTTON_NOTHING = 0 + } +} + +#[derive(Debug)] +pub struct MessageBoxColorScheme { + pub background:(u8,u8,u8), + pub text:(u8,u8,u8), + pub button_border:(u8,u8,u8), + pub button_background:(u8,u8,u8), + pub button_selected:(u8,u8,u8) +} + +/// button_id is the integer that will be returned +/// by show_message_box. It is not sed by SDL2, +/// and should only be used to know which button has been triggered +#[derive(Debug)] +pub struct ButtonData<'a> { + pub flags:MessageBoxButtonFlag, + pub button_id:i32, + pub text:&'a str +} + +#[derive(Debug)] +pub enum ClickedButton<'a> { + CloseButton, + CustomButton(&'a ButtonData<'a>) +} + +impl From for [ll::SDL_MessageBoxColor ; 5] { + fn from(scheme:MessageBoxColorScheme) -> [ll::SDL_MessageBoxColor ; 5] { + fn to_message_box_color(t:(u8,u8,u8)) -> ll::SDL_MessageBoxColor { + ll::SDL_MessageBoxColor { + r:t.0, + g:t.1, + b:t.2 + } + }; + [to_message_box_color(scheme.background), + to_message_box_color(scheme.text), + to_message_box_color(scheme.button_border), + to_message_box_color(scheme.button_background), + to_message_box_color(scheme.button_selected)] + } +} + + +#[derive(Debug)] pub enum ShowMessageError { InvalidTitle(NulError), InvalidMessage(NulError), + /// Second argument of the tuple (i32) corresponds to the + /// first button_id having an error + InvalidButton(NulError,i32), SdlError(String), } +/// Show a simple message box, meant to be informative only. +/// +/// There is no way to know if the user clicked "Ok" or closed the message box, +/// If you want to retrieve which button was clicked and customize a bit more +/// your message box, use `show_message_box` instead. pub fn show_simple_message_box(flags: MessageBoxFlag, title: &str, message: &str, window: Option<&WindowRef>) -> Result<(), ShowMessageError> { @@ -48,3 +113,74 @@ pub fn show_simple_message_box(flags: MessageBoxFlag, title: &str, Err(SdlError(get_error())) } } + +/// Show a customizable message box. +/// +/// An array of buttons is required for it to work. The array can be empty, +/// but it will have no button beside the close button. +/// +/// On success, it will return either the button clicked or the close button. +/// Note that the variant of the `ClickedButton` enum will also be returned if the message box +/// has been forcefully closed (Alt-F4, ...) +/// +pub fn show_message_box<'a>(flags:MessageBoxFlag, buttons:&'a [ButtonData], title:&str, + message:&str, window:Option<&WindowRef>, scheme:Option) + -> Result,ShowMessageError> { + use self::ShowMessageError::*; + let mut button_id : c_int = 0; + let title = match CString::new(title) { + Ok(s) => s, + Err(err) => return Err(InvalidTitle(err)), + }; + let message = match CString::new(message) { + Ok(s) => s, + Err(err) => return Err(InvalidMessage(err)), + }; + let button_texts : Result,(_,i32)> = buttons.iter().map(|b|{ + CString::new(b.text).map_err(|e|(e,b.button_id)) + }).collect(); // Create CString for every button; and catch any CString Error + let button_texts = match button_texts { + Ok(b) => b, + Err(e) => return Err(InvalidButton(e.0,e.1)) + }; + let raw_buttons : Vec = + buttons.iter().zip(button_texts.iter()).map(|(b,b_text)|{ + ll::SDL_MessageBoxButtonData { + flags:b.flags.bits(), + buttonid:b.button_id as c_int, + text:b_text.as_ptr() + } + }).collect(); + let result = unsafe { + let msg_box_data = ll::SDL_MessageBoxData { + flags:flags.bits(), + window:window.map_or(ptr::null_mut(), |win| win.raw()), + title: title.as_ptr() as *const c_char, + message: message.as_ptr() as *const c_char, + numbuttons: raw_buttons.len() as c_int, + buttons: raw_buttons.as_ptr(), + color_scheme: if let Some(scheme) = scheme { + &ll::SDL_MessageBoxColorScheme { + colors:From::from(scheme) + } as *const _ + } else { + ptr::null() + } + }; + ll::SDL_ShowMessageBox( + &msg_box_data as *const _, + &mut button_id as &mut _ + ) + } == 0; + if result { + match button_id { + -1 => Ok(ClickedButton::CloseButton), + id => { + let button = buttons.iter().find(|b| b.button_id == id); + Ok(ClickedButton::CustomButton(button.unwrap())) + } + } + } else { + Err(SdlError(get_error())) + } +}