1use crate::*;
2
3#[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 #[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(&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 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 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}