Skip to main content

image_collage/tui/
explorer.rs

1use anyhow::Result;
2use std::{env, fs, path::PathBuf};
3
4use crate::utils::is_image_file;
5
6/// File explorer for navigating directories and selecting images.
7pub struct FileExplorer {
8    current_dir: PathBuf,
9    entries: Vec<PathBuf>,
10    selected_index: usize,
11    selected_images: Vec<PathBuf>,
12}
13
14impl FileExplorer {
15    /// Creates a new file explorer starting from the current working directory.
16    pub fn new() -> Result<Self> {
17        let current_dir = env::current_dir()?;
18        let mut explorer = FileExplorer {
19            current_dir: current_dir.clone(),
20            entries: Vec::new(),
21            selected_index: 0,
22            selected_images: Vec::new(),
23        };
24        explorer.refresh_entries()?;
25        Ok(explorer)
26    }
27
28    /// Refreshes the list of entries in the current directory.
29    /// 
30    /// Includes parent directory (if exists), subdirectories, and image files.
31    /// Entries are sorted with directories first, then files alphabetically.
32    fn refresh_entries(&mut self) -> Result<()> {
33        let mut entries = Vec::new();
34        
35        if let Some(parent) = self.current_dir.parent() {
36            entries.push(parent.to_path_buf());
37        }
38        
39        for entry in fs::read_dir(&self.current_dir)? {
40            let entry = entry?;
41            let path = entry.path();
42            
43            if path.is_dir() || is_image_file(&path) {
44                entries.push(path);
45            }
46        }
47        
48        entries.sort_by(|a, b| {
49            let a_is_dir = a.is_dir();
50            let b_is_dir = b.is_dir();
51            match (a_is_dir, b_is_dir) {
52                (true, false) => std::cmp::Ordering::Less,
53                (false, true) => std::cmp::Ordering::Greater,
54                _ => a.file_name().cmp(&b.file_name()),
55            }
56        });
57        
58        self.entries = entries;
59        self.selected_index = 0;
60        Ok(())
61    }
62
63    /// Moves the selection cursor up one entry.
64    pub fn move_up(&mut self) {
65        if self.selected_index > 0 {
66            self.selected_index -= 1;
67        }
68    }
69
70    /// Moves the selection cursor down one entry.
71    pub fn move_down(&mut self) {
72        if self.selected_index < self.entries.len().saturating_sub(1) {
73            self.selected_index += 1;
74        }
75    }
76
77    /// Performs action on the currently selected entry.
78    /// 
79    /// * For directories: enters the directory
80    /// * For images: toggles selection (select if unselected, deselect if selected)
81    /// * Maximum of 4 images can be selected
82    pub fn enter_selected(&mut self) -> Result<()> {
83        if let Some(path) = self.entries.get(self.selected_index) {
84            if path.is_dir() {
85                self.current_dir = path.clone();
86                self.refresh_entries()?;
87            } else if is_image_file(path) {
88                if let Some(pos) = self.selected_images.iter().position(|p| p == path) {
89                    self.selected_images.remove(pos);
90                } else if self.selected_images.len() < 4 {
91                    self.selected_images.push(path.clone());
92                }
93            }
94        }
95        Ok(())
96    }
97
98    /// Navigates to the parent directory.
99    pub fn go_up(&mut self) -> Result<()> {
100        if let Some(parent) = self.current_dir.parent() {
101            self.current_dir = parent.to_path_buf();
102            self.refresh_entries()?;
103        }
104        Ok(())
105    }
106
107    /// Returns the current directory path.
108    pub fn current_dir(&self) -> &PathBuf {
109        &self.current_dir
110    }
111
112    /// Returns all entries in the current directory.
113    pub fn entries(&self) -> &[PathBuf] {
114        &self.entries
115    }
116
117    /// Returns the index of the currently selected entry.
118    pub fn selected_index(&self) -> usize {
119        self.selected_index
120    }
121
122    /// Returns the list of selected image paths.
123    pub fn selected_images(&self) -> &[PathBuf] {
124        &self.selected_images
125    }
126}