icon.rs

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