diff --git a/assets/images/zed_logo.svg b/assets/images/zed_logo.svg
new file mode 100644
index 0000000000000000000000000000000000000000..d1769449c1984079a994f7334f43340b260ad1a7
--- /dev/null
+++ b/assets/images/zed_logo.svg
@@ -0,0 +1,10 @@
+
diff --git a/assets/images/zed_x_copilot.svg b/assets/images/zed_x_copilot.svg
new file mode 100644
index 0000000000000000000000000000000000000000..3c5be71074c1959575b5899e2231cdc8f2d0061b
--- /dev/null
+++ b/assets/images/zed_x_copilot.svg
@@ -0,0 +1,14 @@
+
diff --git a/crates/assets/src/assets.rs b/crates/assets/src/assets.rs
index 395cbf62f6ce5ef4a42b00dcc52656d7b14a673a..ee990085f6de17cc3674876d8bbc1b0fbd343226 100644
--- a/crates/assets/src/assets.rs
+++ b/crates/assets/src/assets.rs
@@ -8,6 +8,7 @@ use rust_embed::RustEmbed;
#[folder = "../../assets"]
#[include = "fonts/**/*"]
#[include = "icons/**/*"]
+#[include = "images/**/*"]
#[include = "themes/**/*"]
#[exclude = "themes/src/*"]
#[include = "sounds/**/*"]
diff --git a/crates/copilot/src/sign_in.rs b/crates/copilot/src/sign_in.rs
index 1d14e5c1aadc55ea2ce2dcb6f0ad817702e8681c..da6b969b7222bb5d6c16cfec52522e226d8d1bfd 100644
--- a/crates/copilot/src/sign_in.rs
+++ b/crates/copilot/src/sign_in.rs
@@ -1,10 +1,10 @@
use crate::{request::PromptUserDeviceFlow, Copilot, Status};
use gpui::{
- div, svg, AppContext, ClipboardItem, DismissEvent, Element, EventEmitter, FocusHandle,
+ div, AppContext, ClipboardItem, DismissEvent, Element, EventEmitter, FocusHandle,
FocusableView, InteractiveElement, IntoElement, Model, MouseDownEvent, ParentElement, Render,
Styled, Subscription, ViewContext,
};
-use ui::{prelude::*, Button, IconName, Label};
+use ui::{prelude::*, Button, Label, Vector, VectorName};
use workspace::ModalView;
const COPILOT_SIGN_UP_URL: &str = "https://github.com/features/copilot";
@@ -198,12 +198,8 @@ impl Render for CopilotCodeVerification {
cx.focus(&this.focus_handle);
}))
.child(
- svg()
- .w_32()
- .h_16()
- .flex_none()
- .path(IconName::ZedXCopilot.path())
- .text_color(cx.theme().colors().icon),
+ Vector::new(VectorName::ZedXCopilot, rems(8.), rems(4.))
+ .color(Color::Custom(cx.theme().colors().icon)),
)
.child(prompt)
}
diff --git a/crates/gpui_macros/src/derive_path_static_str.rs b/crates/gpui_macros/src/derive_path_static_str.rs
new file mode 100644
index 0000000000000000000000000000000000000000..25531fd2adf9d5c04a247da0b3f14f48523a1b0f
--- /dev/null
+++ b/crates/gpui_macros/src/derive_path_static_str.rs
@@ -0,0 +1,73 @@
+use proc_macro::TokenStream;
+use quote::quote;
+use syn::{parse_macro_input, Attribute, Data, DeriveInput, Lit, Meta, NestedMeta};
+
+pub fn derive_path_static_str(input: TokenStream) -> TokenStream {
+ let input = parse_macro_input!(input as DeriveInput);
+ let name = &input.ident;
+
+ let prefix = get_attr_value(&input.attrs, "prefix").unwrap_or_else(|| "".to_string());
+ let suffix = get_attr_value(&input.attrs, "suffix").unwrap_or_else(|| "".to_string());
+ let delimiter = get_attr_value(&input.attrs, "delimiter").unwrap_or_else(|| "/".to_string());
+
+ let path_str_impl = impl_path_str(name, &input.data, &prefix, &suffix, &delimiter);
+
+ let expanded = quote! {
+ impl #name {
+ pub fn path_str(&self) -> &'static str {
+ #path_str_impl
+ }
+ }
+ };
+
+ TokenStream::from(expanded)
+}
+
+fn impl_path_str(
+ name: &syn::Ident,
+ data: &Data,
+ prefix: &str,
+ suffix: &str,
+ delimiter: &str,
+) -> proc_macro2::TokenStream {
+ match *data {
+ Data::Enum(ref data) => {
+ let match_arms = data.variants.iter().map(|variant| {
+ let ident = &variant.ident;
+ let path = format!("{}{}{}{}{}", prefix, delimiter, ident, delimiter, suffix);
+ quote! {
+ #name::#ident => #path,
+ }
+ });
+
+ quote! {
+ match self {
+ #(#match_arms)*
+ }
+ }
+ }
+ _ => panic!("DerivePathStr only supports enums"),
+ }
+}
+
+fn get_attr_value(attrs: &[Attribute], key: &str) -> Option {
+ attrs
+ .iter()
+ .filter(|attr| attr.path.is_ident("derive_path_static_str"))
+ .find_map(|attr| {
+ if let Ok(Meta::List(meta_list)) = attr.parse_meta() {
+ meta_list.nested.iter().find_map(|nested_meta| {
+ if let NestedMeta::Meta(Meta::NameValue(name_value)) = nested_meta {
+ if name_value.path.is_ident(key) {
+ if let Lit::Str(lit_str) = &name_value.lit {
+ return Some(lit_str.value());
+ }
+ }
+ }
+ None
+ })
+ } else {
+ None
+ }
+ })
+}
diff --git a/crates/gpui_macros/src/gpui_macros.rs b/crates/gpui_macros/src/gpui_macros.rs
index c4cf5358b3e756d7ca5dfc3d2b1aad6efa8850eb..09cf4027d2f37c355cda4cec4fcbe157f59827af 100644
--- a/crates/gpui_macros/src/gpui_macros.rs
+++ b/crates/gpui_macros/src/gpui_macros.rs
@@ -1,4 +1,5 @@
mod derive_into_element;
+mod derive_path_static_str;
mod derive_render;
mod register_action;
mod styles;
@@ -27,6 +28,12 @@ pub fn derive_render(input: TokenStream) -> TokenStream {
derive_render::derive_render(input)
}
+#[proc_macro_derive(PathStaticStr)]
+#[doc(hidden)]
+pub fn derive_path_static_str(input: TokenStream) -> TokenStream {
+ derive_path_static_str::derive_path_static_str(input)
+}
+
/// Used by GPUI to generate the style helpers.
#[proc_macro]
#[doc(hidden)]
diff --git a/crates/storybook/src/assets.rs b/crates/storybook/src/assets.rs
index da874e5f2de14350c39a81c79276f1770b5e9e80..f45d1457df91fc5e1b70abe51e7982d678cd9ca1 100644
--- a/crates/storybook/src/assets.rs
+++ b/crates/storybook/src/assets.rs
@@ -8,6 +8,7 @@ use rust_embed::RustEmbed;
#[folder = "../../assets"]
#[include = "fonts/**/*"]
#[include = "icons/**/*"]
+#[include = "images/**/*"]
#[include = "themes/**/*"]
#[include = "sounds/**/*"]
#[include = "*.md"]
diff --git a/crates/storybook/src/story_selector.rs b/crates/storybook/src/story_selector.rs
index 5df02b1df2f4888775fd9d93fb62c6a8d90eab31..881fd83f8f21b9876300b6c1b725608995484da4 100644
--- a/crates/storybook/src/story_selector.rs
+++ b/crates/storybook/src/story_selector.rs
@@ -40,6 +40,7 @@ pub enum ComponentStory {
ToolStrip,
ViewportUnits,
WithRemSize,
+ Vector,
}
impl ComponentStory {
@@ -75,6 +76,7 @@ impl ComponentStory {
Self::ToolStrip => cx.new_view(|_| ui::ToolStripStory).into(),
Self::ViewportUnits => cx.new_view(|_| crate::stories::ViewportUnitsStory).into(),
Self::WithRemSize => cx.new_view(|_| crate::stories::WithRemSizeStory).into(),
+ Self::Vector => cx.new_view(|_| ui::VectorStory).into(),
}
}
}
diff --git a/crates/ui/src/components.rs b/crates/ui/src/components.rs
index 3a56e46eae49c7067b969b1584231986cf267482..fe63b035027afbf40f0f74f5608de337daeb2548 100644
--- a/crates/ui/src/components.rs
+++ b/crates/ui/src/components.rs
@@ -7,6 +7,7 @@ mod divider;
mod dropdown_menu;
mod facepile;
mod icon;
+mod image;
mod indicator;
mod keybinding;
mod label;
@@ -37,6 +38,7 @@ pub use divider::*;
pub use dropdown_menu::*;
pub use facepile::*;
pub use icon::*;
+pub use image::*;
pub use indicator::*;
pub use keybinding::*;
pub use label::*;
@@ -55,5 +57,7 @@ pub use tab_bar::*;
pub use tool_strip::*;
pub use tooltip::*;
+#[cfg(feature = "stories")]
+pub use image::story::*;
#[cfg(feature = "stories")]
pub use stories::*;
diff --git a/crates/ui/src/components/icon.rs b/crates/ui/src/components/icon.rs
index 0001ab4a2b275b8ae9236e21a3dbe612d1ea6c8c..fd4f17ac0e0ef7efe2cb730aeacdf511d56e5977 100644
--- a/crates/ui/src/components/icon.rs
+++ b/crates/ui/src/components/icon.rs
@@ -271,7 +271,6 @@ pub enum IconName {
XCircle,
ZedAssistant,
ZedAssistantFilled,
- ZedXCopilot,
Visible,
}
@@ -443,7 +442,6 @@ impl IconName {
IconName::XCircle => "icons/error.svg",
IconName::ZedAssistant => "icons/zed_assistant.svg",
IconName::ZedAssistantFilled => "icons/zed_assistant_filled.svg",
- IconName::ZedXCopilot => "icons/zed_x_copilot.svg",
IconName::Visible => "icons/visible.svg",
}
}
diff --git a/crates/ui/src/components/image.rs b/crates/ui/src/components/image.rs
new file mode 100644
index 0000000000000000000000000000000000000000..286fe7f56f3e31ae97624b90897158d98b6cc8ac
--- /dev/null
+++ b/crates/ui/src/components/image.rs
@@ -0,0 +1,115 @@
+use gpui::{svg, IntoElement, Rems, RenderOnce, Size, Styled, WindowContext};
+use serde::{Deserialize, Serialize};
+use strum::{EnumIter, EnumString, IntoStaticStr};
+use ui_macros::{path_str, DerivePathStr};
+
+use crate::Color;
+
+#[derive(
+ Debug,
+ PartialEq,
+ Eq,
+ Copy,
+ Clone,
+ EnumIter,
+ EnumString,
+ IntoStaticStr,
+ Serialize,
+ Deserialize,
+ DerivePathStr,
+)]
+#[strum(serialize_all = "snake_case")]
+#[path_str(prefix = "images", suffix = ".svg")]
+pub enum VectorName {
+ ZedLogo,
+ ZedXCopilot,
+}
+
+/// A vector image, such as an SVG.
+///
+/// A [Vector] is different from an [Icon] in that it is intended
+/// to be displayed at a specific size, or series of sizes, rather
+/// than conforming to the standard size of an icons.
+#[derive(IntoElement)]
+pub struct Vector {
+ path: &'static str,
+ color: Color,
+ size: Size,
+}
+
+impl Vector {
+ /// Create a new [Vector] image with the given [VectorName] and size.
+ pub fn new(vector: VectorName, width: Rems, height: Rems) -> Self {
+ Self {
+ path: vector.path(),
+ color: Color::default(),
+ size: Size { width, height },
+ }
+ }
+
+ /// Create a new [Vector] image where the width and height are the same.
+ pub fn square(vector: VectorName, size: Rems) -> Self {
+ Self::new(vector, size, size)
+ }
+
+ /// Set the image color
+ pub fn color(mut self, color: Color) -> Self {
+ self.color = color;
+ self
+ }
+
+ /// Set the image size
+ pub fn size(mut self, size: impl Into>) -> Self {
+ let size = size.into();
+
+ self.size = size;
+ self
+ }
+}
+
+impl RenderOnce for Vector {
+ fn render(self, cx: &mut WindowContext) -> impl IntoElement {
+ let width = self.size.width;
+ let height = self.size.height;
+
+ svg()
+ // By default, prevent the SVG from stretching
+ // to fill its container.
+ .flex_none()
+ .w(width)
+ .h(height)
+ .path(self.path)
+ .text_color(self.color.color(cx))
+ }
+}
+
+#[cfg(feature = "stories")]
+pub mod story {
+ use gpui::Render;
+ use story::{Story, StoryItem, StorySection};
+ use strum::IntoEnumIterator;
+
+ use crate::prelude::*;
+
+ use super::{Vector, VectorName};
+
+ pub struct VectorStory;
+
+ impl Render for VectorStory {
+ fn render(&mut self, _cx: &mut ViewContext) -> impl IntoElement {
+ Story::container().child(StorySection::new().children(VectorName::iter().map(
+ |vector| StoryItem::new(format!("{:?}", vector), Vector::square(vector, rems(8.))),
+ )))
+ }
+ }
+}
+
+#[cfg(test)]
+mod tests {
+ use super::*;
+
+ #[test]
+ fn vector_path() {
+ assert_eq!(VectorName::ZedLogo.path(), "images/zed_logo.svg");
+ }
+}