1use crate::Theme;
3use iced::border;
4use iced_core::event::{self, Event};
5use iced_core::layout;
6use iced_core::mouse;
7use iced_core::overlay;
8use iced_core::renderer;
9use iced_core::touch;
10use iced_core::widget::tree::Tree;
11use iced_core::{
12    Border, Clipboard, Element, Layout, Length, Pixels, Rectangle, Shell, Size, Vector, Widget,
13};
14
15use iced_widget::radio as iced_radio;
16pub use iced_widget::radio::Catalog;
17
18pub fn radio<'a, Message: Clone, V, F>(
19    label: impl Into<Element<'a, Message, Theme, crate::Renderer>>,
20    value: V,
21    selected: Option<V>,
22    f: F,
23) -> Radio<'a, Message, crate::Renderer>
24where
25    V: Eq + Copy,
26    F: FnOnce(V) -> Message,
27{
28    Radio::new(label, value, selected, f)
29}
30
31#[allow(missing_debug_implementations)]
89pub struct Radio<'a, Message, Renderer = crate::Renderer>
90where
91    Renderer: iced_core::Renderer,
92{
93    is_selected: bool,
94    on_click: Message,
95    label: Element<'a, Message, Theme, Renderer>,
96    width: Length,
97    size: f32,
98    spacing: f32,
99}
100
101impl<'a, Message, Renderer> Radio<'a, Message, Renderer>
102where
103    Message: Clone,
104    Renderer: iced_core::Renderer,
105{
106    pub const DEFAULT_SIZE: f32 = 16.0;
108
109    pub const DEFAULT_SPACING: f32 = 8.0;
111
112    pub fn new<T, F, V>(label: T, value: V, selected: Option<V>, f: F) -> Self
121    where
122        V: Eq + Copy,
123        F: FnOnce(V) -> Message,
124        T: Into<Element<'a, Message, Theme, Renderer>>,
125    {
126        Radio {
127            is_selected: Some(value) == selected,
128            on_click: f(value),
129            label: label.into(),
130            width: Length::Shrink,
131            size: Self::DEFAULT_SIZE,
132            spacing: Self::DEFAULT_SPACING,
133        }
134    }
135
136    #[must_use]
137    pub fn size(mut self, size: impl Into<Pixels>) -> Self {
139        self.size = size.into().0;
140        self
141    }
142
143    #[must_use]
144    pub fn width(mut self, width: impl Into<Length>) -> Self {
146        self.width = width.into();
147        self
148    }
149
150    #[must_use]
151    pub fn spacing(mut self, spacing: impl Into<Pixels>) -> Self {
153        self.spacing = spacing.into().0;
154        self
155    }
156}
157
158impl<Message, Renderer> Widget<Message, Theme, Renderer> for Radio<'_, Message, Renderer>
159where
160    Message: Clone,
161    Renderer: iced_core::Renderer,
162{
163    fn children(&self) -> Vec<Tree> {
164        vec![Tree::new(&self.label)]
165    }
166
167    fn diff(&mut self, tree: &mut Tree) {
168        tree.children[0].diff(&mut self.label);
169    }
170    fn size(&self) -> Size<Length> {
171        Size {
172            width: self.width,
173            height: Length::Shrink,
174        }
175    }
176
177    fn layout(
178        &self,
179        tree: &mut Tree,
180        renderer: &Renderer,
181        limits: &layout::Limits,
182    ) -> layout::Node {
183        layout::next_to_each_other(
184            &limits.width(self.width),
185            self.spacing,
186            |_| layout::Node::new(Size::new(self.size, self.size)),
187            |limits| {
188                self.label
189                    .as_widget()
190                    .layout(&mut tree.children[0], renderer, limits)
191            },
192        )
193    }
194
195    fn operate(
196        &self,
197        tree: &mut Tree,
198        layout: Layout<'_>,
199        renderer: &Renderer,
200        operation: &mut dyn iced_core::widget::Operation<()>,
201    ) {
202        self.label.as_widget().operate(
203            &mut tree.children[0],
204            layout.children().nth(1).unwrap(),
205            renderer,
206            operation,
207        );
208    }
209
210    fn on_event(
211        &mut self,
212        tree: &mut Tree,
213        event: Event,
214        layout: Layout<'_>,
215        cursor: mouse::Cursor,
216        renderer: &Renderer,
217        clipboard: &mut dyn Clipboard,
218        shell: &mut Shell<'_, Message>,
219        viewport: &Rectangle,
220    ) -> event::Status {
221        let status = self.label.as_widget_mut().on_event(
222            &mut tree.children[0],
223            event.clone(),
224            layout.children().nth(1).unwrap(),
225            cursor,
226            renderer,
227            clipboard,
228            shell,
229            viewport,
230        );
231
232        if status == event::Status::Ignored {
233            match event {
234                Event::Mouse(mouse::Event::ButtonPressed(mouse::Button::Left))
235                | Event::Touch(touch::Event::FingerPressed { .. }) => {
236                    if cursor.is_over(layout.bounds()) {
237                        shell.publish(self.on_click.clone());
238
239                        return event::Status::Captured;
240                    }
241                }
242                _ => {}
243            }
244
245            event::Status::Ignored
246        } else {
247            status
248        }
249    }
250
251    fn mouse_interaction(
252        &self,
253        tree: &Tree,
254        layout: Layout<'_>,
255        cursor: mouse::Cursor,
256        viewport: &Rectangle,
257        renderer: &Renderer,
258    ) -> mouse::Interaction {
259        let interaction = self.label.as_widget().mouse_interaction(
260            &tree.children[0],
261            layout.children().nth(1).unwrap(),
262            cursor,
263            viewport,
264            renderer,
265        );
266
267        if interaction == mouse::Interaction::default() {
268            if cursor.is_over(layout.bounds()) {
269                mouse::Interaction::Pointer
270            } else {
271                mouse::Interaction::default()
272            }
273        } else {
274            interaction
275        }
276    }
277
278    fn draw(
279        &self,
280        tree: &Tree,
281        renderer: &mut Renderer,
282        theme: &Theme,
283        style: &renderer::Style,
284        layout: Layout<'_>,
285        cursor: mouse::Cursor,
286        viewport: &Rectangle,
287    ) {
288        let is_mouse_over = cursor.is_over(layout.bounds());
289
290        let mut children = layout.children();
291
292        let custom_style = if is_mouse_over {
293            theme.style(
294                &(),
295                iced_radio::Status::Hovered {
296                    is_selected: self.is_selected,
297                },
298            )
299        } else {
300            theme.style(
301                &(),
302                iced_radio::Status::Active {
303                    is_selected: self.is_selected,
304                },
305            )
306        };
307
308        {
309            let layout = children.next().unwrap();
310            let bounds = layout.bounds();
311
312            let size = bounds.width;
313            let dot_size = 6.0;
314
315            renderer.fill_quad(
316                renderer::Quad {
317                    bounds,
318                    border: Border {
319                        radius: (size / 2.0).into(),
320                        width: custom_style.border_width,
321                        color: custom_style.border_color,
322                    },
323                    ..renderer::Quad::default()
324                },
325                custom_style.background,
326            );
327
328            if self.is_selected {
329                renderer.fill_quad(
330                    renderer::Quad {
331                        bounds: Rectangle {
332                            x: bounds.x + (size - dot_size) / 2.0,
333                            y: bounds.y + (size - dot_size) / 2.0,
334                            width: dot_size,
335                            height: dot_size,
336                        },
337                        border: border::rounded(dot_size / 2.0),
338                        ..renderer::Quad::default()
339                    },
340                    custom_style.dot_color,
341                );
342            }
343        }
344
345        {
346            let label_layout = children.next().unwrap();
347            self.label.as_widget().draw(
348                &tree.children[0],
349                renderer,
350                theme,
351                style,
352                label_layout,
353                cursor,
354                viewport,
355            );
356        }
357    }
358
359    fn overlay<'b>(
360        &'b mut self,
361        tree: &'b mut Tree,
362        layout: Layout<'_>,
363        renderer: &Renderer,
364        translation: Vector,
365    ) -> Option<overlay::Element<'b, Message, Theme, Renderer>> {
366        self.label.as_widget_mut().overlay(
367            &mut tree.children[0],
368            layout.children().nth(1).unwrap(),
369            renderer,
370            translation,
371        )
372    }
373
374    fn drag_destinations(
375        &self,
376        state: &Tree,
377        layout: Layout<'_>,
378        renderer: &Renderer,
379        dnd_rectangles: &mut iced_core::clipboard::DndDestinationRectangles,
380    ) {
381        self.label.as_widget().drag_destinations(
382            &state.children[0],
383            layout.children().nth(1).unwrap(),
384            renderer,
385            dnd_rectangles,
386        );
387    }
388}
389
390impl<'a, Message, Renderer> From<Radio<'a, Message, Renderer>>
391    for Element<'a, Message, Theme, Renderer>
392where
393    Message: 'a + Clone,
394    Renderer: 'a + iced_core::Renderer,
395{
396    fn from(radio: Radio<'a, Message, Renderer>) -> Element<'a, Message, Theme, Renderer> {
397        Element::new(radio)
398    }
399}