Skip to main content

egui/widgets/
radio_button.rs

1use crate::{
2    Atom, AtomLayout, Atoms, Id, IntoAtoms, NumExt as _, Response, Sense, Ui, Vec2, Widget,
3    WidgetInfo, WidgetType, epaint,
4};
5
6/// One out of several alternatives, either selected or not.
7///
8/// Usually you'd use [`Ui::radio_value`] or [`Ui::radio`] instead.
9///
10/// ```
11/// # egui::__run_test_ui(|ui| {
12/// #[derive(PartialEq)]
13/// enum Enum { First, Second, Third }
14/// let mut my_enum = Enum::First;
15///
16/// ui.radio_value(&mut my_enum, Enum::First, "First");
17///
18/// // is equivalent to:
19///
20/// if ui.add(egui::RadioButton::new(my_enum == Enum::First, "First")).clicked() {
21///     my_enum = Enum::First
22/// }
23/// # });
24/// ```
25#[must_use = "You should put this widget in a ui with `ui.add(widget);`"]
26pub struct RadioButton<'a> {
27    checked: bool,
28    atoms: Atoms<'a>,
29}
30
31impl<'a> RadioButton<'a> {
32    pub fn new(checked: bool, atoms: impl IntoAtoms<'a>) -> Self {
33        Self {
34            checked,
35            atoms: atoms.into_atoms(),
36        }
37    }
38
39    /// Output the [`RadioButton`]'s [`Atoms`].
40    ///
41    /// This includes any images you have on the [`RadioButton`].
42    pub fn atoms(&self) -> &Atoms<'a> {
43        &self.atoms
44    }
45}
46
47impl Widget for RadioButton<'_> {
48    fn ui(self, ui: &mut Ui) -> Response {
49        let Self { checked, mut atoms } = self;
50
51        let spacing = &ui.spacing();
52        let icon_width = spacing.icon_width;
53
54        let mut min_size = Vec2::splat(spacing.interact_size.y);
55        min_size.y = min_size.y.at_least(icon_width);
56
57        // In order to center the checkbox based on min_size we set the icon height to at least min_size.y
58        let mut icon_size = Vec2::splat(icon_width);
59        icon_size.y = icon_size.y.at_least(min_size.y);
60        let rect_id = Id::new("egui::radio_button");
61        atoms.push_left(Atom::custom(rect_id, icon_size));
62
63        let text = atoms.text().map(String::from);
64
65        let mut prepared = AtomLayout::new(atoms)
66            .sense(Sense::click())
67            .min_size(min_size)
68            .allocate(ui);
69
70        prepared.response.widget_info(|| {
71            WidgetInfo::selected(
72                WidgetType::RadioButton,
73                ui.is_enabled(),
74                checked,
75                text.as_deref().unwrap_or(""),
76            )
77        });
78
79        if ui.is_rect_visible(prepared.response.rect) {
80            // let visuals = ui.style().interact_selectable(&response, checked); // too colorful
81            let visuals = *ui.style().interact(&prepared.response);
82
83            prepared.fallback_text_color = visuals.text_color();
84            let response = prepared.paint(ui);
85
86            if let Some(rect) = response.rect(rect_id) {
87                let (small_icon_rect, big_icon_rect) = ui.spacing().icon_rectangles(rect);
88
89                let painter = ui.painter();
90
91                painter.add(epaint::CircleShape {
92                    center: big_icon_rect.center(),
93                    radius: big_icon_rect.width() / 2.0 + visuals.expansion,
94                    fill: visuals.bg_fill,
95                    stroke: visuals.bg_stroke,
96                });
97
98                if checked {
99                    painter.add(epaint::CircleShape {
100                        center: small_icon_rect.center(),
101                        radius: small_icon_rect.width() / 3.0,
102                        fill: visuals.fg_stroke.color, // Intentional to use stroke and not fill
103                        // fill: ui.visuals().selection.stroke.color, // too much color
104                        stroke: Default::default(),
105                    });
106                }
107            }
108            response.response
109        } else {
110            prepared.response
111        }
112    }
113}