Skip to main content

guise/input/
radio.rs

1//! `Radio` — a controlled single-choice button with an optional label.
2
3use gpui::prelude::*;
4use gpui::{div, px, App, ClickEvent, ElementId, IntoElement, SharedString, Window};
5
6use super::{control_box_size, ClickHandler};
7use crate::devtools::Probed;
8use crate::theme::{theme, ColorName, Size};
9
10/// A controlled radio button. Grouping/exclusivity is the
11/// parent view's responsibility — give each a `checked` and a change handler.
12#[derive(IntoElement)]
13pub struct Radio {
14    id: ElementId,
15    checked: bool,
16    label: Option<SharedString>,
17    size: Size,
18    color: ColorName,
19    disabled: bool,
20    on_change: Option<ClickHandler>,
21}
22
23impl Radio {
24    pub fn new(id: impl Into<ElementId>) -> Self {
25        Radio {
26            id: id.into(),
27            checked: false,
28            label: None,
29            size: Size::Sm,
30            color: ColorName::Blue,
31            disabled: false,
32            on_change: None,
33        }
34    }
35
36    pub fn checked(mut self, checked: bool) -> Self {
37        self.checked = checked;
38        self
39    }
40
41    pub fn label(mut self, label: impl Into<SharedString>) -> Self {
42        self.label = Some(label.into());
43        self
44    }
45
46    pub fn size(mut self, size: Size) -> Self {
47        self.size = size;
48        self
49    }
50
51    pub fn color(mut self, color: ColorName) -> Self {
52        self.color = color;
53        self
54    }
55
56    pub fn disabled(mut self, disabled: bool) -> Self {
57        self.disabled = disabled;
58        self
59    }
60
61    pub fn on_change(
62        mut self,
63        handler: impl Fn(&ClickEvent, &mut Window, &mut App) + 'static,
64    ) -> Self {
65        self.on_change = Some(Box::new(handler));
66        self
67    }
68}
69
70impl RenderOnce for Radio {
71    fn render(self, _window: &mut Window, cx: &mut App) -> impl IntoElement {
72        let t = theme(cx);
73        let outer = control_box_size(self.size);
74        let accent = t.color(self.color, t.primary_shade());
75
76        let mut ring = div()
77            .w(px(outer))
78            .h(px(outer))
79            .rounded(px(outer))
80            .flex()
81            .items_center()
82            .justify_center();
83        if self.checked {
84            ring = ring.bg(accent.hsla()).child(
85                div()
86                    .w(px(outer * 0.36))
87                    .h(px(outer * 0.36))
88                    .rounded(px(outer))
89                    .bg(t.white.hsla()),
90            );
91        } else {
92            ring = ring
93                .bg(t.surface().hsla())
94                .border_1()
95                .border_color(t.border().hsla());
96        }
97
98        let mut row = div()
99            .id(self.id)
100            .flex()
101            .items_center()
102            .gap(px(8.0))
103            .child(ring);
104        if let Some(label) = self.label {
105            row = row.child(
106                div()
107                    .text_size(px(t.font_size(self.size)))
108                    .text_color(t.text().hsla())
109                    .child(label),
110            );
111        }
112
113        let element = if self.disabled {
114            row.opacity(0.5)
115        } else {
116            if let Some(handler) = self.on_change {
117                row = row.on_click(handler);
118            }
119            row
120        };
121
122        element
123            .probe("Radio")
124            .attr("size", self.size.label())
125            .attr_if("checked", self.checked)
126            .attr_if("disabled", self.disabled)
127    }
128}