1#![allow(missing_docs)]
2
3use gpui::{div, prelude::*, ElementId, IntoElement, Styled, WindowContext};
4
5use crate::prelude::*;
6use crate::{Color, Icon, IconName, Selection};
7
8/// # Checkbox
9///
10/// Checkboxes are used for multiple choices, not for mutually exclusive choices.
11/// Each checkbox works independently from other checkboxes in the list,
12/// therefore checking an additional box does not affect any other selections.
13#[derive(IntoElement)]
14pub struct Checkbox {
15 id: ElementId,
16 checked: Selection,
17 disabled: bool,
18 on_click: Option<Box<dyn Fn(&Selection, &mut WindowContext) + 'static>>,
19}
20
21impl Checkbox {
22 pub fn new(id: impl Into<ElementId>, checked: Selection) -> Self {
23 Self {
24 id: id.into(),
25 checked,
26 disabled: false,
27 on_click: None,
28 }
29 }
30
31 pub fn disabled(mut self, disabled: bool) -> Self {
32 self.disabled = disabled;
33 self
34 }
35
36 pub fn on_click(mut self, handler: impl Fn(&Selection, &mut WindowContext) + 'static) -> Self {
37 self.on_click = Some(Box::new(handler));
38 self
39 }
40}
41
42impl RenderOnce for Checkbox {
43 fn render(self, cx: &mut WindowContext) -> impl IntoElement {
44 let group_id = format!("checkbox_group_{:?}", self.id);
45
46 let icon = match self.checked {
47 Selection::Selected => Some(Icon::new(IconName::Check).size(IconSize::Small).color(
48 if self.disabled {
49 Color::Disabled
50 } else {
51 Color::Selected
52 },
53 )),
54 Selection::Indeterminate => Some(
55 Icon::new(IconName::Dash)
56 .size(IconSize::Small)
57 .color(if self.disabled {
58 Color::Disabled
59 } else {
60 Color::Selected
61 }),
62 ),
63 Selection::Unselected => None,
64 };
65
66 let selected =
67 self.checked == Selection::Selected || self.checked == Selection::Indeterminate;
68
69 let (bg_color, border_color) = match (self.disabled, selected) {
70 (true, _) => (
71 cx.theme().colors().ghost_element_disabled,
72 cx.theme().colors().border_disabled,
73 ),
74 (false, true) => (
75 cx.theme().colors().element_selected,
76 cx.theme().colors().border,
77 ),
78 (false, false) => (
79 cx.theme().colors().element_background,
80 cx.theme().colors().border,
81 ),
82 };
83
84 h_flex()
85 .id(self.id)
86 .justify_center()
87 .items_center()
88 .size(crate::styles::custom_spacing(cx, 20.))
89 .group(group_id.clone())
90 .child(
91 div()
92 .flex()
93 .flex_none()
94 .justify_center()
95 .items_center()
96 .m(Spacing::Small.px(cx))
97 .size(crate::styles::custom_spacing(cx, 16.))
98 .rounded_sm()
99 .bg(bg_color)
100 .border_1()
101 .border_color(border_color)
102 .when(!self.disabled, |this| {
103 this.group_hover(group_id.clone(), |el| {
104 el.bg(cx.theme().colors().element_hover)
105 })
106 })
107 .children(icon),
108 )
109 .when_some(
110 self.on_click.filter(|_| !self.disabled),
111 |this, on_click| this.on_click(move |_, cx| on_click(&self.checked.inverse(), cx)),
112 )
113 }
114}