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