diff --git a/crates/gpui3/src/assets.rs b/crates/gpui3/src/assets.rs new file mode 100644 index 0000000000000000000000000000000000000000..5d737fcd2f84685c15278fddb9df9df64df7f54b --- /dev/null +++ b/crates/gpui3/src/assets.rs @@ -0,0 +1,60 @@ +use crate::{size, DevicePixels, Result, SharedString, Size}; +use anyhow::anyhow; +use image::{Bgra, ImageBuffer}; +use std::{ + borrow::Cow, + fmt, + sync::atomic::{AtomicUsize, Ordering::SeqCst}, +}; + +pub trait AssetSource: 'static + Send + Sync { + fn load(&self, path: &SharedString) -> Result>; + fn list(&self, path: &SharedString) -> Result>; +} + +impl AssetSource for () { + fn load(&self, path: &SharedString) -> Result> { + Err(anyhow!( + "get called on empty asset provider with \"{}\"", + path + )) + } + + fn list(&self, _path: &SharedString) -> Result> { + Ok(vec![]) + } +} + +pub struct ImageData { + pub id: usize, + data: ImageBuffer, Vec>, +} + +impl ImageData { + pub fn from_raw(size: Size, bytes: Vec) -> Self { + static NEXT_ID: AtomicUsize = AtomicUsize::new(0); + + Self { + id: NEXT_ID.fetch_add(1, SeqCst), + data: ImageBuffer::from_raw(size.width.into(), size.height.into(), bytes).unwrap(), + } + } + + pub fn as_bytes(&self) -> &[u8] { + &self.data + } + + pub fn size(&self) -> Size { + let (width, height) = self.data.dimensions(); + size(width.into(), height.into()) + } +} + +impl fmt::Debug for ImageData { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + f.debug_struct("ImageData") + .field("id", &self.id) + .field("size", &self.data.dimensions()) + .finish() + } +} diff --git a/crates/gpui3/src/geometry.rs b/crates/gpui3/src/geometry.rs index 183554204493ff8f4acbd4043f6d64793e4b4c84..98bf802402c6fd0732919ee95b534a453e2e2e36 100644 --- a/crates/gpui3/src/geometry.rs +++ b/crates/gpui3/src/geometry.rs @@ -182,10 +182,10 @@ impl, S: Clone> MulAssign for Size { impl Eq for Size {} impl From>> for Size> { - fn from(val: Size>) -> Self { + fn from(size: Size>) -> Self { Size { - width: val.width.map(|p| p.0 as f32), - height: val.height.map(|p| p.0 as f32), + width: size.width.map(|p| p.0 as f32), + height: size.height.map(|p| p.0 as f32), } } } @@ -548,14 +548,14 @@ impl std::hash::Hash for Pixels { } impl From for Pixels { - fn from(val: f64) -> Self { - Pixels(val as f32) + fn from(pixels: f64) -> Self { + Pixels(pixels as f32) } } impl From for Pixels { - fn from(val: f32) -> Self { - Pixels(val) + fn from(pixels: f32) -> Self { + Pixels(pixels) } } @@ -608,8 +608,20 @@ impl From for i32 { } impl From for DevicePixels { - fn from(val: i32) -> Self { - DevicePixels(val) + fn from(device_pixels: i32) -> Self { + DevicePixels(device_pixels) + } +} + +impl From for DevicePixels { + fn from(device_pixels: u32) -> Self { + DevicePixels(device_pixels as i32) + } +} + +impl From for u32 { + fn from(device_pixels: DevicePixels) -> Self { + device_pixels.0 as u32 } } @@ -620,8 +632,8 @@ impl From for u64 { } impl From for DevicePixels { - fn from(val: u64) -> Self { - DevicePixels(val as i32) + fn from(device_pixels: u64) -> Self { + DevicePixels(device_pixels as i32) } } @@ -903,7 +915,7 @@ impl IsZero for Point { impl IsZero for Size { fn is_zero(&self) -> bool { - self.width.is_zero() && self.height.is_zero() + self.width.is_zero() || self.height.is_zero() } } diff --git a/crates/gpui3/src/gpui3.rs b/crates/gpui3/src/gpui3.rs index 8f6a2680519fb2d17279a8b99056db6ab397b223..b37c621a2b66e2d0b71acd8db1d9217071eedad6 100644 --- a/crates/gpui3/src/gpui3.rs +++ b/crates/gpui3/src/gpui3.rs @@ -1,4 +1,5 @@ mod app; +mod assets; mod color; mod element; mod elements; @@ -9,6 +10,7 @@ mod scene; mod style; mod style_helpers; mod styled; +mod svg_library; mod taffy; mod text_system; mod util; @@ -17,12 +19,14 @@ mod window; pub use anyhow::Result; pub use app::*; +pub use assets::*; pub use color::*; pub use element::*; pub use elements::*; pub use executor::*; pub use geometry::*; pub use gpui3_macros::*; +pub use svg_library::*; pub use platform::*; pub use refineable::*; @@ -145,6 +149,12 @@ impl std::fmt::Debug for SharedString { } } +impl std::fmt::Display for SharedString { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + write!(f, "{}", self.0.as_ref()) + } +} + impl>> From for SharedString { fn from(value: T) -> Self { Self(value.into()) diff --git a/crates/gpui3/src/svg_library.rs b/crates/gpui3/src/svg_library.rs new file mode 100644 index 0000000000000000000000000000000000000000..19023f2980bbb0d69ab3939b487c5ee826bf27d2 --- /dev/null +++ b/crates/gpui3/src/svg_library.rs @@ -0,0 +1,102 @@ +use crate::{AssetSource, DevicePixels, ImageData, IsZero, Result, SharedString, Size}; +use anyhow::anyhow; +use collections::HashMap; +use parking_lot::{RwLock, RwLockUpgradableReadGuard}; +use std::hash::Hash; +use std::sync::Arc; +use usvg::Tree as SvgTree; + +#[derive(Clone, PartialEq, Hash, Eq)] +pub struct SvgRenderParams { + path: SharedString, + size: Size, +} + +pub struct SvgRenderer { + asset_source: Arc, + trees_by_path: RwLock>, + rendered: RwLock>>, +} + +impl SvgRenderer { + pub fn render(&self, params: SvgRenderParams) -> Result> { + if params.size.is_zero() { + return Err(anyhow!("can't render at a zero size")); + } + + let rendered = self.rendered.upgradable_read(); + if let Some(image_data) = rendered.get(¶ms) { + Ok(image_data.clone()) + } else { + // There's no rendered SVG for the path at the requested size. + // Have we already loaded a tree for the path? + let trees_by_path = self.trees_by_path.upgradable_read(); + let tree = if let Some(tree) = trees_by_path.get(¶ms.path) { + tree.clone() + } else { + // Load the tree + let bytes = self.asset_source.load(¶ms.path)?; + let tree = usvg::Tree::from_data(&bytes, &usvg::Options::default())?; + let mut trees_by_path = RwLockUpgradableReadGuard::upgrade(trees_by_path); + trees_by_path.insert(params.path.clone(), tree.clone()); + tree + }; + + // Render the SVG to a pixmap with the specified width and height. + // Convert the pixmap's pixels into an image data and cache it in `rendered`. + let mut pixmap = + tiny_skia::Pixmap::new(params.size.width.into(), params.size.height.into()) + .unwrap(); + resvg::render( + &tree, + usvg::FitTo::Width(params.size.width.into()), + pixmap.as_mut(), + ); + let alpha_mask = pixmap + .pixels() + .iter() + .map(|p| p.alpha()) + .collect::>(); + let mut rendered = RwLockUpgradableReadGuard::upgrade(rendered); + let image_data = Arc::new(ImageData::from_raw(params.size, alpha_mask)); + rendered.insert(params, image_data.clone()); + + Ok(image_data) + } + } +} + +// impl SvgRenderer { +// pub fn render_svg( +// &mut self, +// size: Vector2I, +// path: Cow<'static, str>, +// svg: usvg::Tree, +// ) -> Option { +// let mut pixmap = tiny_skia::Pixmap::new(size.x() as u32, size.y() as u32)?; +// resvg::render(&svg, usvg::FitTo::Width(size.x() as u32), pixmap.as_mut()); + +// let atlases = &mut self.atlases; +// match self.icons.entry(IconDescriptor { +// path, +// width: size.x(), +// height: size.y(), +// }) { +// Entry::Occupied(entry) => Some(entry.get().clone()), +// Entry::Vacant(entry) => { +// let mask = pixmap +// .pixels() +// .iter() +// .map(|a| a.alpha()) +// .collect::>(); +// let (alloc_id, atlas_bounds) = atlases.upload(size, &mask)?; +// let icon_sprite = IconSprite { +// atlas_id: alloc_id.atlas_id, +// atlas_origin: atlas_bounds.origin(), +// size, +// }; +// Some(entry.insert(icon_sprite).clone()) +// } +// } +// } +// }