use crate::*; impl App { /// Return reference to content browser if open. /// /// ``` /// assert_eq!(tek::App::default().browser(), None); /// ``` pub fn browser (&self) -> Option<&Browse> { if let Dialog::Browse(_, ref b) = self.dialog { Some(b) } else { None } } } #[tek_proc::commands(BrowseCommand = "browse")] pub trait BrowseController: for<'a> Namespace<'a, usize> + for<'a> Namespace<'a, PathBuf> + for<'a> Namespace<'a, Arc> { /// Toggle visibility of browser #[command(Show = "show")] fn show (&mut self) -> Perhaps { todo!() } /// Set current directory #[command(Chdir = "chdir")] fn chdir (&mut self, _path: PathBuf) -> Perhaps { todo!() } /// Set current filter #[command(SetSearch = "set-search")] fn set_search (&mut self, _filter: Arc) -> Perhaps { todo!() } /// Set selected item #[command(SetCursor = "set-cursor")] fn set_cursor (&mut self, _index: usize) -> Perhaps { todo!() } } /// Browses for files to load/save. /// /// ``` /// let browse = tek::Browse::default(); /// ``` #[derive(Debug, Clone, Default, PartialEq)] pub struct Browse { pub cwd: PathBuf, pub dirs: Vec<(OsString, String)>, pub files: Vec<(OsString, String)>, pub filter: String, pub index: usize, pub scroll: usize, pub size: Sizer, } pub(crate) struct EntriesIterator<'a, S: Screen> { pub browser: &'a Browse, pub offset: usize, pub length: usize, pub index: usize, _screen: std::marker::PhantomData } #[derive(Clone, Debug)] pub enum BrowseTarget { SaveProject, LoadProject, ImportSample(Arc>>), ExportSample(Arc>>), ImportClip(Arc>>), ExportClip(Arc>>), } // Commands supported by [Browse] //#[derive(Debug, Clone, PartialEq)] //pub enum BrowseCommand { //Begin, //Cancel, //Confirm, //Select(usize), //Chdir(PathBuf), //Filter(Arc), //} impl Browse { pub fn new (cwd: Option) -> Usually { let cwd = if let Some(cwd) = cwd { cwd } else { std::env::current_dir()? }; let mut dirs = vec![]; let mut files = vec![]; for entry in std::fs::read_dir(&cwd)? { let entry = entry?; let name = entry.file_name(); let decoded = name.clone().into_string().unwrap_or_else(|_|"".to_string()); let meta = entry.metadata()?; if meta.is_dir() { dirs.push((name, format!("📁 {decoded}"))); } else if meta.is_file() { files.push((name, format!("📄 {decoded}"))); } } Ok(Self { cwd, dirs, files, ..Default::default() }) } pub fn to_chdir (&self) -> Usually { Self::new(Some(self.path())) } pub fn len (&self) -> usize { self.dirs.len() + self.files.len() } pub fn is_dir (&self) -> bool { self.index < self.dirs.len() } pub fn is_file (&self) -> bool { self.index >= self.dirs.len() } pub fn path (&self) -> PathBuf { self.cwd.join(if self.is_dir() { &self.dirs[self.index].0 } else if self.is_file() { &self.files[self.index - self.dirs.len()].0 } else { unreachable!() }) } fn _todo_stub_path_buf (&self) -> PathBuf { todo!() } fn _todo_stub_usize (&self) -> usize { todo!() } fn _todo_stub_arc_str (&self) -> Arc { todo!() } fn tui (&self) -> impl Draw { iter_south_fixed(1, ||self.tui_entries(), |entry, _index|entry.origin_w().full_w()) } fn tui_entries (&self) -> EntriesIterator<'_, Tui> { EntriesIterator { offset: 0, index: 0, length: self.dirs.len() + self.files.len(), browser: self, _screen: Default::default(), } } } impl<'a> Iterator for EntriesIterator<'a, Tui> { type Item = impl Draw; fn next (&mut self) -> Option { let dirs = self.browser.dirs.len(); let files = self.browser.files.len(); let index = self.index; if self.index < dirs { self.index += 1; Some(bold(true, self.browser.dirs[index].1.as_str())) } else if self.index < dirs + files { self.index += 1; Some(bold(false, self.browser.files[index - dirs].1.as_str())) } else { None } } } impl PartialEq for BrowseTarget { fn eq (&self, other: &Self) -> bool { match self { Self::ImportSample(_) => false, Self::ExportSample(_) => false, Self::ImportClip(_) => false, Self::ExportClip(_) => false, #[allow(unused)] t => matches!(other, t) } } } pub fn scan (dir: &PathBuf) -> Usually<(Vec, Vec)> { let (mut subdirs, mut files) = std::fs::read_dir(dir)? .fold((vec!["..".into()], vec![]), |(mut subdirs, mut files), entry|{ let entry = entry.expect("failed to read drectory entry"); let meta = entry.metadata().expect("failed to read entry metadata"); if meta.is_file() { files.push(entry.file_name()); } else if meta.is_dir() { subdirs.push(entry.file_name()); } (subdirs, files) }); subdirs.sort(); files.sort(); Ok((subdirs, files)) } pub fn view_browse_title (state: &App) -> impl Draw { field_v(ItemTheme::default(), match state.dialog.browser_target().unwrap() { BrowseTarget::SaveProject => "Save project:", BrowseTarget::LoadProject => "Load project:", BrowseTarget::ImportSample(_) => "Import sample:", BrowseTarget::ExportSample(_) => "Export sample:", BrowseTarget::ImportClip(_) => "Import clip:", BrowseTarget::ExportClip(_) => "Export clip:", }, fg(g(96), x_repeat("🭻")).exact_h(1)).align_w().full_w() }