tinyfiledialogs/
lib.rs

1extern crate libc;
2use libc::{c_char, c_uchar, c_int};
3use std::ffi::{CStr, CString};
4use std::ptr;
5
6extern {
7    fn tinyfd_messageBox (
8        aTitle: *const c_char ,
9        aMessage: *const c_char,
10        aDialogType: *const c_char,
11        aIconType: *const c_char,
12        aDefaultButton: c_int) -> c_int;
13
14    fn tinyfd_inputBox (
15        aTitle: *const c_char ,
16        aMessage: *const c_char,
17        aDefaultInput: *const c_char) -> *const c_char;
18
19    fn tinyfd_saveFileDialog (
20        aTitle: *const c_char,
21        aDefaultPathAndFile: *const c_char,
22        aNumOfFilterPatterns: c_int,
23        aFilterPatterns: *const *const c_char,
24        aSingleFilterDescription: *const c_char) -> *const c_char;
25
26    fn tinyfd_openFileDialog (
27        aTitle: *const c_char,
28        aDefaultPathAndFile: *const c_char,
29        aNumOfFilterPatterns: c_int,
30        aFilterPatterns: *const *const c_char,
31        aSingleFilterDescription: *const c_char,
32        aAllowMultipleSelects: c_int) -> *const c_char;
33
34    fn tinyfd_selectFolderDialog (
35        aTitle: *const c_char,
36        aDefaultPath: *const c_char) -> *const c_char;
37
38    fn tinyfd_colorChooser (
39        aTitle: *const c_char,
40        aDefaultHexRGB: *const c_char,
41        aDefaultRGB: *const c_uchar,
42        aoResultRGB: *mut c_uchar) -> *const c_char;
43}
44
45fn message_box(title: &str, message: &str, box_type: &str, icon: &str, button: i32) -> i32 {
46    let message_box_title          = CString::new(title).unwrap();
47    let message_box_message        = CString::new(message).unwrap();
48    let message_box_type           = CString::new(box_type).unwrap();
49    let message_box_icon           = CString::new(icon).unwrap();
50
51    unsafe {
52        tinyfd_messageBox(
53            message_box_title.as_ptr(),
54            message_box_message.as_ptr(),
55            message_box_type.as_ptr(),
56            message_box_icon.as_ptr(),
57            button)
58    }
59}
60
61pub enum MessageBoxIcon {
62    Info,
63    Warning,
64    Error,
65    Question,
66}
67
68impl MessageBoxIcon {
69    fn to_str(&self) -> &'static str {
70        match *self {
71            MessageBoxIcon::Info => "info",
72            MessageBoxIcon::Warning => "warning",
73            MessageBoxIcon::Error => "error",
74            MessageBoxIcon::Question => "question",
75        }
76    }
77}
78
79pub fn message_box_ok(title: &str, message: &str, icon: MessageBoxIcon) {
80    let _ = message_box(title, message, "ok", icon.to_str(), 0);
81}
82
83#[derive(Debug, PartialEq, Copy, Clone)]
84pub enum OkCancel {
85    Cancel = 0,
86    Ok,
87}
88
89pub fn message_box_ok_cancel(title: &str, message: &str, icon: MessageBoxIcon, default: OkCancel) -> OkCancel {
90    match message_box(title, message, "okcancel", icon.to_str(), default as i32) {
91        0 => OkCancel::Cancel,
92        1 => OkCancel::Ok,
93        _ => unreachable!(),
94    }
95}
96
97#[derive(Debug, PartialEq, Copy, Clone)]
98pub enum YesNo {
99    No = 0,
100    Yes,
101}
102
103pub fn message_box_yes_no(title: &str, message: &str, icon: MessageBoxIcon, default: YesNo) -> YesNo {
104    match message_box(title, message, "yesno", icon.to_str(), default as i32) {
105        0 => YesNo::No,
106        1 => YesNo::Yes,
107        _ => unreachable!(),
108    }
109}
110
111fn input_box_impl(title: &str, message: &str, default: Option<&str>) -> Option<String> {
112    let input_box_title   = CString::new(title).unwrap();
113    let input_box_message = CString::new(message).unwrap();
114    let input_box_default = default.map(|default| CString::new(default).unwrap());
115
116    let c_input = unsafe {
117        tinyfd_inputBox(input_box_title.as_ptr(),
118                        input_box_message.as_ptr(),
119                        input_box_default.as_ref().map(|d| d.as_ptr()).unwrap_or(ptr::null()))
120    };
121
122    if !c_input.is_null() {
123        unsafe {
124            Some(CStr::from_ptr(c_input).to_string_lossy().into_owned())
125        }
126    } else {
127        None
128    }
129}
130
131pub fn input_box(title: &str, message: &str, default: &str) -> Option<String> {
132    input_box_impl(title, message, Some(default))
133}
134
135pub fn password_box(title: &str, message: &str) -> Option<String> {
136    input_box_impl(title, message, None)
137}
138
139fn save_file_dialog_impl(title: &str, path: &str, filter: Option<(&[&str], &str)>) -> Option<String> {
140    let save_dialog_title                = CString::new(title).unwrap();
141    let save_dialog_path                 = CString::new(path).unwrap();
142    let save_dialog_des                  = CString::new(filter.map_or("", |f| f.1)).unwrap();
143
144    let filter_patterns =
145        filter.map_or(vec![], |f| f.0.iter().map(|s| CString::new(*s).unwrap()).collect());
146    let ptr_filter_patterns = filter_patterns.iter().map(|c| c.as_ptr()).collect::<Vec<*const c_char>>();
147
148    let c_file_name = unsafe {
149        tinyfd_saveFileDialog(
150            save_dialog_title.as_ptr(),
151            save_dialog_path.as_ptr(),
152            ptr_filter_patterns.len() as c_int,
153            ptr_filter_patterns.as_ptr(),
154            save_dialog_des.as_ptr())
155    };
156
157    if !c_file_name.is_null() {
158        unsafe {
159            Some(CStr::from_ptr(c_file_name).to_string_lossy().into_owned())
160        }
161    } else {
162        None
163    }
164}
165
166pub fn save_file_dialog_with_filter(title: &str,
167                                    path: &str,
168                                    filter_patterns: &[&str],
169                                    description: &str) -> Option<String> {
170    save_file_dialog_impl(title, path, Some((filter_patterns, description)))
171}
172
173pub fn save_file_dialog(title: &str, path: &str) -> Option<String> {
174    save_file_dialog_impl(title, path, None)
175}
176
177fn open_file_dialog_impl(title: &str,
178                         path: &str,
179                         filter: Option<(&[&str], &str)>,
180                         multi: bool) -> Option<Vec<String>> {
181    let open_dialog_title                = CString::new(title).unwrap();
182    let open_dialog_path                 = CString::new(path).unwrap();
183    let open_dialog_des                  = CString::new(filter.map_or("", |f| f.1)).unwrap();
184
185    let filter_patterns =
186        filter.map_or(vec![], |f| f.0.iter().map(|s| CString::new(*s).unwrap()).collect());
187    let ptr_filter_patterns =
188        filter_patterns.iter().map(|c| c.as_ptr()).collect::<Vec<*const c_char>>();
189
190    let c_file_name = unsafe {
191        tinyfd_openFileDialog(
192            open_dialog_title.as_ptr(),
193            open_dialog_path.as_ptr(),
194            ptr_filter_patterns.len() as c_int,
195            ptr_filter_patterns.as_ptr(),
196            open_dialog_des.as_ptr(),
197            multi as c_int)
198    };
199
200    if !c_file_name.is_null() {
201        let result = unsafe {
202            CStr::from_ptr(c_file_name).to_string_lossy().into_owned()
203        };
204        Some(if multi {
205            result.split('|').map(|s| s.to_owned()).collect()
206        } else {
207            vec![result]
208        })
209    } else {
210        None
211    }
212}
213
214pub fn open_file_dialog(title: &str,
215                        path: &str,
216                        filter: Option<(&[&str], &str)>) -> Option<String> {
217    open_file_dialog_impl(title, path, filter, false).and_then(|v| v.into_iter().next())
218}
219
220pub fn open_file_dialog_multi(title: &str,
221                              path: &str,
222                              filter: Option<(&[&str], &str)>) -> Option<Vec<String>> {
223    open_file_dialog_impl(title, path, filter, true)
224}
225
226pub fn select_folder_dialog(title: &str, path: &str) -> Option<String> {
227    let select_folder_title = CString::new(title).unwrap();
228    let select_folder_path  = CString::new(path).unwrap();
229
230    let folder = unsafe {
231        tinyfd_selectFolderDialog(select_folder_title.as_ptr(), select_folder_path.as_ptr())
232    };
233
234    if !folder.is_null() {
235        unsafe {
236            Some(CStr::from_ptr(folder).to_string_lossy().into_owned())
237        }
238    } else {
239        None
240    }
241}
242
243pub enum DefaultColorValue<'a> {
244    Hex(&'a str),
245    RGB(&'a [u8; 3]),
246}
247
248pub fn color_chooser_dialog(title: &str, default: DefaultColorValue)
249                            -> Option<(String, [u8; 3])> {
250    let color_title                      = CString::new(title).unwrap();
251    let rubbish = [0, 0, 0];
252    let (color_default_hex, color_default_rgb) = match default {
253        DefaultColorValue::Hex(hex) => (Some(CString::new(hex).unwrap()), &rubbish),
254        DefaultColorValue::RGB(rgb) => (None, rgb),
255    };
256    let mut color_result_rgb = [0, 0, 0];
257
258    let result = unsafe {
259        tinyfd_colorChooser(color_title.as_ptr(),
260                            color_default_hex.as_ref().map_or(ptr::null(), |h| h.as_ptr()),
261                            color_default_rgb.as_ptr(),
262                            color_result_rgb.as_mut_ptr())
263    };
264
265    if !result.is_null() {
266        unsafe {
267            Some((CStr::from_ptr(result).to_string_lossy().into_owned(), color_result_rgb))
268        }
269    } else {
270        None
271    }
272}