#[cfg(target_os = "linux")] use std::fmt::Write as _; use std::{ path::PathBuf, sync::{Arc, Weak}, }; use winit::window::Window; pub struct FilePicker { window: Option>, } impl FilePicker { pub fn new() -> Self { Self { window: None } } pub fn set_window(&mut self, window: &Arc) { self.window = Some(Arc::downgrade(window)); } pub fn pick_file_dialog(&self) -> FileDialogBuilder { self.new_dialog(FileDialogAction::PickFile, false) } pub fn save_file_dialog(&self) -> FileDialogBuilder { self.new_dialog(FileDialogAction::SaveFile, cfg!(not(target_os = "windows"))) } fn new_dialog(&self, action: FileDialogAction, attach_window: bool) -> FileDialogBuilder { #[cfg(not(target_os = "macos"))] let mut dialog = rfd::AsyncFileDialog::new(); #[cfg(target_os = "macos")] let mut dialog = rfd::FileDialog::new(); if attach_window && let Some(window) = self.window.as_ref().and_then(|w| w.upgrade()) { dialog = dialog.set_parent(&window); } FileDialogBuilder { dialog, action } } } pub struct FileDialogBuilder { #[cfg(not(target_os = "macos"))] dialog: rfd::AsyncFileDialog, #[cfg(target_os = "macos")] dialog: rfd::FileDialog, action: FileDialogAction, } impl FileDialogBuilder { pub fn add_filter(self, name: impl Into, extensions: &[impl ToString]) -> Self { #[cfg(target_os = "linux")] let exts = extensions .iter() .map(|ext| { let mut normalized = String::new(); for char in ext.to_string().chars() { if char.is_ascii_lowercase() { let _ = write!(&mut normalized, "[{char}{}]", char.to_ascii_uppercase()); } else { normalized.push(char); } } normalized }) .collect::>(); #[cfg(target_os = "linux")] let extensions = &exts; Self { dialog: self.dialog.add_filter(name, extensions), ..self } } pub fn set_file_name(self, file_name: impl Into) -> Self { Self { dialog: self.dialog.set_file_name(file_name), ..self } } pub fn open(self) -> Option { #[cfg(not(target_os = "macos"))] fn resolve(action: impl Future>) -> Option { pollster::block_on(action).map(Into::into) } #[cfg(target_os = "macos")] fn resolve(action: Option) -> Option { action } match self.action { FileDialogAction::PickFile => resolve(self.dialog.pick_file()), FileDialogAction::SaveFile => resolve(self.dialog.save_file()), } } } enum FileDialogAction { PickFile, SaveFile, }