Files
DSA/libraries/zxq5_egui/src/file_tree.rs
T

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(),
},
}
}
}