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