126 lines
3.8 KiB
Rust
126 lines
3.8 KiB
Rust
use egui::{CollapsingHeader, InnerResponse, Widget};
|
|
use std::cmp::Ordering;
|
|
use std::path::{Path, PathBuf};
|
|
|
|
pub enum FileTree {
|
|
Branch {
|
|
path: PathBuf,
|
|
children: Vec<FileTree>,
|
|
},
|
|
Leaf {
|
|
filename: PathBuf,
|
|
},
|
|
}
|
|
|
|
impl Eq for FileTree {}
|
|
|
|
impl PartialEq<Self> for FileTree {
|
|
fn eq(&self, other: &Self) -> bool {
|
|
matches!(
|
|
(self, other),
|
|
(Self::Branch { .. }, Self::Branch { .. }) | (Self::Leaf { .. }, Self::Leaf { .. })
|
|
)
|
|
}
|
|
}
|
|
|
|
impl PartialOrd<Self> for FileTree {
|
|
fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
|
|
Some(self.cmp(other))
|
|
}
|
|
}
|
|
|
|
impl Ord for FileTree {
|
|
fn cmp(&self, other: &Self) -> Ordering {
|
|
match (self, other) {
|
|
(Self::Leaf { filename: a }, Self::Leaf { filename: b }) => a.cmp(b),
|
|
(Self::Branch { path: a, .. }, Self::Branch { path: b, .. }) => a.cmp(b),
|
|
(Self::Branch { .. }, _) => Ordering::Less,
|
|
(_, Self::Branch { .. }) => Ordering::Greater,
|
|
}
|
|
}
|
|
}
|
|
|
|
pub enum FileTreeEvent<A> {
|
|
Clicked(PathBuf),
|
|
ContextAction(A),
|
|
}
|
|
|
|
impl FileTree {
|
|
pub fn ui<A>(
|
|
&self,
|
|
ui: &mut egui::Ui,
|
|
ctx: &mut impl FnMut(&mut egui::Ui, &Path) -> Option<A>,
|
|
) -> InnerResponse<Option<FileTreeEvent<A>>> {
|
|
let mut action = None;
|
|
let frame_response = egui::Frame::new().show(ui, |ui| {
|
|
egui::ScrollArea::both()
|
|
.auto_shrink([false; 2])
|
|
.show(ui, |ui| {
|
|
self.ui_inner(ui, &mut action, ctx);
|
|
})
|
|
});
|
|
InnerResponse::new(action, frame_response.response)
|
|
}
|
|
|
|
fn ui_inner<A>(
|
|
&self,
|
|
ui: &mut egui::Ui,
|
|
action: &mut Option<FileTreeEvent<A>>,
|
|
ctx: &mut impl FnMut(&mut egui::Ui, &Path) -> Option<A>,
|
|
) {
|
|
match self {
|
|
Self::Leaf { filename } => {
|
|
let response = egui::Button::new(filename.file_name().unwrap().to_string_lossy())
|
|
.frame(false)
|
|
.ui(ui);
|
|
|
|
response.context_menu(|ui| {
|
|
if let Some(a) = ctx(ui, filename) {
|
|
*action = Some(FileTreeEvent::ContextAction(a));
|
|
}
|
|
});
|
|
|
|
if response.clicked() {
|
|
*action = Some(FileTreeEvent::Clicked(PathBuf::from(filename)));
|
|
}
|
|
}
|
|
Self::Branch { path, children } => {
|
|
CollapsingHeader::new(path.file_name().unwrap().to_string_lossy())
|
|
.show(ui, |ui| {
|
|
for child in children {
|
|
child.ui_inner(ui, action, ctx);
|
|
}
|
|
})
|
|
.header_response
|
|
.context_menu(|ui| {
|
|
if let Some(a) = ctx(ui, path) {
|
|
*action = Some(FileTreeEvent::ContextAction(a));
|
|
}
|
|
});
|
|
}
|
|
}
|
|
}
|
|
|
|
pub fn build(path: &Path) -> Self {
|
|
match path.is_dir() {
|
|
true => Self::Branch {
|
|
path: path.to_path_buf(),
|
|
children: {
|
|
let mut children: Vec<Self> = std::fs::read_dir(path)
|
|
.map(|ok| {
|
|
ok.filter_map(|file| file.ok())
|
|
.map(|file| Self::build(&path.join(file.file_name())))
|
|
.collect()
|
|
})
|
|
.unwrap_or_default();
|
|
children.sort_unstable();
|
|
children
|
|
},
|
|
},
|
|
false => Self::Leaf {
|
|
filename: path.to_path_buf(),
|
|
},
|
|
}
|
|
}
|
|
}
|