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    position: Point<Pixels>,
 67    group_name: Option<SharedString>,
 68}
 69
 70impl IconDecoration {
 71    /// Creates a new [`IconDecoration`].
 72    pub fn new(kind: IconDecorationKind, knockout_color: Hsla, cx: &App) -> Self {
 73        let color = cx.theme().colors().icon;
 74        let position = Point::default();
 75
 76        Self {
 77            kind,
 78            color,
 79            knockout_color,
 80            knockout_hover_color: knockout_color,
 81            position,
 82            group_name: None,
 83        }
 84    }
 85
 86    /// Sets the kind of decoration.
 87    pub fn kind(mut self, kind: IconDecorationKind) -> Self {
 88        self.kind = kind;
 89        self
 90    }
 91
 92    /// Sets the color of the decoration.
 93    pub fn color(mut self, color: Hsla) -> Self {
 94        self.color = color;
 95        self
 96    }
 97
 98    /// Sets the color of the decoration's knockout
 99    ///
100    /// Match this to the background of the element the icon will be rendered
101    /// on.
102    pub fn knockout_color(mut self, color: Hsla) -> Self {
103        self.knockout_color = color;
104        self
105    }
106
107    /// Sets the color of the decoration that is used on hover.
108    pub fn knockout_hover_color(mut self, color: Hsla) -> Self {
109        self.knockout_hover_color = color;
110        self
111    }
112
113    /// Sets the position of the decoration.
114    pub fn position(mut self, position: Point<Pixels>) -> Self {
115        self.position = position;
116        self
117    }
118
119    /// Sets the name of the group the decoration belongs to
120    pub fn group_name(mut self, name: Option<SharedString>) -> Self {
121        self.group_name = name;
122        self
123    }
124}
125
126impl RenderOnce for IconDecoration {
127    fn render(self, _window: &mut Window, _cx: &mut App) -> impl IntoElement {
128        let foreground = svg()
129            .absolute()
130            .bottom_0()
131            .right_0()
132            .size(ICON_DECORATION_SIZE)
133            .path(self.kind.fg().path())
134            .text_color(self.color);
135
136        let background = svg()
137            .absolute()
138            .bottom_0()
139            .right_0()
140            .size(ICON_DECORATION_SIZE)
141            .path(self.kind.bg().path())
142            .text_color(self.knockout_color)
143            .map(|this| match self.group_name {
144                Some(group_name) => this.group_hover(group_name, |style| {
145                    style.text_color(self.knockout_hover_color)
146                }),
147                None => this.hover(|style| style.text_color(self.knockout_hover_color)),
148            });
149
150        div()
151            .size(ICON_DECORATION_SIZE)
152            .flex_none()
153            .absolute()
154            .bottom(self.position.y)
155            .right(self.position.x)
156            .child(foreground)
157            .child(background)
158    }
159}