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