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