guise/input/
checkboxgroup.rs1use 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#[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 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 pub fn bind(mut self, binding: Binding<Vec<usize>>) -> Self {
76 self.binding = Some(binding);
77 self
78 }
79
80 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}