1use gpui::{Context, Entity, IntoElement, ParentElement, Render, Subscription, div};
2use project::image_store::{ImageFormat, ImageMetadata};
3use settings::Settings;
4use ui::prelude::*;
5use util::size::format_file_size;
6use workspace::{ItemHandle, StatusItemView, Workspace};
7
8use crate::{ImageFileSizeUnit, ImageView, ImageViewerSettings};
9
10pub struct ImageInfo {
11 metadata: Option<ImageMetadata>,
12 _observe_active_image: Option<Subscription>,
13 observe_image_item: Option<Subscription>,
14}
15
16impl ImageInfo {
17 pub fn new(_workspace: &Workspace) -> Self {
18 Self {
19 metadata: None,
20 _observe_active_image: None,
21 observe_image_item: None,
22 }
23 }
24
25 fn update_metadata(&mut self, image_view: &Entity<ImageView>, cx: &mut Context<Self>) {
26 let image_item = image_view.read(cx).image_item.clone();
27 let current_metadata = image_item.read(cx).image_metadata;
28 if current_metadata.is_some() {
29 self.metadata = current_metadata;
30 cx.notify();
31 } else {
32 self.observe_image_item = Some(cx.observe(&image_item, |this, item, cx| {
33 this.metadata = item.read(cx).image_metadata;
34 cx.notify();
35 }));
36 }
37 }
38}
39
40fn format_image_size(size: u64, image_unit_type: ImageFileSizeUnit) -> String {
41 let use_decimal = matches!(image_unit_type, ImageFileSizeUnit::Decimal);
42 format_file_size(size, use_decimal)
43}
44
45impl Render for ImageInfo {
46 fn render(&mut self, _window: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {
47 let settings = ImageViewerSettings::get_global(cx);
48
49 let Some(metadata) = self.metadata.as_ref() else {
50 return div();
51 };
52
53 let mut components = Vec::new();
54 components.push(format!("{}x{}", metadata.width, metadata.height));
55 components.push(format_image_size(metadata.file_size, settings.unit));
56
57 if let Some(colors) = metadata.colors {
58 components.push(format!(
59 "{} channels, {} bits per pixel",
60 colors.channels,
61 colors.bits_per_pixel()
62 ));
63 }
64
65 components.push(
66 match metadata.format {
67 ImageFormat::Png => "PNG",
68 ImageFormat::Jpeg => "JPEG",
69 ImageFormat::Gif => "GIF",
70 ImageFormat::WebP => "WebP",
71 ImageFormat::Tiff => "TIFF",
72 ImageFormat::Bmp => "BMP",
73 ImageFormat::Ico => "ICO",
74 ImageFormat::Avif => "Avif",
75 _ => "Unknown",
76 }
77 .to_string(),
78 );
79
80 div().child(
81 Button::new("image-metadata", components.join(" • ")).label_size(LabelSize::Small),
82 )
83 }
84}
85
86impl StatusItemView for ImageInfo {
87 fn set_active_pane_item(
88 &mut self,
89 active_pane_item: Option<&dyn ItemHandle>,
90 _window: &mut Window,
91 cx: &mut Context<Self>,
92 ) {
93 self._observe_active_image = None;
94 self.observe_image_item = None;
95
96 if let Some(image_view) = active_pane_item.and_then(|item| item.act_as::<ImageView>(cx)) {
97 self.update_metadata(&image_view, cx);
98
99 self._observe_active_image = Some(cx.observe(&image_view, |this, view, cx| {
100 this.update_metadata(&view, cx);
101 }));
102 } else {
103 self.metadata = None;
104 }
105 cx.notify();
106 }
107}