This commit is contained in:
Crispy 2023-08-17 00:18:01 +02:00
commit 50f2c4255b
5 changed files with 3058 additions and 0 deletions

1
.gitignore vendored Normal file
View file

@ -0,0 +1 @@
/target

2854
Cargo.lock generated Normal file

File diff suppressed because it is too large Load diff

11
Cargo.toml Normal file
View file

@ -0,0 +1,11 @@
[package]
name = "file-gui"
version = "0.1.0"
edition = "2021"
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
[dependencies]
eframe = "0.22.0"
egui_extras = "0.22.0"
opener = "0.6.1"

1
rustfmt.toml Normal file
View file

@ -0,0 +1 @@
hard_tabs = true

191
src/main.rs Normal file
View file

@ -0,0 +1,191 @@
use std::fs::DirEntry;
use std::path::PathBuf;
use eframe::{egui, Frame, NativeOptions};
use egui_extras::{Column, TableBuilder};
fn main() {
eframe::run_native(
"File Exploder",
NativeOptions::default(),
Box::new(|cc| Box::new(FileGui::new(cc))),
)
.unwrap();
}
struct FileGui {
current_dir: PathBuf,
loaded_dir: PathBuf,
path_input: String,
home: String,
entries: Vec<Entry>,
}
struct Entry {
name: String,
path: PathBuf,
filetype: FileType,
}
#[derive(PartialEq)]
enum FileType {
Dir,
File,
SymLink, // todo separate symlinks to directories and files
Unknown, // e.g. device files on linux
}
impl FileGui {
pub fn new(_cc: &eframe::CreationContext<'_>) -> Self {
let home = std::env::var("HOME").unwrap();
let current_dir = std::env::current_dir().unwrap();
let mut s = Self {
current_dir,
loaded_dir: PathBuf::new(),
path_input: String::new(),
home,
entries: Vec::new(),
};
s.override_input();
s.refresh_entries();
s
}
fn override_input(&mut self) {
self.path_input = self
.current_dir
.to_str()
.unwrap()
.replace('\\', "/")
.replace(&self.home, "~");
}
fn enter_input(&mut self) {
let path = self.path_input.replace("~", &self.home).replace('\\', "/");
self.current_dir = PathBuf::from(path);
}
fn refresh_entries(&mut self) {
if !self.current_dir.is_dir() {
self.current_dir = self.loaded_dir.clone();
}
if !self.current_dir.is_dir() {
return;
}
self.entries = self
.current_dir
.read_dir()
.unwrap()
.filter_map(|dir_entry| dir_entry.ok().map(Entry::new))
.flatten()
.collect();
self.entries
.sort_by(|a, b| b.is_dir().cmp(&a.is_dir()).then(a.name.cmp(&b.name)));
self.loaded_dir = self.current_dir.clone();
}
fn to_parent(&mut self) {
if let Some(path) = self.current_dir.parent() {
self.current_dir = path.to_owned();
self.refresh_entries();
self.override_input();
}
}
fn open(&mut self, index: usize) {
let entry = &self.entries[index];
match entry.filetype {
FileType::Dir => {
self.current_dir = entry.path.clone();
self.refresh_entries();
self.override_input();
}
_ => {
opener::open(&entry.path).unwrap();
}
}
}
}
impl eframe::App for FileGui {
fn update(&mut self, ctx: &egui::Context, _frame: &mut Frame) {
egui::TopBottomPanel::top("top_bar").show(ctx, |ui| {
ui.horizontal(|ui| {
if ui.button("").clicked() {
self.to_parent();
}
if ui.button("").clicked() {
self.refresh_entries();
}
let path_bar = ui.text_edit_singleline(&mut self.path_input);
if path_bar.lost_focus() {
if ui.input(|i| i.key_pressed(egui::Key::Enter)) {
self.enter_input();
self.refresh_entries();
} else if ui.input(|i| i.key_pressed(egui::Key::Escape)) {
self.override_input();
}
}
if let Some(filename) = self.loaded_dir.file_name() {
ui.label(filename.to_str().unwrap());
}
});
});
egui::CentralPanel::default().show(ctx, |ui| {
TableBuilder::new(ui)
.striped(true)
.max_scroll_height(1200.0)
.column(Column::auto())
.column(Column::remainder())
.body(|body| {
let mut to_open = None;
body.rows(18.0, self.entries.len(), |row_index, mut row| {
let entry = &self.entries[row_index];
row.col(|ui| {
ui.label(match entry.filetype {
FileType::Dir => "🗁",
FileType::File => "🗋",
FileType::SymLink => "",
FileType::Unknown => "",
});
});
row.col(|ui| {
if ui.button(&entry.name).clicked() {
to_open = Some(row_index);
}
});
});
if let Some(row_index) = to_open {
self.open(row_index);
}
});
});
}
}
impl Entry {
fn new(dir_entry: DirEntry) -> Option<Self> {
let name = dir_entry.file_name().into_string().ok()?;
let path = dir_entry.path();
let filetype = dir_entry.file_type().ok()?;
let filetype = if filetype.is_dir() {
FileType::Dir
} else if filetype.is_file() {
FileType::File
} else if filetype.is_symlink() {
FileType::SymLink
} else {
FileType::Unknown
};
Some(Self {
name,
path,
filetype,
})
}
fn is_dir(&self) -> bool {
self.filetype == FileType::Dir
}
}