1#![allow(missing_docs)]
2mod checkbox_with_label;
3
4pub use checkbox_with_label::*;
5
6use gpui::{div, prelude::*, ElementId, IntoElement, Styled, WindowContext};
7
8use crate::prelude::*;
9use crate::{Color, Icon, IconName, Selection};
10
11/// # Checkbox
12///
13/// Checkboxes are used for multiple choices, not for mutually exclusive choices.
14/// Each checkbox works independently from other checkboxes in the list,
15/// therefore checking an additional box does not affect any other selections.
16#[derive(IntoElement)]
17pub struct Checkbox {
18 id: ElementId,
19 checked: Selection,
20 disabled: bool,
21 on_click: Option<Box<dyn Fn(&Selection, &mut WindowContext) + 'static>>,
22}
23
24impl Checkbox {
25 pub fn new(id: impl Into<ElementId>, checked: Selection) -> Self {
26 Self {
27 id: id.into(),
28 checked,
29 disabled: false,
30 on_click: None,
31 }
32 }
33
34 pub fn disabled(mut self, disabled: bool) -> Self {
35 self.disabled = disabled;
36 self
37 }
38
39 pub fn on_click(mut self, handler: impl Fn(&Selection, &mut WindowContext) + 'static) -> Self {
40 self.on_click = Some(Box::new(handler));
41 self
42 }
43}
44
45impl RenderOnce for Checkbox {
46 fn render(self, cx: &mut WindowContext) -> impl IntoElement {
47 let group_id = format!("checkbox_group_{:?}", self.id);
48
49 let icon = match self.checked {
50 Selection::Selected => Some(Icon::new(IconName::Check).size(IconSize::Small).color(
51 if self.disabled {
52 Color::Disabled
53 } else {
54 Color::Selected
55 },
56 )),
57 Selection::Indeterminate => Some(
58 Icon::new(IconName::Dash)
59 .size(IconSize::Small)
60 .color(if self.disabled {
61 Color::Disabled
62 } else {
63 Color::Selected
64 }),
65 ),
66 Selection::Unselected => None,
67 };
68
69 let selected =
70 self.checked == Selection::Selected || self.checked == Selection::Indeterminate;
71
72 let (bg_color, border_color) = match (self.disabled, selected) {
73 (true, _) => (
74 cx.theme().colors().ghost_element_disabled,
75 cx.theme().colors().border_disabled,
76 ),
77 (false, true) => (
78 cx.theme().colors().element_selected,
79 cx.theme().colors().border,
80 ),
81 (false, false) => (
82 cx.theme().colors().element_background,
83 cx.theme().colors().border,
84 ),
85 };
86
87 h_flex()
88 .id(self.id)
89 .justify_center()
90 .items_center()
91 .size(crate::styles::custom_spacing(cx, 20.))
92 .group(group_id.clone())
93 .child(
94 div()
95 .flex()
96 .flex_none()
97 .justify_center()
98 .items_center()
99 .m(Spacing::Small.px(cx))
100 .size(crate::styles::custom_spacing(cx, 16.))
101 .rounded_sm()
102 .bg(bg_color)
103 .border_1()
104 .border_color(border_color)
105 .when(!self.disabled, |this| {
106 this.group_hover(group_id.clone(), |el| {
107 el.bg(cx.theme().colors().element_hover)
108 })
109 })
110 .children(icon),
111 )
112 .when_some(
113 self.on_click.filter(|_| !self.disabled),
114 |this, on_click| this.on_click(move |_, cx| on_click(&self.checked.inverse(), cx)),
115 )
116 }
117}