1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
//! `Icon` — the web `FontAwesome` icon: a glyph name, a style, a size, and a
//! color that falls back to the theme's text color, the way the web's
//! `currentColor` resolves.
//!
//! Glyphs resolve at runtime through [`crate::Assets`]; without the icons
//! directory nothing paints, mirroring how fonts degrade.
use fluidattacks_designs_domain::icon::{Size, Style};
use gpui::{
img, px, rgb, svg, App, IntoElement, RenderOnce, Rgba, SharedString, Styled as _, Window,
};
#[derive(IntoElement)]
pub struct Icon {
name: SharedString,
style: Style,
size: Size,
side: Option<f32>,
color: Option<Rgba>,
}
impl Icon {
pub fn new(name: impl Into<SharedString>) -> Self {
Self {
name: name.into(),
style: Style::default(),
size: Size::default(),
side: None,
color: None,
}
}
#[must_use]
pub const fn style(mut self, style: Style) -> Self {
self.style = style;
self
}
#[must_use]
pub const fn size(mut self, size: Size) -> Self {
self.size = size;
self
}
/// A side off the icon scale, which the web takes as a custom px size.
/// Crate-private: published it would put an unchecked `f32` on the
/// rasterizer, and only a component overriding its own glyph needs it —
/// the checkbox paints its mark at 8px inside a 16px box.
#[must_use]
pub(crate) const fn size_px(mut self, side: f32) -> Self {
self.side = Some(side);
self
}
#[must_use]
pub fn color(mut self, color: u32) -> Self {
self.color = Some(rgb(color));
self
}
/// Tint with an already-resolved color (e.g. a transition frame).
#[must_use]
pub const fn color_rgba(mut self, color: Rgba) -> Self {
self.color = Some(color);
self
}
}
impl RenderOnce for Icon {
fn render(self, _window: &mut Window, cx: &mut App) -> impl IntoElement {
let path = format!("icons/{}/{}.svg", self.style.dir(), self.name);
let side = px(self.side.unwrap_or_else(|| self.size.px()));
// gpui reads an svg's color from the element's own refinement, never
// from an ancestor, so a glyph without one is silently skipped
let color = self
.color
.unwrap_or_else(|| rgb(crate::ActiveTheme::theme(cx).colors.text));
// gradient glyphs carry their fill; `svg` renders monochrome, so they
// go through the full-color `img` pipeline instead
match self.style {
Style::Gradient => img(path).size(side).flex_none().into_any_element(),
Style::Solid | Style::Regular | Style::Light => svg()
.path(path)
.size(side)
.flex_none()
.text_color(color)
.into_any_element(),
}
}
}