mirui 0.28.1

A lightweight, no_std ECS-driven UI framework for embedded, desktop, and WebAssembly
Documentation
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,
}

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;
    }
}

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)
}