egui/widgets/
radio_button.rs

1use crate::*;
2
3/// One out of several alternatives, either selected or not.
4///
5/// Usually you'd use [`Ui::radio_value`] or [`Ui::radio`] instead.
6///
7/// ```
8/// # egui::__run_test_ui(|ui| {
9/// #[derive(PartialEq)]
10/// enum Enum { First, Second, Third }
11/// let mut my_enum = Enum::First;
12///
13/// ui.radio_value(&mut my_enum, Enum::First, "First");
14///
15/// // is equivalent to:
16///
17/// if ui.add(egui::RadioButton::new(my_enum == Enum::First, "First")).clicked() {
18///     my_enum = Enum::First
19/// }
20/// # });
21/// ```
22#[must_use = "You should put this widget in an ui with `ui.add(widget);`"]
23pub struct RadioButton {
24    checked: bool,
25    text: WidgetText,
26}
27
28impl RadioButton {
29    pub fn new(checked: bool, text: impl Into<WidgetText>) -> Self {
30        Self {
31            checked,
32            text: text.into(),
33        }
34    }
35}
36
37impl Widget for RadioButton {
38    fn ui(self, ui: &mut Ui) -> Response {
39        let Self { checked, text } = self;
40
41        let spacing = &ui.spacing();
42        let icon_width = spacing.icon_width;
43        let icon_spacing = spacing.icon_spacing;
44
45        let (galley, mut desired_size) = if text.is_empty() {
46            (None, vec2(icon_width, 0.0))
47        } else {
48            let total_extra = vec2(icon_width + icon_spacing, 0.0);
49
50            let wrap_width = ui.available_width() - total_extra.x;
51            let text = text.into_galley(ui, None, wrap_width, TextStyle::Button);
52
53            let mut desired_size = total_extra + text.size();
54            desired_size = desired_size.at_least(spacing.interact_size);
55
56            (Some(text), desired_size)
57        };
58
59        desired_size = desired_size.at_least(Vec2::splat(spacing.interact_size.y));
60        desired_size.y = desired_size.y.max(icon_width);
61        let (rect, response) = ui.allocate_exact_size(desired_size, Sense::click());
62
63        response.widget_info(|| {
64            WidgetInfo::selected(
65                WidgetType::RadioButton,
66                ui.is_enabled(),
67                checked,
68                galley.as_ref().map_or("", |x| x.text()),
69            )
70        });
71
72        if ui.is_rect_visible(rect) {
73            // let visuals = ui.style().interact_selectable(&response, checked); // too colorful
74            let visuals = ui.style().interact(&response);
75
76            let (small_icon_rect, big_icon_rect) = ui.spacing().icon_rectangles(rect);
77
78            let painter = ui.painter();
79
80            painter.add(epaint::CircleShape {
81                center: big_icon_rect.center(),
82                radius: big_icon_rect.width() / 2.0 + visuals.expansion,
83                fill: visuals.bg_fill,
84                stroke: visuals.bg_stroke,
85            });
86
87            if checked {
88                painter.add(epaint::CircleShape {
89                    center: small_icon_rect.center(),
90                    radius: small_icon_rect.width() / 3.0,
91                    fill: visuals.fg_stroke.color, // Intentional to use stroke and not fill
92                    // fill: ui.visuals().selection.stroke.color, // too much color
93                    stroke: Default::default(),
94                });
95            }
96
97            if let Some(galley) = galley {
98                let text_pos = pos2(
99                    rect.min.x + icon_width + icon_spacing,
100                    rect.center().y - 0.5 * galley.size().y,
101                );
102                ui.painter().galley(text_pos, galley, visuals.text_color());
103            }
104        }
105
106        response
107    }
108}