Skip to main content

guise/input/
checkboxgroup.rs

1//! `CheckboxGroup` — a controlled set of [`Checkbox`]es over a shared value.
2//!
3//! The parent owns the selected indices (a sorted `Vec<usize>`); each toggle
4//! reports the *next* full selection through `on_change`.
5
6use std::rc::Rc;
7
8use gpui::prelude::*;
9use gpui::{div, px, App, IntoElement, SharedString, Window};
10
11use super::Checkbox;
12use crate::devtools::Probed;
13use crate::reactive::Binding;
14use crate::theme::{theme, ColorName, Size};
15
16type GroupHandler = Rc<dyn Fn(Vec<usize>, &mut Window, &mut App) + 'static>;
17
18/// A vertical group of checkboxes sharing one selection set.
19#[derive(IntoElement)]
20pub struct CheckboxGroup {
21  options: Vec<SharedString>,
22  value: Vec<usize>,
23  color: ColorName,
24  size: Size,
25  label: Option<SharedString>,
26  binding: Option<Binding<Vec<usize>>>,
27  on_change: Option<GroupHandler>,
28}
29
30impl CheckboxGroup {
31  pub fn new() -> Self {
32    CheckboxGroup {
33      options: Vec::new(),
34      value: Vec::new(),
35      color: ColorName::Blue,
36      size: Size::Sm,
37      label: None,
38      binding: None,
39      on_change: None,
40    }
41  }
42
43  pub fn options<I, S>(mut self, options: I) -> Self
44  where
45    I: IntoIterator<Item = S>,
46    S: Into<SharedString>,
47  {
48    self.options = options.into_iter().map(Into::into).collect();
49    self
50  }
51
52  /// The currently selected indices.
53  pub fn value(mut self, value: impl IntoIterator<Item = usize>) -> Self {
54    self.value = value.into_iter().collect();
55    self
56  }
57
58  pub fn color(mut self, color: ColorName) -> Self {
59    self.color = color;
60    self
61  }
62
63  pub fn size(mut self, size: Size) -> Self {
64    self.size = size;
65    self
66  }
67
68  pub fn label(mut self, label: impl Into<SharedString>) -> Self {
69    self.label = Some(label.into());
70    self
71  }
72
73  /// Two-way bind the selection set. Overrides `value`; each toggle writes
74  /// the full next selection back through the binding, then runs any
75  /// `on_change`.
76  pub fn bind(mut self, binding: Binding<Vec<usize>>) -> Self {
77    self.binding = Some(binding);
78    self
79  }
80
81  /// Called with the full next selection (sorted) when any box is toggled.
82  pub fn on_change(
83    mut self,
84    handler: impl Fn(Vec<usize>, &mut Window, &mut App) + 'static,
85  ) -> Self {
86    self.on_change = Some(Rc::new(handler));
87    self
88  }
89}
90
91impl Default for CheckboxGroup {
92  fn default() -> Self {
93    CheckboxGroup::new()
94  }
95}
96
97impl RenderOnce for CheckboxGroup {
98  fn render(self, _window: &mut Window, cx: &mut App) -> impl IntoElement {
99    let t = theme(cx);
100    let gap = t.spacing(Size::Xs);
101    let text = t.text().hsla();
102    let font = t.font_size(Size::Sm);
103    let value = self
104      .binding
105      .as_ref()
106      .map_or_else(|| self.value.clone(), |b| b.get(cx));
107
108    let mut column = div().flex().flex_col().gap(px(gap));
109    if let Some(label) = self.label.clone() {
110      column = column.child(div().text_size(px(font)).text_color(text).child(label));
111    }
112
113    let current = Rc::new(value.clone());
114    for (i, option) in self.options.iter().enumerate() {
115      let mut checkbox = Checkbox::new(("guise-checkboxgroup", i))
116        .label(option.clone())
117        .checked(value.contains(&i))
118        .color(self.color)
119        .size(self.size);
120      if self.binding.is_some() || self.on_change.is_some() {
121        let binding = self.binding.clone();
122        let handler = self.on_change.clone();
123        let current = current.clone();
124        checkbox = checkbox.on_change(move |_ev, window, cx| {
125          let mut next = (*current).clone();
126          if let Some(pos) = next.iter().position(|x| *x == i) {
127            next.remove(pos);
128          } else {
129            next.push(i);
130            next.sort_unstable();
131          }
132          if let Some(binding) = &binding {
133            binding.set(cx, next.clone());
134          }
135          if let Some(handler) = &handler {
136            handler(next, window, cx);
137          }
138        });
139      }
140      column = column.child(checkbox);
141    }
142    column.probe("CheckboxGroup")
143  }
144}