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