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}