ricecoder_tui/
image_widget.rs1use std::path::PathBuf;
15
16#[derive(Debug, Clone)]
33pub struct ImageWidget {
34 pub images: Vec<PathBuf>,
36 pub visible: bool,
38 pub max_width: u32,
40 pub max_height: u32,
42 pub show_metadata: bool,
44 pub use_ascii_placeholder: bool,
46}
47
48impl ImageWidget {
49 pub fn new() -> Self {
51 Self {
52 images: Vec::new(),
53 visible: true,
54 max_width: 80,
55 max_height: 30,
56 show_metadata: true,
57 use_ascii_placeholder: true,
58 }
59 }
60
61 pub fn with_dimensions(width: u32, height: u32) -> Self {
63 Self {
64 images: Vec::new(),
65 visible: true,
66 max_width: width,
67 max_height: height,
68 show_metadata: true,
69 use_ascii_placeholder: true,
70 }
71 }
72
73 pub fn add_image(&mut self, path: PathBuf) {
83 if !self.images.contains(&path) {
84 self.images.push(path);
85 }
86 }
87
88 pub fn add_images(&mut self, paths: Vec<PathBuf>) {
98 for path in paths {
99 self.add_image(path);
100 }
101 }
102
103 pub fn remove_image(&mut self, path: &PathBuf) -> bool {
113 if let Some(pos) = self.images.iter().position(|p| p == path) {
114 self.images.remove(pos);
115 true
116 } else {
117 false
118 }
119 }
120
121 pub fn clear_images(&mut self) {
123 self.images.clear();
124 }
125
126 pub fn image_count(&self) -> usize {
128 self.images.len()
129 }
130
131 pub fn has_images(&self) -> bool {
133 !self.images.is_empty()
134 }
135
136 pub fn get_images(&self) -> &[PathBuf] {
138 &self.images
139 }
140
141 pub fn show(&mut self) {
143 self.visible = true;
144 }
145
146 pub fn hide(&mut self) {
148 self.visible = false;
149 }
150
151 pub fn toggle_visibility(&mut self) {
153 self.visible = !self.visible;
154 }
155
156 pub fn set_dimensions(&mut self, width: u32, height: u32) {
167 self.max_width = width;
168 self.max_height = height;
169 }
170
171 pub fn enable_metadata(&mut self) {
173 self.show_metadata = true;
174 }
175
176 pub fn disable_metadata(&mut self) {
178 self.show_metadata = false;
179 }
180
181 pub fn enable_ascii_placeholder(&mut self) {
183 self.use_ascii_placeholder = true;
184 }
185
186 pub fn disable_ascii_placeholder(&mut self) {
188 self.use_ascii_placeholder = false;
189 }
190
191 pub fn render(&self) -> String {
205 if !self.visible || self.images.is_empty() {
206 return String::new();
207 }
208
209 let mut output = String::new();
210
211 for (index, _path) in self.images.iter().enumerate() {
213 if index > 0 {
215 output.push_str(&self.render_separator());
216 output.push('\n');
217 }
218
219 output.push_str(&self.render_image_placeholder(index));
221
222 if index < self.images.len() - 1 {
224 output.push('\n');
225 }
226 }
227
228 output
229 }
230
231 fn render_image_placeholder(&self, index: usize) -> String {
233 let mut output = String::new();
234
235 if self.show_metadata {
237 output.push_str(&format!("[Image {}] ", index + 1));
238 if let Some(path) = self.images.get(index) {
239 output.push_str(&format!("{}", path.display()));
240 }
241 output.push('\n');
242 }
243
244 if self.use_ascii_placeholder {
246 output.push_str(&self.render_ascii_placeholder());
247 }
248
249 output
250 }
251
252 fn render_ascii_placeholder(&self) -> String {
254 let placeholder_char = "█";
255 let width = self.max_width as usize;
256 let height = 10; let mut output = String::new();
259
260 for row in 0..height {
262 if row == 0 || row == height - 1 {
263 output.push_str(&placeholder_char.repeat(width));
265 } else if row == 1 || row == height - 2 {
266 output.push_str(placeholder_char);
268 output.push_str(&" ".repeat(width.saturating_sub(2)));
269 output.push_str(placeholder_char);
270 } else {
271 output.push_str(placeholder_char);
273 output.push_str(&" ".repeat(width.saturating_sub(2)));
274 output.push_str(placeholder_char);
275 }
276 output.push('\n');
277 }
278
279 output
280 }
281
282 fn render_separator(&self) -> String {
284 let separator_char = "─";
285 separator_char.repeat(self.max_width as usize)
286 }
287}
288
289impl Default for ImageWidget {
290 fn default() -> Self {
291 Self::new()
292 }
293}
294
295#[cfg(test)]
296mod tests {
297 use super::*;
298
299 #[test]
300 fn test_image_widget_creation() {
301 let widget = ImageWidget::new();
302 assert!(widget.visible);
303 assert_eq!(widget.max_width, 80);
304 assert_eq!(widget.max_height, 30);
305 assert!(widget.show_metadata);
306 assert!(widget.use_ascii_placeholder);
307 assert_eq!(widget.image_count(), 0);
308 }
309
310 #[test]
311 fn test_image_widget_with_dimensions() {
312 let widget = ImageWidget::with_dimensions(100, 50);
313 assert_eq!(widget.max_width, 100);
314 assert_eq!(widget.max_height, 50);
315 }
316
317 #[test]
318 fn test_add_image() {
319 let mut widget = ImageWidget::new();
320 let path = PathBuf::from("/path/to/image.png");
321
322 widget.add_image(path.clone());
323 assert_eq!(widget.image_count(), 1);
324 assert!(widget.has_images());
325 assert_eq!(widget.get_images()[0], path);
326 }
327
328 #[test]
329 fn test_add_duplicate_image() {
330 let mut widget = ImageWidget::new();
331 let path = PathBuf::from("/path/to/image.png");
332
333 widget.add_image(path.clone());
334 widget.add_image(path.clone());
335
336 assert_eq!(widget.image_count(), 1);
338 }
339
340 #[test]
341 fn test_add_multiple_images() {
342 let mut widget = ImageWidget::new();
343 let paths = vec![
344 PathBuf::from("/path/to/image1.png"),
345 PathBuf::from("/path/to/image2.jpg"),
346 PathBuf::from("/path/to/image3.gif"),
347 ];
348
349 widget.add_images(paths.clone());
350 assert_eq!(widget.image_count(), 3);
351 }
352
353 #[test]
354 fn test_remove_image() {
355 let mut widget = ImageWidget::new();
356 let path = PathBuf::from("/path/to/image.png");
357
358 widget.add_image(path.clone());
359 assert_eq!(widget.image_count(), 1);
360
361 let removed = widget.remove_image(&path);
362 assert!(removed);
363 assert_eq!(widget.image_count(), 0);
364 }
365
366 #[test]
367 fn test_remove_image_not_found() {
368 let mut widget = ImageWidget::new();
369 let path = PathBuf::from("/path/to/image.png");
370
371 let removed = widget.remove_image(&path);
372 assert!(!removed);
373 }
374
375 #[test]
376 fn test_clear_images() {
377 let mut widget = ImageWidget::new();
378 widget.add_images(vec![
379 PathBuf::from("/path/to/image1.png"),
380 PathBuf::from("/path/to/image2.jpg"),
381 ]);
382
383 assert_eq!(widget.image_count(), 2);
384 widget.clear_images();
385 assert_eq!(widget.image_count(), 0);
386 }
387
388 #[test]
389 fn test_visibility() {
390 let mut widget = ImageWidget::new();
391 assert!(widget.visible);
392
393 widget.hide();
394 assert!(!widget.visible);
395
396 widget.show();
397 assert!(widget.visible);
398
399 widget.toggle_visibility();
400 assert!(!widget.visible);
401 }
402
403 #[test]
404 fn test_set_dimensions() {
405 let mut widget = ImageWidget::new();
406 widget.set_dimensions(100, 50);
407
408 assert_eq!(widget.max_width, 100);
409 assert_eq!(widget.max_height, 50);
410 }
411
412 #[test]
413 fn test_metadata_toggle() {
414 let mut widget = ImageWidget::new();
415 assert!(widget.show_metadata);
416
417 widget.disable_metadata();
418 assert!(!widget.show_metadata);
419
420 widget.enable_metadata();
421 assert!(widget.show_metadata);
422 }
423
424 #[test]
425 fn test_ascii_placeholder_toggle() {
426 let mut widget = ImageWidget::new();
427 assert!(widget.use_ascii_placeholder);
428
429 widget.disable_ascii_placeholder();
430 assert!(!widget.use_ascii_placeholder);
431
432 widget.enable_ascii_placeholder();
433 assert!(widget.use_ascii_placeholder);
434 }
435
436 #[test]
437 fn test_render_empty_widget() {
438 let widget = ImageWidget::new();
439 let rendered = widget.render();
440 assert_eq!(rendered, "");
441 }
442
443 #[test]
444 fn test_render_hidden_widget() {
445 let mut widget = ImageWidget::new();
446 widget.add_image(PathBuf::from("/path/to/image.png"));
447 widget.hide();
448
449 let rendered = widget.render();
450 assert_eq!(rendered, "");
451 }
452
453 #[test]
454 fn test_render_single_image() {
455 let mut widget = ImageWidget::new();
456 widget.add_image(PathBuf::from("/path/to/image.png"));
457
458 let rendered = widget.render();
459 assert!(!rendered.is_empty());
460 assert!(rendered.contains("Image 1"));
461 assert!(rendered.contains("image.png"));
462 assert!(rendered.contains("█")); }
464
465 #[test]
466 fn test_render_multiple_images() {
467 let mut widget = ImageWidget::new();
468 widget.add_images(vec![
469 PathBuf::from("/path/to/image1.png"),
470 PathBuf::from("/path/to/image2.jpg"),
471 ]);
472
473 let rendered = widget.render();
474 assert!(rendered.contains("Image 1"));
475 assert!(rendered.contains("Image 2"));
476 assert!(rendered.contains("image1.png"));
477 assert!(rendered.contains("image2.jpg"));
478 assert!(rendered.contains("─")); }
480
481 #[test]
482 fn test_render_without_metadata() {
483 let mut widget = ImageWidget::new();
484 widget.add_image(PathBuf::from("/path/to/image.png"));
485 widget.disable_metadata();
486
487 let rendered = widget.render();
488 assert!(!rendered.contains("Image 1"));
489 assert!(rendered.contains("█")); }
491
492 #[test]
493 fn test_render_without_ascii_placeholder() {
494 let mut widget = ImageWidget::new();
495 widget.add_image(PathBuf::from("/path/to/image.png"));
496 widget.disable_ascii_placeholder();
497
498 let rendered = widget.render();
499 assert!(rendered.contains("Image 1"));
500 assert!(!rendered.contains("█")); }
502
503 #[test]
504 fn test_render_fits_within_bounds() {
505 let mut widget = ImageWidget::with_dimensions(80, 30);
506 widget.add_image(PathBuf::from("/path/to/image.png"));
507
508 let rendered = widget.render();
509 let lines: Vec<&str> = rendered.lines().collect();
510
511 assert!(lines.len() as u32 <= widget.max_height);
513
514 for line in lines {
516 assert!(line.chars().count() as u32 <= widget.max_width);
517 }
518 }
519}