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