1use crate::{
2 Atom, AtomLayout, Atoms, Id, IntoAtoms, NumExt as _, Response, Sense, Shape, Ui, Vec2, Widget,
3 WidgetInfo, WidgetType, epaint, pos2,
4};
5
6#[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 #[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 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(&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 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 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}