checkbox.rs

  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}