1use eframe::egui::{self, Color32, Pos2, Rect, Response, Sense, Stroke, Ui, Vec2, Widget};
2
3pub struct MaterialIcon {
4 name: String,
5 size: f32,
6 color: Option<Color32>,
7 filled: bool,
8}
9
10impl MaterialIcon {
11 pub fn new(name: impl Into<String>) -> Self {
12 Self {
13 name: name.into(),
14 size: 24.0,
15 color: None,
16 filled: false,
17 }
18 }
19
20 pub fn size(mut self, size: f32) -> Self {
21 self.size = size;
22 self
23 }
24
25 pub fn color(mut self, color: Color32) -> Self {
26 self.color = Some(color);
27 self
28 }
29
30 pub fn filled(mut self, filled: bool) -> Self {
31 self.filled = filled;
32 self
33 }
34}
35
36impl Widget for MaterialIcon {
37 fn ui(self, ui: &mut Ui) -> Response {
38 let desired_size = Vec2::splat(self.size);
39 let (rect, response) = ui.allocate_exact_size(desired_size, Sense::hover());
40
41 let icon_color = self.color.unwrap_or_else(|| {
42 Color32::from_gray(if ui.visuals().dark_mode { 230 } else { 30 })
43 });
44
45 match self.name.as_str() {
47 "home" => draw_home_icon(ui, rect, icon_color, self.filled),
48 "search" => draw_search_icon(ui, rect, icon_color, self.filled),
49 "settings" => draw_settings_icon(ui, rect, icon_color, self.filled),
50 "menu" => draw_menu_icon(ui, rect, icon_color, self.filled),
51 "close" => draw_close_icon(ui, rect, icon_color, self.filled),
52 "check" => draw_check_icon(ui, rect, icon_color, self.filled),
53 "add" => draw_add_icon(ui, rect, icon_color, self.filled),
54 "remove" => draw_remove_icon(ui, rect, icon_color, self.filled),
55 "arrow_back" => draw_arrow_back_icon(ui, rect, icon_color, self.filled),
56 "arrow_forward" => draw_arrow_forward_icon(ui, rect, icon_color, self.filled),
57 "favorite" => draw_favorite_icon(ui, rect, icon_color, self.filled),
58 "star" => draw_star_icon(ui, rect, icon_color, self.filled),
59 "bookmark" => draw_bookmark_icon(ui, rect, icon_color, self.filled),
60 "notifications" => draw_notifications_icon(ui, rect, icon_color, self.filled),
61 _ => draw_default_icon(ui, rect, icon_color, self.filled),
62 }
63
64 response
65 }
66}
67
68fn draw_home_icon(ui: &mut Ui, rect: Rect, color: Color32, filled: bool) {
69 let center = rect.center();
70 let size = rect.width() * 0.8;
71
72 let points = [
74 Pos2::new(center.x, center.y - size * 0.3), Pos2::new(center.x - size * 0.3, center.y), Pos2::new(center.x - size * 0.2, center.y), Pos2::new(center.x - size * 0.2, center.y + size * 0.3), Pos2::new(center.x + size * 0.2, center.y + size * 0.3), Pos2::new(center.x + size * 0.2, center.y), Pos2::new(center.x + size * 0.3, center.y), ];
82
83 if filled {
84 ui.painter().add(egui::Shape::convex_polygon(points.to_vec(), color, Stroke::NONE));
85 } else {
86 for i in 0..points.len() {
87 let next = (i + 1) % points.len();
88 ui.painter().line_segment([points[i], points[next]], Stroke::new(2.0, color));
89 }
90 }
91}
92
93fn draw_search_icon(ui: &mut Ui, rect: Rect, color: Color32, filled: bool) {
94 let center = rect.center();
95 let radius = rect.width() * 0.25;
96
97 if filled {
99 ui.painter().circle_filled(center - Vec2::new(radius * 0.3, radius * 0.3), radius, color);
100 } else {
101 ui.painter().circle_stroke(center - Vec2::new(radius * 0.3, radius * 0.3), radius, Stroke::new(2.0, color));
102 }
103
104 let handle_start = center + Vec2::new(radius * 0.3, radius * 0.3);
106 let handle_end = center + Vec2::new(radius * 0.8, radius * 0.8);
107 ui.painter().line_segment([handle_start, handle_end], Stroke::new(3.0, color));
108}
109
110fn draw_settings_icon(ui: &mut Ui, rect: Rect, color: Color32, filled: bool) {
111 let center = rect.center();
112 let radius = rect.width() * 0.3;
113
114 for i in 0..8 {
116 let angle = i as f32 * std::f32::consts::TAU / 8.0;
117 let outer_radius = if i % 2 == 0 { radius } else { radius * 0.7 };
118 let point = center + Vec2::new(angle.cos() * outer_radius, angle.sin() * outer_radius);
119
120 if i == 0 {
121 continue;
122 }
123
124 let prev_angle = (i - 1) as f32 * std::f32::consts::TAU / 8.0;
125 let prev_radius = if (i - 1) % 2 == 0 { radius } else { radius * 0.7 };
126 let prev_point = center + Vec2::new(prev_angle.cos() * prev_radius, prev_angle.sin() * prev_radius);
127
128 ui.painter().line_segment([prev_point, point], Stroke::new(2.0, color));
129 }
130
131 if filled {
133 ui.painter().circle_filled(center, radius * 0.3, color);
134 } else {
135 ui.painter().circle_stroke(center, radius * 0.3, Stroke::new(2.0, color));
136 }
137}
138
139fn draw_menu_icon(ui: &mut Ui, rect: Rect, color: Color32, _filled: bool) {
140 let center = rect.center();
141 let width = rect.width() * 0.6;
142 let line_height = 2.0;
143 let spacing = rect.height() * 0.15;
144
145 for i in 0..3 {
147 let y = center.y + (i as f32 - 1.0) * spacing;
148 let start = Pos2::new(center.x - width / 2.0, y);
149 let end = Pos2::new(center.x + width / 2.0, y);
150 ui.painter().line_segment([start, end], Stroke::new(line_height, color));
151 }
152}
153
154fn draw_close_icon(ui: &mut Ui, rect: Rect, color: Color32, _filled: bool) {
155 let center = rect.center();
156 let size = rect.width() * 0.4;
157
158 ui.painter().line_segment([
160 center - Vec2::splat(size / 2.0),
161 center + Vec2::splat(size / 2.0),
162 ], Stroke::new(2.0, color));
163
164 ui.painter().line_segment([
165 center + Vec2::new(-size / 2.0, size / 2.0),
166 center + Vec2::new(size / 2.0, -size / 2.0),
167 ], Stroke::new(2.0, color));
168}
169
170fn draw_check_icon(ui: &mut Ui, rect: Rect, color: Color32, _filled: bool) {
171 let center = rect.center();
172 let size = rect.width() * 0.4;
173
174 let start = Pos2::new(center.x - size * 0.3, center.y);
176 let middle = Pos2::new(center.x - size * 0.1, center.y + size * 0.2);
177 let end = Pos2::new(center.x + size * 0.3, center.y - size * 0.2);
178
179 ui.painter().line_segment([start, middle], Stroke::new(2.0, color));
180 ui.painter().line_segment([middle, end], Stroke::new(2.0, color));
181}
182
183fn draw_add_icon(ui: &mut Ui, rect: Rect, color: Color32, _filled: bool) {
184 let center = rect.center();
185 let size = rect.width() * 0.4;
186
187 ui.painter().line_segment([
189 Pos2::new(center.x - size / 2.0, center.y),
190 Pos2::new(center.x + size / 2.0, center.y),
191 ], Stroke::new(2.0, color));
192
193 ui.painter().line_segment([
194 Pos2::new(center.x, center.y - size / 2.0),
195 Pos2::new(center.x, center.y + size / 2.0),
196 ], Stroke::new(2.0, color));
197}
198
199fn draw_remove_icon(ui: &mut Ui, rect: Rect, color: Color32, _filled: bool) {
200 let center = rect.center();
201 let size = rect.width() * 0.4;
202
203 ui.painter().line_segment([
205 Pos2::new(center.x - size / 2.0, center.y),
206 Pos2::new(center.x + size / 2.0, center.y),
207 ], Stroke::new(2.0, color));
208}
209
210fn draw_arrow_back_icon(ui: &mut Ui, rect: Rect, color: Color32, _filled: bool) {
211 let center = rect.center();
212 let size = rect.width() * 0.4;
213
214 let tip = Pos2::new(center.x - size / 2.0, center.y);
216 let top = Pos2::new(center.x, center.y - size / 2.0);
217 let bottom = Pos2::new(center.x, center.y + size / 2.0);
218 let tail_end = Pos2::new(center.x + size / 2.0, center.y);
219
220 ui.painter().line_segment([tip, top], Stroke::new(2.0, color));
221 ui.painter().line_segment([tip, bottom], Stroke::new(2.0, color));
222 ui.painter().line_segment([tip, tail_end], Stroke::new(2.0, color));
223}
224
225fn draw_arrow_forward_icon(ui: &mut Ui, rect: Rect, color: Color32, _filled: bool) {
226 let center = rect.center();
227 let size = rect.width() * 0.4;
228
229 let tip = Pos2::new(center.x + size / 2.0, center.y);
231 let top = Pos2::new(center.x, center.y - size / 2.0);
232 let bottom = Pos2::new(center.x, center.y + size / 2.0);
233 let tail_end = Pos2::new(center.x - size / 2.0, center.y);
234
235 ui.painter().line_segment([tip, top], Stroke::new(2.0, color));
236 ui.painter().line_segment([tip, bottom], Stroke::new(2.0, color));
237 ui.painter().line_segment([tail_end, tip], Stroke::new(2.0, color));
238}
239
240fn draw_favorite_icon(ui: &mut Ui, rect: Rect, color: Color32, filled: bool) {
241 let center = rect.center();
242 let size = rect.width() * 0.7;
243
244 let left_circle = Pos2::new(center.x - size * 0.2, center.y - size * 0.1);
246 let right_circle = Pos2::new(center.x + size * 0.2, center.y - size * 0.1);
247 let radius = size * 0.15;
248
249 if filled {
250 ui.painter().circle_filled(left_circle, radius, color);
252 ui.painter().circle_filled(right_circle, radius, color);
253
254 let points = [
256 Pos2::new(center.x - size * 0.3, center.y),
257 Pos2::new(center.x + size * 0.3, center.y),
258 Pos2::new(center.x, center.y + size * 0.3),
259 ];
260 ui.painter().add(egui::Shape::convex_polygon(points.to_vec(), color, Stroke::NONE));
261 } else {
262 ui.painter().circle_stroke(left_circle, radius, Stroke::new(2.0, color));
264 ui.painter().circle_stroke(right_circle, radius, Stroke::new(2.0, color));
265
266 let points = [
267 Pos2::new(center.x - size * 0.3, center.y),
268 Pos2::new(center.x + size * 0.3, center.y),
269 Pos2::new(center.x, center.y + size * 0.3),
270 ];
271 for i in 0..points.len() {
272 let next = (i + 1) % points.len();
273 ui.painter().line_segment([points[i], points[next]], Stroke::new(2.0, color));
274 }
275 }
276}
277
278fn draw_star_icon(ui: &mut Ui, rect: Rect, color: Color32, filled: bool) {
279 let center = rect.center();
280 let size = rect.width() * 0.4;
281
282 let mut points = Vec::new();
284 for i in 0..10 {
285 let angle = std::f32::consts::PI * 2.0 * i as f32 / 10.0 - std::f32::consts::PI / 2.0;
286 let radius = if i % 2 == 0 { size } else { size * 0.4 };
287 points.push(Pos2::new(
288 center.x + radius * angle.cos(),
289 center.y + radius * angle.sin(),
290 ));
291 }
292
293 if filled {
294 ui.painter().add(egui::Shape::convex_polygon(points, color, Stroke::NONE));
295 } else {
296 for i in 0..points.len() {
297 let next = (i + 1) % points.len();
298 ui.painter().line_segment([points[i], points[next]], Stroke::new(2.0, color));
299 }
300 }
301}
302
303fn draw_bookmark_icon(ui: &mut Ui, rect: Rect, color: Color32, filled: bool) {
304 let center = rect.center();
305 let size = rect.width() * 0.6;
306
307 let points = [
309 Pos2::new(center.x - size * 0.3, center.y - size * 0.4), Pos2::new(center.x + size * 0.3, center.y - size * 0.4), Pos2::new(center.x + size * 0.3, center.y + size * 0.4), Pos2::new(center.x, center.y + size * 0.2), Pos2::new(center.x - size * 0.3, center.y + size * 0.4), ];
315
316 if filled {
317 ui.painter().add(egui::Shape::convex_polygon(points.to_vec(), color, Stroke::NONE));
318 } else {
319 for i in 0..points.len() {
320 let next = (i + 1) % points.len();
321 ui.painter().line_segment([points[i], points[next]], Stroke::new(2.0, color));
322 }
323 }
324}
325
326fn draw_notifications_icon(ui: &mut Ui, rect: Rect, color: Color32, filled: bool) {
327 let center = rect.center();
328 let size = rect.width() * 0.6;
329
330 let bell_top = Pos2::new(center.x, center.y - size * 0.3);
332 let bell_left = Pos2::new(center.x - size * 0.3, center.y + size * 0.1);
333 let bell_right = Pos2::new(center.x + size * 0.3, center.y + size * 0.1);
334 let bell_bottom_left = Pos2::new(center.x - size * 0.2, center.y + size * 0.2);
335 let bell_bottom_right = Pos2::new(center.x + size * 0.2, center.y + size * 0.2);
336
337 if filled {
338 let points = [bell_top, bell_left, bell_bottom_left, bell_bottom_right, bell_right];
339 ui.painter().add(egui::Shape::convex_polygon(points.to_vec(), color, Stroke::NONE));
340 } else {
341 ui.painter().line_segment([bell_top, bell_left], Stroke::new(2.0, color));
343 ui.painter().line_segment([bell_left, bell_bottom_left], Stroke::new(2.0, color));
344 ui.painter().line_segment([bell_bottom_left, bell_bottom_right], Stroke::new(2.0, color));
345 ui.painter().line_segment([bell_bottom_right, bell_right], Stroke::new(2.0, color));
346 ui.painter().line_segment([bell_right, bell_top], Stroke::new(2.0, color));
347 }
348
349 let clapper = Pos2::new(center.x, center.y + size * 0.35);
351 ui.painter().circle_filled(clapper, 1.5, color);
352}
353
354fn draw_default_icon(ui: &mut Ui, rect: Rect, color: Color32, filled: bool) {
355 let center = rect.center();
356 let radius = rect.width() * 0.3;
357
358 if filled {
359 ui.painter().circle_filled(center, radius, color);
360 } else {
361 ui.painter().circle_stroke(center, radius, Stroke::new(2.0, color));
362 }
363}
364
365pub fn icon(name: impl Into<String>) -> MaterialIcon {
366 MaterialIcon::new(name)
367}