use egui::{Pos2, Response, Sense, Shape, Stroke, Ui, Vec2};
#[derive(Clone, Copy)]
pub enum Icon {
Rotate,
Flip,
Recenter,
Undo,
Redo,
Arrows,
Numbers,
Targets,
New,
Copy,
Export,
Import,
Play,
Pause,
Stop,
Sun,
Moon,
Info,
Menu,
Close,
}
pub fn icon_button(ui: &mut Ui, icon: Icon, selected: bool, enabled: bool) -> Response {
let size = Vec2::splat(28.0);
let sense = if enabled {
Sense::click()
} else {
Sense::hover()
};
let (rect, resp) = ui.allocate_exact_size(size, sense);
let visuals = ui.style().interact_selectable(&resp, selected);
let painter = ui.painter();
painter.rect(
rect,
5.0,
visuals.weak_bg_fill,
visuals.bg_stroke,
egui::StrokeKind::Inside,
);
let col = if enabled {
visuals.fg_stroke.color
} else {
ui.visuals().weak_text_color()
};
let r = rect.shrink(5.5);
let w = (r.width() * 0.13).max(1.8);
let stroke = Stroke::new(w, col);
let p = |fx: f32, fy: f32| Pos2::new(r.left() + fx * r.width(), r.top() + fy * r.height());
let painter = ui.painter();
let arc = |cx: f32, cy: f32, rad: f32, a0: f32, a1: f32| -> Vec<Pos2> {
(0..=24)
.map(|i| {
let t = a0 + (a1 - a0) * i as f32 / 24.0;
Pos2::new(
r.left() + (cx + rad * t.cos()) * r.width(),
r.top() + (cy + rad * t.sin()) * r.height(),
)
})
.collect()
};
let tri = |tip: Pos2, back: Pos2, half: f32| {
let d = (tip - back).normalized();
let n = Vec2::new(-d.y, d.x) * half;
Shape::convex_polygon(vec![tip, back + n, back - n], col, Stroke::NONE)
};
use std::f32::consts::PI;
match icon {
Icon::Rotate => {
let pts = arc(0.5, 0.5, 0.34, -0.35 * PI, 1.15 * PI);
painter.add(Shape::line(pts.clone(), stroke));
let start = pts[0];
let tang = (pts[0] - pts[1]).normalized();
painter.add(tri(
start + tang * r.width() * 0.16,
start,
r.width() * 0.13,
));
}
Icon::Flip => {
painter.line_segment([p(0.5, 0.12), p(0.5, 0.88)], stroke);
painter.add(tri(p(0.06, 0.5), p(0.36, 0.5), r.height() * 0.18));
painter.add(tri(p(0.94, 0.5), p(0.64, 0.5), r.height() * 0.18));
}
Icon::Recenter => {
painter.rect_stroke(
egui::Rect::from_min_max(p(0.12, 0.12), p(0.88, 0.88)),
2.0,
stroke,
egui::StrokeKind::Inside,
);
painter.circle_filled(p(0.5, 0.5), r.width() * 0.1, col);
}
Icon::Undo | Icon::Redo => {
let pts = arc(0.5, 0.45, 0.30, 0.0, -0.92 * PI);
let mirror = |p: Pos2| Pos2::new(2.0 * r.center().x - p.x, p.y);
let pts: Vec<Pos2> = match icon {
Icon::Redo => pts.iter().map(|&p| mirror(p)).collect(),
_ => pts,
};
painter.add(Shape::line(pts.clone(), stroke));
let end = *pts.last().unwrap();
let tang = (end - pts[pts.len() - 2]).normalized();
painter.add(tri(end + tang * r.width() * 0.34, end, r.width() * 0.26));
}
Icon::Arrows => {
painter.add(tri(p(0.82, 0.5), p(0.3, 0.5), r.height() * 0.3));
}
Icon::Numbers => {
painter.circle_stroke(p(0.5, 0.5), r.width() * 0.38, stroke);
painter.line_segment([p(0.5, 0.3), p(0.5, 0.7)], stroke);
painter.line_segment([p(0.4, 0.4), p(0.5, 0.3)], stroke);
}
Icon::Targets => {
painter.circle_stroke(p(0.5, 0.5), r.width() * 0.34, stroke);
painter.circle_filled(p(0.5, 0.5), r.width() * 0.1, col);
}
Icon::New => {
let plus = Stroke::new((w * 1.3).max(2.2), col);
painter.line_segment([p(0.5, 0.16), p(0.5, 0.84)], plus);
painter.line_segment([p(0.16, 0.5), p(0.84, 0.5)], plus);
}
Icon::Copy => {
let bg = visuals.weak_bg_fill;
painter.rect_stroke(
egui::Rect::from_min_max(p(0.18, 0.18), p(0.6, 0.6)),
2.0,
stroke,
egui::StrokeKind::Inside,
);
painter.rect_filled(
egui::Rect::from_min_max(p(0.4, 0.4), p(0.82, 0.82)),
2.0,
bg,
);
painter.rect_stroke(
egui::Rect::from_min_max(p(0.4, 0.4), p(0.82, 0.82)),
2.0,
stroke,
egui::StrokeKind::Inside,
);
}
Icon::Export => {
painter.line_segment([p(0.5, 0.66), p(0.5, 0.2)], stroke);
painter.add(tri(p(0.5, 0.12), p(0.5, 0.36), r.width() * 0.17));
painter.add(Shape::line(
vec![p(0.22, 0.6), p(0.22, 0.84), p(0.78, 0.84), p(0.78, 0.6)],
stroke,
));
}
Icon::Import => {
painter.line_segment([p(0.5, 0.14), p(0.5, 0.6)], stroke);
painter.add(tri(p(0.5, 0.68), p(0.5, 0.44), r.width() * 0.17));
painter.add(Shape::line(
vec![p(0.22, 0.6), p(0.22, 0.84), p(0.78, 0.84), p(0.78, 0.6)],
stroke,
));
}
Icon::Play => {
painter.add(Shape::convex_polygon(
vec![p(0.32, 0.18), p(0.32, 0.82), p(0.84, 0.5)],
col,
Stroke::NONE,
));
}
Icon::Pause => {
painter.rect_filled(
egui::Rect::from_min_max(p(0.3, 0.2), p(0.44, 0.8)),
1.0,
col,
);
painter.rect_filled(
egui::Rect::from_min_max(p(0.56, 0.2), p(0.7, 0.8)),
1.0,
col,
);
}
Icon::Sun => {
painter.circle_filled(p(0.5, 0.5), r.width() * 0.2, col);
let c = r.center();
for i in 0..8 {
let a = i as f32 * PI / 4.0;
let (cs, sn) = (a.cos(), a.sin());
painter.line_segment(
[
Pos2::new(c.x + cs * r.width() * 0.32, c.y + sn * r.width() * 0.32),
Pos2::new(c.x + cs * r.width() * 0.46, c.y + sn * r.width() * 0.46),
],
stroke,
);
}
}
Icon::Moon => {
let c = p(0.5, 0.5);
painter.circle_filled(c, r.width() * 0.34, col);
painter.circle_filled(
Pos2::new(c.x + r.width() * 0.17, c.y - r.width() * 0.07),
r.width() * 0.3,
visuals.weak_bg_fill,
);
}
Icon::Info => {
painter.circle_stroke(p(0.5, 0.5), r.width() * 0.42, stroke);
painter.circle_filled(p(0.5, 0.3), r.width() * 0.07, col);
painter.line_segment([p(0.5, 0.44), p(0.5, 0.72)], stroke);
}
Icon::Stop => {
painter.rect_filled(
egui::Rect::from_min_max(p(0.24, 0.24), p(0.76, 0.76)),
2.0,
col,
);
}
Icon::Menu => {
painter.line_segment([p(0.18, 0.3), p(0.82, 0.3)], stroke);
painter.line_segment([p(0.18, 0.5), p(0.82, 0.5)], stroke);
painter.line_segment([p(0.18, 0.7), p(0.82, 0.7)], stroke);
}
Icon::Close => {
painter.line_segment([p(0.26, 0.26), p(0.74, 0.74)], stroke);
painter.line_segment([p(0.74, 0.26), p(0.26, 0.74)], stroke);
}
}
resp
}