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}