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(foreground)
167            .child(background)
168    }
169}