egui_material3/
checkbox.rs1use eframe::egui::{self, Color32, Pos2, Rect, Response, Sense, Stroke, Ui, Vec2, Widget};
2use crate::get_global_color;
3
4pub struct MaterialCheckbox<'a> {
35 checked: &'a mut bool,
37 text: String,
39 indeterminate: bool,
41 enabled: bool,
43}
44
45impl<'a> MaterialCheckbox<'a> {
46 pub fn new(checked: &'a mut bool, text: impl Into<String>) -> Self {
55 Self {
56 checked,
57 text: text.into(),
58 indeterminate: false,
59 enabled: true,
60 }
61 }
62
63 pub fn indeterminate(mut self, indeterminate: bool) -> Self {
71 self.indeterminate = indeterminate;
72 self
73 }
74
75 pub fn enabled(mut self, enabled: bool) -> Self {
82 self.enabled = enabled;
83 self
84 }
85}
86
87impl<'a> Widget for MaterialCheckbox<'a> {
88 fn ui(self, ui: &mut Ui) -> Response {
89 let desired_size = Vec2::new(
90 ui.available_width().min(300.0),
91 24.0,
92 );
93
94 let (rect, mut response) = ui.allocate_exact_size(desired_size, Sense::click());
95
96 if response.clicked() && self.enabled {
97 if self.indeterminate {
98 *self.checked = true;
99 } else {
100 *self.checked = !*self.checked;
101 }
102 response.mark_changed();
103 }
104
105 let _visuals = ui.style().interact(&response);
106 let checkbox_size = 18.0;
107 let checkbox_rect = Rect::from_min_size(
108 Pos2::new(rect.min.x, rect.center().y - checkbox_size / 2.0),
109 Vec2::splat(checkbox_size),
110 );
111
112 let primary_color = get_global_color("primary");
114 let on_surface = get_global_color("onSurface");
115 let surface_variant = get_global_color("surfaceVariant");
116 let outline = get_global_color("outline");
117
118 let (bg_color, border_color, check_color) = if !self.enabled {
119 let disabled_color = on_surface.gamma_multiply(0.38);
121 (
122 Color32::TRANSPARENT,
123 disabled_color,
124 disabled_color,
125 )
126 } else if *self.checked || self.indeterminate {
127 (primary_color, primary_color, get_global_color("onPrimary"))
128 } else if response.hovered() {
129 (surface_variant, outline, on_surface)
130 } else {
131 (Color32::TRANSPARENT, outline, on_surface)
132 };
133
134 ui.painter().rect_filled(
136 checkbox_rect,
137 2.0,
138 bg_color,
139 );
140
141 ui.painter().rect_stroke(
143 checkbox_rect,
144 2.0,
145 Stroke::new(2.0, border_color),
146 egui::epaint::StrokeKind::Outside,
147 );
148
149 if *self.checked && !self.indeterminate {
151 let center = checkbox_rect.center();
153 let checkmark_size = checkbox_size * 0.6;
154
155 let start = Pos2::new(
156 center.x - checkmark_size * 0.3,
157 center.y,
158 );
159 let middle = Pos2::new(
160 center.x - checkmark_size * 0.1,
161 center.y + checkmark_size * 0.2,
162 );
163 let end = Pos2::new(
164 center.x + checkmark_size * 0.3,
165 center.y - checkmark_size * 0.2,
166 );
167
168 ui.painter().line_segment([start, middle], Stroke::new(2.0, check_color));
169 ui.painter().line_segment([middle, end], Stroke::new(2.0, check_color));
170 } else if self.indeterminate {
171 let center = checkbox_rect.center();
173 let line_width = checkbox_size * 0.5;
174 let start = Pos2::new(center.x - line_width / 2.0, center.y);
175 let end = Pos2::new(center.x + line_width / 2.0, center.y);
176
177 ui.painter().line_segment([start, end], Stroke::new(2.0, check_color));
178 }
179
180 if !self.text.is_empty() {
182 let text_pos = Pos2::new(
183 checkbox_rect.max.x + 8.0,
184 rect.center().y,
185 );
186
187 let text_color = if self.enabled {
188 on_surface
189 } else {
190 on_surface.gamma_multiply(0.38)
191 };
192
193 ui.painter().text(
194 text_pos,
195 egui::Align2::LEFT_CENTER,
196 &self.text,
197 egui::FontId::default(),
198 text_color,
199 );
200 }
201
202 if response.hovered() && self.enabled {
204 let ripple_rect = Rect::from_center_size(checkbox_rect.center(), Vec2::splat(40.0));
205 let ripple_color = if *self.checked || self.indeterminate {
206 Color32::from_rgba_premultiplied(primary_color.r(), primary_color.g(), primary_color.b(), 20)
207 } else {
208 Color32::from_rgba_premultiplied(on_surface.r(), on_surface.g(), on_surface.b(), 20)
209 };
210
211 ui.painter().circle_filled(
212 ripple_rect.center(),
213 ripple_rect.width() / 2.0,
214 ripple_color,
215 );
216 }
217
218 response
219 }
220}
221
222pub fn checkbox(checked: &mut bool, text: impl Into<String>) -> MaterialCheckbox<'_> {
223 MaterialCheckbox::new(checked, text)
224}