1use super::*;
4
5type StyleFn<'a> = Box<dyn Fn(&Theme, iced_toggler::Status) -> iced_toggler::Style + 'a>;
6
7const SWITCH_ON_ICON_SVG: &[u8] = br##"
8<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24">
9 <path d="M9.55 18.2 3.65 12.3 5.275 10.675 9.55 14.95 18.725 5.775 20.35 7.4Z"/>
10</svg>
11"##;
12
13const SWITCH_OFF_ICON_SVG: &[u8] = br##"
14<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24">
15 <path d="M6.4 19.2 4.8 17.6 10.4 12 4.8 6.4 6.4 4.8 12 10.4 17.6 4.8 19.2 6.4 13.6 12 19.2 17.6 17.6 19.2 12 13.6Z"/>
16</svg>
17"##;
18
19pub struct Toggler<'a, Message, Renderer = iced_widget::Renderer>
21where
22 Renderer: core_text::Renderer,
23{
24 is_toggled: bool,
25 on_toggle: Option<Box<dyn Fn(bool) -> Message + 'a>>,
26 on_toggle_with_origin: Option<Box<dyn Fn(bool, Point) -> Message + 'a>>,
27 label: Option<text::Fragment<'a>>,
28 width: Length,
29 track_height: f32,
30 spacing: f32,
31 text_size: Option<Pixels>,
32 text_line_height: LineHeight,
33 text_alignment: text::Alignment,
34 text_shaping: text::Shaping,
35 text_wrapping: text::Wrapping,
36 font: Option<Renderer::Font>,
37 icons: bool,
38 show_only_selected_icon: bool,
39 style: StyleFn<'a>,
40}
41
42impl<Message, Renderer> std::fmt::Debug for Toggler<'_, Message, Renderer>
43where
44 Renderer: core_text::Renderer,
45{
46 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
47 f.debug_struct("Toggler")
48 .field("is_toggled", &self.is_toggled)
49 .field("has_on_toggle", &self.on_toggle.is_some())
50 .field(
51 "has_on_toggle_with_origin",
52 &self.on_toggle_with_origin.is_some(),
53 )
54 .field("has_label", &self.label.is_some())
55 .field("width", &self.width)
56 .field("track_height", &self.track_height)
57 .field("spacing", &self.spacing)
58 .field("text_size", &self.text_size)
59 .field("text_line_height", &self.text_line_height)
60 .field("text_alignment", &self.text_alignment)
61 .field("icons", &self.icons)
62 .field("show_only_selected_icon", &self.show_only_selected_icon)
63 .finish_non_exhaustive()
64 }
65}
66
67impl<'a, Message, Renderer> Toggler<'a, Message, Renderer>
68where
69 Renderer: core_text::Renderer,
70{
71 pub fn new(is_toggled: bool) -> Self {
72 Self {
73 is_toggled,
74 on_toggle: None,
75 on_toggle_with_origin: None,
76 label: None,
77 width: Length::Shrink,
78 track_height: tokens::component::switch::TRACK_HEIGHT,
79 spacing: f32::from(tokens::component::divider::LIST_ITEM_LEADING_SPACE),
80 text_size: Some(Pixels(tokens::component::switch::LABEL_TEXT_SIZE)),
81 text_line_height: absolute_line_height(
82 tokens::component::switch::LABEL_TEXT_LINE_HEIGHT,
83 ),
84 text_alignment: text::Alignment::Default,
85 text_shaping: text::Shaping::default(),
86 text_wrapping: text::Wrapping::default(),
87 font: None,
88 icons: false,
89 show_only_selected_icon: false,
90 style: Box::new(toggler_style::default),
91 }
92 }
93
94 pub fn label(mut self, label: impl text::IntoFragment<'a>) -> Self {
95 self.label = Some(label.into_fragment());
96 self
97 }
98
99 pub fn on_toggle(mut self, on_toggle: impl Fn(bool) -> Message + 'a) -> Self {
100 self.on_toggle = Some(Box::new(on_toggle));
101 self.on_toggle_with_origin = None;
102 self
103 }
104
105 pub fn on_toggle_with_origin(
106 mut self,
107 on_toggle: impl Fn(bool, Point) -> Message + 'a,
108 ) -> Self {
109 self.on_toggle = None;
110 self.on_toggle_with_origin = Some(Box::new(on_toggle));
111 self
112 }
113
114 pub fn on_toggle_maybe(mut self, on_toggle: Option<impl Fn(bool) -> Message + 'a>) -> Self {
115 self.on_toggle = on_toggle.map(|on_toggle| Box::new(on_toggle) as _);
116 self.on_toggle_with_origin = None;
117 self
118 }
119
120 pub fn size(mut self, size: impl Into<Pixels>) -> Self {
121 self.track_height = size.into().0;
122 self
123 }
124
125 pub fn width(mut self, width: impl Into<Length>) -> Self {
126 self.width = width.into();
127 self
128 }
129
130 pub fn spacing(mut self, spacing: impl Into<Pixels>) -> Self {
131 self.spacing = spacing.into().0;
132 self
133 }
134
135 pub fn text_size(mut self, text_size: impl Into<Pixels>) -> Self {
136 self.text_size = Some(text_size.into());
137 self
138 }
139
140 pub fn text_line_height(mut self, line_height: impl Into<LineHeight>) -> Self {
141 self.text_line_height = line_height.into();
142 self
143 }
144
145 pub fn text_alignment(mut self, alignment: impl Into<text::Alignment>) -> Self {
146 self.text_alignment = alignment.into();
147 self
148 }
149
150 pub fn text_shaping(mut self, shaping: text::Shaping) -> Self {
151 self.text_shaping = shaping;
152 self
153 }
154
155 pub fn text_wrapping(mut self, wrapping: text::Wrapping) -> Self {
156 self.text_wrapping = wrapping;
157 self
158 }
159
160 pub fn font(mut self, font: impl Into<Renderer::Font>) -> Self {
161 self.font = Some(font.into());
162 self
163 }
164
165 pub fn icons(mut self, icons: bool) -> Self {
166 self.icons = icons;
167 self
168 }
169
170 pub fn show_only_selected_icon(mut self, show_only_selected_icon: bool) -> Self {
171 self.show_only_selected_icon = show_only_selected_icon;
172 self
173 }
174
175 pub fn style(
176 mut self,
177 style: impl Fn(&Theme, iced_toggler::Status) -> iced_toggler::Style + 'a,
178 ) -> Self {
179 self.style = Box::new(style);
180 self
181 }
182}
183
184impl<Message, Renderer> Toggler<'_, Message, Renderer>
185where
186 Renderer: core_text::Renderer,
187{
188 fn track_size(&self) -> Size {
189 let scale = self.track_height / tokens::component::switch::TRACK_HEIGHT;
190
191 Size::new(
192 tokens::component::switch::TRACK_WIDTH * scale,
193 self.track_height,
194 )
195 }
196
197 fn handle_size_for(&self, is_toggled: bool, is_pressed: bool) -> f32 {
198 if is_pressed {
199 tokens::component::switch::PRESSED_HANDLE_SIZE
200 } else if self.icons || (self.show_only_selected_icon && is_toggled) {
201 tokens::component::switch::WITH_ICON_HANDLE_SIZE
202 } else if is_toggled {
203 tokens::component::switch::SELECTED_HANDLE_SIZE
204 } else {
205 tokens::component::switch::UNSELECTED_HANDLE_SIZE
206 }
207 }
208
209 fn shows_icons(&self) -> bool {
210 self.icons || self.show_only_selected_icon
211 }
212
213 fn shows_off_icon(&self) -> bool {
214 self.icons && !self.show_only_selected_icon
215 }
216
217 fn current_status(&self, bounds: Rectangle, cursor: mouse::Cursor) -> iced_toggler::Status {
218 if !self.has_on_toggle() {
219 iced_toggler::Status::Disabled {
220 is_toggled: self.is_toggled,
221 }
222 } else if cursor.is_over(bounds) {
223 iced_toggler::Status::Hovered {
224 is_toggled: self.is_toggled,
225 }
226 } else {
227 iced_toggler::Status::Active {
228 is_toggled: self.is_toggled,
229 }
230 }
231 }
232
233 fn has_on_toggle(&self) -> bool {
234 self.on_toggle.is_some() || self.on_toggle_with_origin.is_some()
235 }
236
237 fn toggle_message(&self, is_toggled: bool, origin: Point) -> Option<Message> {
238 if let Some(on_toggle) = &self.on_toggle_with_origin {
239 Some((on_toggle)(is_toggled, origin))
240 } else {
241 self.on_toggle
242 .as_ref()
243 .map(|on_toggle| (on_toggle)(is_toggled))
244 }
245 }
246}
247
248fn toggler_event_origin(event: &Event, bounds: Rectangle, cursor: mouse::Cursor) -> Point {
249 cursor
250 .position()
251 .or(match event {
252 Event::Touch(
253 touch::Event::FingerPressed { position, .. }
254 | touch::Event::FingerLifted { position, .. },
255 ) => Some(*position),
256 _ => None,
257 })
258 .unwrap_or_else(|| bounds.center())
259}
260
261impl<Message, Renderer> Widget<Message, Theme, Renderer> for Toggler<'_, Message, Renderer>
262where
263 Renderer: core_text::Renderer + core_svg::Renderer,
264{
265 fn tag(&self) -> tree::Tag {
266 tree::Tag::of::<SelectionState<Renderer::Paragraph, iced_toggler::Status>>()
267 }
268
269 fn state(&self) -> tree::State {
270 let mut state =
271 SelectionState::<Renderer::Paragraph, iced_toggler::Status>::new(self.is_toggled);
272
273 state.size = AnimatedScalar::new(self.handle_size_for(self.is_toggled, false));
274
275 tree::State::new(state)
276 }
277
278 fn size(&self) -> Size<Length> {
279 Size {
280 width: self.width,
281 height: Length::Shrink,
282 }
283 }
284
285 fn layout(
286 &mut self,
287 tree: &mut Tree,
288 renderer: &Renderer,
289 limits: &layout::Limits,
290 ) -> layout::Node {
291 let track_size = self.track_size();
292
293 layout::next_to_each_other(
294 &limits.width(self.width),
295 if self.label.is_some() {
296 self.spacing
297 } else {
298 0.0
299 },
300 |_| layout::Node::new(track_size),
301 |limits| {
302 if let Some(label) = self.label.as_deref() {
303 let state = tree
304 .state
305 .downcast_mut::<SelectionState<Renderer::Paragraph, iced_toggler::Status>>(
306 );
307
308 core_widget::text::layout(
309 &mut state.text,
310 renderer,
311 limits,
312 label,
313 core_widget::text::Format {
314 width: self.width,
315 height: Length::Shrink,
316 line_height: self.text_line_height,
317 size: self.text_size,
318 font: self.font,
319 align_x: self.text_alignment,
320 align_y: alignment::Vertical::Top,
321 shaping: self.text_shaping,
322 wrapping: self.text_wrapping,
323 },
324 )
325 } else {
326 layout::Node::new(Size::ZERO)
327 }
328 },
329 )
330 }
331
332 fn update(
333 &mut self,
334 tree: &mut Tree,
335 event: &Event,
336 layout: Layout<'_>,
337 cursor: mouse::Cursor,
338 _renderer: &Renderer,
339 _clipboard: &mut dyn Clipboard,
340 shell: &mut Shell<'_, Message>,
341 _viewport: &Rectangle,
342 ) {
343 let state = tree
344 .state
345 .downcast_mut::<SelectionState<Renderer::Paragraph, iced_toggler::Status>>();
346 let hit_bounds =
347 selection_control_hit_bounds(layout, tokens::component::switch::STATE_LAYER_SIZE);
348
349 match event {
350 Event::Mouse(mouse::Event::ButtonPressed(mouse::Button::Left))
351 | Event::Touch(touch::Event::FingerPressed { .. })
352 if self.has_on_toggle() && press_is_over(event, hit_bounds, cursor) =>
353 {
354 state.is_pressed = true;
355 state.press_origin = Some(toggler_event_origin(event, layout.bounds(), cursor));
356 shell.capture_event();
357 shell.request_redraw();
358 }
359 Event::Mouse(mouse::Event::ButtonReleased(mouse::Button::Left))
360 | Event::Touch(touch::Event::FingerLifted { .. })
361 if state.is_pressed =>
362 {
363 let is_released_over = release_is_over(event, hit_bounds, cursor);
364 let origin = state
365 .press_origin
366 .unwrap_or_else(|| toggler_event_origin(event, layout.bounds(), cursor));
367
368 state.is_pressed = false;
369 state.press_origin = None;
370
371 if is_released_over
372 && let Some(message) = self.toggle_message(!self.is_toggled, origin)
373 {
374 shell.publish(message);
375 }
376
377 shell.capture_event();
378 shell.request_redraw();
379 }
380 Event::Touch(touch::Event::FingerLost { .. }) if state.is_pressed => {
381 state.is_pressed = false;
382 state.press_origin = None;
383 shell.request_redraw();
384 }
385 _ => {}
386 }
387
388 let now = match event {
389 Event::Window(window::Event::RedrawRequested(now)) => Some(*now),
390 _ => None,
391 };
392
393 if state.target != self.is_toggled {
394 let now = now.unwrap_or_else(Instant::now);
395
396 state.target = self.is_toggled;
397 state.position.set_target(
398 bool_value(self.is_toggled),
399 now,
400 duration_ms(tokens::component::switch::HANDLE_POSITION_TRANSITION_DURATION_MS),
401 tokens::component::switch::HANDLE_POSITION_TRANSITION_EASING,
402 );
403 state.color.set_target(
404 bool_value(self.is_toggled),
405 now,
406 duration_ms(tokens::component::switch::TRACK_COLOR_TRANSITION_DURATION_MS),
407 tokens::motion::EASING_LINEAR,
408 );
409 state.size.set_target(
410 self.handle_size_for(self.is_toggled, state.is_pressed),
411 now,
412 if state.is_pressed {
413 duration_ms(
414 tokens::component::switch::PRESSED_HANDLE_SIZE_TRANSITION_DURATION_MS,
415 )
416 } else {
417 duration_ms(tokens::component::switch::HANDLE_SIZE_TRANSITION_DURATION_MS)
418 },
419 if state.is_pressed {
420 tokens::motion::EASING_LINEAR
421 } else {
422 tokens::motion::EASING_STANDARD
423 },
424 );
425 state.icon.set_target(
426 bool_value(self.is_toggled),
427 now,
428 duration_ms(tokens::component::switch::ICON_TRANSFORM_TRANSITION_DURATION_MS),
429 tokens::motion::EASING_STANDARD,
430 );
431 state.icon_opacity.set_target(
432 bool_value(self.is_toggled),
433 now,
434 duration_ms(tokens::component::switch::ICON_OPACITY_TRANSITION_DURATION_MS),
435 tokens::motion::EASING_LINEAR,
436 );
437 shell.request_redraw();
438 }
439
440 let target_handle_size = self.handle_size_for(self.is_toggled, state.is_pressed);
441
442 if (state.size.to - target_handle_size).abs() > f32::EPSILON {
443 let now = now.unwrap_or_else(Instant::now);
444
445 state.size.set_target(
446 target_handle_size,
447 now,
448 if state.is_pressed {
449 duration_ms(
450 tokens::component::switch::PRESSED_HANDLE_SIZE_TRANSITION_DURATION_MS,
451 )
452 } else {
453 duration_ms(tokens::component::switch::HANDLE_SIZE_TRANSITION_DURATION_MS)
454 },
455 if state.is_pressed {
456 tokens::motion::EASING_LINEAR
457 } else {
458 tokens::motion::EASING_STANDARD
459 },
460 );
461 shell.request_redraw();
462 }
463
464 let current_status = self.current_status(hit_bounds, cursor);
465
466 if let Some(now) = now {
467 if state.advance(now) {
468 shell.request_redraw();
469 }
470
471 state.last_status = Some(current_status);
472 } else if state
473 .last_status
474 .is_some_and(|status| status != current_status)
475 || state.is_animating()
476 {
477 shell.request_redraw();
478 }
479 }
480
481 fn mouse_interaction(
482 &self,
483 _tree: &Tree,
484 layout: Layout<'_>,
485 cursor: mouse::Cursor,
486 _viewport: &Rectangle,
487 _renderer: &Renderer,
488 ) -> mouse::Interaction {
489 let hit_bounds =
490 selection_control_hit_bounds(layout, tokens::component::switch::STATE_LAYER_SIZE);
491
492 if cursor.is_over(hit_bounds) {
493 if self.has_on_toggle() {
494 mouse::Interaction::Pointer
495 } else {
496 mouse::Interaction::NotAllowed
497 }
498 } else {
499 mouse::Interaction::default()
500 }
501 }
502
503 fn draw(
504 &self,
505 tree: &Tree,
506 renderer: &mut Renderer,
507 theme: &Theme,
508 defaults: &renderer::Style,
509 layout: Layout<'_>,
510 _cursor: mouse::Cursor,
511 viewport: &Rectangle,
512 ) {
513 let state = tree
514 .state
515 .downcast_ref::<SelectionState<Renderer::Paragraph, iced_toggler::Status>>();
516
517 let mut children = layout.children();
518 let toggler_layout = children.next().unwrap();
519 let bounds = toggler_layout.bounds();
520
521 let status = state.last_status.unwrap_or(iced_toggler::Status::Disabled {
522 is_toggled: self.is_toggled,
523 });
524 let unselected_status = match status {
525 iced_toggler::Status::Active { .. } => {
526 iced_toggler::Status::Active { is_toggled: false }
527 }
528 iced_toggler::Status::Hovered { .. } => {
529 iced_toggler::Status::Hovered { is_toggled: false }
530 }
531 iced_toggler::Status::Disabled { .. } => {
532 iced_toggler::Status::Disabled { is_toggled: false }
533 }
534 };
535 let selected_status = match status {
536 iced_toggler::Status::Active { .. } => {
537 iced_toggler::Status::Active { is_toggled: true }
538 }
539 iced_toggler::Status::Hovered { .. } => {
540 iced_toggler::Status::Hovered { is_toggled: true }
541 }
542 iced_toggler::Status::Disabled { .. } => {
543 iced_toggler::Status::Disabled { is_toggled: true }
544 }
545 };
546
547 let current_style = (self.style)(theme, status);
548 let unselected_style = (self.style)(theme, unselected_status);
549 let selected_style = (self.style)(theme, selected_status);
550
551 let color_progress = state.color.value.clamp(0.0, 1.0);
552 let scale = bounds.height / tokens::component::switch::TRACK_HEIGHT;
553 let colors = theme.colors();
554 let is_disabled = matches!(status, iced_toggler::Status::Disabled { .. });
555 let track_radius = current_style
556 .border_radius
557 .unwrap_or_else(|| border::Radius::new(bounds.height / 2.0));
558 let track_border_width = lerp(
559 unselected_style.background_border_width,
560 selected_style.background_border_width,
561 color_progress,
562 );
563 let track_border_color = mix(
564 unselected_style.background_border_color,
565 selected_style.background_border_color,
566 color_progress,
567 );
568 let track_background = mix(
569 solid_color(unselected_style.background),
570 solid_color(selected_style.background),
571 color_progress,
572 );
573
574 renderer.fill_quad(
575 renderer::Quad {
576 bounds,
577 border: Border {
578 radius: track_radius,
579 width: track_border_width,
580 color: track_border_color,
581 },
582 ..renderer::Quad::default()
583 },
584 track_background,
585 );
586
587 let handle_size = state.size.value * scale;
588 let center_x = bounds.x
589 + scale
590 * (tokens::component::switch::TRACK_HEIGHT / 2.0
591 + (tokens::component::switch::TRACK_WIDTH
592 - tokens::component::switch::TRACK_HEIGHT)
593 * state.position.value);
594 let center_y = bounds.center_y();
595 let handle_bounds = Rectangle {
596 x: center_x - handle_size / 2.0,
597 y: center_y - handle_size / 2.0,
598 width: handle_size,
599 height: handle_size,
600 };
601 let handle_background = mix(
602 solid_color(unselected_style.foreground),
603 solid_color(selected_style.foreground),
604 color_progress,
605 );
606 let handle_border_width = lerp(
607 unselected_style.foreground_border_width,
608 selected_style.foreground_border_width,
609 color_progress,
610 );
611 let handle_border_color = mix(
612 unselected_style.foreground_border_color,
613 selected_style.foreground_border_color,
614 color_progress,
615 );
616
617 let state_layer_opacity = if is_disabled {
618 0.0
619 } else if state.is_pressed {
620 tokens::state::PRESSED_STATE_LAYER_OPACITY
621 } else if matches!(status, iced_toggler::Status::Hovered { .. }) {
622 tokens::state::HOVER_STATE_LAYER_OPACITY
623 } else {
624 0.0
625 };
626
627 if state_layer_opacity > 0.0 {
628 let state_layer_size = tokens::component::switch::STATE_LAYER_SIZE * scale;
629 let state_layer_color = if self.is_toggled {
630 colors.primary.color
631 } else {
632 colors.surface.text
633 };
634
635 renderer.fill_quad(
636 renderer::Quad {
637 bounds: Rectangle {
638 x: center_x - state_layer_size / 2.0,
639 y: center_y - state_layer_size / 2.0,
640 width: state_layer_size,
641 height: state_layer_size,
642 },
643 border: Border {
644 radius: border::Radius::new(state_layer_size / 2.0),
645 ..Border::default()
646 },
647 ..renderer::Quad::default()
648 },
649 alpha_color(state_layer_color, state_layer_opacity),
650 );
651 }
652
653 renderer.fill_quad(
654 renderer::Quad {
655 bounds: handle_bounds,
656 border: Border {
657 radius: border::Radius::new(handle_size / 2.0),
658 width: handle_border_width,
659 color: handle_border_color,
660 },
661 ..renderer::Quad::default()
662 },
663 handle_background,
664 );
665
666 if self.shows_icons() {
667 let icon_progress = state.icon.value.clamp(0.0, 1.0);
668 let selected_icon_opacity = state.icon_opacity.value.clamp(0.0, 1.0);
669 let unselected_icon_opacity = if self.shows_off_icon() {
670 1.0 - selected_icon_opacity
671 } else {
672 0.0
673 };
674 let selected_icon_color = if is_disabled {
675 alpha_color(
676 colors.surface.text,
677 tokens::component::switch::DISABLED_SELECTED_ICON_OPACITY,
678 )
679 } else {
680 colors.primary.container_text
681 };
682 let unselected_icon_color = if is_disabled {
683 alpha_color(
684 colors.surface.container.highest,
685 tokens::component::switch::DISABLED_UNSELECTED_ICON_OPACITY,
686 )
687 } else {
688 colors.surface.container.highest
689 };
690
691 if selected_icon_opacity > 0.0 {
692 let icon_scale = 0.82 + 0.18 * icon_progress;
693 let icon_size = tokens::component::switch::SELECTED_ICON_SIZE * scale * icon_scale;
694
695 renderer.draw_svg(
696 core_svg::Svg::new(core_svg::Handle::from_memory(SWITCH_ON_ICON_SVG))
697 .color(selected_icon_color)
698 .opacity(selected_icon_opacity),
699 scaled_rect(handle_bounds, icon_size, icon_size),
700 *viewport,
701 );
702 }
703
704 if unselected_icon_opacity > 0.0 {
705 let icon_size = tokens::component::switch::UNSELECTED_ICON_SIZE * scale;
706
707 renderer.draw_svg(
708 core_svg::Svg::new(core_svg::Handle::from_memory(SWITCH_OFF_ICON_SVG))
709 .color(unselected_icon_color)
710 .opacity(unselected_icon_opacity),
711 scaled_rect(handle_bounds, icon_size, icon_size),
712 *viewport,
713 );
714 }
715 }
716
717 if self.label.is_none() {
718 return;
719 }
720
721 let label_layout = children.next().unwrap();
722
723 core_widget::text::draw(
724 renderer,
725 defaults,
726 label_layout.bounds(),
727 state.text.raw(),
728 core_widget::text::Style {
729 color: current_style.text_color,
730 },
731 viewport,
732 );
733 }
734
735 fn operate(
736 &mut self,
737 _tree: &mut Tree,
738 layout: Layout<'_>,
739 _renderer: &Renderer,
740 operation: &mut dyn core_widget::Operation,
741 ) {
742 if let Some(label) = self.label.as_deref() {
743 operation.text(None, layout.bounds(), label);
744 }
745 }
746}
747
748impl<'a, Message, Renderer> From<Toggler<'a, Message, Renderer>>
749 for Element<'a, Message, Theme, Renderer>
750where
751 Message: 'a,
752 Renderer: core_text::Renderer + core_svg::Renderer + 'a,
753{
754 fn from(toggler: Toggler<'a, Message, Renderer>) -> Self {
755 Element::new(toggler)
756 }
757}
758
759pub fn control<'a, Message, Renderer>(is_toggled: bool) -> Toggler<'a, Message, Renderer>
760where
761 Renderer: core_text::Renderer + core_svg::Renderer + 'a,
762{
763 Toggler::new(is_toggled)
764 .size(tokens::component::switch::TRACK_HEIGHT)
765 .spacing(f32::from(
766 tokens::component::divider::LIST_ITEM_LEADING_SPACE,
767 ))
768 .text_size(tokens::component::switch::LABEL_TEXT_SIZE)
769 .text_line_height(absolute_line_height(
770 tokens::component::switch::LABEL_TEXT_LINE_HEIGHT,
771 ))
772 .show_only_selected_icon(true)
773 .style(toggler_style::default)
774}
775
776pub fn standard<'a, Message, Renderer>(
777 is_toggled: bool,
778 label: impl text::IntoFragment<'a>,
779 on_toggle: impl Fn(bool) -> Message + 'a,
780) -> Element<'a, Message, Theme, Renderer>
781where
782 Message: 'a,
783 Renderer: iced_widget::core::Renderer + core_text::Renderer + core_svg::Renderer + 'a,
784{
785 Container::new(control(is_toggled).label(label).on_toggle(on_toggle))
786 .center_y(Length::Fixed(tokens::component::switch::STATE_LAYER_SIZE))
787 .into()
788}
789
790pub fn standard_with_origin<'a, Message, Renderer>(
791 is_toggled: bool,
792 label: impl text::IntoFragment<'a>,
793 on_toggle: impl Fn(bool, Point) -> Message + 'a,
794) -> Element<'a, Message, Theme, Renderer>
795where
796 Message: 'a,
797 Renderer: iced_widget::core::Renderer + core_text::Renderer + core_svg::Renderer + 'a,
798{
799 Container::new(
800 control(is_toggled)
801 .label(label)
802 .on_toggle_with_origin(on_toggle),
803 )
804 .center_y(Length::Fixed(tokens::component::switch::STATE_LAYER_SIZE))
805 .into()
806}