1use std::sync::Arc;
2
3use gpui::{App, IntoElement, Rems, RenderOnce, Size, Styled, Window, svg};
4use serde::{Deserialize, Serialize};
5use strum::{EnumIter, EnumString, IntoStaticStr};
6
7use crate::Color;
8use crate::prelude::*;
9
10#[derive(
11 Debug, PartialEq, Eq, Copy, Clone, EnumIter, EnumString, IntoStaticStr, Serialize, Deserialize,
12)]
13#[strum(serialize_all = "snake_case")]
14pub enum VectorName {
15 ZedLogo,
16 ZedXCopilot,
17 Grid,
18 AiGrid,
19}
20
21impl VectorName {
22 /// Returns the path to this vector image.
23 pub fn path(&self) -> Arc<str> {
24 let file_stem: &'static str = self.into();
25 format!("images/{file_stem}.svg").into()
26 }
27}
28
29/// A vector image, such as an SVG.
30///
31/// A [`Vector`] is different from an [`crate::Icon`] in that it is intended
32/// to be displayed at a specific size, or series of sizes, rather
33/// than conforming to the standard size of an icon.
34#[derive(IntoElement, RegisterComponent)]
35pub struct Vector {
36 path: Arc<str>,
37 color: Color,
38 size: Size<Rems>,
39}
40
41impl Vector {
42 /// Creates a new [`Vector`] image with the given [`VectorName`] and size.
43 pub fn new(vector: VectorName, width: Rems, height: Rems) -> Self {
44 Self {
45 path: vector.path(),
46 color: Color::default(),
47 size: Size { width, height },
48 }
49 }
50
51 /// Creates a new [`Vector`] image where the width and height are the same.
52 pub fn square(vector: VectorName, size: Rems) -> Self {
53 Self::new(vector, size, size)
54 }
55
56 /// Sets the vector color.
57 pub fn color(mut self, color: Color) -> Self {
58 self.color = color;
59 self
60 }
61
62 /// Sets the vector size.
63 pub fn size(mut self, size: impl Into<Size<Rems>>) -> Self {
64 let size = size.into();
65 self.size = size;
66 self
67 }
68}
69
70impl RenderOnce for Vector {
71 fn render(self, _window: &mut Window, cx: &mut App) -> impl IntoElement {
72 let width = self.size.width;
73 let height = self.size.height;
74
75 svg()
76 // By default, prevent the SVG from stretching
77 // to fill its container.
78 .flex_none()
79 .w(width)
80 .h(height)
81 .path(self.path)
82 .text_color(self.color.color(cx))
83 }
84}
85
86impl Component for Vector {
87 fn scope() -> ComponentScope {
88 ComponentScope::Images
89 }
90
91 fn name() -> &'static str {
92 "Vector"
93 }
94
95 fn description() -> Option<&'static str> {
96 Some("A vector image component that can be displayed at specific sizes.")
97 }
98
99 fn preview(_window: &mut Window, _cx: &mut App) -> Option<AnyElement> {
100 Some(
101 v_flex()
102 .gap_6()
103 .children(vec![
104 example_group_with_title(
105 "Basic Usage",
106 vec![
107 single_example(
108 "Default",
109 Vector::square(VectorName::ZedLogo, rems(8.)).into_any_element(),
110 ),
111 single_example(
112 "Custom Size",
113 Vector::new(VectorName::ZedLogo, rems(12.), rems(6.))
114 .into_any_element(),
115 ),
116 ],
117 ),
118 example_group_with_title(
119 "Colored",
120 vec![
121 single_example(
122 "Accent Color",
123 Vector::square(VectorName::ZedLogo, rems(8.))
124 .color(Color::Accent)
125 .into_any_element(),
126 ),
127 single_example(
128 "Error Color",
129 Vector::square(VectorName::ZedLogo, rems(8.))
130 .color(Color::Error)
131 .into_any_element(),
132 ),
133 ],
134 ),
135 example_group_with_title(
136 "Different Vectors",
137 vec![
138 single_example(
139 "Zed Logo",
140 Vector::square(VectorName::ZedLogo, rems(8.)).into_any_element(),
141 ),
142 single_example(
143 "Zed X Copilot",
144 Vector::square(VectorName::ZedXCopilot, rems(8.))
145 .into_any_element(),
146 ),
147 ],
148 ),
149 ])
150 .into_any_element(),
151 )
152 }
153}
154
155#[cfg(test)]
156mod tests {
157 use super::*;
158
159 #[test]
160 fn vector_path() {
161 assert_eq!(VectorName::ZedLogo.path().as_ref(), "images/zed_logo.svg");
162 }
163}