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