1use gpui::prelude::*;
4use gpui::{div, px, App, ClickEvent, ElementId, FontWeight, IntoElement, SharedString, Window};
5
6use super::{control_box_size, ClickHandler};
7use crate::devtools::Probed;
8use crate::reactive::Binding;
9use crate::theme::{theme, ColorName, Size};
10
11#[derive(IntoElement)]
15pub struct Checkbox {
16 id: ElementId,
17 checked: bool,
18 indeterminate: bool,
19 label: Option<SharedString>,
20 size: Size,
21 color: ColorName,
22 disabled: bool,
23 binding: Option<Binding<bool>>,
24 on_change: Option<ClickHandler>,
25}
26
27impl Checkbox {
28 pub fn new(id: impl Into<ElementId>) -> Self {
29 Checkbox {
30 id: id.into(),
31 checked: false,
32 indeterminate: false,
33 label: None,
34 size: Size::Sm,
35 color: ColorName::Blue,
36 disabled: false,
37 binding: None,
38 on_change: None,
39 }
40 }
41
42 pub fn checked(mut self, checked: bool) -> Self {
43 self.checked = checked;
44 self
45 }
46
47 pub fn indeterminate(mut self, indeterminate: bool) -> Self {
48 self.indeterminate = indeterminate;
49 self
50 }
51
52 pub fn label(mut self, label: impl Into<SharedString>) -> Self {
53 self.label = Some(label.into());
54 self
55 }
56
57 pub fn size(mut self, size: Size) -> Self {
58 self.size = size;
59 self
60 }
61
62 pub fn color(mut self, color: ColorName) -> Self {
63 self.color = color;
64 self
65 }
66
67 pub fn disabled(mut self, disabled: bool) -> Self {
68 self.disabled = disabled;
69 self
70 }
71
72 pub fn bind(mut self, binding: Binding<bool>) -> Self {
75 self.binding = Some(binding);
76 self
77 }
78
79 pub fn on_change(
80 mut self,
81 handler: impl Fn(&ClickEvent, &mut Window, &mut App) + 'static,
82 ) -> Self {
83 self.on_change = Some(Box::new(handler));
84 self
85 }
86}
87
88impl RenderOnce for Checkbox {
89 fn render(self, _window: &mut Window, cx: &mut App) -> impl IntoElement {
90 let t = theme(cx);
91 let checked = self.binding.as_ref().map_or(self.checked, |b| b.get(cx));
92 let on = checked || self.indeterminate;
93 let accent = t.color(self.color, t.primary_shade());
94 let box_size = control_box_size(self.size);
95
96 let mut check = div()
97 .w(px(box_size))
98 .h(px(box_size))
99 .flex()
100 .items_center()
101 .justify_center()
102 .rounded(px(t.radius(Size::Xs) + 2.0))
103 .text_size(px(box_size * 0.7))
104 .font_weight(FontWeight::BOLD);
105 if on {
106 check = check
107 .bg(accent.hsla())
108 .text_color(accent.contrasting().hsla())
109 .child(SharedString::new_static(if self.indeterminate {
110 "\u{2212}"
111 } else {
112 "\u{2713}"
113 }));
114 } else {
115 check = check
116 .bg(t.surface().hsla())
117 .border_1()
118 .border_color(t.border().hsla());
119 }
120
121 let mut row = div()
122 .id(self.id)
123 .flex()
124 .items_center()
125 .gap(px(8.0))
126 .child(check);
127 if let Some(label) = self.label {
128 row = row.child(
129 div()
130 .text_size(px(t.font_size(self.size)))
131 .text_color(t.text().hsla())
132 .child(label),
133 );
134 }
135
136 let element = if self.disabled {
137 row.opacity(0.5)
138 } else {
139 if self.binding.is_some() || self.on_change.is_some() {
140 let binding = self.binding;
141 let handler = self.on_change;
142 let next = !checked;
143 row = row.on_click(move |ev, window, cx| {
144 if let Some(binding) = &binding {
145 binding.set(cx, next);
146 }
147 if let Some(handler) = &handler {
148 handler(ev, window, cx);
149 }
150 });
151 }
152 row
153 };
154
155 element
156 .probe("Checkbox")
157 .attr("size", self.size.label())
158 .attr_if("checked", self.checked)
159 .attr_if("indeterminate", self.indeterminate)
160 .attr_if("disabled", self.disabled)
161 }
162}