use crate::ComponentTheme;
use crate::theme::ThemeExt;
use gpui::prelude::*;
use gpui::*;
#[derive(Debug, Clone, ComponentTheme)]
pub struct CheckboxTheme {
#[theme(default = 0x007acc, from = accent)]
pub checked_bg: Rgba,
#[theme(default = 0x00000000, from = transparent)]
pub unchecked_bg: Rgba,
#[theme(default = 0x555555, from = border)]
pub unchecked_border: Rgba,
#[theme(default = 0xffffff, from = text_primary)]
pub check_color: Rgba,
#[theme(default = 0xcccccc, from = text_secondary)]
pub label: Rgba,
#[theme(default = 0x007acc, from = accent)]
pub hover_border: Rgba,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
pub enum CheckboxSize {
Sm,
#[default]
Md,
Lg,
}
impl CheckboxSize {
fn size(&self) -> Pixels {
match self {
CheckboxSize::Sm => px(14.0),
CheckboxSize::Md => px(18.0),
CheckboxSize::Lg => px(22.0),
}
}
}
impl From<crate::ComponentSize> for CheckboxSize {
fn from(size: crate::ComponentSize) -> Self {
match size {
crate::ComponentSize::Xs | crate::ComponentSize::Sm => Self::Sm,
crate::ComponentSize::Md => Self::Md,
crate::ComponentSize::Lg | crate::ComponentSize::Xl => Self::Lg,
}
}
}
pub struct Checkbox {
id: ElementId,
checked: bool,
indeterminate: bool,
label: Option<SharedString>,
size: CheckboxSize,
disabled: bool,
on_change: Option<Box<dyn Fn(bool, &mut Window, &mut App) + 'static>>,
}
impl Checkbox {
pub fn new(id: impl Into<ElementId>) -> Self {
Self {
id: id.into(),
checked: false,
indeterminate: false,
label: None,
size: CheckboxSize::default(),
disabled: false,
on_change: None,
}
}
pub fn checked(mut self, checked: bool) -> Self {
self.checked = checked;
self
}
pub fn indeterminate(mut self, indeterminate: bool) -> Self {
self.indeterminate = indeterminate;
self
}
pub fn label(mut self, label: impl Into<SharedString>) -> Self {
self.label = Some(label.into());
self
}
pub fn size(mut self, size: CheckboxSize) -> Self {
self.size = size;
self
}
pub fn disabled(mut self, disabled: bool) -> Self {
self.disabled = disabled;
self
}
pub fn on_change(mut self, handler: impl Fn(bool, &mut Window, &mut App) + 'static) -> Self {
self.on_change = Some(Box::new(handler));
self
}
pub fn build_with_theme(self, theme: &CheckboxTheme) -> Stateful<Div> {
let size = self.size.size();
let checked = self.checked;
let indeterminate = self.indeterminate;
let (bg, border_color) = if checked || indeterminate {
(theme.checked_bg, theme.checked_bg)
} else {
(theme.unchecked_bg, theme.unchecked_border)
};
let mut container = div()
.id(self.id)
.flex()
.items_center()
.gap_2()
.cursor_pointer();
if self.disabled {
container = container.opacity(0.5).cursor_not_allowed();
}
let mut checkbox = div()
.flex()
.items_center()
.justify_center()
.w(size)
.h(size)
.rounded(px(3.0))
.border_1()
.border_color(border_color)
.bg(bg);
if indeterminate {
checkbox = checkbox.child(
div()
.w(size - px(6.0))
.h(px(2.0))
.bg(theme.check_color)
.rounded(px(1.0)),
);
} else if checked {
checkbox = checkbox.child(
div()
.text_color(theme.check_color)
.text_xs()
.font_weight(FontWeight::BOLD)
.child("✓"),
);
}
if !self.disabled {
let hover_border = theme.hover_border;
checkbox = checkbox.hover(move |s| s.border_color(hover_border));
}
container = container.child(checkbox);
if let Some(label) = self.label {
let label_el = match self.size {
CheckboxSize::Sm => div().text_xs(),
CheckboxSize::Md => div().text_sm(),
CheckboxSize::Lg => div(),
};
container = container.child(label_el.text_color(theme.label).child(label));
}
if !self.disabled
&& let Some(handler) = self.on_change
{
let handler_rc = std::rc::Rc::new(handler);
let new_checked = !checked;
let click_handler = handler_rc.clone();
container = container.on_mouse_up(MouseButton::Left, move |_event, window, cx| {
click_handler(new_checked, window, cx);
});
let key_handler = handler_rc.clone();
container = container.on_key_down(move |event, window, cx| {
match event.keystroke.key.as_str() {
"space" | " " | "enter" => {
key_handler(new_checked, window, cx);
}
_ => {}
}
});
}
container
}
}
impl RenderOnce for Checkbox {
fn render(self, _window: &mut Window, cx: &mut App) -> impl IntoElement {
let global_theme = cx.theme();
let checkbox_theme = CheckboxTheme::from(&global_theme);
self.build_with_theme(&checkbox_theme)
}
}
impl IntoElement for Checkbox {
type Element = gpui::Component<Self>;
fn into_element(self) -> Self::Element {
gpui::Component::new(self)
}
}