1use gpui::prelude::*;
6use gpui::*;
7
8#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
10pub enum CheckboxSize {
11 Sm,
13 #[default]
15 Md,
16 Lg,
18}
19
20impl CheckboxSize {
21 fn size(&self) -> Pixels {
22 match self {
23 CheckboxSize::Sm => px(14.0),
24 CheckboxSize::Md => px(18.0),
25 CheckboxSize::Lg => px(22.0),
26 }
27 }
28}
29
30pub struct Checkbox {
32 id: ElementId,
33 checked: bool,
34 indeterminate: bool,
35 label: Option<SharedString>,
36 size: CheckboxSize,
37 disabled: bool,
38 on_change: Option<Box<dyn Fn(bool, &mut Window, &mut App) + 'static>>,
39}
40
41impl Checkbox {
42 pub fn new(id: impl Into<ElementId>) -> Self {
44 Self {
45 id: id.into(),
46 checked: false,
47 indeterminate: false,
48 label: None,
49 size: CheckboxSize::default(),
50 disabled: false,
51 on_change: None,
52 }
53 }
54
55 pub fn checked(mut self, checked: bool) -> Self {
57 self.checked = checked;
58 self
59 }
60
61 pub fn indeterminate(mut self, indeterminate: bool) -> Self {
63 self.indeterminate = indeterminate;
64 self
65 }
66
67 pub fn label(mut self, label: impl Into<SharedString>) -> Self {
69 self.label = Some(label.into());
70 self
71 }
72
73 pub fn size(mut self, size: CheckboxSize) -> Self {
75 self.size = size;
76 self
77 }
78
79 pub fn disabled(mut self, disabled: bool) -> Self {
81 self.disabled = disabled;
82 self
83 }
84
85 pub fn on_change(mut self, handler: impl Fn(bool, &mut Window, &mut App) + 'static) -> Self {
87 self.on_change = Some(Box::new(handler));
88 self
89 }
90
91 pub fn build(self) -> Stateful<Div> {
93 let size = self.size.size();
94 let checked = self.checked;
95 let indeterminate = self.indeterminate;
96
97 let (bg, border_color) = if checked || indeterminate {
98 (rgb(0x007acc), rgb(0x007acc))
99 } else {
100 (rgba(0x00000000), rgb(0x555555))
101 };
102
103 let mut container = div()
104 .id(self.id)
105 .flex()
106 .items_center()
107 .gap_2()
108 .cursor_pointer();
109
110 if self.disabled {
111 container = container.opacity(0.5).cursor_not_allowed();
112 }
113
114 let mut checkbox = div()
116 .flex()
117 .items_center()
118 .justify_center()
119 .w(size)
120 .h(size)
121 .rounded(px(3.0))
122 .border_1()
123 .border_color(border_color)
124 .bg(bg);
125
126 if indeterminate {
128 checkbox = checkbox.child(
129 div()
130 .w(size - px(6.0))
131 .h(px(2.0))
132 .bg(rgb(0xffffff))
133 .rounded(px(1.0)),
134 );
135 } else if checked {
136 checkbox = checkbox.child(
137 div()
138 .text_color(rgb(0xffffff))
139 .text_xs()
140 .font_weight(FontWeight::BOLD)
141 .child("✓"),
142 );
143 }
144
145 if !self.disabled {
146 checkbox = checkbox.hover(|s| s.border_color(rgb(0x007acc)));
147 }
148
149 container = container.child(checkbox);
150
151 if let Some(label) = self.label {
153 let label_el = match self.size {
154 CheckboxSize::Sm => div().text_xs(),
155 CheckboxSize::Md => div().text_sm(),
156 CheckboxSize::Lg => div(),
157 };
158 container = container.child(label_el.text_color(rgb(0xcccccc)).child(label));
159 }
160
161 if !self.disabled {
163 if let Some(handler) = self.on_change {
164 let new_checked = !checked;
165 container = container.on_mouse_up(MouseButton::Left, move |_event, window, cx| {
166 handler(new_checked, window, cx);
167 });
168 }
169 }
170
171 container
172 }
173}
174
175impl IntoElement for Checkbox {
176 type Element = Stateful<Div>;
177
178 fn into_element(self) -> Self::Element {
179 self.build()
180 }
181}