Checkpoint

Nathan Sobo created

Change summary

crates/gpui3/src/assets.rs      |  60 ++++++++++++++++++++
crates/gpui3/src/geometry.rs    |  36 ++++++++----
crates/gpui3/src/gpui3.rs       |  10 +++
crates/gpui3/src/svg_library.rs | 102 +++++++++++++++++++++++++++++++++++
4 files changed, 196 insertions(+), 12 deletions(-)

Detailed changes

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<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()
+    }
+}

crates/gpui3/src/geometry.rs 🔗

@@ -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()
     }
 }
 

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<T: Into<ArcCow<'static, str>>> From<T> for SharedString {
     fn from(value: T) -> Self {
         Self(value.into())

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<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(&params) {
+            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(&params.path) {
+                tree.clone()
+            } else {
+                // Load the tree
+                let bytes = self.asset_source.load(&params.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())
+//             }
+//         }
+//     }
+// }