use alloc::boxed::Box;
use alloc::vec::Vec;
use embedded_graphics::geometry::Point;
use embedded_graphics::primitives::Rectangle;
use crate::color::UiColor;
use crate::el::{El, ElId};
use crate::event::{Capture, CommonEvent, Event, Propagate};
use crate::icons::IconKind;
use crate::layout::{Layout, Viewport};
use crate::padding::Padding;
use crate::size::{Length, Size};
use crate::state::{State, StateNode, StateTag};
use crate::style::component_style;
use crate::ui::UiCtx;
use crate::widget::Widget;
use crate::{block::Border, render::Renderer};
use super::icon::Icon;
#[derive(Clone, Copy)]
pub struct CheckboxState {
is_pressed: bool,
is_checked: bool,
}
impl Default for CheckboxState {
fn default() -> Self {
Self { is_pressed: false, is_checked: false }
}
}
#[derive(Clone, Copy)]
pub enum CheckboxStatus {
Normal,
Pressed,
Focused,
Checked,
}
component_style! {
pub CheckboxStyle: CheckboxStyler(CheckboxStatus) {
background: background,
border: border,
}
}
pub struct Checkbox<'a, Message, R, S>
where
R: Renderer,
S: CheckboxStyler<R::Color>,
{
id: ElId,
check_icon: Icon<R>,
size: Size<Length>,
on_change: Box<dyn Fn(bool) -> Message + 'a>,
class: S::Class<'a>,
}
impl<'a, Message, R, S> Checkbox<'a, Message, R, S>
where
R: Renderer,
S: CheckboxStyler<R::Color>,
{
pub fn new<F>(on_change: F) -> Self
where
F: 'a + Fn(bool) -> Message,
{
Self {
id: ElId::unique(),
check_icon: Icon::new(crate::icons::IconKind::Check),
size: Size::fill(),
on_change: Box::new(on_change),
class: S::default(),
}
}
pub fn icon(mut self, icon: IconKind) -> Self {
self.check_icon = Icon::new(icon);
self
}
pub fn width(mut self, width: impl Into<Length>) -> Self {
self.size.width = width.into();
self
}
pub fn height(mut self, height: impl Into<Length>) -> Self {
self.size.height = height.into();
self
}
fn status<E: Event>(&self, ctx: &UiCtx<Message>, state: &StateNode) -> CheckboxStatus {
let focused = UiCtx::is_focused::<R, E, S>(&ctx, self);
match (state.get::<CheckboxState>(), focused) {
(CheckboxState { is_pressed: true, .. }, _) => CheckboxStatus::Pressed,
(CheckboxState { is_checked: true, .. }, false) => CheckboxStatus::Checked,
(CheckboxState { .. }, true) => CheckboxStatus::Focused,
(CheckboxState { .. }, false) => CheckboxStatus::Normal,
}
}
}
impl<'a, Message, R, E, S> Widget<Message, R, E, S> for Checkbox<'a, Message, R, S>
where
R: Renderer,
E: Event,
S: CheckboxStyler<R::Color>,
{
fn id(&self) -> Option<crate::el::ElId> {
Some(self.id)
}
fn tree_ids(&self) -> Vec<ElId> {
vec![self.id]
}
fn size(&self) -> crate::size::Size<crate::size::Length> {
self.size
}
fn state_tag(&self) -> crate::state::StateTag {
StateTag::of::<CheckboxState>()
}
fn state(&self) -> crate::state::State {
State::new(CheckboxState::default())
}
fn state_children(&self) -> Vec<StateNode> {
vec![]
}
fn on_event(
&mut self,
ctx: &mut UiCtx<Message>,
event: E,
state: &mut StateNode,
) -> crate::event::EventResponse<E> {
let focused = UiCtx::is_focused::<R, E, S>(&ctx, self);
let current_state = state.get::<CheckboxState>();
if let Some(common) = event.as_common() {
match common {
CommonEvent::FocusMove(_) if focused => {
return Propagate::BubbleUp(self.id, event).into()
},
CommonEvent::FocusClickDown if focused => {
state.get_mut::<CheckboxState>().is_pressed = true;
return Capture::Captured.into();
},
CommonEvent::FocusClickUp if focused => {
let was_pressed = current_state.is_pressed;
state.get_mut::<CheckboxState>().is_pressed = false;
if was_pressed {
let new_state = !state.get::<CheckboxState>().is_checked;
state.get_mut::<CheckboxState>().is_checked = new_state;
ctx.publish((self.on_change)(new_state));
return Capture::Captured.into();
}
},
CommonEvent::FocusClickDown
| CommonEvent::FocusClickUp
| CommonEvent::FocusMove(_) => {
state.get_mut::<CheckboxState>().is_pressed = false;
},
}
}
Propagate::Ignored.into()
}
fn layout(
&self,
ctx: &mut UiCtx<Message>,
_state_tree: &mut StateNode,
styler: &S,
limits: &crate::layout::Limits,
viewport: &Viewport,
) -> crate::layout::LayoutNode {
Layout::container(
limits,
self.size,
crate::layout::Position::Relative,
viewport,
Padding::zero(),
Padding::new_equal(1),
crate::align::Alignment::Center,
crate::align::Alignment::Center,
|limits| {
Widget::<Message, R, E, S>::layout(
&self.check_icon,
ctx,
&mut StateNode::stateless(),
styler,
limits,
viewport,
)
},
)
}
fn draw(
&self,
ctx: &mut UiCtx<Message>,
state: &mut StateNode,
renderer: &mut R,
styler: &S,
layout: crate::layout::Layout,
) {
let style = styler.style(&self.class, self.status::<E>(ctx, state));
let state = state.get::<CheckboxState>();
let bounds = layout.bounds();
renderer.block(crate::block::Block {
border: style.border,
rect: Rectangle::new(bounds.position, bounds.size.into()),
background: style.background,
});
if state.is_checked {
Widget::<Message, R, E, S>::draw(
&self.check_icon,
ctx,
&mut StateNode::stateless(),
renderer,
styler,
layout.children().next().unwrap(),
)
}
}
}
impl<'a, Message, R, E, S> From<Checkbox<'a, Message, R, S>> for El<'a, Message, R, E, S>
where
Message: 'a,
R: Renderer + 'a,
E: Event + 'a,
S: CheckboxStyler<R::Color> + 'a,
{
fn from(value: Checkbox<'a, Message, R, S>) -> Self {
Self::new(value)
}
}