use crate::get_global_color;
use egui::{self, Color32, Pos2, Rect, Response, Sense, Stroke, Ui, Vec2, Widget};
pub struct MaterialCheckbox<'a> {
checked: &'a mut bool,
text: String,
indeterminate: bool,
enabled: bool,
is_error: bool,
check_color: Option<Color32>,
fill_color: Option<Color32>,
border_width: f32,
}
impl<'a> MaterialCheckbox<'a> {
pub fn new(checked: &'a mut bool, text: impl Into<String>) -> Self {
Self {
checked,
text: text.into(),
indeterminate: false,
enabled: true,
is_error: false,
check_color: None,
fill_color: None,
border_width: 2.0,
}
}
pub fn indeterminate(mut self, indeterminate: bool) -> Self {
self.indeterminate = indeterminate;
self
}
pub fn enabled(mut self, enabled: bool) -> Self {
self.enabled = enabled;
self
}
pub fn is_error(mut self, is_error: bool) -> Self {
self.is_error = is_error;
self
}
pub fn check_color(mut self, color: Color32) -> Self {
self.check_color = Some(color);
self
}
pub fn fill_color(mut self, color: Color32) -> Self {
self.fill_color = Some(color);
self
}
pub fn border_width(mut self, width: f32) -> Self {
self.border_width = width;
self
}
}
impl<'a> Widget for MaterialCheckbox<'a> {
fn ui(self, ui: &mut Ui) -> Response {
let checkbox_size = 18.0;
let spacing = 4.0;
let text_width = if !self.text.is_empty() {
let font_id = ui.style().text_styles.get(&egui::TextStyle::Body)
.cloned()
.unwrap_or_else(egui::FontId::default);
let galley = ui.painter().layout_no_wrap(self.text.clone(), font_id, egui::Color32::WHITE);
galley.size().x
} else {
0.0
};
let desired_width = checkbox_size + spacing + text_width;
let desired_size = Vec2::new(desired_width, 24.0);
let (rect, mut response) = ui.allocate_exact_size(desired_size, Sense::click());
if response.clicked() && self.enabled {
if self.indeterminate {
*self.checked = true;
} else {
*self.checked = !*self.checked;
}
response.mark_changed();
}
let _visuals = ui.style().interact(&response);
let checkbox_rect = Rect::from_min_size(
Pos2::new(rect.min.x, rect.center().y - checkbox_size / 2.0),
Vec2::splat(checkbox_size),
);
let primary = self.fill_color.unwrap_or_else(|| get_global_color("primary")); let on_primary = self.check_color.unwrap_or_else(|| get_global_color("onPrimary")); let error = get_global_color("error"); let on_error = get_global_color("onError"); let on_surface = get_global_color("onSurface"); let on_surface_variant = get_global_color("onSurfaceVariant");
let (bg_color, border_color, check_color, border_width) = if !self.enabled {
let disabled_color = on_surface.linear_multiply(0.38);
if *self.checked || self.indeterminate {
(disabled_color, Color32::TRANSPARENT, disabled_color, 0.0)
} else {
(Color32::TRANSPARENT, disabled_color, disabled_color, self.border_width)
}
} else if self.is_error {
if *self.checked || self.indeterminate {
(error, Color32::TRANSPARENT, on_error, 0.0)
} else {
(Color32::TRANSPARENT, error, on_surface, self.border_width)
}
} else if *self.checked || self.indeterminate {
(primary, Color32::TRANSPARENT, on_primary, 0.0)
} else if response.hovered() {
(Color32::TRANSPARENT, on_surface, on_surface, self.border_width)
} else {
(Color32::TRANSPARENT, on_surface_variant, on_surface, self.border_width)
};
ui.painter().rect_filled(checkbox_rect, 2.0, bg_color);
if border_width > 0.0 {
ui.painter().rect_stroke(
checkbox_rect,
2.0,
Stroke::new(border_width, border_color),
egui::epaint::StrokeKind::Outside,
);
}
if *self.checked && !self.indeterminate {
let center = checkbox_rect.center();
let checkmark_size = checkbox_size * 0.6;
let start = Pos2::new(center.x - checkmark_size * 0.3, center.y);
let middle = Pos2::new(
center.x - checkmark_size * 0.1,
center.y + checkmark_size * 0.2,
);
let end = Pos2::new(
center.x + checkmark_size * 0.3,
center.y - checkmark_size * 0.2,
);
ui.painter()
.line_segment([start, middle], Stroke::new(2.0, check_color));
ui.painter()
.line_segment([middle, end], Stroke::new(2.0, check_color));
} else if self.indeterminate {
let center = checkbox_rect.center();
let line_width = checkbox_size * 0.5;
let start = Pos2::new(center.x - line_width / 2.0, center.y);
let end = Pos2::new(center.x + line_width / 2.0, center.y);
ui.painter()
.line_segment([start, end], Stroke::new(2.0, check_color));
}
if !self.text.is_empty() {
let text_pos = Pos2::new(checkbox_rect.max.x + 4.0, rect.center().y);
let text_color = if self.enabled {
on_surface
} else {
on_surface.linear_multiply(0.38)
};
ui.painter().text(
text_pos,
egui::Align2::LEFT_CENTER,
&self.text,
egui::FontId::default(),
text_color,
);
}
if self.enabled {
let overlay_rect = Rect::from_center_size(checkbox_rect.center(), Vec2::splat(40.0));
let overlay_color = if response.is_pointer_button_down_on() {
if self.is_error {
error.linear_multiply(0.10)
} else if *self.checked || self.indeterminate {
primary.linear_multiply(0.10)
} else {
on_surface.linear_multiply(0.10)
}
} else if response.hovered() {
if self.is_error {
error.linear_multiply(0.08)
} else if *self.checked || self.indeterminate {
primary.linear_multiply(0.08)
} else {
on_surface.linear_multiply(0.08)
}
} else if response.has_focus() {
if self.is_error {
error.linear_multiply(0.10)
} else if *self.checked || self.indeterminate {
primary.linear_multiply(0.10)
} else {
on_surface.linear_multiply(0.10)
}
} else {
Color32::TRANSPARENT
};
if overlay_color != Color32::TRANSPARENT {
ui.painter().circle_filled(
overlay_rect.center(),
overlay_rect.width() / 2.0,
overlay_color,
);
}
}
response
}
}
pub fn checkbox(checked: &mut bool, text: impl Into<String>) -> MaterialCheckbox<'_> {
MaterialCheckbox::new(checked, text)
}