1use crate::{
2 epaint, pos2, vec2, NumExt, Response, Sense, Shape, TextStyle, Ui, Vec2, Widget, WidgetInfo,
3 WidgetText, WidgetType,
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 text: WidgetText,
23 indeterminate: bool,
24}
25
26impl<'a> Checkbox<'a> {
27 pub fn new(checked: &'a mut bool, text: impl Into<WidgetText>) -> Self {
28 Checkbox {
29 checked,
30 text: text.into(),
31 indeterminate: false,
32 }
33 }
34
35 pub fn without_text(checked: &'a mut bool) -> Self {
36 Self::new(checked, WidgetText::default())
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 text,
55 indeterminate,
56 } = self;
57
58 let spacing = &ui.spacing();
59 let icon_width = spacing.icon_width;
60 let icon_spacing = spacing.icon_spacing;
61
62 let (galley, mut desired_size) = if text.is_empty() {
63 (None, vec2(icon_width, 0.0))
64 } else {
65 let total_extra = vec2(icon_width + icon_spacing, 0.0);
66
67 let wrap_width = ui.available_width() - total_extra.x;
68 let galley = text.into_galley(ui, None, wrap_width, TextStyle::Button);
69
70 let mut desired_size = total_extra + galley.size();
71 desired_size = desired_size.at_least(spacing.interact_size);
72
73 (Some(galley), desired_size)
74 };
75
76 desired_size = desired_size.at_least(Vec2::splat(spacing.interact_size.y));
77 desired_size.y = desired_size.y.max(icon_width);
78 let (rect, mut response) = ui.allocate_exact_size(desired_size, Sense::click());
79
80 if response.clicked() {
81 *checked = !*checked;
82 response.mark_changed();
83 }
84 response.widget_info(|| {
85 if indeterminate {
86 WidgetInfo::labeled(
87 WidgetType::Checkbox,
88 ui.is_enabled(),
89 galley.as_ref().map_or("", |x| x.text()),
90 )
91 } else {
92 WidgetInfo::selected(
93 WidgetType::Checkbox,
94 ui.is_enabled(),
95 *checked,
96 galley.as_ref().map_or("", |x| x.text()),
97 )
98 }
99 });
100
101 if ui.is_rect_visible(rect) {
102 let visuals = ui.style().interact(&response);
104 let (small_icon_rect, big_icon_rect) = ui.spacing().icon_rectangles(rect);
105 ui.painter().add(epaint::RectShape::new(
106 big_icon_rect.expand(visuals.expansion),
107 visuals.corner_radius,
108 visuals.bg_fill,
109 visuals.bg_stroke,
110 epaint::StrokeKind::Inside,
111 ));
112
113 if indeterminate {
114 ui.painter().add(Shape::hline(
116 small_icon_rect.x_range(),
117 small_icon_rect.center().y,
118 visuals.fg_stroke,
119 ));
120 } else if *checked {
121 ui.painter().add(Shape::line(
123 vec![
124 pos2(small_icon_rect.left(), small_icon_rect.center().y),
125 pos2(small_icon_rect.center().x, small_icon_rect.bottom()),
126 pos2(small_icon_rect.right(), small_icon_rect.top()),
127 ],
128 visuals.fg_stroke,
129 ));
130 }
131 if let Some(galley) = galley {
132 let text_pos = pos2(
133 rect.min.x + icon_width + icon_spacing,
134 rect.center().y - 0.5 * galley.size().y,
135 );
136 ui.painter().galley(text_pos, galley, visuals.text_color());
137 }
138 }
139
140 response
141 }
142}