Add png image loading to gpui

add zed logo into welcome experience

Co-authored-by: Nathan <nathan@zed.dev>
This commit is contained in:
Mikayla Maki 2023-03-06 10:39:35 -08:00
parent f89f33347d
commit 4c179875ab
11 changed files with 76 additions and 33 deletions

Binary file not shown.

After

Width:  |  Height:  |  Size: 12 KiB

View file

@ -823,7 +823,7 @@ impl CollabTitlebarItem {
avatar_style: AvatarStyle, avatar_style: AvatarStyle,
background_color: Color, background_color: Color,
) -> ElementBox { ) -> ElementBox {
Image::new(avatar) Image::from_data(avatar)
.with_style(avatar_style.image) .with_style(avatar_style.image)
.aligned() .aligned()
.contained() .contained()

View file

@ -128,7 +128,7 @@ impl PickerDelegate for ContactFinder {
.style_for(mouse_state, selected); .style_for(mouse_state, selected);
Flex::row() Flex::row()
.with_children(user.avatar.clone().map(|avatar| { .with_children(user.avatar.clone().map(|avatar| {
Image::new(avatar) Image::from_data(avatar)
.with_style(theme.contact_finder.contact_avatar) .with_style(theme.contact_finder.contact_avatar)
.aligned() .aligned()
.left() .left()

View file

@ -726,7 +726,7 @@ impl ContactList {
) -> ElementBox { ) -> ElementBox {
Flex::row() Flex::row()
.with_children(user.avatar.clone().map(|avatar| { .with_children(user.avatar.clone().map(|avatar| {
Image::new(avatar) Image::from_data(avatar)
.with_style(theme.contact_avatar) .with_style(theme.contact_avatar)
.aligned() .aligned()
.left() .left()
@ -1080,7 +1080,7 @@ impl ContactList {
}; };
Stack::new() Stack::new()
.with_child( .with_child(
Image::new(avatar) Image::from_data(avatar)
.with_style(theme.contact_avatar) .with_style(theme.contact_avatar)
.aligned() .aligned()
.left() .left()
@ -1173,7 +1173,7 @@ impl ContactList {
let mut row = Flex::row() let mut row = Flex::row()
.with_children(user.avatar.clone().map(|avatar| { .with_children(user.avatar.clone().map(|avatar| {
Image::new(avatar) Image::from_data(avatar)
.with_style(theme.contact_avatar) .with_style(theme.contact_avatar)
.aligned() .aligned()
.left() .left()

View file

@ -108,7 +108,7 @@ impl IncomingCallNotification {
.unwrap_or(&default_project); .unwrap_or(&default_project);
Flex::row() Flex::row()
.with_children(self.call.calling_user.avatar.clone().map(|avatar| { .with_children(self.call.calling_user.avatar.clone().map(|avatar| {
Image::new(avatar) Image::from_data(avatar)
.with_style(theme.caller_avatar) .with_style(theme.caller_avatar)
.aligned() .aligned()
.boxed() .boxed()

View file

@ -24,7 +24,7 @@ pub fn render_user_notification<V: View, A: Action + Clone>(
.with_child( .with_child(
Flex::row() Flex::row()
.with_children(user.avatar.clone().map(|avatar| { .with_children(user.avatar.clone().map(|avatar| {
Image::new(avatar) Image::from_data(avatar)
.with_style(theme.header_avatar) .with_style(theme.header_avatar)
.aligned() .aligned()
.constrained() .constrained()

View file

@ -108,7 +108,7 @@ impl ProjectSharedNotification {
let theme = &cx.global::<Settings>().theme.project_shared_notification; let theme = &cx.global::<Settings>().theme.project_shared_notification;
Flex::row() Flex::row()
.with_children(self.owner.avatar.clone().map(|avatar| { .with_children(self.owner.avatar.clone().map(|avatar| {
Image::new(avatar) Image::from_data(avatar)
.with_style(theme.owner_avatar) .with_style(theme.owner_avatar)
.aligned() .aligned()
.boxed() .boxed()

View file

@ -1,5 +1,8 @@
use anyhow::{anyhow, Result}; use anyhow::{anyhow, Result};
use std::{borrow::Cow, cell::RefCell, collections::HashMap}; use image::ImageFormat;
use std::{borrow::Cow, cell::RefCell, collections::HashMap, sync::Arc};
use crate::ImageData;
pub trait AssetSource: 'static + Send + Sync { pub trait AssetSource: 'static + Send + Sync {
fn load(&self, path: &str) -> Result<Cow<[u8]>>; fn load(&self, path: &str) -> Result<Cow<[u8]>>;
@ -22,6 +25,7 @@ impl AssetSource for () {
pub struct AssetCache { pub struct AssetCache {
source: Box<dyn AssetSource>, source: Box<dyn AssetSource>,
svgs: RefCell<HashMap<String, usvg::Tree>>, svgs: RefCell<HashMap<String, usvg::Tree>>,
pngs: RefCell<HashMap<String, Arc<ImageData>>>,
} }
impl AssetCache { impl AssetCache {
@ -29,6 +33,7 @@ impl AssetCache {
Self { Self {
source: Box::new(source), source: Box::new(source),
svgs: RefCell::new(HashMap::new()), svgs: RefCell::new(HashMap::new()),
pngs: RefCell::new(HashMap::new()),
} }
} }
@ -43,4 +48,18 @@ impl AssetCache {
Ok(svg) Ok(svg)
} }
} }
pub fn png(&self, path: &str) -> Result<Arc<ImageData>> {
let mut pngs = self.pngs.borrow_mut();
if let Some(png) = pngs.get(path) {
Ok(png.clone())
} else {
let bytes = self.source.load(path)?;
let image = ImageData::new(
image::load_from_memory_with_format(&bytes, ImageFormat::Png)?.into_bgra8(),
);
pngs.insert(path.to_string(), image.clone());
Ok(image)
}
}
} }

View file

@ -11,8 +11,13 @@ use crate::{
use serde::Deserialize; use serde::Deserialize;
use std::{ops::Range, sync::Arc}; use std::{ops::Range, sync::Arc};
enum ImageSource {
Path(&'static str),
Data(Arc<ImageData>),
}
pub struct Image { pub struct Image {
data: Arc<ImageData>, source: ImageSource,
style: ImageStyle, style: ImageStyle,
} }
@ -31,9 +36,16 @@ pub struct ImageStyle {
} }
impl Image { impl Image {
pub fn new(data: Arc<ImageData>) -> Self { pub fn new(asset_path: &'static str) -> Self {
Self { Self {
data, source: ImageSource::Path(asset_path),
style: Default::default(),
}
}
pub fn from_data(data: Arc<ImageData>) -> Self {
Self {
source: ImageSource::Data(data),
style: Default::default(), style: Default::default(),
} }
} }
@ -45,39 +57,53 @@ impl Image {
} }
impl Element for Image { impl Element for Image {
type LayoutState = (); type LayoutState = Option<Arc<ImageData>>;
type PaintState = (); type PaintState = ();
fn layout( fn layout(
&mut self, &mut self,
constraint: SizeConstraint, constraint: SizeConstraint,
_: &mut LayoutContext, cx: &mut LayoutContext,
) -> (Vector2F, Self::LayoutState) { ) -> (Vector2F, Self::LayoutState) {
let data = match &self.source {
ImageSource::Path(path) => match cx.asset_cache.png(path) {
Ok(data) => data,
Err(error) => {
log::error!("could not load image: {}", error);
return (Vector2F::zero(), None);
}
},
ImageSource::Data(data) => data.clone(),
};
let desired_size = vec2f( let desired_size = vec2f(
self.style.width.unwrap_or_else(|| constraint.max.x()), self.style.width.unwrap_or_else(|| constraint.max.x()),
self.style.height.unwrap_or_else(|| constraint.max.y()), self.style.height.unwrap_or_else(|| constraint.max.y()),
); );
let size = constrain_size_preserving_aspect_ratio( let size = constrain_size_preserving_aspect_ratio(
constraint.constrain(desired_size), constraint.constrain(desired_size),
self.data.size().to_f32(), data.size().to_f32(),
); );
(size, ())
(size, Some(data))
} }
fn paint( fn paint(
&mut self, &mut self,
bounds: RectF, bounds: RectF,
_: RectF, _: RectF,
_: &mut Self::LayoutState, layout: &mut Self::LayoutState,
cx: &mut PaintContext, cx: &mut PaintContext,
) -> Self::PaintState { ) -> Self::PaintState {
cx.scene.push_image(scene::Image { if let Some(data) = layout {
bounds, cx.scene.push_image(scene::Image {
border: self.style.border, bounds,
corner_radius: self.style.corner_radius, border: self.style.border,
grayscale: self.style.grayscale, corner_radius: self.style.corner_radius,
data: self.data.clone(), grayscale: self.style.grayscale,
}); data: data.clone(),
});
}
} }
fn rect_for_text_range( fn rect_for_text_range(

View file

@ -1325,7 +1325,7 @@ impl View for ProjectPanel {
Canvas::new(|bounds, _visible_bounds, cx| { Canvas::new(|bounds, _visible_bounds, cx| {
cx.scene.push_quad(gpui::Quad { cx.scene.push_quad(gpui::Quad {
bounds, bounds,
background: Some(Color::red()), background: Some(Color::transparent_black()),
..Default::default() ..Default::default()
}) })
}) })

View file

@ -1,14 +1,13 @@
use std::borrow::Cow; use std::borrow::Cow;
use gpui::{ use gpui::{
color::Color, elements::{Canvas, Empty, Flex, Image, Label, MouseEventHandler, ParentElement, Stack},
elements::{Canvas, Empty, Flex, Label, MouseEventHandler, ParentElement, Stack, Svg},
geometry::rect::RectF, geometry::rect::RectF,
Action, Element, ElementBox, Entity, MouseButton, MouseRegion, MutableAppContext, Action, Element, ElementBox, Entity, MouseButton, MouseRegion, MutableAppContext,
RenderContext, Subscription, View, ViewContext, RenderContext, Subscription, View, ViewContext,
}; };
use settings::{settings_file::SettingsFile, Settings, SettingsFileContent}; use settings::{settings_file::SettingsFile, Settings, SettingsFileContent};
use theme::{CheckboxStyle, ContainedText, Interactive}; use theme::CheckboxStyle;
use workspace::{item::Item, Welcome, Workspace}; use workspace::{item::Item, Welcome, Workspace};
pub fn init(cx: &mut MutableAppContext) { pub fn init(cx: &mut MutableAppContext) {
@ -72,15 +71,14 @@ impl View for WelcomePage {
.with_children([ .with_children([
Flex::row() Flex::row()
.with_children([ .with_children([
Svg::new("icons/terminal_16.svg") Image::new("images/zed-logo-90x90.png")
.with_color(Color::red())
.constrained() .constrained()
.with_width(100.) .with_width(90.)
.with_height(100.) .with_height(90.)
.aligned() .aligned()
.contained() .contained()
.boxed(), .boxed(),
Label::new("Zed", theme.editor.hover_popover.prose.clone()).boxed(), // Label::new("Zed", theme.editor.hover_popover.prose.clone()).boxed(),
]) ])
.boxed(), .boxed(),
Label::new( Label::new(