image_info.rs

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