egui/widgets/
checkbox.rs

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