Skip to main content

guise/input/
radiogroup.rs

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