1use azul_css::{
11 corety::OptionString,
12 impl_option, impl_option_inner,
13 props::basic::color::{ColorU, OptionColorU},
14 AzString, OptionStringVec, StringVec,
15};
16
17#[cfg(not(any(target_os = "android", target_os = "ios")))]
18use tfd::{DefaultColorValue, MessageBoxIcon};
19
20#[derive(Debug, Copy, Clone, PartialEq, Eq, Hash)]
22#[repr(C)]
23pub struct MsgBox {
24 pub _reserved: u8,
25}
26
27#[derive(Debug, Copy, Clone, PartialEq, Eq, Hash)]
29#[repr(C)]
30pub struct FileDialog {
31 pub _reserved: u8,
32}
33
34#[derive(Debug, Copy, Clone, PartialEq, Eq, Hash)]
36#[repr(C)]
37pub struct ColorPickerDialog {
38 pub _reserved: u8,
39}
40
41#[derive(Debug, Copy, Clone, PartialEq, Eq, Hash)]
42#[repr(C)]
43pub enum OkCancel {
44 Ok,
45 Cancel,
46}
47
48#[cfg(not(any(target_os = "android", target_os = "ios")))]
49impl From<tfd::OkCancel> for OkCancel {
50 #[inline]
51 fn from(e: tfd::OkCancel) -> Self {
52 match e {
53 tfd::OkCancel::Ok => OkCancel::Ok,
54 tfd::OkCancel::Cancel => OkCancel::Cancel,
55 }
56 }
57}
58
59#[cfg(not(any(target_os = "android", target_os = "ios")))]
60impl From<OkCancel> for tfd::OkCancel {
61 #[inline]
62 fn from(e: OkCancel) -> Self {
63 match e {
64 OkCancel::Ok => tfd::OkCancel::Ok,
65 OkCancel::Cancel => tfd::OkCancel::Cancel,
66 }
67 }
68}
69
70#[derive(Debug, Copy, Clone, Eq, PartialEq, Hash)]
71#[repr(C)]
72pub enum YesNo {
73 Yes,
74 No,
75}
76
77#[cfg(not(any(target_os = "android", target_os = "ios")))]
78impl From<YesNo> for tfd::YesNo {
79 #[inline]
80 fn from(e: YesNo) -> Self {
81 match e {
82 YesNo::Yes => tfd::YesNo::Yes,
83 YesNo::No => tfd::YesNo::No,
84 }
85 }
86}
87
88#[cfg(not(any(target_os = "android", target_os = "ios")))]
89impl From<tfd::YesNo> for YesNo {
90 #[inline]
91 fn from(e: tfd::YesNo) -> Self {
92 match e {
93 tfd::YesNo::Yes => YesNo::Yes,
94 tfd::YesNo::No => YesNo::No,
95 }
96 }
97}
98
99#[derive(Debug, Copy, Clone, Eq, PartialEq, Hash)]
100#[repr(C)]
101pub enum MsgBoxIcon {
102 Info,
103 Warning,
104 Error,
105 Question,
106}
107
108#[cfg(not(any(target_os = "android", target_os = "ios")))]
109impl From<MsgBoxIcon> for MessageBoxIcon {
110 #[inline]
111 fn from(e: MsgBoxIcon) -> Self {
112 match e {
113 MsgBoxIcon::Info => MessageBoxIcon::Info,
114 MsgBoxIcon::Warning => MessageBoxIcon::Warning,
115 MsgBoxIcon::Error => MessageBoxIcon::Error,
116 MsgBoxIcon::Question => MessageBoxIcon::Question,
117 }
118 }
119}
120
121impl MsgBox {
122 pub const fn new() -> Self {
126 Self { _reserved: 0 }
127 }
128
129 pub fn ok(title: AzString, message: AzString, icon: MsgBoxIcon) {
133 #[cfg(not(any(target_os = "android", target_os = "ios")))]
134 {
135 let mut msg = message.as_str().to_string();
136 msg = msg.replace('\"', "");
137 msg = msg.replace('\'', "");
138 tfd::MessageBox::new(title.as_str(), &msg)
139 .with_icon(icon.into())
140 .run_modal();
141 }
142 #[cfg(any(target_os = "android", target_os = "ios"))]
143 {
144 let _ = (title, message, icon);
145 }
146 }
147
148 pub fn ok_cancel(
150 title: AzString,
151 message: AzString,
152 icon: MsgBoxIcon,
153 default: OkCancel,
154 ) -> OkCancel {
155 #[cfg(not(any(target_os = "android", target_os = "ios")))]
156 {
157 tfd::MessageBox::new(title.as_str(), message.as_str())
158 .with_icon(icon.into())
159 .run_modal_ok_cancel(default.into())
160 .into()
161 }
162 #[cfg(any(target_os = "android", target_os = "ios"))]
163 {
164 let _ = (title, message, icon);
165 default
166 }
167 }
168
169 pub fn yes_no(
171 title: AzString,
172 message: AzString,
173 icon: MsgBoxIcon,
174 default: YesNo,
175 ) -> YesNo {
176 #[cfg(not(any(target_os = "android", target_os = "ios")))]
177 {
178 tfd::MessageBox::new(title.as_str(), message.as_str())
179 .with_icon(icon.into())
180 .run_modal_yes_no(default.into())
181 .into()
182 }
183 #[cfg(any(target_os = "android", target_os = "ios"))]
184 {
185 let _ = (title, message, icon);
186 default
187 }
188 }
189
190 pub fn info(content: AzString) {
192 Self::ok(AzString::from("Info"), content, MsgBoxIcon::Info);
193 }
194}
195
196impl ColorPickerDialog {
197 pub const fn new() -> Self {
200 Self { _reserved: 0 }
201 }
202
203 pub fn open(title: AzString, default_value: OptionColorU) -> OptionColorU {
205 #[cfg(not(any(target_os = "android", target_os = "ios")))]
206 {
207 let rgb = default_value
208 .into_option()
209 .map_or([0, 0, 0], |c| [c.r, c.g, c.b]);
210 let default_color = DefaultColorValue::RGB(rgb);
211 let result = tfd::ColorChooser::new(title.as_str())
212 .with_default_color(default_color)
213 .run_modal();
214 match result {
215 Some(r) => OptionColorU::Some(ColorU {
216 r: r.1[0],
217 g: r.1[1],
218 b: r.1[2],
219 a: ColorU::ALPHA_OPAQUE,
220 }),
221 None => OptionColorU::None,
222 }
223 }
224 #[cfg(any(target_os = "android", target_os = "ios"))]
225 {
226 let _ = title;
227 default_value
228 }
229 }
230}
231
232#[derive(Debug, Clone, PartialEq, PartialOrd)]
233#[repr(C)]
234pub struct FileTypeList {
235 pub document_types: StringVec,
236 pub document_descriptor: AzString,
237}
238
239impl_option!(
240 FileTypeList,
241 OptionFileTypeList,
242 copy = false,
243 [Debug, Clone, PartialEq, PartialOrd]
244);
245
246#[cfg(not(any(target_os = "android", target_os = "ios")))]
248fn apply_filter(mut dialog: tfd::FileDialog, filter: FileTypeList) -> tfd::FileDialog {
249 let v = filter.document_types.clone().into_library_owned_vec();
250 let patterns: Vec<&str> = v.iter().map(|s| s.as_str()).collect();
251 dialog = dialog.with_filter(&patterns, filter.document_descriptor.as_str());
252 dialog
253}
254
255impl FileDialog {
256 pub const fn new() -> Self {
259 Self { _reserved: 0 }
260 }
261
262 pub fn open_file(
264 title: AzString,
265 default_path: OptionString,
266 filter_list: OptionFileTypeList,
267 ) -> OptionString {
268 #[cfg(not(any(target_os = "android", target_os = "ios")))]
269 {
270 let mut dialog = tfd::FileDialog::new(title.as_str());
271 if let Some(path) = default_path.as_option() {
272 dialog = dialog.with_path(path.as_str());
273 }
274 if let Some(filter) = filter_list.into_option() {
275 dialog = apply_filter(dialog, filter);
276 }
277 dialog.open_file().map(AzString::from).into()
278 }
279 #[cfg(any(target_os = "android", target_os = "ios"))]
280 {
281 let _ = (title, default_path, filter_list);
282 OptionString::None
283 }
284 }
285
286 pub fn open_directory(title: AzString, default_path: OptionString) -> OptionString {
288 #[cfg(not(any(target_os = "android", target_os = "ios")))]
289 {
290 let mut dialog = tfd::FileDialog::new(title.as_str());
291 if let Some(path) = default_path.as_option() {
292 dialog = dialog.with_path(path.as_str());
293 }
294 dialog.select_folder().map(AzString::from).into()
295 }
296 #[cfg(any(target_os = "android", target_os = "ios"))]
297 {
298 let _ = (title, default_path);
299 OptionString::None
300 }
301 }
302
303 pub fn open_multiple_files(
305 title: AzString,
306 default_path: OptionString,
307 filter_list: OptionFileTypeList,
308 ) -> OptionStringVec {
309 #[cfg(not(any(target_os = "android", target_os = "ios")))]
310 {
311 let mut dialog =
312 tfd::FileDialog::new(title.as_str()).with_multiple_selection(true);
313 if let Some(path) = default_path.as_option() {
314 dialog = dialog.with_path(path.as_str());
315 }
316 if let Some(filter) = filter_list.into_option() {
317 dialog = apply_filter(dialog, filter);
318 }
319 dialog.open_files().map(StringVec::from).into()
320 }
321 #[cfg(any(target_os = "android", target_os = "ios"))]
322 {
323 let _ = (title, default_path, filter_list);
324 OptionStringVec::None
325 }
326 }
327
328 pub fn save_file(title: AzString, default_path: OptionString) -> OptionString {
330 #[cfg(not(any(target_os = "android", target_os = "ios")))]
331 {
332 let mut dialog = tfd::FileDialog::new(title.as_str());
333 if let Some(path) = default_path.as_option() {
334 dialog = dialog.with_path(path.as_str());
335 }
336 dialog.save_file().map(AzString::from).into()
337 }
338 #[cfg(any(target_os = "android", target_os = "ios"))]
339 {
340 let _ = (title, default_path);
341 OptionString::None
342 }
343 }
344}
345
346pub fn msg_box(content: &str) {
348 MsgBox::info(AzString::from(content));
349}