1use crate::{ActiveTheme, Sizable, Size};
2use gpui::{
3 AnyElement, App, AppContext, Context, Entity, Hsla, IntoElement, Radians, Render, RenderOnce,
4 SharedString, StyleRefinement, Styled, Svg, Transformation, Window,
5 prelude::FluentBuilder as _, svg,
6};
7use gpui_component_macros::icon_named;
8
9pub trait IconNamed {
14 fn path(self) -> SharedString;
16}
17
18impl<T: IconNamed> From<T> for Icon {
19 fn from(value: T) -> Self {
20 Icon::build(value)
21 }
22}
23
24icon_named!(IconName, "$GPUI_KIT_DEFAULT_ICONS_DIR");
30
31impl IconName {
32 pub fn view(self, cx: &mut App) -> Entity<Icon> {
34 Icon::build(self).view(cx)
35 }
36}
37
38impl From<IconName> for AnyElement {
39 fn from(val: IconName) -> Self {
40 Icon::build(val).into_any_element()
41 }
42}
43
44impl RenderOnce for IconName {
45 fn render(self, _: &mut Window, _cx: &mut App) -> impl IntoElement {
46 Icon::build(self)
47 }
48}
49
50#[derive(IntoElement)]
51pub struct Icon {
52 base: Svg,
53 style: StyleRefinement,
54 path: SharedString,
55 text_color: Option<Hsla>,
56 size: Option<Size>,
57 rotation: Option<Radians>,
58}
59
60impl Default for Icon {
61 fn default() -> Self {
62 Self {
63 base: svg().flex_none().size_4(),
64 style: StyleRefinement::default(),
65 path: "".into(),
66 text_color: None,
67 size: None,
68 rotation: None,
69 }
70 }
71}
72
73impl Clone for Icon {
74 fn clone(&self) -> Self {
75 let mut this = Self::default().path(self.path.clone());
76 this.style = self.style.clone();
77 this.rotation = self.rotation;
78 this.size = self.size;
79 this.text_color = self.text_color;
80 this
81 }
82}
83
84impl Icon {
85 pub fn new(icon: impl Into<Icon>) -> Self {
86 icon.into()
87 }
88
89 fn build(name: impl IconNamed) -> Self {
90 Self::default().path(name.path())
91 }
92
93 pub fn path(mut self, path: impl Into<SharedString>) -> Self {
97 self.path = path.into();
98 self
99 }
100
101 #[cfg(any(target_os = "macos", target_os = "windows", test))]
102 pub(crate) fn path_ref(&self) -> &SharedString {
103 &self.path
104 }
105
106 pub fn view(self, cx: &mut App) -> Entity<Icon> {
108 cx.new(|_| self)
109 }
110
111 pub fn transform(mut self, transformation: gpui::Transformation) -> Self {
112 self.base = self.base.with_transformation(transformation);
113 self
114 }
115
116 pub fn empty() -> Self {
117 Self::default()
118 }
119
120 pub fn rotate(mut self, radians: impl Into<Radians>) -> Self {
122 self.base = self
123 .base
124 .with_transformation(Transformation::rotate(radians));
125 self
126 }
127}
128
129impl Styled for Icon {
130 fn style(&mut self) -> &mut StyleRefinement {
131 &mut self.style
132 }
133
134 fn text_color(mut self, color: impl Into<Hsla>) -> Self {
135 self.text_color = Some(color.into());
136 self
137 }
138}
139
140impl Sizable for Icon {
141 fn with_size(mut self, size: impl Into<Size>) -> Self {
142 self.size = Some(size.into());
143 self
144 }
145}
146
147impl RenderOnce for Icon {
148 fn render(self, window: &mut Window, _cx: &mut App) -> impl IntoElement {
149 let text_color = self.text_color.unwrap_or_else(|| window.text_style().color);
150 let text_size = window.text_style().font_size.to_pixels(window.rem_size());
151 let has_base_size = self.style.size.width.is_some() || self.style.size.height.is_some();
152
153 let mut base = self.base;
154 *base.style() = self.style;
155
156 base.flex_shrink_0()
157 .text_color(text_color)
158 .when(!has_base_size, |this| this.size(text_size))
159 .when_some(self.size, |this, size| match size {
160 Size::Size(px) => this.size(px),
161 Size::XSmall => this.size_3(),
162 Size::Small => this.size_3p5(),
163 Size::Medium => this.size_4(),
164 Size::Large => this.size_6(),
165 })
166 .path(self.path)
167 }
168}
169
170impl From<Icon> for AnyElement {
171 fn from(val: Icon) -> Self {
172 val.into_any_element()
173 }
174}
175
176impl Render for Icon {
177 fn render(&mut self, window: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {
178 let text_color = self.text_color.unwrap_or_else(|| cx.theme().foreground);
179 let text_size = window.text_style().font_size.to_pixels(window.rem_size());
180 let has_base_size = self.style.size.width.is_some() || self.style.size.height.is_some();
181
182 let mut base = svg().flex_none();
183 *base.style() = self.style.clone();
184
185 base.flex_shrink_0()
186 .text_color(text_color)
187 .when(!has_base_size, |this| this.size(text_size))
188 .when_some(self.size, |this, size| match size {
189 Size::Size(px) => this.size(px),
190 Size::XSmall => this.size_3(),
191 Size::Small => this.size_3p5(),
192 Size::Medium => this.size_4(),
193 Size::Large => this.size_6(),
194 })
195 .path(self.path.clone())
196 .when_some(self.rotation, |this, rotation| {
197 this.with_transformation(Transformation::rotate(rotation))
198 })
199 }
200}