use std::panic::Location;
use crate::anim::Timing;
use crate::cursor::Cursor;
use crate::event::UiEvent;
use crate::icons::icon;
use crate::metrics::MetricsRole;
use crate::style::StyleProfile;
use crate::tokens;
use crate::tree::*;
pub const SIZE: f32 = 16.0;
const CHECK_ICON_SIZE: f32 = 12.0;
#[track_caller]
pub fn checkbox(value: bool) -> El {
let (fill, stroke) = if value {
(tokens::PRIMARY, tokens::PRIMARY)
} else {
(tokens::CARD, tokens::INPUT)
};
let check_opacity = if value { 1.0 } else { 0.0 };
let check_scale = if value { 1.0 } else { 0.6 };
El::new(Kind::Custom("checkbox"))
.at_loc(Location::caller())
.style_profile(StyleProfile::Surface)
.metrics_role(MetricsRole::ChoiceControl)
.focusable()
.paint_overflow(Sides::all(tokens::RING_WIDTH))
.hit_overflow(Sides::all(tokens::HIT_OVERFLOW))
.cursor(Cursor::Pointer)
.axis(Axis::Overlay)
.align(Align::Center)
.justify(Justify::Center)
.default_width(Size::Fixed(SIZE))
.default_height(Size::Fixed(SIZE))
.default_radius(tokens::RADIUS_SM)
.fill(fill)
.stroke(stroke)
.animate(Timing::SPRING_STANDARD)
.child(
icon("check")
.icon_size(CHECK_ICON_SIZE)
.icon_stroke_width(2.5)
.color(tokens::PRIMARY_FOREGROUND)
.opacity(check_opacity)
.scale(check_scale)
.animate(Timing::SPRING_STANDARD),
)
}
pub fn apply_event(value: &mut bool, event: &UiEvent, key: &str) -> bool {
if event.is_click_or_activate(key) {
*value = !*value;
return true;
}
false
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn unchecked_paints_hollow_square_with_invisible_check_child() {
let c = checkbox(false);
assert_eq!(c.children.len(), 1, "check glyph stays in the tree");
assert_eq!(c.children[0].opacity, 0.0);
assert_eq!(c.fill, Some(tokens::CARD));
assert_eq!(c.stroke, Some(tokens::INPUT));
}
#[test]
fn checked_paints_primary_with_visible_check_glyph() {
let c = checkbox(true);
assert_eq!(c.fill, Some(tokens::PRIMARY));
assert_eq!(c.stroke, Some(tokens::PRIMARY));
assert_eq!(c.children.len(), 1);
let glyph = &c.children[0];
assert_eq!(
glyph.icon,
Some(crate::IconSource::Builtin(IconName::Check))
);
assert_eq!(glyph.opacity, 1.0);
}
#[test]
fn box_and_check_animate_so_state_changes_ease() {
let c = checkbox(false);
assert!(c.animate.is_some(), "outer box eases fill/stroke");
assert!(c.children[0].animate.is_some(), "check eases opacity/scale");
}
#[test]
fn checkbox_is_focusable_and_paints_focus_ring_outset() {
let c = checkbox(false);
assert!(c.focusable);
assert!(c.paint_overflow.left > 0.0);
assert_eq!(c.hit_overflow, Sides::all(tokens::HIT_OVERFLOW));
}
#[test]
fn checkbox_declares_pointer_cursor() {
assert_eq!(checkbox(false).cursor, Some(Cursor::Pointer));
}
#[test]
fn apply_event_toggles_on_click() {
let mut value = false;
assert!(apply_event(
&mut value,
&UiEvent::synthetic_click("agree"),
"agree"
));
assert!(value);
assert!(apply_event(
&mut value,
&UiEvent::synthetic_click("agree"),
"agree"
));
assert!(!value);
}
#[test]
fn apply_event_ignores_unrelated_keys() {
let mut value = false;
assert!(!apply_event(
&mut value,
&UiEvent::synthetic_click("other"),
"agree",
));
assert!(!value);
}
}