egui/widgets/
checkbox.rs

1use crate::{
2    Atom, AtomLayout, Atoms, Id, IntoAtoms, NumExt as _, Response, Sense, Shape, Ui, Vec2, Widget,
3    WidgetInfo, WidgetType, epaint, pos2,
4};
5
6// TODO(emilk): allow checkbox without a text label
7/// Boolean on/off control with text label.
8///
9/// Usually you'd use [`Ui::checkbox`] instead.
10///
11/// ```
12/// # egui::__run_test_ui(|ui| {
13/// # let mut my_bool = true;
14/// // These are equivalent:
15/// ui.checkbox(&mut my_bool, "Checked");
16/// ui.add(egui::Checkbox::new(&mut my_bool, "Checked"));
17/// # });
18/// ```
19#[must_use = "You should put this widget in a ui with `ui.add(widget);`"]
20pub struct Checkbox<'a> {
21    checked: &'a mut bool,
22    atoms: Atoms<'a>,
23    indeterminate: bool,
24}
25
26impl<'a> Checkbox<'a> {
27    pub fn new(checked: &'a mut bool, atoms: impl IntoAtoms<'a>) -> Self {
28        Checkbox {
29            checked,
30            atoms: atoms.into_atoms(),
31            indeterminate: false,
32        }
33    }
34
35    pub fn without_text(checked: &'a mut bool) -> Self {
36        Self::new(checked, ())
37    }
38
39    /// Display an indeterminate state (neither checked nor unchecked)
40    ///
41    /// This only affects the checkbox's appearance. It will still toggle its boolean value when
42    /// clicked.
43    #[inline]
44    pub fn indeterminate(mut self, indeterminate: bool) -> Self {
45        self.indeterminate = indeterminate;
46        self
47    }
48}
49
50impl Widget for Checkbox<'_> {
51    fn ui(self, ui: &mut Ui) -> Response {
52        let Checkbox {
53            checked,
54            mut atoms,
55            indeterminate,
56        } = self;
57
58        let spacing = &ui.spacing();
59        let icon_width = spacing.icon_width;
60
61        let mut min_size = Vec2::splat(spacing.interact_size.y);
62        min_size.y = min_size.y.at_least(icon_width);
63
64        // In order to center the checkbox based on min_size we set the icon height to at least min_size.y
65        let mut icon_size = Vec2::splat(icon_width);
66        icon_size.y = icon_size.y.at_least(min_size.y);
67        let rect_id = Id::new("egui::checkbox");
68        atoms.push_left(Atom::custom(rect_id, icon_size));
69
70        let text = atoms.text().map(String::from);
71
72        let mut prepared = AtomLayout::new(atoms)
73            .sense(Sense::click())
74            .min_size(min_size)
75            .allocate(ui);
76
77        if prepared.response.clicked() {
78            *checked = !*checked;
79            prepared.response.mark_changed();
80        }
81        prepared.response.widget_info(|| {
82            if indeterminate {
83                WidgetInfo::labeled(
84                    WidgetType::Checkbox,
85                    ui.is_enabled(),
86                    text.as_deref().unwrap_or(""),
87                )
88            } else {
89                WidgetInfo::selected(
90                    WidgetType::Checkbox,
91                    ui.is_enabled(),
92                    *checked,
93                    text.as_deref().unwrap_or(""),
94                )
95            }
96        });
97
98        if ui.is_rect_visible(prepared.response.rect) {
99            // let visuals = ui.style().interact_selectable(&response, *checked); // too colorful
100            let visuals = *ui.style().interact(&prepared.response);
101            prepared.fallback_text_color = visuals.text_color();
102            let response = prepared.paint(ui);
103
104            if let Some(rect) = response.rect(rect_id) {
105                let (small_icon_rect, big_icon_rect) = ui.spacing().icon_rectangles(rect);
106                ui.painter().add(epaint::RectShape::new(
107                    big_icon_rect.expand(visuals.expansion),
108                    visuals.corner_radius,
109                    visuals.bg_fill,
110                    visuals.bg_stroke,
111                    epaint::StrokeKind::Inside,
112                ));
113
114                if indeterminate {
115                    // Horizontal line:
116                    ui.painter().add(Shape::hline(
117                        small_icon_rect.x_range(),
118                        small_icon_rect.center().y,
119                        visuals.fg_stroke,
120                    ));
121                } else if *checked {
122                    // Check mark:
123                    ui.painter().add(Shape::line(
124                        vec![
125                            pos2(small_icon_rect.left(), small_icon_rect.center().y),
126                            pos2(small_icon_rect.center().x, small_icon_rect.bottom()),
127                            pos2(small_icon_rect.right(), small_icon_rect.top()),
128                        ],
129                        visuals.fg_stroke,
130                    ));
131                }
132            }
133            response.response
134        } else {
135            prepared.response
136        }
137    }
138}