icon.rs

  1use gpui::{svg, AnimationElement, Hsla, IntoElement, Rems, Transformation};
  2use serde::{Deserialize, Serialize};
  3use strum::{EnumIter, EnumString, IntoStaticStr};
  4use ui_macros::DerivePathStr;
  5
  6use crate::{prelude::*, Indicator};
  7
  8#[derive(IntoElement)]
  9pub enum AnyIcon {
 10    Icon(Icon),
 11    AnimatedIcon(AnimationElement<Icon>),
 12}
 13
 14impl AnyIcon {
 15    /// Returns a new [`AnyIcon`] after applying the given mapping function
 16    /// to the contained [`Icon`].
 17    pub fn map(self, f: impl FnOnce(Icon) -> Icon) -> Self {
 18        match self {
 19            Self::Icon(icon) => Self::Icon(f(icon)),
 20            Self::AnimatedIcon(animated_icon) => Self::AnimatedIcon(animated_icon.map_element(f)),
 21        }
 22    }
 23}
 24
 25impl From<Icon> for AnyIcon {
 26    fn from(value: Icon) -> Self {
 27        Self::Icon(value)
 28    }
 29}
 30
 31impl From<AnimationElement<Icon>> for AnyIcon {
 32    fn from(value: AnimationElement<Icon>) -> Self {
 33        Self::AnimatedIcon(value)
 34    }
 35}
 36
 37impl RenderOnce for AnyIcon {
 38    fn render(self, _cx: &mut WindowContext) -> impl IntoElement {
 39        match self {
 40            Self::Icon(icon) => icon.into_any_element(),
 41            Self::AnimatedIcon(animated_icon) => animated_icon.into_any_element(),
 42        }
 43    }
 44}
 45
 46/// The decoration for an icon.
 47///
 48/// For example, this can show an indicator, an "x",
 49/// or a diagonal strkethrough to indicate something is disabled.
 50#[derive(Debug, PartialEq, Copy, Clone, EnumIter)]
 51pub enum IconDecoration {
 52    Strikethrough,
 53    IndicatorDot,
 54    X,
 55}
 56
 57#[derive(Default, PartialEq, Copy, Clone)]
 58pub enum IconSize {
 59    /// 10px
 60    Indicator,
 61    /// 12px
 62    XSmall,
 63    /// 14px
 64    Small,
 65    #[default]
 66    /// 16px
 67    Medium,
 68}
 69
 70impl IconSize {
 71    pub fn rems(self) -> Rems {
 72        match self {
 73            IconSize::Indicator => rems_from_px(10.),
 74            IconSize::XSmall => rems_from_px(12.),
 75            IconSize::Small => rems_from_px(14.),
 76            IconSize::Medium => rems_from_px(16.),
 77        }
 78    }
 79
 80    /// Returns the individual components of the square that contains this [`IconSize`].
 81    ///
 82    /// The returned tuple contains:
 83    ///   1. The length of one side of the square
 84    ///   2. The padding of one side of the square
 85    pub fn square_components(&self, cx: &mut WindowContext) -> (Pixels, Pixels) {
 86        let icon_size = self.rems() * cx.rem_size();
 87        let padding = match self {
 88            IconSize::Indicator => Spacing::None.px(cx),
 89            IconSize::XSmall => Spacing::XSmall.px(cx),
 90            IconSize::Small => Spacing::XSmall.px(cx),
 91            IconSize::Medium => Spacing::XSmall.px(cx),
 92        };
 93
 94        (icon_size, padding)
 95    }
 96
 97    /// Returns the length of a side of the square that contains this [`IconSize`], with padding.
 98    pub fn square(&self, cx: &mut WindowContext) -> Pixels {
 99        let (icon_size, padding) = self.square_components(cx);
100
101        icon_size + padding * 2.
102    }
103}
104
105#[derive(
106    Debug,
107    PartialEq,
108    Eq,
109    Copy,
110    Clone,
111    EnumIter,
112    EnumString,
113    IntoStaticStr,
114    Serialize,
115    Deserialize,
116    DerivePathStr,
117)]
118#[strum(serialize_all = "snake_case")]
119#[path_str(prefix = "icons", suffix = ".svg")]
120pub enum IconName {
121    Ai,
122    AiAnthropic,
123    AiAnthropicHosted,
124    AiGoogle,
125    AiOllama,
126    AiOpenAi,
127    AiZed,
128    ArrowCircle,
129    ArrowDown,
130    ArrowDownFromLine,
131    ArrowLeft,
132    ArrowRight,
133    ArrowUp,
134    ArrowUpFromLine,
135    ArrowUpRight,
136    AtSign,
137    AudioOff,
138    AudioOn,
139    Backspace,
140    Bell,
141    BellDot,
142    BellOff,
143    BellRing,
144    Bolt,
145    Book,
146    BookCopy,
147    BookPlus,
148    CaseSensitive,
149    Check,
150    ChevronDown,
151    ChevronDownSmall, // This chevron indicates a popover menu.
152    ChevronLeft,
153    ChevronRight,
154    ChevronUp,
155    ChevronUpDown,
156    Close,
157    Code,
158    Command,
159    Context,
160    Control,
161    Copilot,
162    CopilotDisabled,
163    CopilotError,
164    CopilotInit,
165    Copy,
166    CountdownTimer,
167    CursorIBeam,
168    CursorText,
169    Dash,
170    DatabaseZap,
171    Delete,
172    Disconnected,
173    Download,
174    Ellipsis,
175    EllipsisVertical,
176    Envelope,
177    Escape,
178    Exit,
179    ExpandVertical,
180    ExternalLink,
181    Eye,
182    File,
183    FileCode,
184    FileDoc,
185    FileGeneric,
186    FileGit,
187    FileLock,
188    FileRust,
189    FileText,
190    FileToml,
191    FileTree,
192    Filter,
193    Folder,
194    FolderOpen,
195    FolderX,
196    Font,
197    FontSize,
198    FontWeight,
199    GenericClose,
200    GenericMaximize,
201    GenericMinimize,
202    GenericRestore,
203    Github,
204    Hash,
205    HistoryRerun,
206    Indicator,
207    IndicatorX,
208    InlayHint,
209    Library,
210    LineHeight,
211    Link,
212    ListTree,
213    MagnifyingGlass,
214    MailOpen,
215    Maximize,
216    Menu,
217    MessageBubbles,
218    Mic,
219    MicMute,
220    Microscope,
221    Minimize,
222    Option,
223    PageDown,
224    PageUp,
225    Pencil,
226    Person,
227    Pin,
228    Play,
229    Plus,
230    PocketKnife,
231    Public,
232    PullRequest,
233    Quote,
234    Regex,
235    ReplNeutral,
236    Replace,
237    ReplaceAll,
238    ReplaceNext,
239    ReplyArrowRight,
240    Rerun,
241    Return,
242    Reveal,
243    RotateCcw,
244    RotateCw,
245    Route,
246    Save,
247    Screen,
248    SearchCode,
249    SearchSelection,
250    SelectAll,
251    Server,
252    Settings,
253    SettingsAlt,
254    Shift,
255    Slash,
256    SlashSquare,
257    Sliders,
258    Snip,
259    Space,
260    Sparkle,
261    SparkleAlt,
262    SparkleFilled,
263    Spinner,
264    Split,
265    Star,
266    StarFilled,
267    Stop,
268    Strikethrough,
269    Supermaven,
270    SupermavenDisabled,
271    SupermavenError,
272    SupermavenInit,
273    Tab,
274    Terminal,
275    Trash,
276    TriangleRight,
277    Undo,
278    Unpin,
279    Update,
280    UserGroup,
281    Visible,
282    Warning,
283    WholeWord,
284    XCircle,
285    ZedAssistant,
286    ZedAssistantFilled,
287}
288
289#[derive(IntoElement)]
290pub struct Icon {
291    path: SharedString,
292    color: Color,
293    size: Rems,
294    transformation: Transformation,
295}
296
297impl Icon {
298    pub fn new(icon: IconName) -> Self {
299        Self {
300            path: icon.path().into(),
301            color: Color::default(),
302            size: IconSize::default().rems(),
303            transformation: Transformation::default(),
304        }
305    }
306
307    pub fn from_path(path: impl Into<SharedString>) -> Self {
308        Self {
309            path: path.into(),
310            color: Color::default(),
311            size: IconSize::default().rems(),
312            transformation: Transformation::default(),
313        }
314    }
315
316    pub fn color(mut self, color: Color) -> Self {
317        self.color = color;
318        self
319    }
320
321    pub fn size(mut self, size: IconSize) -> Self {
322        self.size = size.rems();
323        self
324    }
325
326    /// Sets a custom size for the icon, in [`Rems`].
327    ///
328    /// Not to be exposed outside of the `ui` crate.
329    pub(crate) fn custom_size(mut self, size: Rems) -> Self {
330        self.size = size;
331        self
332    }
333
334    pub fn transform(mut self, transformation: Transformation) -> Self {
335        self.transformation = transformation;
336        self
337    }
338}
339
340impl RenderOnce for Icon {
341    fn render(self, cx: &mut WindowContext) -> impl IntoElement {
342        svg()
343            .with_transformation(self.transformation)
344            .size(self.size)
345            .flex_none()
346            .path(self.path)
347            .text_color(self.color.color(cx))
348    }
349}
350
351#[derive(IntoElement)]
352pub struct DecoratedIcon {
353    icon: Icon,
354    decoration: IconDecoration,
355    decoration_color: Color,
356    parent_background: Option<Hsla>,
357}
358
359impl DecoratedIcon {
360    pub fn new(icon: Icon, decoration: IconDecoration) -> Self {
361        Self {
362            icon,
363            decoration,
364            decoration_color: Color::Default,
365            parent_background: None,
366        }
367    }
368
369    pub fn decoration_color(mut self, color: Color) -> Self {
370        self.decoration_color = color;
371        self
372    }
373
374    pub fn parent_background(mut self, background: Option<Hsla>) -> Self {
375        self.parent_background = background;
376        self
377    }
378}
379
380impl RenderOnce for DecoratedIcon {
381    fn render(self, cx: &mut WindowContext) -> impl IntoElement {
382        let background = self
383            .parent_background
384            .unwrap_or(cx.theme().colors().background);
385
386        let size = self.icon.size;
387
388        let decoration_icon = match self.decoration {
389            IconDecoration::Strikethrough => IconName::Strikethrough,
390            IconDecoration::IndicatorDot => IconName::Indicator,
391            IconDecoration::X => IconName::IndicatorX,
392        };
393
394        let decoration_svg = |icon: IconName| {
395            svg()
396                .absolute()
397                .top_0()
398                .left_0()
399                .path(icon.path())
400                .size(size)
401                .flex_none()
402                .text_color(self.decoration_color.color(cx))
403        };
404
405        let decoration_knockout = |icon: IconName| {
406            svg()
407                .absolute()
408                .top(-rems_from_px(2.))
409                .left(-rems_from_px(3.))
410                .path(icon.path())
411                .size(size + rems_from_px(2.))
412                .flex_none()
413                .text_color(background)
414        };
415
416        div()
417            .relative()
418            .size(self.icon.size)
419            .child(self.icon)
420            .child(decoration_knockout(decoration_icon))
421            .child(decoration_svg(decoration_icon))
422    }
423}
424
425#[derive(IntoElement)]
426pub struct IconWithIndicator {
427    icon: Icon,
428    indicator: Option<Indicator>,
429    indicator_border_color: Option<Hsla>,
430}
431
432impl IconWithIndicator {
433    pub fn new(icon: Icon, indicator: Option<Indicator>) -> Self {
434        Self {
435            icon,
436            indicator,
437            indicator_border_color: None,
438        }
439    }
440
441    pub fn indicator(mut self, indicator: Option<Indicator>) -> Self {
442        self.indicator = indicator;
443        self
444    }
445
446    pub fn indicator_color(mut self, color: Color) -> Self {
447        if let Some(indicator) = self.indicator.as_mut() {
448            indicator.color = color;
449        }
450        self
451    }
452
453    pub fn indicator_border_color(mut self, color: Option<Hsla>) -> Self {
454        self.indicator_border_color = color;
455        self
456    }
457}
458
459impl RenderOnce for IconWithIndicator {
460    fn render(self, cx: &mut WindowContext) -> impl IntoElement {
461        let indicator_border_color = self
462            .indicator_border_color
463            .unwrap_or_else(|| cx.theme().colors().elevated_surface_background);
464
465        div()
466            .relative()
467            .child(self.icon)
468            .when_some(self.indicator, |this, indicator| {
469                this.child(
470                    div()
471                        .absolute()
472                        .w_2()
473                        .h_2()
474                        .border_1()
475                        .border_color(indicator_border_color)
476                        .rounded_full()
477                        .bottom_neg_0p5()
478                        .right_neg_1()
479                        .child(indicator),
480                )
481            })
482    }
483}