Skip to main content

image_collage/tui/
mod.rs

1/// Terminal User Interface module for interactive image selection.
2
3pub mod explorer;
4pub mod ui;
5
6use anyhow::Result;
7use crossterm::{
8    event::{self, DisableMouseCapture, EnableMouseCapture, Event, KeyCode},
9    execute,
10    terminal::{disable_raw_mode, enable_raw_mode, EnterAlternateScreen, LeaveAlternateScreen},
11};
12use ratatui::{backend::CrosstermBackend, Terminal};
13use std::io;
14
15use crate::collage::{create_collage, load_and_process_image};
16use explorer::FileExplorer;
17
18/// Configuration for the TUI session.
19pub struct TuiConfig {
20    pub output: std::path::PathBuf,
21    pub border: u32,
22    pub size: u32,
23    pub quality: u8,
24}
25
26/// Runs the interactive Terminal User Interface for image selection.
27/// 
28/// Provides a file browser with vim-style navigation keys to select
29/// exactly 4 images for collage creation.
30/// 
31/// # Key bindings
32/// 
33/// * `j`/`k` or arrow keys - Navigate up/down
34/// * `l`/`Enter` - Select/deselect image or enter directory
35/// * `h` or left arrow - Go up to parent directory
36/// * `c` - Create collage (when 4 images selected)
37/// * `q` - Quit without creating
38/// 
39/// # Arguments
40/// 
41/// * `config` - Configuration for output path, border width, size, and quality
42/// 
43/// # Returns
44/// 
45/// Ok(()) on successful completion or user quit, Error on failure
46pub fn run(config: TuiConfig) -> Result<()> {
47    enable_raw_mode()?;
48    let mut stdout = io::stdout();
49    execute!(stdout, EnterAlternateScreen, EnableMouseCapture)?;
50    let backend = CrosstermBackend::new(stdout);
51    let mut terminal = Terminal::new(backend)?;
52
53    let mut explorer = FileExplorer::new()?;
54    
55    loop {
56        terminal.draw(|f| ui::draw(f, &explorer))?;
57
58        if let Event::Key(key) = event::read()? {
59            match key.code {
60                KeyCode::Char('q') => break,
61                KeyCode::Char('j') | KeyCode::Down => explorer.move_down(),
62                KeyCode::Char('k') | KeyCode::Up => explorer.move_up(),
63                KeyCode::Char('l') | KeyCode::Enter => explorer.enter_selected()?,
64                KeyCode::Char('h') | KeyCode::Left => explorer.go_up()?,
65                KeyCode::Char('c') => {
66                    if explorer.selected_images().len() == 4 {
67                        disable_raw_mode()?;
68                        execute!(
69                            terminal.backend_mut(),
70                            LeaveAlternateScreen,
71                            DisableMouseCapture
72                        )?;
73                        terminal.show_cursor()?;
74                        
75                        println!("Creating collage...");
76                        let images = explorer.selected_images().iter()
77                            .map(|p| load_and_process_image(p, config.size))
78                            .collect::<Result<Vec<_>>>()?;
79                        
80                        let collage = create_collage(images, config.size, config.border);
81                        collage.save(&config.output)
82                            .map_err(|e| anyhow::anyhow!("Failed to save output: {}", e))?;
83                        
84                        println!("✓ Collage created successfully at {}!", config.output.display());
85                        return Ok(());
86                    }
87                }
88                _ => {}
89            }
90        }
91    }
92
93    disable_raw_mode()?;
94    execute!(
95        terminal.backend_mut(),
96        LeaveAlternateScreen,
97        DisableMouseCapture
98    )?;
99    terminal.show_cursor()?;
100    Ok(())
101}