1use gpui::{rems, svg, Hsla};
2use strum::EnumIter;
3
4use crate::{prelude::*, LabelColor};
5
6#[derive(Default, PartialEq, Copy, Clone)]
7pub enum IconSize {
8 Small,
9 #[default]
10 Medium,
11}
12
13#[derive(Default, PartialEq, Copy, Clone)]
14pub enum IconColor {
15 #[default]
16 Default,
17 Accent,
18 Created,
19 Deleted,
20 Disabled,
21 Error,
22 Hidden,
23 Info,
24 Modified,
25 Muted,
26 Placeholder,
27 Player(u32),
28 Selected,
29 Success,
30 Warning,
31}
32
33impl IconColor {
34 pub fn color(self, cx: &WindowContext) -> Hsla {
35 match self {
36 IconColor::Default => cx.theme().colors().icon,
37 IconColor::Muted => cx.theme().colors().icon_muted,
38 IconColor::Disabled => cx.theme().colors().icon_disabled,
39 IconColor::Placeholder => cx.theme().colors().icon_placeholder,
40 IconColor::Accent => cx.theme().colors().icon_accent,
41 IconColor::Error => cx.theme().status().error,
42 IconColor::Warning => cx.theme().status().warning,
43 IconColor::Success => cx.theme().status().success,
44 IconColor::Info => cx.theme().status().info,
45 IconColor::Selected => cx.theme().colors().icon_accent,
46 IconColor::Player(i) => cx.theme().styles.player.0[i.clone() as usize].cursor,
47 IconColor::Created => cx.theme().status().created,
48 IconColor::Modified => cx.theme().status().modified,
49 IconColor::Deleted => cx.theme().status().deleted,
50 IconColor::Hidden => cx.theme().status().hidden,
51 }
52 }
53}
54
55impl From<LabelColor> for IconColor {
56 fn from(label: LabelColor) -> Self {
57 match label {
58 LabelColor::Default => IconColor::Default,
59 LabelColor::Muted => IconColor::Muted,
60 LabelColor::Disabled => IconColor::Disabled,
61 LabelColor::Placeholder => IconColor::Placeholder,
62 LabelColor::Accent => IconColor::Accent,
63 LabelColor::Error => IconColor::Error,
64 LabelColor::Warning => IconColor::Warning,
65 LabelColor::Success => IconColor::Success,
66 LabelColor::Info => IconColor::Info,
67 LabelColor::Selected => IconColor::Selected,
68 LabelColor::Player(i) => IconColor::Player(i),
69 LabelColor::Created => IconColor::Created,
70 LabelColor::Modified => IconColor::Modified,
71 LabelColor::Deleted => IconColor::Deleted,
72 LabelColor::Hidden => IconColor::Hidden,
73 }
74 }
75}
76
77#[derive(Debug, PartialEq, Copy, Clone, EnumIter)]
78pub enum Icon {
79 Ai,
80 ArrowLeft,
81 ArrowRight,
82 ArrowUpRight,
83 AudioOff,
84 AudioOn,
85 Bolt,
86 Check,
87 ChevronDown,
88 ChevronLeft,
89 ChevronRight,
90 ChevronUp,
91 Close,
92 Dash,
93 Exit,
94 ExclamationTriangle,
95 File,
96 FileGeneric,
97 FileDoc,
98 FileGit,
99 FileLock,
100 FileRust,
101 FileToml,
102 FileTree,
103 Folder,
104 FolderOpen,
105 FolderX,
106 Hash,
107 InlayHint,
108 MagicWand,
109 MagnifyingGlass,
110 Maximize,
111 Menu,
112 MessageBubbles,
113 Mic,
114 MicMute,
115 Plus,
116 Quote,
117 Replace,
118 ReplaceAll,
119 Screen,
120 SelectAll,
121 Split,
122 SplitMessage,
123 Terminal,
124 XCircle,
125 Copilot,
126 Envelope,
127 Bell,
128 BellOff,
129 BellRing,
130 MailOpen,
131 AtSign,
132}
133
134impl Icon {
135 pub fn path(self) -> &'static str {
136 match self {
137 Icon::Ai => "icons/ai.svg",
138 Icon::ArrowLeft => "icons/arrow_left.svg",
139 Icon::ArrowRight => "icons/arrow_right.svg",
140 Icon::ArrowUpRight => "icons/arrow_up_right.svg",
141 Icon::AudioOff => "icons/speaker-off.svg",
142 Icon::AudioOn => "icons/speaker-loud.svg",
143 Icon::Bolt => "icons/bolt.svg",
144 Icon::Check => "icons/check.svg",
145 Icon::ChevronDown => "icons/chevron_down.svg",
146 Icon::ChevronLeft => "icons/chevron_left.svg",
147 Icon::ChevronRight => "icons/chevron_right.svg",
148 Icon::ChevronUp => "icons/chevron_up.svg",
149 Icon::Close => "icons/x.svg",
150 Icon::Dash => "icons/dash.svg",
151 Icon::Exit => "icons/exit.svg",
152 Icon::ExclamationTriangle => "icons/warning.svg",
153 Icon::File => "icons/file.svg",
154 Icon::FileGeneric => "icons/file_icons/file.svg",
155 Icon::FileDoc => "icons/file_icons/book.svg",
156 Icon::FileGit => "icons/file_icons/git.svg",
157 Icon::FileLock => "icons/file_icons/lock.svg",
158 Icon::FileRust => "icons/file_icons/rust.svg",
159 Icon::FileToml => "icons/file_icons/toml.svg",
160 Icon::FileTree => "icons/project.svg",
161 Icon::Folder => "icons/file_icons/folder.svg",
162 Icon::FolderOpen => "icons/file_icons/folder_open.svg",
163 Icon::FolderX => "icons/stop_sharing.svg",
164 Icon::Hash => "icons/hash.svg",
165 Icon::InlayHint => "icons/inlay_hint.svg",
166 Icon::MagicWand => "icons/magic-wand.svg",
167 Icon::MagnifyingGlass => "icons/magnifying_glass.svg",
168 Icon::Maximize => "icons/maximize.svg",
169 Icon::Menu => "icons/menu.svg",
170 Icon::MessageBubbles => "icons/conversations.svg",
171 Icon::Mic => "icons/mic.svg",
172 Icon::MicMute => "icons/mic-mute.svg",
173 Icon::Plus => "icons/plus.svg",
174 Icon::Quote => "icons/quote.svg",
175 Icon::Replace => "icons/replace.svg",
176 Icon::ReplaceAll => "icons/replace_all.svg",
177 Icon::Screen => "icons/desktop.svg",
178 Icon::SelectAll => "icons/select-all.svg",
179 Icon::Split => "icons/split.svg",
180 Icon::SplitMessage => "icons/split_message.svg",
181 Icon::Terminal => "icons/terminal.svg",
182 Icon::XCircle => "icons/error.svg",
183 Icon::Copilot => "icons/copilot.svg",
184 Icon::Envelope => "icons/feedback.svg",
185 Icon::Bell => "icons/bell.svg",
186 Icon::BellOff => "icons/bell-off.svg",
187 Icon::BellRing => "icons/bell-ring.svg",
188 Icon::MailOpen => "icons/mail-open.svg",
189 Icon::AtSign => "icons/at-sign.svg",
190 }
191 }
192}
193
194#[derive(Component)]
195pub struct IconElement {
196 icon: Icon,
197 color: IconColor,
198 size: IconSize,
199}
200
201impl IconElement {
202 pub fn new(icon: Icon) -> Self {
203 Self {
204 icon,
205 color: IconColor::default(),
206 size: IconSize::default(),
207 }
208 }
209
210 pub fn color(mut self, color: IconColor) -> Self {
211 self.color = color;
212 self
213 }
214
215 pub fn size(mut self, size: IconSize) -> Self {
216 self.size = size;
217 self
218 }
219
220 fn render<V: 'static>(self, _view: &mut V, cx: &mut ViewContext<V>) -> impl Component<V> {
221 let svg_size = match self.size {
222 IconSize::Small => rems(0.75),
223 IconSize::Medium => rems(0.9375),
224 };
225
226 svg()
227 .size(svg_size)
228 .flex_none()
229 .path(self.icon.path())
230 .text_color(self.color.color(cx))
231 }
232}
233
234#[cfg(feature = "stories")]
235pub use stories::*;
236
237#[cfg(feature = "stories")]
238mod stories {
239 use gpui::{Div, Render};
240 use strum::IntoEnumIterator;
241
242 use crate::Story;
243
244 use super::*;
245
246 pub struct IconStory;
247
248 impl Render for IconStory {
249 type Element = Div<Self>;
250
251 fn render(&mut self, cx: &mut ViewContext<Self>) -> Self::Element {
252 let icons = Icon::iter();
253
254 Story::container(cx)
255 .child(Story::title_for::<_, IconElement>(cx))
256 .child(Story::label(cx, "All Icons"))
257 .child(div().flex().gap_3().children(icons.map(IconElement::new)))
258 }
259 }
260}