egui_material3/
icon.rs

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        // Draw a simple icon representation (can be replaced with actual icon rendering)
46        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    // House shape
73    let points = [
74        Pos2::new(center.x, center.y - size * 0.3),           // top
75        Pos2::new(center.x - size * 0.3, center.y),          // left
76        Pos2::new(center.x - size * 0.2, center.y),          // left wall
77        Pos2::new(center.x - size * 0.2, center.y + size * 0.3), // left bottom
78        Pos2::new(center.x + size * 0.2, center.y + size * 0.3), // right bottom
79        Pos2::new(center.x + size * 0.2, center.y),          // right wall
80        Pos2::new(center.x + size * 0.3, center.y),          // right
81    ];
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    // Circle
98    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    // Handle
105    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    // Draw gear shape (simplified)
115    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    // Center circle
132    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    // Three horizontal lines
146    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    // X shape
159    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    // Checkmark
175    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    // Plus shape
188    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    // Minus shape
204    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    // Arrow pointing left
215    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    // Arrow pointing right
230    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    // Heart shape using two circles and a triangle
245    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        // Fill heart shape
251        ui.painter().circle_filled(left_circle, radius, color);
252        ui.painter().circle_filled(right_circle, radius, color);
253        
254        // Triangle bottom part
255        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        // Outline heart
263        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    // 5-pointed star
283    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    // Bookmark shape
308    let points = [
309        Pos2::new(center.x - size * 0.3, center.y - size * 0.4), // top left
310        Pos2::new(center.x + size * 0.3, center.y - size * 0.4), // top right
311        Pos2::new(center.x + size * 0.3, center.y + size * 0.4), // bottom right
312        Pos2::new(center.x, center.y + size * 0.2),              // bottom center (notch)
313        Pos2::new(center.x - size * 0.3, center.y + size * 0.4), // bottom left
314    ];
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    // Bell shape
331    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        // Draw bell outline
342        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    // Small clapper at bottom
350    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}