1use egui::{
4 vec2, Color32, CornerRadius, FontSelection, Response, Sense, Stroke, Ui, Vec2, Widget,
5 WidgetInfo, WidgetText, WidgetType,
6};
7
8use crate::theme::Theme;
9
10#[must_use = "Add this widget with `ui.add(...)`."]
20pub struct Checkbox<'a> {
21 checked: &'a mut bool,
22 label: WidgetText,
23}
24
25impl<'a> std::fmt::Debug for Checkbox<'a> {
26 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
27 f.debug_struct("Checkbox")
28 .field("checked", &*self.checked)
29 .field("label", &self.label.text())
30 .finish()
31 }
32}
33
34impl<'a> Checkbox<'a> {
35 pub fn new(checked: &'a mut bool, label: impl Into<WidgetText>) -> Self {
37 Self {
38 checked,
39 label: label.into(),
40 }
41 }
42}
43
44impl<'a> Widget for Checkbox<'a> {
45 fn ui(self, ui: &mut Ui) -> Response {
46 let theme = Theme::current(ui.ctx());
47 let p = &theme.palette;
48 let t = &theme.typography;
49
50 let box_size = 14.0;
51 let gap = 6.0;
52
53 let galley = egui::WidgetText::from(
54 egui::RichText::new(self.label.text())
55 .color(p.text)
56 .size(t.body),
57 )
58 .into_galley(
59 ui,
60 Some(egui::TextWrapMode::Extend),
61 ui.available_width(),
62 FontSelection::FontId(egui::FontId::proportional(t.body)),
63 );
64
65 let text_size = galley.size();
66 let desired = vec2(box_size + gap + text_size.x, box_size.max(text_size.y));
67 let (rect, mut response) = ui.allocate_exact_size(desired, Sense::click());
68
69 if response.clicked() {
70 *self.checked = !*self.checked;
71 response.mark_changed();
72 }
73
74 if ui.is_rect_visible(rect) {
75 let checked = *self.checked;
76 let is_hovered = response.hovered();
77
78 let box_rect = egui::Rect::from_min_size(
79 egui::pos2(rect.min.x, rect.center().y - box_size * 0.5),
80 Vec2::splat(box_size),
81 );
82
83 let (fill, stroke) = if checked {
84 (p.sky, Stroke::new(1.0, p.sky))
85 } else if is_hovered {
86 (p.input_bg, Stroke::new(1.0, p.sky))
87 } else {
88 (p.input_bg, Stroke::new(1.0, p.border))
89 };
90
91 ui.painter().rect(
92 box_rect,
93 CornerRadius::same(3),
94 fill,
95 stroke,
96 egui::StrokeKind::Inside,
97 );
98
99 if checked {
100 let m = box_rect.min;
102 let s = box_size;
103 let a = egui::pos2(m.x + s * 0.22, m.y + s * 0.52);
104 let b = egui::pos2(m.x + s * 0.44, m.y + s * 0.72);
105 let c = egui::pos2(m.x + s * 0.78, m.y + s * 0.30);
106 let stroke = Stroke::new(1.6, Color32::WHITE);
107 ui.painter().line_segment([a, b], stroke);
108 ui.painter().line_segment([b, c], stroke);
109 }
110
111 let text_pos = egui::pos2(box_rect.max.x + gap, rect.center().y - text_size.y * 0.5);
112 ui.painter().galley(text_pos, galley, p.text);
113 }
114
115 response.widget_info(|| WidgetInfo::labeled(WidgetType::Checkbox, true, self.label.text()));
116 response
117 }
118}