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