Detailed changes
@@ -8,9 +8,9 @@ pub use model_context::*;
use refineable::Refineable;
use crate::{
- current_platform, run_on_main, spawn_on_main, Context, LayoutId, MainThread, MainThreadOnly,
- Platform, PlatformDispatcher, RootView, TextStyle, TextStyleRefinement, TextSystem, Window,
- WindowContext, WindowHandle, WindowId,
+ current_platform, run_on_main, spawn_on_main, AssetSource, Context, LayoutId, MainThread,
+ MainThreadOnly, Platform, PlatformDispatcher, RootView, SvgRenderer, TextStyle,
+ TextStyleRefinement, TextSystem, Window, WindowContext, WindowHandle, WindowId,
};
use anyhow::{anyhow, Result};
use collections::{HashMap, VecDeque};
@@ -29,16 +29,18 @@ use util::ResultExt;
pub struct App(Arc<Mutex<MainThread<AppContext>>>);
impl App {
- pub fn production() -> Self {
- Self::new(current_platform())
+ pub fn production(asset_source: Arc<dyn AssetSource>) -> Self {
+ Self::new(current_platform(), asset_source)
}
#[cfg(any(test, feature = "test"))]
pub fn test() -> Self {
- Self::new(Arc::new(super::TestPlatform::new()))
+ let platform = Arc::new(super::TestPlatform::new());
+ let asset_source = Arc::new(());
+ Self::new(platform, asset_source)
}
- fn new(platform: Arc<dyn Platform>) -> Self {
+ fn new(platform: Arc<dyn Platform>, asset_source: Arc<dyn AssetSource>) -> Self {
let dispatcher = platform.dispatcher();
let text_system = Arc::new(TextSystem::new(platform.text_system()));
let entities = EntityMap::new();
@@ -49,6 +51,7 @@ impl App {
platform: MainThreadOnly::new(platform, dispatcher.clone()),
dispatcher,
text_system,
+ svg_renderer: SvgRenderer::new(asset_source),
pending_updates: 0,
text_style_stack: Vec::new(),
state_stacks_by_type: HashMap::default(),
@@ -83,6 +86,7 @@ pub struct AppContext {
dispatcher: Arc<dyn PlatformDispatcher>,
text_system: Arc<TextSystem>,
pending_updates: usize,
+ pub(crate) svg_renderer: SvgRenderer,
pub(crate) text_style_stack: Vec<TextStyleRefinement>,
pub(crate) state_stacks_by_type: HashMap<TypeId, Vec<Box<dyn Any + Send + Sync>>>,
pub(crate) unit_entity: Handle<()>,
@@ -44,6 +44,10 @@ impl ImageData {
&self.data
}
+ pub fn into_bytes(self) -> Vec<u8> {
+ self.data.into_raw()
+ }
+
pub fn size(&self) -> Size<DevicePixels> {
let (width, height) = self.data.dimensions();
size(width.into(), height.into())
@@ -1,9 +1,9 @@
-use crate::{Element, Layout, LayoutId, Result, Style, StyleHelpers, Styled};
+use crate::{Element, Layout, LayoutId, Result, SharedString, Style, StyleHelpers, Styled};
use refineable::RefinementCascade;
-use std::{borrow::Cow, marker::PhantomData};
+use std::marker::PhantomData;
pub struct Svg<S> {
- path: Option<Cow<'static, str>>,
+ path: Option<SharedString>,
style: RefinementCascade<Style>,
state_type: PhantomData<S>,
}
@@ -17,7 +17,7 @@ pub fn svg<S>() -> Svg<S> {
}
impl<S> Svg<S> {
- pub fn path(mut self, path: impl Into<Cow<'static, str>>) -> Self {
+ pub fn path(mut self, path: impl Into<SharedString>) -> Self {
self.path = Some(path.into());
self
}
@@ -41,28 +41,18 @@ impl<S: 'static> Element for Svg<S> {
fn paint(
&mut self,
- _layout: Layout,
+ layout: Layout,
_: &mut Self::State,
_: &mut Self::FrameState,
- _cx: &mut crate::ViewContext<S>,
+ cx: &mut crate::ViewContext<S>,
) -> Result<()>
where
Self: Sized,
{
- // todo!
- // let fill_color = self.computed_style().fill.and_then(|fill| fill.color());
- // if let Some((path, fill_color)) = self.path.as_ref().zip(fill_color) {
- // if let Some(svg_tree) = cx.asset_cache.svg(path).log_err() {
- // let icon = scene::Icon {
- // bounds: layout.bounds + parent_origin,
- // svg: svg_tree,
- // path: path.clone(),
- // color: Rgba::from(fill_color).into(),
- // };
-
- // cx.scene().push_icon(icon);
- // }
- // }
+ let fill_color = self.computed_style().fill.and_then(|fill| fill.color());
+ if let Some((path, fill_color)) = self.path.as_ref().zip(fill_color) {
+ cx.paint_svg(layout.bounds, layout.order, path.clone(), fill_color)?;
+ }
Ok(())
}
}
@@ -10,7 +10,7 @@ mod scene;
mod style;
mod style_helpers;
mod styled;
-mod svg_library;
+mod svg_renderer;
mod taffy;
mod text_system;
mod util;
@@ -26,7 +26,7 @@ pub use elements::*;
pub use executor::*;
pub use geometry::*;
pub use gpui3_macros::*;
-pub use svg_library::*;
+pub use svg_renderer::*;
pub use platform::*;
pub use refineable::*;
@@ -7,7 +7,7 @@ mod test;
use crate::{
AnyWindowHandle, Bounds, DevicePixels, Font, FontId, FontMetrics, GlyphId, Pixels, Point,
- RenderGlyphParams, Result, Scene, ShapedLine, SharedString, Size,
+ RenderGlyphParams, RenderSvgParams, Result, Scene, ShapedLine, SharedString, Size,
};
use anyhow::anyhow;
use async_task::Runnable;
@@ -147,7 +147,7 @@ pub trait PlatformWindow {
fn is_topmost_for_position(&self, position: Point<Pixels>) -> bool;
fn draw(&self, scene: Scene);
- fn glyph_atlas(&self) -> Arc<dyn PlatformAtlas>;
+ fn monochrome_sprite_atlas(&self) -> Arc<dyn PlatformAtlas>;
}
pub trait PlatformDispatcher: Send + Sync {
@@ -178,7 +178,7 @@ pub trait PlatformTextSystem: Send + Sync {
#[derive(PartialEq, Eq, Hash, Clone)]
pub enum AtlasKey {
Glyph(RenderGlyphParams),
- // Svg(RenderSvgParams),
+ Svg(RenderSvgParams),
}
impl From<RenderGlyphParams> for AtlasKey {
@@ -187,6 +187,12 @@ impl From<RenderGlyphParams> for AtlasKey {
}
}
+impl From<RenderSvgParams> for AtlasKey {
+ fn from(params: RenderSvgParams) -> Self {
+ Self::Svg(params)
+ }
+}
+
pub trait PlatformAtlas: Send + Sync {
fn get_or_insert_with(
&self,
@@ -20,7 +20,7 @@ pub struct MetalRenderer {
sprites_pipeline_state: metal::RenderPipelineState,
unit_vertices: metal::Buffer,
instances: metal::Buffer,
- glyph_atlas: Arc<MetalAtlas>,
+ monochrome_sprite_atlas: Arc<MetalAtlas>,
}
impl MetalRenderer {
@@ -99,7 +99,7 @@ impl MetalRenderer {
);
let command_queue = device.new_command_queue();
- let glyph_atlas = Arc::new(MetalAtlas::new(
+ let monochrome_sprite_atlas = Arc::new(MetalAtlas::new(
Size {
width: DevicePixels(1024),
height: DevicePixels(768),
@@ -115,7 +115,7 @@ impl MetalRenderer {
sprites_pipeline_state,
unit_vertices,
instances,
- glyph_atlas,
+ monochrome_sprite_atlas,
}
}
@@ -123,8 +123,8 @@ impl MetalRenderer {
&*self.layer
}
- pub fn glyph_atlas(&self) -> &Arc<MetalAtlas> {
- &self.glyph_atlas
+ pub fn monochrome_sprite_atlas(&self) -> &Arc<MetalAtlas> {
+ &self.monochrome_sprite_atlas
}
pub fn draw(&mut self, scene: &mut Scene) {
@@ -277,7 +277,7 @@ impl MetalRenderer {
}
align_offset(offset);
- let texture = self.glyph_atlas.texture(texture_id);
+ let texture = self.monochrome_sprite_atlas.texture(texture_id);
let texture_size = size(
DevicePixels(texture.width() as i32),
DevicePixels(texture.height() as i32),
@@ -886,8 +886,8 @@ impl PlatformWindow for MacWindow {
}
}
- fn glyph_atlas(&self) -> Arc<dyn PlatformAtlas> {
- self.0.lock().renderer.glyph_atlas().clone()
+ fn monochrome_sprite_atlas(&self) -> Arc<dyn PlatformAtlas> {
+ self.0.lock().renderer.monochrome_sprite_atlas().clone()
}
}
@@ -1,102 +0,0 @@
-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())
-// }
-// }
-// }
-// }
@@ -0,0 +1,47 @@
+use crate::{AssetSource, DevicePixels, IsZero, Result, SharedString, Size};
+use anyhow::anyhow;
+use std::hash::Hash;
+use std::sync::Arc;
+
+#[derive(Clone, PartialEq, Hash, Eq)]
+pub struct RenderSvgParams {
+ pub(crate) path: SharedString,
+ pub(crate) size: Size<DevicePixels>,
+}
+
+pub struct SvgRenderer {
+ asset_source: Arc<dyn AssetSource>,
+}
+
+impl SvgRenderer {
+ pub fn new(asset_source: Arc<dyn AssetSource>) -> Self {
+ Self { asset_source }
+ }
+
+ pub fn render(&self, params: &RenderSvgParams) -> Result<Vec<u8>> {
+ if params.size.is_zero() {
+ return Err(anyhow!("can't render at a zero size"));
+ }
+
+ // Load the tree.
+ let bytes = self.asset_source.load(¶ms.path)?;
+ let tree = usvg::Tree::from_data(&bytes, &usvg::Options::default())?;
+
+ // Render the SVG to a pixmap with the specified width and height.
+ 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(),
+ );
+
+ // Convert the pixmap's pixels into an alpha mask.
+ let alpha_mask = pixmap
+ .pixels()
+ .iter()
+ .map(|p| p.alpha())
+ .collect::<Vec<_>>();
+ Ok(alpha_mask)
+ }
+}
@@ -1,9 +1,9 @@
use crate::{
- px, AnyView, AppContext, AvailableSpace, BorrowAppContext, Bounds, Context, Corners, Effect,
- Element, EntityId, FontId, GlyphId, Handle, Hsla, IsZero, LayerId, LayoutId, MainThread,
- MainThreadOnly, MonochromeSprite, Pixels, PlatformAtlas, PlatformWindow, Point, Reference,
- RenderGlyphParams, ScaledPixels, Scene, Size, Style, TaffyLayoutEngine, WeakHandle,
- WindowOptions, SUBPIXEL_VARIANTS,
+ px, AnyView, AppContext, AvailableSpace, BorrowAppContext, Bounds, Context, Corners,
+ DevicePixels, Effect, Element, EntityId, FontId, GlyphId, Handle, Hsla, IsZero, LayerId,
+ LayoutId, MainThread, MainThreadOnly, MonochromeSprite, Pixels, PlatformAtlas, PlatformWindow,
+ Point, Reference, RenderGlyphParams, RenderSvgParams, ScaledPixels, Scene, SharedString, Size,
+ Style, TaffyLayoutEngine, WeakHandle, WindowOptions, SUBPIXEL_VARIANTS,
};
use anyhow::Result;
use futures::Future;
@@ -16,7 +16,7 @@ pub struct AnyWindow {}
pub struct Window {
handle: AnyWindowHandle,
platform_window: MainThreadOnly<Box<dyn PlatformWindow>>,
- glyph_atlas: Arc<dyn PlatformAtlas>,
+ monochrome_sprite_atlas: Arc<dyn PlatformAtlas>,
rem_size: Pixels,
content_size: Size<Pixels>,
layout_engine: TaffyLayoutEngine,
@@ -35,7 +35,7 @@ impl Window {
cx: &mut MainThread<AppContext>,
) -> Self {
let platform_window = cx.platform().open_window(handle, options);
- let glyph_atlas = platform_window.glyph_atlas();
+ let monochrome_sprite_atlas = platform_window.monochrome_sprite_atlas();
let mouse_position = platform_window.mouse_position();
let content_size = platform_window.content_size();
let scale_factor = platform_window.scale_factor();
@@ -58,7 +58,7 @@ impl Window {
Window {
handle,
platform_window,
- glyph_atlas,
+ monochrome_sprite_atlas,
rem_size: px(16.),
content_size,
layout_engine: TaffyLayoutEngine::new(),
@@ -235,7 +235,7 @@ impl<'a, 'w> WindowContext<'a, 'w> {
let layer_id = self.current_layer_id();
let tile = self
.window
- .glyph_atlas
+ .monochrome_sprite_atlas
.get_or_insert_with(¶ms.clone().into(), &mut || {
self.text_system().rasterize_glyph(¶ms)
})?;
@@ -259,6 +259,47 @@ impl<'a, 'w> WindowContext<'a, 'w> {
Ok(())
}
+ pub fn paint_svg(
+ &mut self,
+ bounds: Bounds<Pixels>,
+ order: u32,
+ path: SharedString,
+ color: Hsla,
+ ) -> Result<()> {
+ let scale_factor = self.scale_factor();
+ let bounds = bounds.scale(scale_factor);
+ // Render the SVG at twice the size to get a higher quality result.
+ let params = RenderSvgParams {
+ path,
+ size: bounds
+ .size
+ .map(|pixels| DevicePixels::from((pixels.0 * 2.).ceil() as i32)),
+ };
+
+ let layer_id = self.current_layer_id();
+ let tile = self.window.monochrome_sprite_atlas.get_or_insert_with(
+ ¶ms.clone().into(),
+ &mut || {
+ let bytes = self.svg_renderer.render(¶ms)?;
+ Ok((params.size, bytes))
+ },
+ )?;
+ let content_mask = self.content_mask().scale(scale_factor);
+
+ self.window.scene.insert(
+ layer_id,
+ MonochromeSprite {
+ order,
+ bounds,
+ content_mask,
+ color,
+ tile,
+ },
+ );
+
+ Ok(())
+ }
+
pub(crate) fn draw(&mut self) -> Result<()> {
let unit_entity = self.unit_entity.clone();
self.update_entity(&unit_entity, |_, cx| {
@@ -0,0 +1,30 @@
+use std::borrow::Cow;
+
+use anyhow::{anyhow, Result};
+use gpui3::{AssetSource, SharedString};
+use rust_embed::RustEmbed;
+
+#[derive(RustEmbed)]
+#[folder = "../../assets"]
+#[include = "fonts/**/*"]
+#[include = "icons/**/*"]
+#[include = "themes/**/*"]
+#[include = "sounds/**/*"]
+#[include = "*.md"]
+#[exclude = "*.DS_Store"]
+pub struct Assets;
+
+impl AssetSource for Assets {
+ fn load(&self, path: &SharedString) -> Result<Cow<[u8]>> {
+ Self::get(path.as_ref())
+ .map(|f| f.data)
+ .ok_or_else(|| anyhow!("could not find asset at path \"{}\"", path))
+ }
+
+ fn list(&self, path: &SharedString) -> Result<Vec<SharedString>> {
+ Ok(Self::iter()
+ .filter(|p| p.starts_with(path.as_ref()))
+ .map(SharedString::from)
+ .collect())
+ }
+}
@@ -1,9 +1,13 @@
#![allow(dead_code, unused_variables)]
+use assets::Assets;
use gpui3::{px, size, Bounds, WindowBounds, WindowOptions};
use log::LevelFilter;
use simplelog::SimpleLogger;
+use std::sync::Arc;
+use workspace::workspace;
+mod assets;
mod collab_panel;
mod theme;
mod themes;
@@ -19,7 +23,8 @@ fn main() {
SimpleLogger::init(LevelFilter::Info, Default::default()).expect("could not initialize logger");
- gpui3::App::production().run(|cx| {
+ let asset_source = Arc::new(Assets);
+ gpui3::App::production(asset_source).run(|cx| {
let window = cx.open_window(
WindowOptions {
bounds: WindowBounds::Fixed(Bounds {
@@ -35,29 +40,6 @@ fn main() {
});
}
-use rust_embed::RustEmbed;
-use workspace::workspace;
-
-#[derive(RustEmbed)]
-#[folder = "../../assets"]
-#[include = "themes/**/*"]
-#[include = "fonts/**/*"]
-#[include = "icons/**/*"]
-#[exclude = "*.DS_Store"]
-pub struct Assets;
-
-// impl AssetSource for Assets {
-// fn load(&self, path: &str) -> Result<std::borrow::Cow<[u8]>> {
-// Self::get(path)
-// .map(|f| f.data)
-// .ok_or_else(|| anyhow!("could not find asset at path \"{}\"", path))
-// }
-
-// fn list(&self, path: &str) -> Vec<std::borrow::Cow<'static, str>> {
-// Self::iter().filter(|p| p.starts_with(path)).collect()
-// }
-// }
-
// fn load_embedded_fonts(platform: &dyn gpui2::Platform) {
// let font_paths = Assets.list("fonts");
// let mut embedded_fonts = Vec::new();
@@ -1,4 +1,5 @@
use std::{
+ borrow::Cow,
fmt::{self, Debug},
sync::Arc,
};
@@ -47,6 +48,15 @@ impl From<String> for ArcCow<'_, str> {
}
}
+impl<'a> From<Cow<'a, str>> for ArcCow<'a, str> {
+ fn from(value: Cow<'a, str>) -> Self {
+ match value {
+ Cow::Borrowed(borrowed) => Self::Borrowed(borrowed),
+ Cow::Owned(owned) => Self::Owned(owned.into()),
+ }
+ }
+}
+
impl<'a, T: ?Sized + ToOwned> std::borrow::Borrow<T> for ArcCow<'a, T> {
fn borrow(&self) -> &T {
match self {