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 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 const 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().hidden();
 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}