ferrite_core/
app.rs

1use eframe::egui::{self, Context, Key};
2use ferrite_cache::CacheHandle;
3use std::{path::PathBuf, sync::Arc};
4use tracing::instrument;
5
6use ferrite_config::FerriteConfig;
7use ferrite_navigation::NavigationManager;
8use ferrite_ui::{HelpMenu, ImageRenderer, RenderResult, ZoomHandler};
9
10pub struct FeriteApp {
11    config:        FerriteConfig,
12    image_manager: ferrite_image::ImageManager,
13    navigation:    NavigationManager,
14    zoom_handler:  ZoomHandler,
15    cache_manager: Arc<CacheHandle>,
16    help_menu:     HelpMenu,
17}
18
19impl FeriteApp {
20    #[instrument(skip(_cc, config, cache_manager), fields(initial_image = ?initial_image))]
21    pub fn new(
22        _cc: &eframe::CreationContext<'_>,
23        initial_image: Option<PathBuf>,
24        config: FerriteConfig,
25        cache_manager: Arc<CacheHandle>,
26    ) -> Self {
27        let image_manager =
28            ferrite_image::ImageManager::new(cache_manager.clone());
29        let navigation = NavigationManager::new();
30        let zoom_handler = ZoomHandler::new(config.zoom.default_zoom);
31
32        let mut app = Self {
33            config,
34            image_manager,
35            navigation,
36            zoom_handler,
37            cache_manager,
38            help_menu: HelpMenu::new(),
39        };
40
41        if let Some(path) = initial_image {
42            if let Ok(()) = app.navigation.load_current_directory(&path) {
43                if let Ok(image_data) =
44                    app.cache_manager.get_image(path.clone())
45                {
46                    app.image_manager.current_image = Some(image_data);
47                    app.image_manager.set_path(path);
48                    app.cache_nearby_images();
49                }
50            }
51        }
52
53        app
54    }
55
56    fn cache_nearby_images(&self) {
57        use tracing::{error, info};
58        let cache_count = self.config.cache.preload_count; // Cache 2 images in each direction
59        let (prev_paths, next_paths) =
60            self.navigation.get_nearby_paths(cache_count);
61
62        // Cache previous images
63        for path in prev_paths {
64            if let Err(e) = self.cache_manager.cache_image(path.clone()) {
65                error!(
66                    "Failed to cache previous image {}: {}",
67                    path.display(),
68                    e
69                );
70            } else {
71                info!("Successfully cached previous image: {}", path.display());
72            }
73        }
74
75        // Cache next images
76        for path in next_paths {
77            if let Err(e) = self.cache_manager.cache_image(path.clone()) {
78                error!("Failed to cache next image {}: {}", path.display(), e);
79            } else {
80                info!("Successfully cached next image: {}", path.display());
81            }
82        }
83    }
84
85    fn handle_navigation(&mut self, ctx: &Context) {
86        let next_pressed = ctx
87            .input(|i| i.key_pressed(Key::ArrowRight) || i.key_pressed(Key::D));
88        let prev_pressed = ctx
89            .input(|i| i.key_pressed(Key::ArrowLeft) || i.key_pressed(Key::A));
90        let delete_pressed =
91            ctx.input(|i| i.key_pressed(self.config.controls.delete_key));
92
93        if delete_pressed {
94            self.handle_delete();
95        } else if next_pressed {
96            if let Some(next_path) = self.navigation.next_image() {
97                let _ = self.image_manager.load_image(next_path);
98                self.zoom_handler.reset_view_position();
99                self.cache_nearby_images();
100            }
101        } else if prev_pressed {
102            if let Some(prev_path) = self.navigation.previous_image() {
103                let _ = self.image_manager.load_image(prev_path);
104                self.zoom_handler.reset_view_position();
105                self.cache_nearby_images();
106            }
107        }
108    }
109
110    fn handle_delete(&mut self) {
111        use tracing::{error, info};
112
113        // Delete the current file
114        match self.image_manager.delete_current_file() {
115            Ok(Some(deleted_path)) => {
116                info!("Successfully deleted file: {}", deleted_path.display());
117
118                // Remove the deleted file from navigation and get next image
119                if let Some(next_path) =
120                    self.navigation.remove_deleted_file(&deleted_path)
121                {
122                    // Load the next image
123                    if let Err(e) =
124                        self.image_manager.load_image(next_path.clone())
125                    {
126                        error!(
127                            "Failed to load next image after deletion: {}",
128                            e
129                        );
130                    } else {
131                        self.zoom_handler.reset_view_position();
132                        self.cache_nearby_images();
133                    }
134                } else {
135                    info!("No more images to display after deletion");
136                }
137            },
138            Ok(None) => {
139                info!("No file to delete");
140            },
141            Err(e) => {
142                error!("Failed to delete file: {}", e);
143            },
144        }
145    }
146}
147
148impl eframe::App for FeriteApp {
149    fn update(&mut self, ctx: &Context, _frame: &mut eframe::Frame) {
150        if ctx.input(|i| i.key_pressed(self.config.controls.help_key)) {
151            self.help_menu.toggle();
152        }
153
154        if ctx.input(|i| i.key_pressed(self.config.controls.quit_key)) {
155            ctx.send_viewport_cmd(egui::ViewportCommand::Close);
156        }
157
158        self.handle_navigation(ctx);
159
160        egui::CentralPanel::default().show(ctx, |ui| {
161            let render_result: RenderResult = ImageRenderer::render(
162                ui,
163                ctx,
164                &mut self.image_manager,
165                &mut self.zoom_handler,
166                &self.config,
167                &self.config.controls,
168            );
169
170            // Handle delete button click
171            if render_result.delete_requested {
172                self.handle_delete();
173            }
174
175            self.help_menu.render(
176                ui,
177                &self.config.help_menu,
178                &self.config.controls,
179            );
180        });
181    }
182}