init
This commit is contained in:
commit
50f2c4255b
5 changed files with 3058 additions and 0 deletions
1
.gitignore
vendored
Normal file
1
.gitignore
vendored
Normal file
|
@ -0,0 +1 @@
|
|||
/target
|
2854
Cargo.lock
generated
Normal file
2854
Cargo.lock
generated
Normal file
File diff suppressed because it is too large
Load diff
11
Cargo.toml
Normal file
11
Cargo.toml
Normal 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
1
rustfmt.toml
Normal file
|
@ -0,0 +1 @@
|
|||
hard_tabs = true
|
191
src/main.rs
Normal file
191
src/main.rs
Normal 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
|
||||
}
|
||||
}
|
Loading…
Reference in a new issue