Skip to main content

ui/components/icon/
icon_decoration.rs

1use std::sync::Arc;
2
3use gpui::{Hsla, IntoElement, Point, svg};
4use strum::{EnumIter, EnumString, IntoStaticStr};
5
6use crate::prelude::*;
7
8const ICON_DECORATION_SIZE: Pixels = px(11.);
9
10/// An icon silhouette used to knockout the background of an element for an icon
11/// to sit on top of it, emulating a stroke/border.
12#[derive(Debug, PartialEq, Eq, Copy, Clone, EnumIter, EnumString, IntoStaticStr)]
13#[strum(serialize_all = "snake_case")]
14pub enum KnockoutIconName {
15    XFg,
16    XBg,
17    DotFg,
18    DotBg,
19    TriangleFg,
20    TriangleBg,
21}
22
23impl KnockoutIconName {
24    /// Returns the path to this icon.
25    pub fn path(&self) -> Arc<str> {
26        let file_stem: &'static str = self.into();
27        format!("icons/knockouts/{file_stem}.svg").into()
28    }
29}
30
31#[derive(Debug, PartialEq, Eq, Copy, Clone, EnumIter, EnumString)]
32pub enum IconDecorationKind {
33    X,
34    Dot,
35    Triangle,
36}
37
38impl IconDecorationKind {
39    fn fg(&self) -> KnockoutIconName {
40        match self {
41            Self::X => KnockoutIconName::XFg,
42            Self::Dot => KnockoutIconName::DotFg,
43            Self::Triangle => KnockoutIconName::TriangleFg,
44        }
45    }
46
47    fn bg(&self) -> KnockoutIconName {
48        match self {
49            Self::X => KnockoutIconName::XBg,
50            Self::Dot => KnockoutIconName::DotBg,
51            Self::Triangle => KnockoutIconName::TriangleBg,
52        }
53    }
54}
55
56/// The decoration for an icon.
57///
58/// For example, this can show an indicator, an "x", or a diagonal strikethrough
59/// to indicate something is disabled.
60#[derive(IntoElement)]
61pub struct IconDecoration {
62    kind: IconDecorationKind,
63    color: Hsla,
64    knockout_color: Hsla,
65    knockout_hover_color: Hsla,
66    size: Pixels,
67    position: Point<Pixels>,
68    group_name: Option<SharedString>,
69}
70
71impl IconDecoration {
72    /// Creates a new [`IconDecoration`].
73    pub fn new(kind: IconDecorationKind, knockout_color: Hsla, cx: &App) -> Self {
74        let color = cx.theme().colors().icon;
75        let position = Point::default();
76
77        Self {
78            kind,
79            color,
80            knockout_color,
81            knockout_hover_color: knockout_color,
82            size: ICON_DECORATION_SIZE,
83            position,
84            group_name: None,
85        }
86    }
87
88    /// Sets the kind of decoration.
89    pub fn kind(mut self, kind: IconDecorationKind) -> Self {
90        self.kind = kind;
91        self
92    }
93
94    /// Sets the color of the decoration.
95    pub fn color(mut self, color: Hsla) -> Self {
96        self.color = color;
97        self
98    }
99
100    /// Sets the color of the decoration's knockout
101    ///
102    /// Match this to the background of the element the icon will be rendered
103    /// on.
104    pub fn knockout_color(mut self, color: Hsla) -> Self {
105        self.knockout_color = color;
106        self
107    }
108
109    /// Sets the color of the decoration that is used on hover.
110    pub fn knockout_hover_color(mut self, color: Hsla) -> Self {
111        self.knockout_hover_color = color;
112        self
113    }
114
115    /// Sets the position of the decoration.
116    pub fn position(mut self, position: Point<Pixels>) -> Self {
117        self.position = position;
118        self
119    }
120
121    /// Sets the size of the decoration.
122    pub fn size(mut self, size: Pixels) -> Self {
123        self.size = size;
124        self
125    }
126
127    /// Sets the name of the group the decoration belongs to
128    pub fn group_name(mut self, name: Option<SharedString>) -> Self {
129        self.group_name = name;
130        self
131    }
132}
133
134impl RenderOnce for IconDecoration {
135    fn render(self, _window: &mut Window, _cx: &mut App) -> impl IntoElement {
136        let size = self.size;
137
138        let foreground = svg()
139            .absolute()
140            .bottom_0()
141            .right_0()
142            .size(size)
143            .path(self.kind.fg().path())
144            .text_color(self.color);
145
146        let background = svg()
147            .absolute()
148            .bottom_0()
149            .right_0()
150            .size(size)
151            .path(self.kind.bg().path())
152            .text_color(self.knockout_color)
153            .map(|this| match self.group_name {
154                Some(group_name) => this.group_hover(group_name, |style| {
155                    style.text_color(self.knockout_hover_color)
156                }),
157                None => this.hover(|style| style.text_color(self.knockout_hover_color)),
158            });
159
160        div()
161            .size(size)
162            .flex_none()
163            .absolute()
164            .bottom(self.position.y)
165            .right(self.position.x)
166            .child(background)
167            .child(foreground)
168    }
169}