use std::rc::Rc;
use gpui::{
AnyElement, App, ClickEvent, ElementId, InteractiveElement as _, IntoElement, MouseButton,
ParentElement, RenderOnce, StatefulInteractiveElement as _, StyleRefinement, Styled, Window, div,
prelude::FluentBuilder as _,
};
use crate::{ActiveTheme, Icon, IconName, Sizable, Size, StyleSized, StyledExt, h_flex};
type CheckboxClickHandler = Rc<dyn Fn(&bool, &mut Window, &mut App) + 'static>;
#[derive(IntoElement)]
pub struct Checkbox {
id: ElementId,
style: StyleRefinement,
label: Option<AnyElement>,
children: Vec<AnyElement>,
checked: bool,
disabled: bool,
size: Size,
tab_stop: bool,
tab_index: isize,
on_click: Option<CheckboxClickHandler>,
}
impl Checkbox {
pub fn new(id: impl Into<ElementId>) -> Self {
Self {
id: id.into(),
style: StyleRefinement::default(),
label: None,
children: Vec::new(),
checked: false,
disabled: false,
size: Size::default(),
tab_stop: true,
tab_index: 0,
on_click: None,
}
}
pub fn label(mut self, label: impl IntoElement) -> Self {
self.label = Some(label.into_any_element());
self
}
pub fn checked(mut self, checked: bool) -> Self {
self.checked = checked;
self
}
pub fn on_click(mut self, handler: impl Fn(&bool, &mut Window, &mut App) + 'static) -> Self {
self.on_click = Some(Rc::new(handler));
self
}
pub fn tab_stop(mut self, tab_stop: bool) -> Self {
self.tab_stop = tab_stop;
self
}
pub fn tab_index(mut self, tab_index: isize) -> Self {
self.tab_index = tab_index;
self
}
}
impl_disableable!(Checkbox);
impl_selectable!(Checkbox, checked);
impl_sizable!(Checkbox);
impl_styled!(Checkbox);
impl_parent_element!(Checkbox);
impl RenderOnce for Checkbox {
fn render(self, window: &mut Window, cx: &mut App) -> impl IntoElement {
let checked = self.checked;
let focus_handle = window
.use_keyed_state(self.id.clone(), cx, |_, cx| cx.focus_handle())
.read(cx)
.clone();
let indicator_color = if checked {
cx.theme().primary
} else {
cx.theme().background
};
let border_color = if checked {
cx.theme().primary
} else {
cx.theme().input
};
h_flex()
.id(self.id)
.items_center()
.component_gap(self.size)
.text_color(if self.disabled {
cx.theme().muted_foreground
} else {
cx.theme().foreground
})
.child(
div()
.flex_none()
.size(self.size.component_height() * 0.5)
.rounded(self.size.component_radius())
.border_1()
.border_color(border_color)
.bg(indicator_color)
.child(
h_flex().size_full().items_center().justify_center().child(
Icon::new(IconName::Checkmark)
.with_size(self.size.smaller())
.text_color(cx.theme().primary_foreground)
.when(!checked, |this| this.opacity(0.0)),
),
),
)
.when_some(self.label, |this, label| this.child(label))
.children(self.children)
.when(!self.disabled, |this| {
this.track_focus(
&focus_handle
.tab_stop(self.tab_stop)
.tab_index(self.tab_index),
)
})
.when(!self.disabled, |this| {
this
.cursor_pointer()
.hover(|this| this.opacity(0.9))
.active(|this| this.opacity(0.8))
})
.on_mouse_down(MouseButton::Left, |_, window, _| {
window.prevent_default();
})
.when_some(
self.on_click.filter(|_| !self.disabled),
|this, on_click| {
this.on_click(move |_event: &ClickEvent, window, cx| on_click(&!checked, window, cx))
},
)
.refine_style(&self.style)
}
}