#![warn(
clippy::all,
clippy::doc_markdown,
clippy::dbg_macro,
clippy::todo,
clippy::empty_enum,
clippy::enum_glob_use,
clippy::pub_enum_variant_names,
clippy::mem_forget,
clippy::use_self,
clippy::filter_map_next,
clippy::needless_continue,
clippy::needless_borrow,
rust_2018_idioms,
future_incompatible,
missing_copy_implementations,
trivial_numeric_casts,
unstable_features,
nonstandard_style,
unused_import_braces,
unused_qualifications,
unused_results
)]
pub mod error;
pub mod ffi;
use error::NfdError;
use ffi::*;
use std::{
ffi::{CStr, CString},
os::raw::c_char,
path::{Path, PathBuf},
};
#[derive(Clone, PartialEq)]
pub enum Response {
Okay(PathBuf),
OkayMultiple(Vec<PathBuf>),
Cancel,
}
#[derive(Copy, Clone, PartialEq)]
pub enum DialogType {
SingleFile,
MultipleFiles,
SaveFile,
PickFolder,
}
pub struct DialogBuilder<'a> {
filter: Option<&'a str>,
default_path: Option<&'a Path>,
dialog_type: DialogType,
}
impl<'a> DialogBuilder<'a> {
pub fn new(dialog_type: DialogType) -> Self {
DialogBuilder {
filter: None,
default_path: None,
dialog_type,
}
}
pub fn single() -> Self {
Self::new(DialogType::SingleFile)
}
pub fn multiple() -> Self {
Self::new(DialogType::MultipleFiles)
}
pub fn filter(&'a mut self, filter: &'a str) -> &mut DialogBuilder<'a> {
self.filter = Some(filter);
self
}
pub fn default_path<P: AsRef<Path>>(&'a mut self, path: &'a P) -> &mut DialogBuilder<'a> {
self.default_path = Some(path.as_ref());
self
}
pub fn open(&self) -> Result<Response> {
open_dialog(self.filter, self.default_path, self.dialog_type)
}
}
pub fn dialog<'a>() -> DialogBuilder<'a> {
DialogBuilder::new(DialogType::SingleFile)
}
pub fn dialog_multiple<'a>() -> DialogBuilder<'a> {
DialogBuilder::new(DialogType::MultipleFiles)
}
pub fn dialog_save<'a>() -> DialogBuilder<'a> {
DialogBuilder::new(DialogType::SaveFile)
}
pub type Result<T> = std::result::Result<T, NfdError>;
pub fn open_file_dialog(
filter_list: Option<&str>,
default_path: Option<&Path>,
) -> Result<Response> {
open_dialog(filter_list, default_path, DialogType::SingleFile)
}
pub fn open_file_multiple_dialog(
filter_list: Option<&str>,
default_path: Option<&Path>,
) -> Result<Response> {
open_dialog(filter_list, default_path, DialogType::MultipleFiles)
}
pub fn open_save_dialog(
filter_list: Option<&str>,
default_path: Option<&Path>,
) -> Result<Response> {
open_dialog(filter_list, default_path, DialogType::SaveFile)
}
pub fn open_pick_folder(default_path: Option<&Path>) -> Result<Response> {
open_dialog(None, default_path, DialogType::PickFolder)
}
pub fn open_dialog(
filter_list: Option<&str>,
default_path: Option<&Path>,
dialog_type: DialogType,
) -> Result<Response> {
let result;
let filter_list_cstring;
let default_path_cstring;
let filter_list_ptr = match filter_list {
Some(fl_str) => {
filter_list_cstring = CString::new(fl_str)?;
filter_list_cstring.as_ptr()
}
None => std::ptr::null(),
};
let default_path_ptr = match default_path {
Some(dp_str) => {
default_path_cstring = CString::new(dp_str.to_str().ok_or_else(|| {
NfdError::Error("unable to convert default path to utf-8".to_owned())
})?)?;
default_path_cstring.as_ptr()
}
None => std::ptr::null(),
};
let mut out_path: *mut c_char = std::ptr::null_mut();
let ptr_out_path = &mut out_path as *mut *mut c_char;
let mut out_multiple = nfdpathset_t::default();
let ptr_out_multiple = &mut out_multiple as *mut nfdpathset_t;
unsafe {
result = match dialog_type {
DialogType::SingleFile => {
NFD_OpenDialog(filter_list_ptr, default_path_ptr, ptr_out_path)
}
DialogType::MultipleFiles => {
NFD_OpenDialogMultiple(filter_list_ptr, default_path_ptr, ptr_out_multiple)
}
DialogType::SaveFile => NFD_SaveDialog(filter_list_ptr, default_path_ptr, ptr_out_path),
DialogType::PickFolder => NFD_PickFolder(default_path_ptr, ptr_out_path),
};
match result {
nfdresult_t::Okay => {
if dialog_type == DialogType::MultipleFiles {
let count = NFD_PathSet_GetCount(&out_multiple);
let mut res = Vec::with_capacity(count);
for i in 0..count {
let path = CStr::from_ptr(NFD_PathSet_GetPath(&out_multiple, i))
.to_string_lossy()
.into_owned();
res.push(PathBuf::from(path));
}
NFD_PathSet_Free(ptr_out_multiple);
Ok(Response::OkayMultiple(res))
} else {
let path =
PathBuf::from(CStr::from_ptr(out_path).to_string_lossy().into_owned());
NFD_Free(out_path as *mut _);
Ok(Response::Okay(path))
}
}
nfdresult_t::Cancel => Ok(Response::Cancel),
nfdresult_t::Error => Err(NfdError::Error(
CStr::from_ptr(NFD_GetError())
.to_string_lossy()
.into_owned(),
)),
}
}
}