mirror of
https://codeberg.org/unspeaker/tek.git
synced 2026-08-28 12:56:57 +02:00
188 lines
6 KiB
Rust
188 lines
6 KiB
Rust
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<str>>
|
|
{
|
|
/// Toggle visibility of browser
|
|
#[command(Show = "show")]
|
|
fn show (&mut self) -> Perhaps<BrowseCommand> {
|
|
todo!()
|
|
}
|
|
/// Set current directory
|
|
#[command(Chdir = "chdir")]
|
|
fn chdir (&mut self, _path: PathBuf) -> Perhaps<BrowseCommand> {
|
|
todo!()
|
|
}
|
|
/// Set current filter
|
|
#[command(SetSearch = "set-search")]
|
|
fn set_search (&mut self, _filter: Arc<str>) -> Perhaps<BrowseCommand> {
|
|
todo!()
|
|
}
|
|
/// Set selected item
|
|
#[command(SetCursor = "set-cursor")]
|
|
fn set_cursor (&mut self, _index: usize) -> Perhaps<BrowseCommand> {
|
|
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<S>
|
|
}
|
|
|
|
#[derive(Clone, Debug)] pub enum BrowseTarget {
|
|
SaveProject,
|
|
LoadProject,
|
|
ImportSample(Arc<RwLock<Option<Sample>>>),
|
|
ExportSample(Arc<RwLock<Option<Sample>>>),
|
|
ImportClip(Arc<RwLock<Option<MidiClip>>>),
|
|
ExportClip(Arc<RwLock<Option<MidiClip>>>),
|
|
}
|
|
|
|
// Commands supported by [Browse]
|
|
//#[derive(Debug, Clone, PartialEq)]
|
|
//pub enum BrowseCommand {
|
|
//Begin,
|
|
//Cancel,
|
|
//Confirm,
|
|
//Select(usize),
|
|
//Chdir(PathBuf),
|
|
//Filter(Arc<str>),
|
|
//}
|
|
impl Browse {
|
|
pub fn new (cwd: Option<PathBuf>) -> Usually<Self> {
|
|
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(|_|"<unreadable>".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> { 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<str> { todo!() }
|
|
fn tui (&self) -> impl Draw<Tui> {
|
|
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<Tui>;
|
|
fn next (&mut self) -> Option<Self::Item> {
|
|
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<OsString>, Vec<OsString>)> {
|
|
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<Tui> {
|
|
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()
|
|
}
|