1use egui::{pos2, vec2, Color32, Pos2, Rect, Response, Sense, Stroke, Ui, Widget};
14use egui_components_theme::Theme;
15
16#[derive(Clone, Copy, Debug, PartialEq, Eq)]
17pub enum IconKind {
18 Check,
19 Close,
20 ChevronRight,
21 ChevronDown,
22 ChevronLeft,
23 ChevronUp,
24 Search,
25 Menu,
26 Plus,
27 Minus,
28 Bell,
29 Info,
30 Warning,
31 Error,
32 Home,
33 Settings,
34 User,
35 File,
36 Folder,
37 Trash,
38 Star,
39 Heart,
40}
41
42pub struct Icon {
43 kind: IconKind,
44 size: f32,
45 color: Option<Color32>,
46 stroke_width: f32,
47}
48
49impl Icon {
50 pub fn new(kind: IconKind) -> Self {
51 Self {
52 kind,
53 size: 16.0,
54 color: None,
55 stroke_width: 1.6,
56 }
57 }
58 pub fn size(mut self, s: f32) -> Self {
59 self.size = s;
60 self
61 }
62 pub fn color(mut self, c: Color32) -> Self {
63 self.color = Some(c);
64 self
65 }
66 pub fn stroke_width(mut self, w: f32) -> Self {
67 self.stroke_width = w;
68 self
69 }
70}
71
72impl Widget for Icon {
73 fn ui(self, ui: &mut Ui) -> Response {
74 let (rect, response) = ui.allocate_exact_size(vec2(self.size, self.size), Sense::hover());
75 if ui.is_rect_visible(rect) {
76 let color = self
77 .color
78 .unwrap_or_else(|| Theme::get(ui.ctx()).colors.foreground);
79 paint_icon(ui.painter(), self.kind, rect, color, self.stroke_width);
80 }
81 response
82 }
83}
84
85pub fn paint_icon(painter: &egui::Painter, kind: IconKind, rect: Rect, color: Color32, sw: f32) {
88 let stroke = Stroke::new(sw, color);
89 let inset = rect.size().min_elem() * 0.12;
91 let b = rect.shrink(inset);
92 let p = |x: f32, y: f32| -> Pos2 { pos2(b.left() + x * b.width(), b.top() + y * b.height()) };
93 let line = |a: Pos2, c: Pos2| {
94 painter.line_segment([a, c], stroke);
95 };
96 let poly = |pts: &[Pos2]| {
97 for w in pts.windows(2) {
98 painter.line_segment([w[0], w[1]], stroke);
99 }
100 };
101
102 match kind {
103 IconKind::Check => poly(&[p(0.15, 0.55), p(0.42, 0.8), p(0.85, 0.22)]),
104 IconKind::Close => {
105 line(p(0.2, 0.2), p(0.8, 0.8));
106 line(p(0.8, 0.2), p(0.2, 0.8));
107 }
108 IconKind::ChevronRight => poly(&[p(0.4, 0.2), p(0.7, 0.5), p(0.4, 0.8)]),
109 IconKind::ChevronLeft => poly(&[p(0.6, 0.2), p(0.3, 0.5), p(0.6, 0.8)]),
110 IconKind::ChevronDown => poly(&[p(0.2, 0.4), p(0.5, 0.7), p(0.8, 0.4)]),
111 IconKind::ChevronUp => poly(&[p(0.2, 0.6), p(0.5, 0.3), p(0.8, 0.6)]),
112 IconKind::Search => {
113 painter.circle_stroke(p(0.42, 0.42), b.width() * 0.26, stroke);
114 line(p(0.62, 0.62), p(0.85, 0.85));
115 }
116 IconKind::Menu => {
117 line(p(0.15, 0.28), p(0.85, 0.28));
118 line(p(0.15, 0.5), p(0.85, 0.5));
119 line(p(0.15, 0.72), p(0.85, 0.72));
120 }
121 IconKind::Plus => {
122 line(p(0.5, 0.18), p(0.5, 0.82));
123 line(p(0.18, 0.5), p(0.82, 0.5));
124 }
125 IconKind::Minus => line(p(0.18, 0.5), p(0.82, 0.5)),
126 IconKind::Bell => {
127 poly(&[
129 p(0.25, 0.68),
130 p(0.25, 0.45),
131 p(0.32, 0.28),
132 p(0.5, 0.22),
133 p(0.68, 0.28),
134 p(0.75, 0.45),
135 p(0.75, 0.68),
136 ]);
137 line(p(0.18, 0.68), p(0.82, 0.68));
138 poly(&[p(0.43, 0.78), p(0.5, 0.84), p(0.57, 0.78)]);
139 }
140 IconKind::Info => {
141 painter.circle_stroke(b.center(), b.width() * 0.42, stroke);
142 painter.circle_filled(p(0.5, 0.32), sw * 0.8, color);
143 line(p(0.5, 0.46), p(0.5, 0.72));
144 }
145 IconKind::Warning => {
146 poly(&[p(0.5, 0.18), p(0.9, 0.82), p(0.1, 0.82), p(0.5, 0.18)]);
147 line(p(0.5, 0.4), p(0.5, 0.62));
148 painter.circle_filled(p(0.5, 0.72), sw * 0.7, color);
149 }
150 IconKind::Error => {
151 painter.circle_stroke(b.center(), b.width() * 0.42, stroke);
152 line(p(0.35, 0.35), p(0.65, 0.65));
153 line(p(0.65, 0.35), p(0.35, 0.65));
154 }
155 IconKind::Home => {
156 poly(&[p(0.15, 0.5), p(0.5, 0.18), p(0.85, 0.5)]);
157 poly(&[
158 p(0.25, 0.45),
159 p(0.25, 0.82),
160 p(0.75, 0.82),
161 p(0.75, 0.45),
162 ]);
163 }
164 IconKind::Settings => {
165 painter.circle_stroke(b.center(), b.width() * 0.18, stroke);
166 painter.circle_stroke(b.center(), b.width() * 0.4, stroke);
167 for k in 0..8 {
168 let a = std::f32::consts::TAU * (k as f32) / 8.0;
169 let (s, c) = a.sin_cos();
170 let inner = b.center() + vec2(c, s) * b.width() * 0.4;
171 let outer = b.center() + vec2(c, s) * b.width() * 0.5;
172 painter.line_segment([inner, outer], stroke);
173 }
174 }
175 IconKind::User => {
176 painter.circle_stroke(p(0.5, 0.35), b.width() * 0.18, stroke);
177 poly(&[p(0.22, 0.82), p(0.3, 0.6), p(0.7, 0.6), p(0.78, 0.82)]);
178 }
179 IconKind::File => {
180 poly(&[
181 p(0.28, 0.15),
182 p(0.6, 0.15),
183 p(0.75, 0.3),
184 p(0.75, 0.85),
185 p(0.28, 0.85),
186 p(0.28, 0.15),
187 ]);
188 poly(&[p(0.6, 0.15), p(0.6, 0.3), p(0.75, 0.3)]);
189 }
190 IconKind::Folder => {
191 poly(&[
192 p(0.15, 0.78),
193 p(0.15, 0.3),
194 p(0.42, 0.3),
195 p(0.5, 0.4),
196 p(0.85, 0.4),
197 p(0.85, 0.78),
198 p(0.15, 0.78),
199 ]);
200 }
201 IconKind::Trash => {
202 line(p(0.2, 0.28), p(0.8, 0.28));
203 poly(&[p(0.4, 0.28), p(0.42, 0.18), p(0.58, 0.18), p(0.6, 0.28)]);
204 poly(&[p(0.28, 0.28), p(0.32, 0.85), p(0.68, 0.85), p(0.72, 0.28)]);
205 }
206 IconKind::Star => {
207 let pts = star_points(b.center(), b.width() * 0.45, b.width() * 0.18, 5);
208 poly(&pts);
209 }
210 IconKind::Heart => {
211 painter.circle_stroke(p(0.33, 0.38), b.width() * 0.16, stroke);
212 painter.circle_stroke(p(0.67, 0.38), b.width() * 0.16, stroke);
213 poly(&[p(0.19, 0.45), p(0.5, 0.82), p(0.81, 0.45)]);
214 }
215 }
216}
217
218fn star_points(center: Pos2, outer: f32, inner: f32, points: usize) -> Vec<Pos2> {
219 let mut out = Vec::with_capacity(points * 2 + 1);
220 let start = -std::f32::consts::FRAC_PI_2;
221 for k in 0..points * 2 {
222 let r = if k % 2 == 0 { outer } else { inner };
223 let a = start + std::f32::consts::PI * (k as f32) / points as f32;
224 let (s, c) = a.sin_cos();
225 out.push(center + vec2(c, s) * r);
226 }
227 out.push(out[0]);
228 out
229}