This commit is contained in:
2025-08-23 19:32:03 -05:00
parent a493adedd0
commit 251c5a07ce
2 changed files with 82 additions and 39 deletions
+25 -16
View File
@@ -1,7 +1,6 @@
use std::ops::Index; use std::cell::RefCell;
use std::path::PathBuf; use std::path::PathBuf;
use std::process::Command; use std::process::Command;
use rfd::MessageDialogResult::No;
use thiserror::Error; use thiserror::Error;
use crate::fleen_app::FleenError::{RootDirNonexistenceError, RootDirPopulatedError}; use crate::fleen_app::FleenError::{RootDirNonexistenceError, RootDirPopulatedError};
use crate::fleen_app::TreeEntry::{CloseDir, Dir}; use crate::fleen_app::TreeEntry::{CloseDir, Dir};
@@ -11,7 +10,9 @@ pub enum FleenError {
#[error("Can't reach root dir {0}")] #[error("Can't reach root dir {0}")]
RootDirNonexistenceError(PathBuf), RootDirNonexistenceError(PathBuf),
#[error("Root dir is nonempty, you probably don't want to create an app here: {0}")] #[error("Root dir is nonempty, you probably don't want to create an app here: {0}")]
RootDirPopulatedError(PathBuf) RootDirPopulatedError(PathBuf),
#[error("Failed to open {0}: {1}")]
FileOpenError(String, String)
} }
#[derive(Clone, Debug)] #[derive(Clone, Debug)]
@@ -21,16 +22,15 @@ pub enum TreeEntry {
CloseDir CloseDir
} }
#[derive(Clone)]
pub struct FleenApp { pub struct FleenApp {
root: PathBuf, root: PathBuf,
files_cache: Option<Vec<TreeEntry>> files_cache: RefCell<Option<Vec<TreeEntry>>>
} }
impl FleenApp { impl FleenApp {
pub fn open(root: PathBuf) -> Result<Self, FleenError> { pub fn open(root: PathBuf) -> Result<Self, FleenError> {
match root.try_exists() { match root.try_exists() {
Ok(true) => Ok(Self { root, files_cache: None }), Ok(true) => Ok(Self { root, files_cache: RefCell::new(None) }),
_ => Err(RootDirNonexistenceError(root)) _ => Err(RootDirNonexistenceError(root))
} }
} }
@@ -41,7 +41,7 @@ impl FleenApp {
if iter.next().is_some() { if iter.next().is_some() {
Err(RootDirPopulatedError(root)) Err(RootDirPopulatedError(root))
} else { } else {
Ok(Self { root, files_cache: None }) Ok(Self { root, files_cache: RefCell::new(None) })
} }
} }
Err(_) => { Err(_) => {
@@ -50,14 +50,15 @@ impl FleenApp {
} }
} }
pub fn file_tree_entries(&mut self) -> impl IntoIterator<Item=TreeEntry> { // TODO: This is panicky as hell, make it return a Result
if self.files_cache.is_none() { fn refresh_file_cache(&self, force: bool) {
if self.files_cache.borrow().is_none() || force {
let mut entries = vec![]; let mut entries = vec![];
fn visit_dir(dir: &PathBuf, entries: &mut Vec<TreeEntry>) { fn visit_dir(dir: &PathBuf, entries: &mut Vec<TreeEntry>) {
for entry in dir.read_dir().unwrap() { for entry in dir.read_dir().unwrap() {
let path = entry.unwrap().path(); let path = entry.unwrap().path();
if path.is_file() { if path.is_file() && !path.file_name().unwrap().to_str().unwrap().starts_with('.') {
entries.push(TreeEntry::File(path)) entries.push(TreeEntry::File(path))
} else if path.is_dir() { } else if path.is_dir() {
entries.push(Dir(path.clone())); entries.push(Dir(path.clone()));
@@ -67,15 +68,23 @@ impl FleenApp {
} }
} }
entries.push(Dir(self.root.clone()));
visit_dir(&self.root, &mut entries); visit_dir(&self.root, &mut entries);
self.files_cache = Some(entries) entries.push(CloseDir);
self.files_cache.replace(Some(entries));
} }
self.files_cache.as_ref().unwrap().clone()
} }
pub fn open_file_at_index(&self, index: usize) { pub fn file_tree_entries(&self) -> impl IntoIterator<Item=TreeEntry> {
if let TreeEntry::File(path) = &self.files_cache.as_ref().unwrap()[index] { self.refresh_file_cache(false);
Command::new("open").arg(path).spawn(); self.files_cache.borrow().clone().expect("Can't happen because we just refreshed the cache")
} }
pub fn open_filename(&self, filename: &String) -> Result<(), FleenError> {
Command::new("open").arg(filename.clone()).spawn().map_err(|err| {
FleenError::FileOpenError(filename.clone(), err.to_string())
})?;
Ok(())
} }
} }
+57 -23
View File
@@ -1,30 +1,36 @@
mod fleen_app; mod fleen_app;
use std::fmt::format; use std::path::PathBuf;
use std::path::{Path, PathBuf}; use eframe::egui::{Color32, Context, Id, Stroke};
use eframe::egui::{Context, Id};
use eframe::{egui, Frame}; use eframe::{egui, Frame};
use egui_ltreeview::Action; use egui_ltreeview::Action;
use crate::fleen_app::{FleenApp, FleenError, TreeEntry}; use crate::fleen_app::{FleenApp, FleenError, TreeEntry};
fn main() { fn main() {
let native_options = eframe::NativeOptions::default(); let native_options = eframe::NativeOptions::default();
eframe::run_native("Fleen", native_options, Box::new(|cc| Ok(Box::new(FleenUi::default())))); eframe::run_native("Fleen", native_options, Box::new(|_cc| {
Ok(Box::new(FleenUi::default()))
})).expect("Error running application");
} }
struct FleenUi { struct FleenUi {
app: Option<FleenApp>, app: Option<FleenApp>,
error: Option<FleenError> error: Option<FleenError>,
selected_file: Option<String>
} }
impl Default for FleenUi { impl Default for FleenUi {
fn default() -> Self { fn default() -> Self {
Self { app: None, error: None } Self {
app: None,
error: None,
selected_file: None
}
} }
} }
impl FleenUi { impl FleenUi {
fn site_chooser(&mut self, ctx: &Context, frame: &mut Frame) { fn site_chooser(&mut self, ctx: &Context) {
egui::CentralPanel::default().show(ctx, |ui| { egui::CentralPanel::default().show(ctx, |ui| {
ui.label("No site selected!"); ui.label("No site selected!");
@@ -48,39 +54,63 @@ impl FleenUi {
}); });
} }
fn display(&mut self, ctx: &Context, frame: &mut Frame) { fn display(&mut self, ctx: &Context) {
egui::CentralPanel::default().show(ctx, |ui| { egui::CentralPanel::default().show(ctx, |ui| {
ui.horizontal(|ui| { ui.horizontal(|ui| {
for action in self.tree_view(ui) { ui.vertical(|ui| {
if let Action::Activate(activate) = action { self.tree_view(ui);
for index in activate.selected { if ui.add(egui::Button::new("Open")).clicked() {
self.app.as_ref().unwrap().open_file_at_index(index) if let Some(fname) = &self.selected_file {
self.handle_error(self.app.as_ref().unwrap().open_filename(fname))
} }
} }
} ui.add(egui::Button::new("New page")).clicked();
});
}) })
}); });
} }
fn tree_view(&mut self, ui: &mut egui::Ui) -> Vec<Action<usize>> { fn tree_view(&mut self, ui: &mut egui::Ui) {
let (_, actions) = egui_ltreeview::TreeView::new(Id::from("tree")).show(ui, |builder| { let tv = egui_ltreeview::TreeView::new(Id::from("tree"))
for (id, entry) in self.app.as_mut().unwrap().file_tree_entries().into_iter().enumerate() { .allow_multi_selection(false)
.allow_drag_and_drop(false);
let (_, actions) = tv.show(ui, |builder| {
for entry in self.app.as_mut().unwrap().file_tree_entries().into_iter() {
match entry { match entry {
TreeEntry::File(p) => builder.leaf(id, label_for_path(p)), TreeEntry::File(p) => builder.leaf(id_for_path(&p), label_for_path(&p)),
TreeEntry::Dir(p) => { builder.dir(id, label_for_path(p)); }, TreeEntry::Dir(p) => { builder.dir(id_for_path(&p), label_for_path(&p)); },
TreeEntry::CloseDir => builder.close_dir() TreeEntry::CloseDir => builder.close_dir()
} }
} }
}); });
actions
for action in actions {
match action {
Action::SetSelected(files) => {
self.selected_file = files.first().map(String::clone)
}
Action::Activate(activate) => {
for fname in activate.selected {
self.handle_error(self.app.as_ref().unwrap().open_filename(&fname))
}
}
_ => {}
}
}
}
fn handle_error(&mut self, result: Result<(), FleenError>) {
if let Err(e) = result {
self.error = Some(e)
}
} }
} }
impl eframe::App for FleenUi { impl eframe::App for FleenUi {
fn update(&mut self, ctx: &Context, frame: &mut Frame) { fn update(&mut self, ctx: &Context, _frame: &mut Frame) {
match &mut self.app { match &mut self.app {
None => self.site_chooser(ctx, frame), None => self.site_chooser(ctx),
Some(app) => self.display(ctx, frame) Some(_) => self.display(ctx)
} }
if let Some(err) = &self.error { if let Some(err) = &self.error {
@@ -95,6 +125,10 @@ impl eframe::App for FleenUi {
} }
} }
fn label_for_path(path: PathBuf) -> String { fn label_for_path(path: &PathBuf) -> String {
path.file_name().unwrap().to_string_lossy().to_string() path.file_name().unwrap().to_string_lossy().to_string()
}
fn id_for_path(path: &PathBuf) -> String {
path.to_string_lossy().to_string()
} }