use gpui::{div, Context, Entity, IntoElement, ParentElement, Render, Subscription}; use project::image_store::{ImageFormat, ImageMetadata}; use settings::Settings; use ui::prelude::*; use workspace::{ItemHandle, StatusItemView, Workspace}; use crate::{ImageFileSizeUnit, ImageView, ImageViewerSettings}; pub struct ImageInfo { metadata: Option, _observe_active_image: Option, observe_image_item: Option, } impl ImageInfo { pub fn new(_workspace: &Workspace) -> Self { Self { metadata: None, _observe_active_image: None, observe_image_item: None, } } fn update_metadata(&mut self, image_view: &Entity, cx: &mut Context) { let image_item = image_view.read(cx).image_item.clone(); let current_metadata = image_item.read(cx).image_metadata; if current_metadata.is_some() { self.metadata = current_metadata; cx.notify(); } else { self.observe_image_item = Some(cx.observe(&image_item, |this, item, cx| { this.metadata = item.read(cx).image_metadata; cx.notify(); })); } } } fn format_file_size(size: u64, image_unit_type: ImageFileSizeUnit) -> String { match image_unit_type { ImageFileSizeUnit::Binary => { if size < 1024 { format!("{size}B") } else if size < 1024 * 1024 { format!("{:.1}KiB", size as f64 / 1024.0) } else { format!("{:.1}MiB", size as f64 / (1024.0 * 1024.0)) } } ImageFileSizeUnit::Decimal => { if size < 1000 { format!("{size}B") } else if size < 1000 * 1000 { format!("{:.1}KB", size as f64 / 1000.0) } else { format!("{:.1}MB", size as f64 / (1000.0 * 1000.0)) } } } } impl Render for ImageInfo { fn render(&mut self, _window: &mut Window, cx: &mut Context) -> impl IntoElement { let settings = ImageViewerSettings::get_global(cx); let Some(metadata) = self.metadata.as_ref() else { return div(); }; let mut components = Vec::new(); components.push(format!("{}x{}", metadata.width, metadata.height)); components.push(format_file_size(metadata.file_size, settings.unit)); if let Some(colors) = metadata.colors { components.push(format!( "{} channels, {} bits per pixel", colors.channels, colors.bits_per_pixel() )); } components.push( match metadata.format { ImageFormat::Png => "PNG", ImageFormat::Jpeg => "JPEG", ImageFormat::Gif => "GIF", ImageFormat::WebP => "WebP", ImageFormat::Tiff => "TIFF", ImageFormat::Bmp => "BMP", ImageFormat::Ico => "ICO", ImageFormat::Avif => "Avif", _ => "Unknown", } .to_string(), ); div().child( Button::new("image-metadata", components.join(" • ")).label_size(LabelSize::Small), ) } } impl StatusItemView for ImageInfo { fn set_active_pane_item( &mut self, active_pane_item: Option<&dyn ItemHandle>, _window: &mut Window, cx: &mut Context, ) { self._observe_active_image = None; self.observe_image_item = None; if let Some(image_view) = active_pane_item.and_then(|item| item.act_as::(cx)) { self.update_metadata(&image_view, cx); self._observe_active_image = Some(cx.observe(&image_view, |this, view, cx| { this.update_metadata(&view, cx); })); } else { self.metadata = None; } cx.notify(); } }