Skip to main content

egui/widgets/
checkbox.rs

1use emath::Rect;
2
3use crate::{
4    Atom, AtomLayout, Atoms, Id, IntoAtoms, NumExt as _, Response, Sense, Shape, Ui, Vec2, Widget,
5    WidgetInfo, WidgetType, epaint, pos2,
6    widget_style::{CheckboxStyle, Classes, HasClasses},
7};
8
9// TODO(emilk): allow checkbox without a text label
10/// Boolean on/off control with text label.
11///
12/// Usually you'd use [`Ui::checkbox`] instead.
13///
14/// ```
15/// # egui::__run_test_ui(|ui| {
16/// # let mut my_bool = true;
17/// // These are equivalent:
18/// ui.checkbox(&mut my_bool, "Checked");
19/// ui.add(egui::Checkbox::new(&mut my_bool, "Checked"));
20/// # });
21/// ```
22#[must_use = "You should put this widget in a ui with `ui.add(widget);`"]
23pub struct Checkbox<'a> {
24    checked: &'a mut bool,
25    atoms: Atoms<'a>,
26    indeterminate: bool,
27    classes: Classes,
28}
29
30impl<'a> Checkbox<'a> {
31    pub fn new(checked: &'a mut bool, atoms: impl IntoAtoms<'a>) -> Self {
32        Checkbox {
33            checked,
34            atoms: atoms.into_atoms(),
35            indeterminate: false,
36            classes: Classes::default(),
37        }
38    }
39
40    pub fn without_text(checked: &'a mut bool) -> Self {
41        Self::new(checked, ())
42    }
43
44    /// Output the checkbox's [`Atoms`].
45    ///
46    /// This includes any images you have on the checkbox.
47    pub fn atoms(&self) -> &Atoms<'a> {
48        &self.atoms
49    }
50
51    /// Display an indeterminate state (neither checked nor unchecked)
52    ///
53    /// This only affects the checkbox's appearance. It will still toggle its boolean value when
54    /// clicked.
55    #[inline]
56    pub fn indeterminate(mut self, indeterminate: bool) -> Self {
57        self.indeterminate = indeterminate;
58        self
59    }
60}
61
62impl Widget for Checkbox<'_> {
63    fn ui(self, ui: &mut Ui) -> Response {
64        let Checkbox {
65            checked,
66            mut atoms,
67            indeterminate,
68            classes,
69        } = self;
70
71        // Get the widget style by reading the response from the previous pass
72        let id = ui.next_auto_id();
73        let response: Option<Response> = ui.ctx().read_response(id);
74        let state = response.map(|r| r.widget_state()).unwrap_or_default();
75
76        let CheckboxStyle {
77            check_size,
78            checkbox_frame,
79            checkbox_size,
80            frame,
81            check_stroke,
82            text_style,
83        } = ui.style().checkbox_style(&classes, state);
84
85        let mut min_size = Vec2::splat(ui.spacing().interact_size.y);
86        min_size.y = min_size.y.at_least(checkbox_size);
87
88        // In order to center the checkbox based on min_size we set the icon height to at least min_size.y
89        let mut icon_size = Vec2::splat(checkbox_size);
90        icon_size.y = icon_size.y.at_least(min_size.y);
91        let rect_id = Id::new("egui::checkbox");
92        atoms.push_left(Atom::custom(rect_id, icon_size));
93
94        let text = atoms.text().map(String::from);
95
96        let mut prepared = AtomLayout::new(atoms)
97            .sense(Sense::click())
98            .min_size(min_size)
99            .frame(frame)
100            .allocate(ui);
101
102        if prepared.response.clicked() {
103            *checked = !*checked;
104            prepared.response.mark_changed();
105        }
106        prepared.response.widget_info(|| {
107            if indeterminate {
108                WidgetInfo::labeled(
109                    WidgetType::Checkbox,
110                    ui.is_enabled(),
111                    text.as_deref().unwrap_or(""),
112                )
113            } else {
114                WidgetInfo::selected(
115                    WidgetType::Checkbox,
116                    ui.is_enabled(),
117                    *checked,
118                    text.as_deref().unwrap_or(""),
119                )
120            }
121        });
122
123        if ui.is_rect_visible(prepared.response.rect) {
124            prepared.fallback_text_color = text_style.color;
125            let response = prepared.paint(ui);
126
127            if let Some(rect) = response.rect(rect_id) {
128                let big_icon_rect = Rect::from_center_size(
129                    pos2(rect.left() + checkbox_size / 2.0, rect.center().y),
130                    Vec2::splat(checkbox_size),
131                );
132                let small_icon_rect =
133                    Rect::from_center_size(big_icon_rect.center(), Vec2::splat(check_size));
134                ui.painter().add(epaint::RectShape::new(
135                    big_icon_rect.expand(checkbox_frame.inner_margin.left.into()),
136                    checkbox_frame.corner_radius,
137                    checkbox_frame.fill,
138                    checkbox_frame.stroke,
139                    epaint::StrokeKind::Inside,
140                ));
141
142                if indeterminate {
143                    // Horizontal line:
144                    ui.painter().add(Shape::hline(
145                        small_icon_rect.x_range(),
146                        small_icon_rect.center().y,
147                        check_stroke,
148                    ));
149                } else if *checked {
150                    // Check mark:
151                    ui.painter().add(Shape::line(
152                        vec![
153                            pos2(small_icon_rect.left(), small_icon_rect.center().y),
154                            pos2(small_icon_rect.center().x, small_icon_rect.bottom()),
155                            pos2(small_icon_rect.right(), small_icon_rect.top()),
156                        ],
157                        check_stroke,
158                    ));
159                }
160            }
161            response.response
162        } else {
163            prepared.response
164        }
165    }
166}
167
168impl HasClasses for Checkbox<'_> {
169    fn classes(&self) -> &Classes {
170        &self.classes
171    }
172
173    fn classes_mut(&mut self) -> &mut Classes {
174        &mut self.classes
175    }
176}