use std::path::{Path, PathBuf};
pub fn clipboard_text() -> Option<String> {
corundum_framework_platform::clipboard::text()
}
pub fn window_insets() -> Option<[i32; 4]> {
corundum_framework_platform::insets::window_insets()
}
#[cfg(all(target_os = "android", feature = "android"))]
pub(crate) fn set_android_activity(activity: *mut std::ffi::c_void) {
corundum_framework_platform::android::set_activity(activity);
}
pub fn needs_paste_button() -> bool {
cfg!(target_os = "android")
}
const DESKTOP: bool = cfg!(not(any(target_os = "android", target_os = "ios")));
pub const IMAGE_EXTENSIONS: &[&str] = &["png", "jpg", "jpeg", "gif", "webp", "bmp", "heic", "avif"];
pub const AUDIO_EXTENSIONS: &[&str] = &["mp3", "m4a", "aac", "flac", "ogg", "opus", "wav"];
pub const VIDEO_EXTENSIONS: &[&str] = &["mp4", "m4v", "mov", "mkv", "webm"];
pub fn file_picker_available() -> bool {
DESKTOP
}
pub fn folder_picker_available() -> bool {
DESKTOP
}
pub fn pick_folder(title: &str) -> Option<PathBuf> {
#[cfg(not(any(target_os = "android", target_os = "ios")))]
{
rfd::FileDialog::new().set_title(title).pick_folder()
}
#[cfg(any(target_os = "android", target_os = "ios"))]
{
let _ = title;
None
}
}
#[derive(Clone, Debug, Default, PartialEq)]
pub struct FilePicker {
title: Option<String>,
filters: Vec<(String, Vec<String>)>,
directory: Option<PathBuf>,
}
impl FilePicker {
pub fn new() -> FilePicker {
FilePicker::default()
}
pub fn title(mut self, title: impl Into<String>) -> FilePicker {
self.title = Some(title.into());
self
}
pub fn filter(mut self, name: impl Into<String>, extensions: &[&str]) -> FilePicker {
let extensions = extensions.iter().map(|e| e.to_string()).collect();
self.filters.push((name.into(), extensions));
self
}
pub fn directory(mut self, dir: impl Into<PathBuf>) -> FilePicker {
self.directory = Some(dir.into());
self
}
pub fn pick_file(self) -> Option<PathBuf> {
#[cfg(not(any(target_os = "android", target_os = "ios")))]
{
self.dialog().pick_file()
}
#[cfg(any(target_os = "android", target_os = "ios"))]
{
None
}
}
pub fn pick_files(self) -> Vec<PathBuf> {
#[cfg(not(any(target_os = "android", target_os = "ios")))]
{
self.dialog().pick_files().unwrap_or_default()
}
#[cfg(any(target_os = "android", target_os = "ios"))]
{
Vec::new()
}
}
#[cfg(not(any(target_os = "android", target_os = "ios")))]
fn dialog(self) -> rfd::FileDialog {
let mut dialog = rfd::FileDialog::new();
if let Some(title) = self.title {
dialog = dialog.set_title(title);
}
for (name, extensions) in &self.filters {
dialog = dialog.add_filter(name, extensions);
}
if let Some(dir) = self.directory {
dialog = dialog.set_directory(dir);
}
dialog
}
}
pub fn file_manager_available() -> bool {
DESKTOP
}
pub fn open_in_file_manager(path: &Path) {
#[cfg(windows)]
let program = Some("explorer");
#[cfg(target_os = "macos")]
let program = Some("open");
#[cfg(all(
unix,
not(any(target_os = "macos", target_os = "android", target_os = "ios"))
))]
let program = Some("xdg-open");
#[cfg(any(target_os = "android", target_os = "ios"))]
let program: Option<&str> = None;
if let Some(program) = program {
let _ = std::process::Command::new(program).arg(path).spawn();
}
}
#[cfg(all(test, not(target_os = "android")))]
mod tests {
use super::*;
#[test]
fn off_android_there_is_no_clipboard_insets_or_paste_button() {
assert_eq!(clipboard_text(), None);
assert_eq!(window_insets(), None);
assert!(!needs_paste_button());
}
#[cfg(not(target_os = "ios"))]
#[test]
fn a_desktop_has_the_dialogs_and_a_file_manager() {
assert!(file_picker_available());
assert!(folder_picker_available());
assert!(file_manager_available());
}
#[test]
fn the_picker_keeps_its_filters_in_order() {
let picker = FilePicker::new()
.title("Upload")
.filter("Images", IMAGE_EXTENSIONS)
.filter("Audio", &["mp3"])
.directory("/tmp");
assert_eq!(picker.title.as_deref(), Some("Upload"));
let names: Vec<_> = picker.filters.iter().map(|(n, _)| n.as_str()).collect();
assert_eq!(names, ["Images", "Audio"]);
assert_eq!(picker.filters[1].1, ["mp3"]);
assert_eq!(picker.directory, Some(PathBuf::from("/tmp")));
}
}