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