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(DynamicSpacing::Base20.rems(cx))
89 .group(group_id.clone())
90 .child(
91 div()
92 .flex()
93 .flex_none()
94 .justify_center()
95 .items_center()
96 .m(DynamicSpacing::Base04.px(cx))
97 .size(DynamicSpacing::Base16.rems(cx))
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}
115
116impl ComponentPreview for Checkbox {
117 fn description() -> impl Into<Option<&'static str>> {
118 "A checkbox lets people choose between a pair of opposing states, like enabled and disabled, using a different appearance to indicate each state."
119 }
120
121 fn examples(_: &WindowContext) -> Vec<ComponentExampleGroup<Self>> {
122 vec![
123 example_group_with_title(
124 "Default",
125 vec![
126 single_example(
127 "Unselected",
128 Checkbox::new("checkbox_unselected", Selection::Unselected),
129 ),
130 single_example(
131 "Indeterminate",
132 Checkbox::new("checkbox_indeterminate", Selection::Indeterminate),
133 ),
134 single_example(
135 "Selected",
136 Checkbox::new("checkbox_selected", Selection::Selected),
137 ),
138 ],
139 ),
140 example_group_with_title(
141 "Disabled",
142 vec![
143 single_example(
144 "Unselected",
145 Checkbox::new("checkbox_disabled_unselected", Selection::Unselected)
146 .disabled(true),
147 ),
148 single_example(
149 "Indeterminate",
150 Checkbox::new("checkbox_disabled_indeterminate", Selection::Indeterminate)
151 .disabled(true),
152 ),
153 single_example(
154 "Selected",
155 Checkbox::new("checkbox_disabled_selected", Selection::Selected)
156 .disabled(true),
157 ),
158 ],
159 ),
160 ]
161 }
162}
163
164use std::sync::Arc;
165
166/// A [`Checkbox`] that has a [`Label`].
167#[derive(IntoElement)]
168pub struct CheckboxWithLabel {
169 id: ElementId,
170 label: Label,
171 checked: Selection,
172 on_click: Arc<dyn Fn(&Selection, &mut WindowContext) + 'static>,
173}
174
175impl CheckboxWithLabel {
176 pub fn new(
177 id: impl Into<ElementId>,
178 label: Label,
179 checked: Selection,
180 on_click: impl Fn(&Selection, &mut WindowContext) + 'static,
181 ) -> Self {
182 Self {
183 id: id.into(),
184 label,
185 checked,
186 on_click: Arc::new(on_click),
187 }
188 }
189}
190
191impl RenderOnce for CheckboxWithLabel {
192 fn render(self, cx: &mut WindowContext) -> impl IntoElement {
193 h_flex()
194 .gap(DynamicSpacing::Base08.rems(cx))
195 .child(Checkbox::new(self.id.clone(), self.checked).on_click({
196 let on_click = self.on_click.clone();
197 move |checked, cx| {
198 (on_click)(checked, cx);
199 }
200 }))
201 .child(
202 div()
203 .id(SharedString::from(format!("{}-label", self.id)))
204 .on_click(move |_event, cx| {
205 (self.on_click)(&self.checked.inverse(), cx);
206 })
207 .child(self.label),
208 )
209 }
210}
211
212impl ComponentPreview for CheckboxWithLabel {
213 fn description() -> impl Into<Option<&'static str>> {
214 "A checkbox with an associated label, allowing users to select an option while providing a descriptive text."
215 }
216
217 fn examples(_: &WindowContext) -> Vec<ComponentExampleGroup<Self>> {
218 vec![example_group(vec![
219 single_example(
220 "Unselected",
221 CheckboxWithLabel::new(
222 "checkbox_with_label_unselected",
223 Label::new("Always save on quit"),
224 Selection::Unselected,
225 |_, _| {},
226 ),
227 ),
228 single_example(
229 "Indeterminate",
230 CheckboxWithLabel::new(
231 "checkbox_with_label_indeterminate",
232 Label::new("Always save on quit"),
233 Selection::Indeterminate,
234 |_, _| {},
235 ),
236 ),
237 single_example(
238 "Selected",
239 CheckboxWithLabel::new(
240 "checkbox_with_label_selected",
241 Label::new("Always save on quit"),
242 Selection::Selected,
243 |_, _| {},
244 ),
245 ),
246 ])]
247 }
248}