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.diff_children(std::slice::from_mut(&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 &mut 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_mut()
190 .layout(&mut tree.children[0], renderer, limits)
191 },
192 )
193 }
194
195 fn operate(
196 &mut 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_mut().operate(
203 &mut tree.children[0],
204 layout.children().nth(1).unwrap(),
205 renderer,
206 operation,
207 );
208 }
209
210 fn update(
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 ) {
221 self.label.as_widget_mut().update(
222 &mut tree.children[0],
223 event,
224 layout.children().nth(1).unwrap(),
225 cursor,
226 renderer,
227 clipboard,
228 shell,
229 viewport,
230 );
231
232 if !shell.is_event_captured() {
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 shell.capture_event();
240 return;
241 }
242 }
243 _ => {}
244 }
245 }
246 }
247
248 fn mouse_interaction(
249 &self,
250 tree: &Tree,
251 layout: Layout<'_>,
252 cursor: mouse::Cursor,
253 viewport: &Rectangle,
254 renderer: &Renderer,
255 ) -> mouse::Interaction {
256 let interaction = self.label.as_widget().mouse_interaction(
257 &tree.children[0],
258 layout.children().nth(1).unwrap(),
259 cursor,
260 viewport,
261 renderer,
262 );
263
264 if interaction == mouse::Interaction::default() {
265 if cursor.is_over(layout.bounds()) {
266 mouse::Interaction::Pointer
267 } else {
268 mouse::Interaction::default()
269 }
270 } else {
271 interaction
272 }
273 }
274
275 fn draw(
276 &self,
277 tree: &Tree,
278 renderer: &mut Renderer,
279 theme: &Theme,
280 style: &renderer::Style,
281 layout: Layout<'_>,
282 cursor: mouse::Cursor,
283 viewport: &Rectangle,
284 ) {
285 let is_mouse_over = cursor.is_over(layout.bounds());
286
287 let mut children = layout.children();
288
289 let custom_style = if is_mouse_over {
290 theme.style(
291 &(),
292 iced_radio::Status::Hovered {
293 is_selected: self.is_selected,
294 },
295 )
296 } else {
297 theme.style(
298 &(),
299 iced_radio::Status::Active {
300 is_selected: self.is_selected,
301 },
302 )
303 };
304
305 {
306 let layout = children.next().unwrap();
307 let bounds = layout.bounds();
308
309 let size = bounds.width;
310 let dot_size = 6.0;
311
312 renderer.fill_quad(
313 renderer::Quad {
314 bounds,
315 border: Border {
316 radius: (size / 2.0).into(),
317 width: custom_style.border_width,
318 color: custom_style.border_color,
319 },
320 ..renderer::Quad::default()
321 },
322 custom_style.background,
323 );
324
325 if self.is_selected {
326 renderer.fill_quad(
327 renderer::Quad {
328 bounds: Rectangle {
329 x: bounds.x + (size - dot_size) / 2.0,
330 y: bounds.y + (size - dot_size) / 2.0,
331 width: dot_size,
332 height: dot_size,
333 },
334 border: border::rounded(dot_size / 2.0),
335 ..renderer::Quad::default()
336 },
337 custom_style.dot_color,
338 );
339 }
340 }
341
342 {
343 let label_layout = children.next().unwrap();
344 self.label.as_widget().draw(
345 &tree.children[0],
346 renderer,
347 theme,
348 style,
349 label_layout,
350 cursor,
351 viewport,
352 );
353 }
354 }
355
356 fn overlay<'b>(
357 &'b mut self,
358 tree: &'b mut Tree,
359 layout: Layout<'b>,
360 renderer: &Renderer,
361 viewport: &Rectangle,
362 translation: Vector,
363 ) -> Option<overlay::Element<'b, Message, Theme, Renderer>> {
364 self.label.as_widget_mut().overlay(
365 &mut tree.children[0],
366 layout.children().nth(1).unwrap(),
367 renderer,
368 viewport,
369 translation,
370 )
371 }
372
373 fn drag_destinations(
374 &self,
375 state: &Tree,
376 layout: Layout<'_>,
377 renderer: &Renderer,
378 dnd_rectangles: &mut iced_core::clipboard::DndDestinationRectangles,
379 ) {
380 self.label.as_widget().drag_destinations(
381 &state.children[0],
382 layout.children().nth(1).unwrap(),
383 renderer,
384 dnd_rectangles,
385 );
386 }
387}
388
389impl<'a, Message, Renderer> From<Radio<'a, Message, Renderer>>
390 for Element<'a, Message, Theme, Renderer>
391where
392 Message: 'a + Clone,
393 Renderer: 'a + iced_core::Renderer,
394{
395 fn from(radio: Radio<'a, Message, Renderer>) -> Element<'a, Message, Theme, Renderer> {
396 Element::new(radio)
397 }
398}