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, widget_style::CheckboxStyle,
6};
7
8#[must_use = "You should put this widget in a ui with `ui.add(widget);`"]
22pub struct Checkbox<'a> {
23 checked: &'a mut bool,
24 atoms: Atoms<'a>,
25 indeterminate: bool,
26}
27
28impl<'a> Checkbox<'a> {
29 pub fn new(checked: &'a mut bool, atoms: impl IntoAtoms<'a>) -> Self {
30 Checkbox {
31 checked,
32 atoms: atoms.into_atoms(),
33 indeterminate: false,
34 }
35 }
36
37 pub fn without_text(checked: &'a mut bool) -> Self {
38 Self::new(checked, ())
39 }
40
41 #[inline]
46 pub fn indeterminate(mut self, indeterminate: bool) -> Self {
47 self.indeterminate = indeterminate;
48 self
49 }
50}
51
52impl Widget for Checkbox<'_> {
53 fn ui(self, ui: &mut Ui) -> Response {
54 let Checkbox {
55 checked,
56 mut atoms,
57 indeterminate,
58 } = self;
59
60 let id = ui.next_auto_id();
62 let response: Option<Response> = ui.ctx().read_response(id);
63 let state = response.map(|r| r.widget_state()).unwrap_or_default();
64
65 let CheckboxStyle {
66 check_size,
67 checkbox_frame,
68 checkbox_size,
69 frame,
70 check_stroke,
71 text_style,
72 } = ui.style().checkbox_style(state);
73
74 let mut min_size = Vec2::splat(ui.spacing().interact_size.y);
75 min_size.y = min_size.y.at_least(checkbox_size);
76
77 let mut icon_size = Vec2::splat(checkbox_size);
79 icon_size.y = icon_size.y.at_least(min_size.y);
80 let rect_id = Id::new("egui::checkbox");
81 atoms.push_left(Atom::custom(rect_id, icon_size));
82
83 let text = atoms.text().map(String::from);
84
85 let mut prepared = AtomLayout::new(atoms)
86 .sense(Sense::click())
87 .min_size(min_size)
88 .frame(frame)
89 .allocate(ui);
90
91 if prepared.response.clicked() {
92 *checked = !*checked;
93 prepared.response.mark_changed();
94 }
95 prepared.response.widget_info(|| {
96 if indeterminate {
97 WidgetInfo::labeled(
98 WidgetType::Checkbox,
99 ui.is_enabled(),
100 text.as_deref().unwrap_or(""),
101 )
102 } else {
103 WidgetInfo::selected(
104 WidgetType::Checkbox,
105 ui.is_enabled(),
106 *checked,
107 text.as_deref().unwrap_or(""),
108 )
109 }
110 });
111
112 if ui.is_rect_visible(prepared.response.rect) {
113 prepared.fallback_text_color = text_style.color;
114 let response = prepared.paint(ui);
115
116 if let Some(rect) = response.rect(rect_id) {
117 let big_icon_rect = Rect::from_center_size(
118 pos2(rect.left() + checkbox_size / 2.0, rect.center().y),
119 Vec2::splat(checkbox_size),
120 );
121 let small_icon_rect =
122 Rect::from_center_size(big_icon_rect.center(), Vec2::splat(check_size));
123 ui.painter().add(epaint::RectShape::new(
124 big_icon_rect.expand(checkbox_frame.inner_margin.left.into()),
125 checkbox_frame.corner_radius,
126 checkbox_frame.fill,
127 checkbox_frame.stroke,
128 epaint::StrokeKind::Inside,
129 ));
130
131 if indeterminate {
132 ui.painter().add(Shape::hline(
134 small_icon_rect.x_range(),
135 small_icon_rect.center().y,
136 check_stroke,
137 ));
138 } else if *checked {
139 ui.painter().add(Shape::line(
141 vec![
142 pos2(small_icon_rect.left(), small_icon_rect.center().y),
143 pos2(small_icon_rect.center().x, small_icon_rect.bottom()),
144 pos2(small_icon_rect.right(), small_icon_rect.top()),
145 ],
146 check_stroke,
147 ));
148 }
149 }
150 response.response
151 } else {
152 prepared.response
153 }
154 }
155}