use accesskit::{Node as AccessKitNode, Toggled};
use glam::Vec2;
use martensite_core::widget::{LayoutConstraints, LayoutContext, Widget};
use martensite_core::Rect;
pub struct CheckBox {
pub label: String,
pub checked: bool,
pub enabled: bool,
cached_bounds: Rect,
}
impl CheckBox {
pub fn new(label: impl Into<String>) -> Self {
Self {
label: label.into(),
checked: false,
enabled: true,
cached_bounds: Rect::default(),
}
}
#[inline]
pub fn checked(mut self, checked: bool) -> Self {
self.checked = checked;
self
}
#[inline]
pub fn enabled(mut self, enabled: bool) -> Self {
self.enabled = enabled;
self
}
#[inline]
pub fn toggle(&mut self) {
self.checked = !self.checked;
}
#[inline]
pub fn cached_bounds(&self) -> Rect {
self.cached_bounds
}
}
impl Widget for CheckBox {
fn measure(&mut self, _cx: &mut LayoutContext, constraints: LayoutConstraints) -> Vec2 {
let min_w = 20.0_f32.min(constraints.max_size.x.max(0.0));
let min_h = 20.0_f32.min(constraints.max_size.y.max(0.0));
Vec2::new(min_w, min_h)
}
fn layout(&mut self, _cx: &mut LayoutContext, bounds: Rect) {
self.cached_bounds = bounds;
}
fn accessibility(&self, node: &mut AccessKitNode) {
node.set_role(accesskit::Role::CheckBox);
node.set_label(self.label.as_str());
node.add_action(accesskit::Action::Click);
node.add_action(accesskit::Action::Focus);
node.set_toggled(if self.checked {
Toggled::True
} else {
Toggled::False
});
if !self.enabled {
node.set_disabled();
}
}
}
impl std::fmt::Debug for CheckBox {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.debug_struct("CheckBox")
.field("label", &self.label)
.field("checked", &self.checked)
.field("enabled", &self.enabled)
.finish()
}
}
impl Clone for CheckBox {
fn clone(&self) -> Self {
Self {
label: self.label.clone(),
checked: self.checked,
enabled: self.enabled,
cached_bounds: self.cached_bounds,
}
}
}
#[cfg(test)]
mod tests {
use super::*;
use martensite_core::HotNode;
fn make_cx(hot: &mut HotNode) -> LayoutContext<'_> {
LayoutContext { hot }
}
#[test]
fn checkbox_new() {
let cb = CheckBox::new("Accept");
assert_eq!(cb.label, "Accept");
assert!(!cb.checked);
assert!(cb.enabled);
}
#[test]
fn checkbox_builder_methods() {
let cb = CheckBox::new("Agree").checked(true).enabled(false);
assert!(cb.checked);
assert!(!cb.enabled);
}
#[test]
fn checkbox_toggle() {
let mut cb = CheckBox::new("Test");
assert!(!cb.checked);
cb.toggle();
assert!(cb.checked);
cb.toggle();
assert!(!cb.checked);
}
#[test]
fn checkbox_measure_returns_size() {
let mut hot = HotNode::default();
let mut cx = make_cx(&mut hot);
let mut cb = CheckBox::new("Test");
let size = cb.measure(
&mut cx,
LayoutConstraints {
min_size: Vec2::ZERO,
max_size: Vec2::new(200.0, 100.0),
},
);
assert!(size.x >= 0.0 && size.y >= 0.0);
}
#[test]
fn checkbox_layout_sets_bounds() {
let mut hot = HotNode::default();
let mut cx = make_cx(&mut hot);
let mut cb = CheckBox::new("Test");
let bounds = Rect::new(0.0, 0.0, 20.0, 20.0);
cb.layout(&mut cx, bounds);
assert_eq!(cb.cached_bounds(), bounds);
}
#[test]
fn checkbox_accessibility_sets_role_label_toggled() {
let cb = CheckBox::new("Accept").checked(true);
let mut node = accesskit::Node::new(accesskit::Role::Unknown);
cb.accessibility(&mut node);
assert_eq!(node.role(), accesskit::Role::CheckBox);
assert_eq!(node.label(), Some("Accept"));
assert_eq!(node.toggled(), Some(Toggled::True));
assert!(node.supports_action(accesskit::Action::Click));
assert!(node.supports_action(accesskit::Action::Focus));
}
#[test]
fn checkbox_accessibility_unchecked_state() {
let cb = CheckBox::new("Decline");
let mut node = accesskit::Node::new(accesskit::Role::Unknown);
cb.accessibility(&mut node);
assert_eq!(node.toggled(), Some(Toggled::False));
}
#[test]
fn checkbox_accessibility_disabled() {
let cb = CheckBox::new("Locked").enabled(false);
let mut node = accesskit::Node::new(accesskit::Role::Unknown);
cb.accessibility(&mut node);
assert!(node.is_disabled());
}
#[test]
fn checkbox_clone() {
let cb = CheckBox::new("Test").checked(true);
let cloned = cb.clone();
assert_eq!(cb.label, cloned.label);
assert_eq!(cb.checked, cloned.checked);
}
#[test]
fn checkbox_debug_format() {
let cb = CheckBox::new("Test");
let debug = format!("{:?}", cb);
assert!(debug.contains("CheckBox"));
assert!(debug.contains("Test"));
}
}