From f76ab74a9d8e6d574bffee8a3e69aa4bfa0e86c0 Mon Sep 17 00:00:00 2001 From: Jeremy Soller Date: Mon, 4 Mar 2024 13:41:18 -0700 Subject: [PATCH] Enable preview of svg --- src/tab.rs | 107 +++++++++++++++++++++++++++-------------------------- 1 file changed, 55 insertions(+), 52 deletions(-) diff --git a/src/tab.rs b/src/tab.rs index 2d74363..eebdc3d 100644 --- a/src/tab.rs +++ b/src/tab.rs @@ -243,10 +243,14 @@ pub fn scan_path(tab_path: &PathBuf, sizes: IconSizes) -> Vec { let open_with = mime_apps(&mime); - let thumbnail_res_opt = if mime.type_() == "image" { - None + let thumbnail_opt = if mime.type_() == mime::IMAGE { + if mime.subtype() == mime::SVG { + Some(ItemThumbnail::Svg) + } else { + None + } } else { - Some(Err(())) + Some(ItemThumbnail::NotImage) }; let children = if metadata.is_dir() { @@ -272,7 +276,7 @@ pub fn scan_path(tab_path: &PathBuf, sizes: IconSizes) -> Vec { icon_handle_list, icon_handle_list_condensed, open_with, - thumbnail_res_opt, + thumbnail_opt, button_id: widget::Id::unique(), pos_opt: Cell::new(None), rect_opt: Cell::new(None), @@ -364,7 +368,7 @@ pub fn scan_trash(sizes: IconSizes) -> Vec { icon_handle_list, icon_handle_list_condensed, open_with: Vec::new(), - thumbnail_res_opt: Some(Err(())), + thumbnail_opt: Some(ItemThumbnail::NotImage), button_id: widget::Id::unique(), pos_opt: Cell::new(None), rect_opt: Cell::new(None), @@ -431,7 +435,7 @@ pub enum Message { RightClick(usize), Scroll(Viewport), SelectAll, - Thumbnail(PathBuf, Result), + Thumbnail(PathBuf, ItemThumbnail), ToggleShowHidden, View(View), ToggleSort(HeadingOptions), @@ -461,6 +465,13 @@ impl ItemMetadata { } } +#[derive(Clone, Debug)] +pub enum ItemThumbnail { + NotImage, + Rgba(image::RgbaImage), + Svg, +} + #[derive(Clone, Debug)] pub struct Item { pub name: String, @@ -472,7 +483,7 @@ pub struct Item { pub icon_handle_list: widget::icon::Handle, pub icon_handle_list_condensed: widget::icon::Handle, pub open_with: Vec, - pub thumbnail_res_opt: Option>, + pub thumbnail_opt: Option, pub button_id: widget::Id, pub pos_opt: Cell>, pub rect_opt: Cell>, @@ -481,6 +492,26 @@ pub struct Item { } impl Item { + fn preview(&self, sizes: IconSizes) -> Element { + // This loads the image only if thumbnailing worked + match self + .thumbnail_opt + .as_ref() + .unwrap_or(&ItemThumbnail::NotImage) + { + ItemThumbnail::NotImage => widget::icon::icon(self.icon_handle_grid.clone()) + .content_fit(ContentFit::Contain) + .size(sizes.grid()) + .into(), + ItemThumbnail::Rgba(_) => { + widget::image::viewer(widget::image::Handle::from_path(&self.path)) + .min_scale(1.0) + .into() + } + ItemThumbnail::Svg => widget::Svg::from_path(&self.path).into(), + } + } + pub fn open_with_view(&self, sizes: IconSizes) -> Element { let cosmic_theme::Spacing { space_xs, @@ -492,21 +523,7 @@ impl Item { column = column.push(widget::row::with_children(vec![ widget::horizontal_space(Length::Fill).into(), - // This loads the image only if thumbnailing worked - if self - .thumbnail_res_opt - .as_ref() - .map_or(false, |res| res.is_ok()) - { - widget::image::viewer(widget::image::Handle::from_path(&self.path)) - .min_scale(1.0) - .into() - } else { - widget::icon::icon(self.icon_handle_grid.clone()) - .content_fit(ContentFit::Contain) - .size(sizes.grid()) - .into() - }, + self.preview(sizes), widget::horizontal_space(Length::Fill).into(), ])); @@ -544,21 +561,7 @@ impl Item { column = column.push(widget::row::with_children(vec![ widget::horizontal_space(Length::Fill).into(), - // This loads the image only if thumbnailing worked - if self - .thumbnail_res_opt - .as_ref() - .map_or(false, |res| res.is_ok()) - { - widget::image::viewer(widget::image::Handle::from_path(&self.path)) - .min_scale(1.0) - .into() - } else { - widget::icon::icon(self.icon_handle_grid.clone()) - .content_fit(ContentFit::Contain) - .size(sizes.grid()) - .into() - }, + self.preview(sizes), widget::horizontal_space(Length::Fill).into(), ])); @@ -1177,21 +1180,21 @@ impl Tab { commands.push(Command::FocusButton(widget::Id::unique())); } } - Message::Thumbnail(path, thumbnail_res) => { + Message::Thumbnail(path, thumbnail) => { if let Some(ref mut items) = self.items_opt { for item in items.iter_mut() { if item.path == path { - if let Ok(thumbnail) = &thumbnail_res { + if let ItemThumbnail::Rgba(rgba) = &thumbnail { //TODO: pass handles already generated to avoid blocking main thread let handle = widget::icon::from_raster_pixels( - thumbnail.width(), - thumbnail.height(), - thumbnail.as_raw().clone(), + rgba.width(), + rgba.height(), + rgba.as_raw().clone(), ); item.icon_handle_grid = handle.clone(); item.icon_handle_list = handle; } - item.thumbnail_res_opt = Some(thumbnail_res); + item.thumbnail_opt = Some(thumbnail); break; } } @@ -1858,7 +1861,7 @@ impl Tab { }; for item in items.iter() { - if item.thumbnail_res_opt.is_some() { + if item.thumbnail_opt.is_some() { // Skip items that already have a thumbnail continue; } @@ -1881,33 +1884,33 @@ impl Tab { path.clone(), 1, |mut output| async move { - let (path, thumbnail_res) = tokio::task::spawn_blocking(move || { + let (path, thumbnail) = tokio::task::spawn_blocking(move || { let start = std::time::Instant::now(); - let thumbnail_res = match image::io::Reader::open(&path) { + let thumbnail = match image::io::Reader::open(&path) { Ok(reader) => match reader.decode() { Ok(image) => { - //TODO: configurable thumbnail size + //TODO: configurable thumbnail size? let thumbnail = image.thumbnail(64, 64); - Ok(thumbnail.to_rgba8()) + ItemThumbnail::Rgba(thumbnail.to_rgba8()) } Err(err) => { log::warn!("failed to decode {:?}: {}", path, err); - Err(()) + ItemThumbnail::NotImage } }, Err(err) => { log::warn!("failed to read {:?}: {}", path, err); - Err(()) + ItemThumbnail::NotImage } }; log::info!("thumbnailed {:?} in {:?}", path, start.elapsed()); - (path, thumbnail_res) + (path, thumbnail) }) .await .unwrap(); match output - .send(Message::Thumbnail(path.clone(), thumbnail_res)) + .send(Message::Thumbnail(path.clone(), thumbnail)) .await { Ok(()) => {}