use emath::Rect;
use crate::{
Atom, Atoms, IdSalt, IntoAtoms, NumExt as _, Response, Role, Sense, Shape, Ui, Vec2, Widget,
WidgetAtom, WidgetInfo,
class::{Classes, HasClasses},
epaint, pos2,
widget_style::CheckboxStyle,
};
#[must_use = "You should put this widget in a ui with `ui.add(widget);`"]
pub struct Checkbox<'a> {
checked: &'a mut bool,
atoms: Atoms<'a>,
indeterminate: bool,
classes: Classes,
}
impl<'a> Checkbox<'a> {
pub fn new(checked: &'a mut bool, atoms: impl IntoAtoms<'a>) -> Self {
Checkbox {
checked,
atoms: atoms.into_atoms(),
indeterminate: false,
classes: Classes::default(),
}
}
pub fn without_text(checked: &'a mut bool) -> Self {
Self::new(checked, ())
}
pub fn atoms(&self) -> &Atoms<'a> {
&self.atoms
}
#[inline]
pub fn indeterminate(mut self, indeterminate: bool) -> Self {
self.indeterminate = indeterminate;
self
}
}
impl Widget for Checkbox<'_> {
fn ui(self, ui: &mut Ui) -> Response {
let Checkbox {
checked,
mut atoms,
indeterminate,
classes,
} = self;
let id = ui.next_auto_id();
let CheckboxStyle {
atom_layout,
check_size,
checkbox_frame,
checkbox_size,
check_stroke,
} = ui.widget_style(id, &classes);
let min_size = atom_layout.min_size.at_least(Vec2::new(0.0, checkbox_size));
let mut icon_size = Vec2::splat(checkbox_size);
icon_size.y = icon_size.y.at_least(min_size.y);
let rect_id = IdSalt::new("checkbox_icon");
atoms.push_left(Atom::custom(rect_id, icon_size));
let text = atoms.text().map(String::from);
let layout = WidgetAtom::new(atoms).sense(Sense::click());
let mut prepared = atom_layout.apply(layout).min_size(min_size).allocate(ui);
if prepared.response.clicked() {
*checked = !*checked;
prepared.response.mark_changed();
}
prepared.response.widget_info(|| {
if indeterminate {
WidgetInfo::labeled(
Role::CheckBox,
ui.is_enabled(),
text.as_deref().unwrap_or(""),
)
} else {
WidgetInfo::selected(
Role::CheckBox,
ui.is_enabled(),
*checked,
text.as_deref().unwrap_or(""),
)
}
});
if ui.is_rect_visible(prepared.response.rect) {
let response = prepared.paint(ui);
if let Some(rect) = response.rect(rect_id) {
let big_icon_rect = Rect::from_center_size(
pos2(rect.left() + checkbox_size * 0.5, rect.center().y),
Vec2::splat(checkbox_size),
);
let small_icon_rect =
Rect::from_center_size(big_icon_rect.center(), Vec2::splat(check_size));
ui.painter().add(epaint::RectShape::new(
big_icon_rect.expand(checkbox_frame.inner_margin.left.into()),
checkbox_frame.corner_radius,
checkbox_frame.fill,
checkbox_frame.stroke,
epaint::StrokeKind::Inside,
));
if indeterminate {
ui.painter().add(Shape::hline(
small_icon_rect.x_range(),
small_icon_rect.center().y,
check_stroke,
));
} else if *checked {
ui.painter().add(Shape::line(
vec![
pos2(small_icon_rect.left(), small_icon_rect.center().y),
pos2(small_icon_rect.center().x, small_icon_rect.bottom()),
pos2(small_icon_rect.right(), small_icon_rect.top()),
],
check_stroke,
));
}
}
response.response
} else {
prepared.response
}
}
}
impl HasClasses for Checkbox<'_> {
fn classes(&self) -> &Classes {
&self.classes
}
fn classes_mut(&mut self) -> &mut Classes {
&mut self.classes
}
}