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)]
23#[allow(clippy::pub_underscore_fields)] pub struct MsgBox {
25 pub _reserved: u8,
26}
27
28#[derive(Debug, Copy, Clone, PartialEq, Eq, Hash)]
30#[repr(C)]
31#[allow(clippy::pub_underscore_fields)] pub struct FileDialog {
33 pub _reserved: u8,
34}
35
36#[derive(Debug, Copy, Clone, PartialEq, Eq, Hash)]
38#[repr(C)]
39#[allow(clippy::pub_underscore_fields)] pub struct ColorPickerDialog {
41 pub _reserved: u8,
42}
43
44#[derive(Debug, Copy, Clone, PartialEq, Eq, Hash)]
45#[repr(C)]
46pub enum OkCancel {
47 Ok,
48 Cancel,
49}
50
51#[cfg(not(any(target_os = "android", target_os = "ios")))]
52impl From<tfd::OkCancel> for OkCancel {
53 #[inline]
54 fn from(e: tfd::OkCancel) -> Self {
55 match e {
56 tfd::OkCancel::Ok => Self::Ok,
57 tfd::OkCancel::Cancel => Self::Cancel,
58 }
59 }
60}
61
62#[cfg(not(any(target_os = "android", target_os = "ios")))]
63impl From<OkCancel> for tfd::OkCancel {
64 #[inline]
65 fn from(e: OkCancel) -> Self {
66 match e {
67 OkCancel::Ok => Self::Ok,
68 OkCancel::Cancel => Self::Cancel,
69 }
70 }
71}
72
73#[derive(Debug, Copy, Clone, Eq, PartialEq, Hash)]
74#[repr(C)]
75pub enum YesNo {
76 Yes,
77 No,
78}
79
80#[cfg(not(any(target_os = "android", target_os = "ios")))]
81impl From<YesNo> for tfd::YesNo {
82 #[inline]
83 fn from(e: YesNo) -> Self {
84 match e {
85 YesNo::Yes => Self::Yes,
86 YesNo::No => Self::No,
87 }
88 }
89}
90
91#[cfg(not(any(target_os = "android", target_os = "ios")))]
92impl From<tfd::YesNo> for YesNo {
93 #[inline]
94 fn from(e: tfd::YesNo) -> Self {
95 match e {
96 tfd::YesNo::Yes => Self::Yes,
97 tfd::YesNo::No => Self::No,
98 }
99 }
100}
101
102#[derive(Debug, Copy, Clone, Eq, PartialEq, Hash)]
103#[repr(C)]
104pub enum MsgBoxIcon {
105 Info,
106 Warning,
107 Error,
108 Question,
109}
110
111#[cfg(not(any(target_os = "android", target_os = "ios")))]
112impl From<MsgBoxIcon> for MessageBoxIcon {
113 #[inline]
114 fn from(e: MsgBoxIcon) -> Self {
115 match e {
116 MsgBoxIcon::Info => Self::Info,
117 MsgBoxIcon::Warning => Self::Warning,
118 MsgBoxIcon::Error => Self::Error,
119 MsgBoxIcon::Question => Self::Question,
120 }
121 }
122}
123
124impl Default for MsgBox {
125 fn default() -> Self {
126 Self::new()
127 }
128}
129
130impl MsgBox {
131 #[must_use] pub const fn new() -> Self {
135 Self { _reserved: 0 }
136 }
137
138 #[allow(clippy::needless_pass_by_value)]
144 pub fn ok(title: AzString, message: AzString, icon: MsgBoxIcon) {
145 #[cfg(not(any(target_os = "android", target_os = "ios")))]
146 {
147 let mut msg = message.as_str().to_string();
148 msg = msg.replace('\"', "");
149 msg = msg.replace('\'', "");
150 tfd::MessageBox::new(title.as_str(), &msg)
151 .with_icon(icon.into())
152 .run_modal();
153 }
154 #[cfg(any(target_os = "android", target_os = "ios"))]
155 {
156 let _ = (title, message, icon);
157 }
158 }
159
160 #[allow(clippy::needless_pass_by_value)]
163 #[must_use] pub fn ok_cancel(
164 title: AzString,
165 message: AzString,
166 icon: MsgBoxIcon,
167 default: OkCancel,
168 ) -> OkCancel {
169 #[cfg(not(any(target_os = "android", target_os = "ios")))]
170 {
171 tfd::MessageBox::new(title.as_str(), message.as_str())
172 .with_icon(icon.into())
173 .run_modal_ok_cancel(default.into())
174 .into()
175 }
176 #[cfg(any(target_os = "android", target_os = "ios"))]
177 {
178 let _ = (title, message, icon);
179 default
180 }
181 }
182
183 #[allow(clippy::needless_pass_by_value)]
186 #[must_use] pub fn yes_no(
187 title: AzString,
188 message: AzString,
189 icon: MsgBoxIcon,
190 default: YesNo,
191 ) -> YesNo {
192 #[cfg(not(any(target_os = "android", target_os = "ios")))]
193 {
194 tfd::MessageBox::new(title.as_str(), message.as_str())
195 .with_icon(icon.into())
196 .run_modal_yes_no(default.into())
197 .into()
198 }
199 #[cfg(any(target_os = "android", target_os = "ios"))]
200 {
201 let _ = (title, message, icon);
202 default
203 }
204 }
205
206 pub fn info(content: AzString) {
208 Self::ok(AzString::from("Info"), content, MsgBoxIcon::Info);
209 }
210}
211
212impl Default for ColorPickerDialog {
213 fn default() -> Self {
214 Self::new()
215 }
216}
217
218impl ColorPickerDialog {
219 #[must_use] pub const fn new() -> Self {
222 Self { _reserved: 0 }
223 }
224
225 #[allow(clippy::needless_pass_by_value)]
228 #[must_use] pub fn open(title: AzString, default_value: OptionColorU) -> OptionColorU {
229 #[cfg(not(any(target_os = "android", target_os = "ios")))]
230 {
231 let rgb = default_value
232 .into_option()
233 .map_or([0, 0, 0], |c| [c.r, c.g, c.b]);
234 let default_color = DefaultColorValue::RGB(rgb);
235 let result = tfd::ColorChooser::new(title.as_str())
236 .with_default_color(default_color)
237 .run_modal();
238 match result {
239 Some(r) => OptionColorU::Some(ColorU {
240 r: r.1[0],
241 g: r.1[1],
242 b: r.1[2],
243 a: ColorU::ALPHA_OPAQUE,
244 }),
245 None => OptionColorU::None,
246 }
247 }
248 #[cfg(any(target_os = "android", target_os = "ios"))]
249 {
250 let _ = title;
251 default_value
252 }
253 }
254}
255
256#[derive(Debug, Clone, PartialEq, Eq, PartialOrd)]
257#[repr(C)]
258pub struct FileTypeList {
259 pub document_types: StringVec,
260 pub document_descriptor: AzString,
261}
262
263impl_option!(
264 FileTypeList,
265 OptionFileTypeList,
266 copy = false,
267 [Debug, Clone, PartialEq, Eq, PartialOrd]
268);
269
270#[cfg(not(any(target_os = "android", target_os = "ios")))]
272#[allow(clippy::needless_pass_by_value)]
274fn apply_filter(mut dialog: tfd::FileDialog, filter: FileTypeList) -> tfd::FileDialog {
275 let v = filter.document_types.clone().into_library_owned_vec();
276 let patterns: Vec<&str> = v.iter().map(AzString::as_str).collect();
277 dialog = dialog.with_filter(&patterns, filter.document_descriptor.as_str());
278 dialog
279}
280
281impl Default for FileDialog {
282 fn default() -> Self {
283 Self::new()
284 }
285}
286
287impl FileDialog {
288 #[must_use] pub const fn new() -> Self {
291 Self { _reserved: 0 }
292 }
293
294 #[allow(clippy::needless_pass_by_value)]
297 pub fn open_file(
298 title: AzString,
299 default_path: OptionString,
300 filter_list: OptionFileTypeList,
301 ) -> OptionString {
302 #[cfg(not(any(target_os = "android", target_os = "ios")))]
303 {
304 let mut dialog = tfd::FileDialog::new(title.as_str());
305 if let Some(path) = default_path.as_option() {
306 dialog = dialog.with_path(path.as_str());
307 }
308 if let Some(filter) = filter_list.into_option() {
309 dialog = apply_filter(dialog, filter);
310 }
311 dialog.open_file().map(AzString::from).into()
312 }
313 #[cfg(any(target_os = "android", target_os = "ios"))]
314 {
315 let _ = (title, default_path, filter_list);
316 OptionString::None
317 }
318 }
319
320 #[allow(clippy::needless_pass_by_value)]
323 pub fn open_directory(title: AzString, default_path: OptionString) -> OptionString {
324 #[cfg(not(any(target_os = "android", target_os = "ios")))]
325 {
326 let mut dialog = tfd::FileDialog::new(title.as_str());
327 if let Some(path) = default_path.as_option() {
328 dialog = dialog.with_path(path.as_str());
329 }
330 dialog.select_folder().map(AzString::from).into()
331 }
332 #[cfg(any(target_os = "android", target_os = "ios"))]
333 {
334 let _ = (title, default_path);
335 OptionString::None
336 }
337 }
338
339 #[allow(clippy::needless_pass_by_value)]
342 pub fn open_multiple_files(
343 title: AzString,
344 default_path: OptionString,
345 filter_list: OptionFileTypeList,
346 ) -> OptionStringVec {
347 #[cfg(not(any(target_os = "android", target_os = "ios")))]
348 {
349 let mut dialog =
350 tfd::FileDialog::new(title.as_str()).with_multiple_selection(true);
351 if let Some(path) = default_path.as_option() {
352 dialog = dialog.with_path(path.as_str());
353 }
354 if let Some(filter) = filter_list.into_option() {
355 dialog = apply_filter(dialog, filter);
356 }
357 dialog.open_files().map(StringVec::from).into()
358 }
359 #[cfg(any(target_os = "android", target_os = "ios"))]
360 {
361 let _ = (title, default_path, filter_list);
362 OptionStringVec::None
363 }
364 }
365
366 #[allow(clippy::needless_pass_by_value)]
369 pub fn save_file(title: AzString, default_path: OptionString) -> OptionString {
370 #[cfg(not(any(target_os = "android", target_os = "ios")))]
371 {
372 let mut dialog = tfd::FileDialog::new(title.as_str());
373 if let Some(path) = default_path.as_option() {
374 dialog = dialog.with_path(path.as_str());
375 }
376 dialog.save_file().map(AzString::from).into()
377 }
378 #[cfg(any(target_os = "android", target_os = "ios"))]
379 {
380 let _ = (title, default_path);
381 OptionString::None
382 }
383 }
384}
385
386pub fn msg_box(content: &str) {
388 MsgBox::info(AzString::from(content));
389}