1use super::*;
8
9pub const KUI_FILE_DIALOG_OPEN: u32 = 0;
11pub const KUI_FILE_DIALOG_SAVE: u32 = 1;
13pub const KUI_FILE_DIALOG_FOLDER: u32 = 2;
15
16#[repr(C)]
18#[derive(Clone, Copy)]
19pub struct KuiFileFilter {
20 pub name: KuiStr,
21 pub extensions: *const KuiStr,
23 pub extension_count: usize,
24}
25
26#[repr(C)]
29#[derive(Clone, Copy)]
30pub struct KuiFileDialog {
31 pub mode: u32,
33 pub multiple: u32,
35 pub title: KuiStr,
37 pub filters: *const KuiFileFilter,
38 pub filter_count: usize,
39 pub directory: KuiStr,
41 pub file_name: KuiStr,
43}
44
45fn opt(s: KuiStr) -> Option<String> {
46 let s = kstr(s);
47 (!s.is_empty()).then(|| s.into_owned())
48}
49
50fn dialog_of(d: &KuiFileDialog) -> Result<kui_core::FileDialog, String> {
51 use kui_core::FileDialogMode;
52 let mode = match d.mode {
53 KUI_FILE_DIALOG_OPEN => FileDialogMode::Open,
54 KUI_FILE_DIALOG_SAVE => FileDialogMode::Save,
55 KUI_FILE_DIALOG_FOLDER => FileDialogMode::Folder,
56 m => {
57 return Err(format!(
58 "KuiFileDialog.mode must be a KUI_FILE_DIALOG_*, not {m}"
59 ));
60 }
61 };
62 let filters = if d.filters.is_null() || d.filter_count == 0 {
63 &[][..]
64 } else {
65 unsafe { std::slice::from_raw_parts(d.filters, d.filter_count) }
67 };
68 let filters = filters
69 .iter()
70 .map(|f| {
71 let exts = if f.extensions.is_null() || f.extension_count == 0 {
72 &[][..]
73 } else {
74 unsafe { std::slice::from_raw_parts(f.extensions, f.extension_count) }
76 };
77 kui_core::FileFilter {
78 name: kstr(f.name).into_owned(),
79 extensions: exts
80 .iter()
81 .map(|e| kstr(*e).trim_start_matches('.').to_string())
82 .collect(),
83 }
84 })
85 .collect();
86 Ok(kui_core::FileDialog {
87 mode,
88 multiple: d.multiple != 0,
89 title: opt(d.title),
90 filters,
91 directory: opt(d.directory),
92 file_name: opt(d.file_name),
93 tag: kui_core::Value::Null,
94 })
95}
96
97#[unsafe(no_mangle)]
103pub extern "C" fn kui_request_files(
104 ptr: *mut KuiCtx,
105 dialog: *const KuiFileDialog,
106 tag: *mut KuiValue,
107) -> bool {
108 guard(false, || {
109 let tag = take_msg(tag).unwrap_or(kui_core::Value::Null);
110 let Some(c) = (unsafe { ctx(ptr) }) else {
111 return false;
112 };
113 let mut d = match unsafe { dialog.as_ref() } {
114 None => kui_core::FileDialog::open(),
115 Some(d) => match dialog_of(d) {
116 Ok(d) => d,
117 Err(why) => {
118 eprintln!("kui: kui_request_files: {why}");
119 return false;
120 }
121 },
122 };
123 d.tag = tag;
124 c.core().request_files(d)
125 })
126}
127
128#[unsafe(no_mangle)]
130pub extern "C" fn kui_awaiting_files(ptr: *mut KuiCtx) -> bool {
131 guard(false, || {
132 unsafe { ctx(ptr) }.is_some_and(|c| c.core().awaiting_files())
133 })
134}
135
136fn lend(out: *mut KuiStr, s: &str) {
137 if let Some(o) = unsafe { out.as_mut() } {
138 *o = KuiStr {
139 ptr: s.as_ptr(),
140 len: s.len(),
141 };
142 }
143}
144
145#[unsafe(no_mangle)]
153pub extern "C" fn kui_take_file_request(
154 ptr: *mut KuiCtx,
155 mode: *mut u32,
156 multiple: *mut bool,
157 title: *mut KuiStr,
158 directory: *mut KuiStr,
159 file_name: *mut KuiStr,
160 filter_count: *mut usize,
161) -> bool {
162 guard(false, || {
163 let Some(c) = (unsafe { ctx(ptr) }) else {
164 return false;
165 };
166 let Some(d) = c.core().take_file_requests().into_iter().next() else {
167 return false;
168 };
169 let code = match d.mode {
170 kui_core::FileDialogMode::Open => KUI_FILE_DIALOG_OPEN,
171 kui_core::FileDialogMode::Save => KUI_FILE_DIALOG_SAVE,
172 kui_core::FileDialogMode::Folder => KUI_FILE_DIALOG_FOLDER,
173 };
174 unsafe {
175 if let Some(m) = mode.as_mut() {
176 *m = code;
177 }
178 if let Some(m) = multiple.as_mut() {
179 *m = d.multiple;
180 }
181 if let Some(n) = filter_count.as_mut() {
182 *n = d.filters.len();
183 }
184 }
185 let d = c.file_request.insert(d);
186 lend(title, d.title.as_deref().unwrap_or(""));
187 lend(directory, d.directory.as_deref().unwrap_or(""));
188 lend(file_name, d.file_name.as_deref().unwrap_or(""));
189 true
190 })
191}
192
193#[unsafe(no_mangle)]
197pub extern "C" fn kui_file_request_filter(
198 ptr: *mut KuiCtx,
199 i: usize,
200 name: *mut KuiStr,
201 extensions: *mut KuiStr,
202) -> bool {
203 guard(false, || {
204 let Some(c) = (unsafe { ctx(ptr) }) else {
205 return false;
206 };
207 let Some(f) = c.file_request.as_ref().and_then(|d| d.filters.get(i)) else {
208 return false;
209 };
210 c.file_filter_text = f.extensions.join(";");
211 let f = &c.file_request.as_ref().expect("checked above").filters[i];
212 lend(name, &f.name);
213 lend(extensions, &c.file_filter_text);
214 true
215 })
216}
217
218#[unsafe(no_mangle)]
222pub extern "C" fn kui_input_files(ptr: *mut KuiCtx, paths: *const KuiStr, count: usize) {
223 guard((), || {
224 let paths = if paths.is_null() || count == 0 {
225 Vec::new()
226 } else {
227 unsafe { std::slice::from_raw_parts(paths, count) }
229 .iter()
230 .map(|s| kstr(*s).into_owned())
231 .collect()
232 };
233 push_input(ptr, kui_core::InputEvent::Files(paths));
234 });
235}