use crate::draw::command::DrawCommand;
use crate::draw::renderer::Renderer;
use crate::ecs::{Entity, World};
use crate::event::gesture::GestureEvent;
use crate::types::Rect;
use crate::widget::dirty::Dirty;
use crate::widget::theme::{ColorToken, ThemedColor};
use crate::widget::view::{View, ViewCtx};
#[derive(Clone, Debug)]
pub enum CheckboxEvent {
Toggled { now: bool },
}
pub struct CheckboxHandler {
pub on_event: fn(&mut World, Entity, &CheckboxEvent) -> bool,
}
#[derive(crate::Component)]
pub struct Checkbox {
pub checked: bool,
pub checked_color: ThemedColor,
pub unchecked_color: ThemedColor,
}
impl Default for Checkbox {
fn default() -> Self {
Self {
checked: false,
checked_color: ThemedColor::Token(ColorToken::Primary),
unchecked_color: ThemedColor::Token(ColorToken::SurfaceVariant),
}
}
}
impl Checkbox {
pub fn new() -> Self {
Self::default()
}
pub fn with_checked_color(mut self, color: impl Into<ThemedColor>) -> Self {
self.checked_color = color.into();
self
}
pub fn with_unchecked_color(mut self, color: impl Into<ThemedColor>) -> Self {
self.unchecked_color = color.into();
self
}
pub fn toggle(&mut self) {
self.checked = !self.checked;
}
pub fn build() -> CheckboxBuilder {
CheckboxBuilder {
checkbox: Checkbox::new(),
style: None,
handler: None,
}
}
}
pub struct CheckboxBuilder {
checkbox: Checkbox,
style: Option<crate::widget::Style>,
handler: Option<CheckboxHandler>,
}
impl CheckboxBuilder {
pub fn style(mut self, style: crate::widget::Style) -> Self {
self.style = Some(style);
self
}
pub fn on_change(mut self, on_event: fn(&mut World, Entity, &CheckboxEvent) -> bool) -> Self {
self.handler = Some(CheckboxHandler { on_event });
self
}
pub fn checked(mut self, v: bool) -> Self {
self.checkbox.checked = v;
self
}
pub fn checked_color(mut self, color: impl Into<ThemedColor>) -> Self {
self.checkbox.checked_color = color.into();
self
}
pub fn unchecked_color(mut self, color: impl Into<ThemedColor>) -> Self {
self.checkbox.unchecked_color = color.into();
self
}
pub fn spawn(self, world: &mut World) -> Entity {
world.spawn(self)
}
}
impl crate::ecs::IntoBundle for CheckboxBuilder {
fn spawn_into(self, world: &mut World, entity: Entity) {
world.insert(entity, self.checkbox);
if let Some(style) = self.style {
world.insert(entity, style);
}
if let Some(handler) = self.handler {
world.insert(entity, handler);
}
}
}
fn checkbox_render(
renderer: &mut dyn Renderer,
world: &World,
entity: Entity,
rect: &Rect,
ctx: &mut ViewCtx,
) {
let Some(cb) = world.get::<Checkbox>(entity) else {
return;
};
let theme = ctx.theme(world);
let color = if cb.checked {
cb.checked_color.resolve_in(theme, ctx.state)
} else {
cb.unchecked_color.resolve_in(theme, ctx.state)
};
renderer.draw(
&DrawCommand::Fill {
area: *rect,
transform: ctx.transform,
quad: ctx.quad,
color,
radius: ctx.style.border_radius,
opa: 255,
},
ctx.clip,
);
ctx.bg_handled = true;
}
pub(crate) fn checkbox_handler(world: &mut World, entity: Entity, event: &GestureEvent) -> bool {
if let GestureEvent::Tap { .. } = event {
let now = if let Some(cb) = world.get_mut::<Checkbox>(entity) {
cb.toggle();
cb.checked
} else {
return false;
};
emit_checkbox_event(world, entity, &CheckboxEvent::Toggled { now });
world.insert(entity, Dirty);
return true;
}
false
}
fn emit_checkbox_event(world: &mut World, entity: Entity, event: &CheckboxEvent) {
let cb = world.get::<CheckboxHandler>(entity).map(|h| h.on_event);
if let Some(f) = cb {
f(world, entity, event);
}
}
fn checkbox_attach(world: &mut World, entity: Entity) {
let _ = world;
let _ = entity;
}
pub fn view() -> View {
View::new("Checkbox", 40, checkbox_render)
.with_filter::<Checkbox>()
.with_attach(checkbox_attach)
.with_internal_gesture(checkbox_handler)
}
#[cfg(test)]
mod tests {
use super::*;
fn h(_: &mut World, _: Entity, _: &CheckboxEvent) -> bool {
true
}
#[test]
fn build_spawns_checkbox_with_style_and_handler() {
let mut world = World::new();
let e = Checkbox::build()
.style(crate::widget::Style::default())
.on_change(h)
.spawn(&mut world);
assert!(world.has::<Checkbox>(e));
assert!(world.has::<crate::widget::Style>(e));
assert!(world.has::<CheckboxHandler>(e));
assert!(world.has::<crate::widget::Widget>(e));
}
#[test]
fn build_without_handler_omits_it() {
let mut world = World::new();
let e = Checkbox::build().spawn(&mut world);
assert!(world.has::<Checkbox>(e));
assert!(!world.has::<CheckboxHandler>(e));
assert!(!world.has::<crate::widget::Style>(e));
}
}