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; let (prev_paths, next_paths) =
60 self.navigation.get_nearby_paths(cache_count);
61
62 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 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 match self.image_manager.delete_current_file() {
115 Ok(Some(deleted_path)) => {
116 info!("Successfully deleted file: {}", deleted_path.display());
117
118 if let Some(next_path) =
120 self.navigation.remove_deleted_file(&deleted_path)
121 {
122 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 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}