Detailed changes
@@ -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<Cow<[u8]>>;
+ fn list(&self, path: &SharedString) -> Result<Vec<SharedString>>;
+}
+
+impl AssetSource for () {
+ fn load(&self, path: &SharedString) -> Result<Cow<[u8]>> {
+ Err(anyhow!(
+ "get called on empty asset provider with \"{}\"",
+ path
+ ))
+ }
+
+ fn list(&self, _path: &SharedString) -> Result<Vec<SharedString>> {
+ Ok(vec![])
+ }
+}
+
+pub struct ImageData {
+ pub id: usize,
+ data: ImageBuffer<Bgra<u8>, Vec<u8>>,
+}
+
+impl ImageData {
+ pub fn from_raw(size: Size<DevicePixels>, bytes: Vec<u8>) -> 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<DevicePixels> {
+ 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()
+ }
+}
@@ -182,10 +182,10 @@ impl<T: Clone + Debug + Mul<S, Output = T>, S: Clone> MulAssign<S> for Size<T> {
impl<T: Eq + Debug + Clone> Eq for Size<T> {}
impl From<Size<Option<Pixels>>> for Size<Option<f32>> {
- fn from(val: Size<Option<Pixels>>) -> Self {
+ fn from(size: Size<Option<Pixels>>) -> 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<f64> for Pixels {
- fn from(val: f64) -> Self {
- Pixels(val as f32)
+ fn from(pixels: f64) -> Self {
+ Pixels(pixels as f32)
}
}
impl From<f32> for Pixels {
- fn from(val: f32) -> Self {
- Pixels(val)
+ fn from(pixels: f32) -> Self {
+ Pixels(pixels)
}
}
@@ -608,8 +608,20 @@ impl From<DevicePixels> for i32 {
}
impl From<i32> for DevicePixels {
- fn from(val: i32) -> Self {
- DevicePixels(val)
+ fn from(device_pixels: i32) -> Self {
+ DevicePixels(device_pixels)
+ }
+}
+
+impl From<u32> for DevicePixels {
+ fn from(device_pixels: u32) -> Self {
+ DevicePixels(device_pixels as i32)
+ }
+}
+
+impl From<DevicePixels> for u32 {
+ fn from(device_pixels: DevicePixels) -> Self {
+ device_pixels.0 as u32
}
}
@@ -620,8 +632,8 @@ impl From<DevicePixels> for u64 {
}
impl From<u64> 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<T: IsZero + Debug + Clone> IsZero for Point<T> {
impl<T: IsZero + Debug + Clone> IsZero for Size<T> {
fn is_zero(&self) -> bool {
- self.width.is_zero() && self.height.is_zero()
+ self.width.is_zero() || self.height.is_zero()
}
}
@@ -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<T: Into<ArcCow<'static, str>>> From<T> for SharedString {
fn from(value: T) -> Self {
Self(value.into())
@@ -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<DevicePixels>,
+}
+
+pub struct SvgRenderer {
+ asset_source: Arc<dyn AssetSource>,
+ trees_by_path: RwLock<HashMap<SharedString, SvgTree>>,
+ rendered: RwLock<HashMap<SvgRenderParams, Arc<ImageData>>>,
+}
+
+impl SvgRenderer {
+ pub fn render(&self, params: SvgRenderParams) -> Result<Arc<ImageData>> {
+ 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::<Vec<_>>();
+ 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<IconSprite> {
+// 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::<Vec<_>>();
+// 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())
+// }
+// }
+// }
+// }