use blinc_core::reactive::Signal;
use blinc_layout::binding::{IntoReactive, Reactive};
use blinc_layout::div::ElementTypeId;
use blinc_layout::element::RenderProps;
use blinc_layout::prelude::*;
use blinc_layout::tree::{LayoutNodeId, LayoutTree};
use blinc_layout::widgets::checkbox as layout_checkbox;
use blinc_theme::ThemeState;
use std::sync::Arc;
use blinc_layout::InstanceKey;
#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)]
pub enum CheckboxSize {
Small,
#[default]
Medium,
Large,
}
impl CheckboxSize {
fn size(&self) -> f32 {
match self {
CheckboxSize::Small => 14.0,
CheckboxSize::Medium => 18.0,
CheckboxSize::Large => 22.0,
}
}
fn border_width(&self) -> f32 {
match self {
CheckboxSize::Small => 1.5,
CheckboxSize::Medium => 2.0,
CheckboxSize::Large => 2.0,
}
}
fn checkmark_size(&self) -> f32 {
match self {
CheckboxSize::Small => 11.0,
CheckboxSize::Medium => 14.0,
CheckboxSize::Large => 18.0,
}
}
}
pub struct Checkbox {
inner: layout_checkbox::CheckboxBuilder,
}
impl Checkbox {
#[track_caller]
pub fn new(checked: Signal<bool>) -> Self {
Self::with_config(
InstanceKey::new("checkbox").get(),
CheckboxConfig::new(checked),
)
}
fn with_config(_instance_key: &str, config: CheckboxConfig) -> Self {
let theme = ThemeState::get();
let radius = match config.size {
CheckboxSize::Small => theme.radius(blinc_theme::RadiusToken::Sm) * 0.75,
_ => theme.radius(blinc_theme::RadiusToken::Sm),
};
let mut inner = layout_checkbox::checkbox(config.checked)
.class("cn-checkbox")
.class("tabindex=0")
.checked_class("cn-checkbox--checked")
.checkbox_size(config.size.size())
.mark_size(config.size.checkmark_size())
.border_width(config.size.border_width())
.rounded(radius)
.disabled(config.disabled);
if let Some(label) = config.label {
inner = inner.label(label);
}
if let Some(on_change) = config.on_change {
inner = inner.on_change(move |checked| on_change(checked));
}
Self { inner }
}
pub fn class(mut self, name: impl AsRef<str>) -> Self {
self.inner = self.inner.class(name);
self
}
pub fn id(mut self, id: &str) -> Self {
self.inner = self.inner.id(id);
self
}
}
impl ElementBuilder for Checkbox {
fn build(&self, tree: &mut LayoutTree) -> LayoutNodeId {
self.inner.build(tree)
}
fn render_props(&self) -> RenderProps {
self.inner.render_props()
}
fn children_builders(&self) -> &[Box<dyn ElementBuilder>] {
self.inner.children_builders()
}
fn element_type_id(&self) -> ElementTypeId {
self.inner.element_type_id()
}
fn semantic_type_name(&self) -> Option<&'static str> {
self.inner.semantic_type_name()
}
fn layout_style(&self) -> Option<&taffy::Style> {
self.inner.layout_style()
}
fn event_handlers(&self) -> Option<&blinc_layout::event_handler::EventHandlers> {
self.inner.event_handlers()
}
fn element_id(&self) -> Option<&str> {
self.inner.element_id()
}
fn element_classes(&self) -> &[std::sync::Arc<str>] {
self.inner.element_classes()
}
}
#[derive(Clone)]
struct CheckboxConfig {
checked: Signal<bool>,
size: CheckboxSize,
label: Option<Reactive<String>>,
disabled: Reactive<bool>,
on_change: Option<Arc<dyn Fn(bool) + Send + Sync>>,
}
impl CheckboxConfig {
fn new(checked: Signal<bool>) -> Self {
Self {
checked,
size: CheckboxSize::default(),
label: None,
disabled: Reactive::Const(false),
on_change: None,
}
}
}
pub struct CheckboxBuilder {
key: InstanceKey,
config: CheckboxConfig,
built: std::cell::OnceCell<Checkbox>,
}
impl CheckboxBuilder {
#[track_caller]
pub fn new(checked: Signal<bool>) -> Self {
Self {
key: InstanceKey::new("checkbox"),
config: CheckboxConfig::new(checked),
built: std::cell::OnceCell::new(),
}
}
pub fn with_key(key: impl Into<String>, checked: Signal<bool>) -> Self {
Self {
key: InstanceKey::explicit(key),
config: CheckboxConfig::new(checked),
built: std::cell::OnceCell::new(),
}
}
fn get_or_build(&self) -> &Checkbox {
::blinc_layout::build_once::build_once(&self.built, || {
Checkbox::with_config(self.key.get(), self.config.clone())
})
}
pub fn size(mut self, size: CheckboxSize) -> Self {
self.config.size = size;
self
}
pub fn label(mut self, label: impl IntoReactive<String>) -> Self {
self.config.label = Some(label.into_reactive());
self
}
pub fn disabled(mut self, disabled: impl IntoReactive<bool>) -> Self {
self.config.disabled = disabled.into_reactive();
self
}
pub fn on_change<F>(mut self, callback: F) -> Self
where
F: Fn(bool) + Send + Sync + 'static,
{
self.config.on_change = Some(Arc::new(callback));
self
}
pub fn build_component(self) -> Checkbox {
Checkbox::with_config(self.key.get(), self.config)
}
}
impl ElementBuilder for CheckboxBuilder {
fn build(&self, tree: &mut LayoutTree) -> LayoutNodeId {
self.get_or_build().build(tree)
}
fn render_props(&self) -> RenderProps {
self.get_or_build().render_props()
}
fn children_builders(&self) -> &[Box<dyn ElementBuilder>] {
self.get_or_build().children_builders()
}
fn element_type_id(&self) -> ElementTypeId {
self.get_or_build().element_type_id()
}
fn layout_style(&self) -> Option<&taffy::Style> {
self.get_or_build().layout_style()
}
fn element_classes(&self) -> &[std::sync::Arc<str>] {
self.get_or_build().element_classes()
}
}
pub fn checkbox(checked: Signal<bool>) -> CheckboxBuilder {
CheckboxBuilder::new(checked)
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_checkbox_sizes() {
assert_eq!(CheckboxSize::Small.size(), 14.0);
assert_eq!(CheckboxSize::Medium.size(), 18.0);
assert_eq!(CheckboxSize::Large.size(), 22.0);
}
#[test]
fn test_checkbox_checkmark_sizes() {
for size in [
CheckboxSize::Small,
CheckboxSize::Medium,
CheckboxSize::Large,
] {
assert_eq!(
size.checkmark_size(),
size.size() - 2.0 * size.border_width()
);
}
}
}