Skip to main content

guise/input/
checkbox.rs

1//! `Checkbox` — a controlled boolean toggle with an optional label.
2
3use gpui::prelude::*;
4use gpui::{div, px, App, ClickEvent, ElementId, FontWeight, IntoElement, SharedString, Window};
5
6use super::{control_box_size, ClickHandler};
7use crate::theme::{theme, ColorName, Size};
8
9/// A controlled checkbox. The Mantine `Checkbox`. Pass `checked` and a change
10/// handler (via `cx.listener`); the parent view owns the value.
11#[derive(IntoElement)]
12pub struct Checkbox {
13    id: ElementId,
14    checked: bool,
15    indeterminate: bool,
16    label: Option<SharedString>,
17    size: Size,
18    color: ColorName,
19    disabled: bool,
20    on_change: Option<ClickHandler>,
21}
22
23impl Checkbox {
24    pub fn new(id: impl Into<ElementId>) -> Self {
25        Checkbox {
26            id: id.into(),
27            checked: false,
28            indeterminate: false,
29            label: None,
30            size: Size::Sm,
31            color: ColorName::Blue,
32            disabled: false,
33            on_change: None,
34        }
35    }
36
37    pub fn checked(mut self, checked: bool) -> Self {
38        self.checked = checked;
39        self
40    }
41
42    pub fn indeterminate(mut self, indeterminate: bool) -> Self {
43        self.indeterminate = indeterminate;
44        self
45    }
46
47    pub fn label(mut self, label: impl Into<SharedString>) -> Self {
48        self.label = Some(label.into());
49        self
50    }
51
52    pub fn size(mut self, size: Size) -> Self {
53        self.size = size;
54        self
55    }
56
57    pub fn color(mut self, color: ColorName) -> Self {
58        self.color = color;
59        self
60    }
61
62    pub fn disabled(mut self, disabled: bool) -> Self {
63        self.disabled = disabled;
64        self
65    }
66
67    pub fn on_change(
68        mut self,
69        handler: impl Fn(&ClickEvent, &mut Window, &mut App) + 'static,
70    ) -> Self {
71        self.on_change = Some(Box::new(handler));
72        self
73    }
74}
75
76impl RenderOnce for Checkbox {
77    fn render(self, _window: &mut Window, cx: &mut App) -> impl IntoElement {
78        let t = theme(cx);
79        let on = self.checked || self.indeterminate;
80        let accent = t.color(self.color, t.primary_shade());
81        let box_size = control_box_size(self.size);
82
83        let mut check = div()
84            .w(px(box_size))
85            .h(px(box_size))
86            .flex()
87            .items_center()
88            .justify_center()
89            .rounded(px(t.radius(Size::Xs) + 2.0))
90            .text_size(px(box_size * 0.7))
91            .font_weight(FontWeight::BOLD);
92        if on {
93            check = check
94                .bg(accent.hsla())
95                .text_color(accent.contrasting().hsla())
96                .child(SharedString::new_static(if self.indeterminate {
97                    "\u{2212}"
98                } else {
99                    "\u{2713}"
100                }));
101        } else {
102            check = check
103                .bg(t.surface().hsla())
104                .border_1()
105                .border_color(t.border().hsla());
106        }
107
108        let mut row = div().id(self.id).flex().items_center().gap(px(8.0)).child(check);
109        if let Some(label) = self.label {
110            row = row.child(
111                div()
112                    .text_size(px(t.font_size(self.size)))
113                    .text_color(t.text().hsla())
114                    .child(label),
115            );
116        }
117
118        if self.disabled {
119            row.opacity(0.5)
120        } else {
121            if let Some(handler) = self.on_change {
122                row = row.on_click(handler);
123            }
124            row
125        }
126    }
127}