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#[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 pub fn atoms(&self) -> &Atoms<'a> {
48 &self.atoms
49 }
50
51 #[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 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 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 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 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}