1use super::*;
4use iced_widget::button::{Catalog, Status, Style, StyleFn};
5use iced_widget::core::overlay;
6use iced_widget::graphics::geometry;
7use iced_widget::renderer::wgpu::primitive;
8
9use super::ripple::{PressRippleState, RippleConfig, RippleStart, RippleStyle, draw_ripples};
10use super::support::{AnimatedScalar, duration_ms};
11use crate::utils::state_layer;
12
13#[cfg(test)]
14use super::ripple::{
15 PressRipple as Ripple, RIPPLE_CLIP_MAX_SAMPLES, RIPPLE_CLIP_MIN_SAMPLES, clamped_ripple_origin,
16 get_ripple_start_radius, ripple_clip_sample_count, ripple_noise_phases, ripple_target_radius,
17 rounded_rect_span_at_y, unbounded_ripple_target_radius,
18};
19
20const TOUCH_CLICK_SLOP: f32 = 8.0;
21
22pub struct Button<'a, Message, Renderer = iced_widget::Renderer>
24where
25 Renderer: geometry::Renderer,
26{
27 content: Element<'a, Message, Theme, Renderer>,
28 on_press: Option<OnPress<'a, Message>>,
29 width: Length,
30 height: Length,
31 padding: Padding,
32 clip: bool,
33 class: <Theme as Catalog>::Class<'a>,
34 status: Option<Status>,
35}
36
37enum OnPress<'a, Message> {
38 Direct(Message),
39 Closure(Box<dyn Fn() -> Message + 'a>),
40}
41
42impl<Message, Renderer> std::fmt::Debug for Button<'_, Message, Renderer>
43where
44 Renderer: geometry::Renderer,
45{
46 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
47 f.debug_struct("Button")
48 .field("width", &self.width)
49 .field("height", &self.height)
50 .field("padding", &self.padding)
51 .field("clip", &self.clip)
52 .field("status", &self.status)
53 .finish_non_exhaustive()
54 }
55}
56
57impl<Message: Clone> OnPress<'_, Message> {
58 fn get(&self) -> Message {
59 match self {
60 OnPress::Direct(message) => message.clone(),
61 OnPress::Closure(f) => f(),
62 }
63 }
64}
65
66impl<'a, Message, Renderer> Button<'a, Message, Renderer>
67where
68 Message: Clone + 'a,
69 Renderer: geometry::Renderer + 'a,
70{
71 pub fn new(content: impl Into<Element<'a, Message, Theme, Renderer>>) -> Self {
73 let content = content.into();
74 let size = content.as_widget().size_hint();
75
76 Self {
77 content,
78 on_press: None,
79 width: size.width.fluid(),
80 height: size.height.fluid(),
81 padding: iced_widget::button::DEFAULT_PADDING,
82 clip: false,
83 class: <Theme as Catalog>::default(),
84 status: None,
85 }
86 }
87
88 pub fn width(mut self, width: impl Into<Length>) -> Self {
90 self.width = width.into();
91 self
92 }
93
94 pub fn height(mut self, height: impl Into<Length>) -> Self {
96 self.height = height.into();
97 self
98 }
99
100 pub fn padding<P: Into<Padding>>(mut self, padding: P) -> Self {
102 self.padding = padding.into();
103 self
104 }
105
106 pub fn on_press(mut self, on_press: Message) -> Self {
108 self.on_press = Some(OnPress::Direct(on_press));
109 self
110 }
111
112 pub fn on_press_with(mut self, on_press: impl Fn() -> Message + 'a) -> Self {
114 self.on_press = Some(OnPress::Closure(Box::new(on_press)));
115 self
116 }
117
118 pub fn on_press_maybe(mut self, on_press: Option<Message>) -> Self {
120 self.on_press = on_press.map(OnPress::Direct);
121 self
122 }
123
124 pub fn clip(mut self, clip: bool) -> Self {
126 self.clip = clip;
127 self
128 }
129
130 #[must_use]
132 pub fn style(mut self, style: impl Fn(&Theme, Status) -> Style + 'a) -> Self {
133 self.class = Box::new(style) as StyleFn<'a, Theme>;
134 self
135 }
136}
137
138#[derive(Debug, Clone)]
139struct ButtonState {
140 is_pressed: bool,
141 is_hovered: bool,
142 state_layer_opacity: AnimatedScalar,
143 touch_press_position: Option<Point>,
144 ripples: PressRippleState,
145 last_status: Option<Status>,
146 now: Option<Instant>,
147}
148
149impl Default for ButtonState {
150 fn default() -> Self {
151 Self {
152 is_pressed: false,
153 is_hovered: false,
154 state_layer_opacity: AnimatedScalar::new(0.0),
155 touch_press_position: None,
156 ripples: PressRippleState::default(),
157 last_status: None,
158 now: None,
159 }
160 }
161}
162
163impl ButtonState {
164 fn press(&mut self, origin: Point, now: Instant) {
165 self.is_pressed = true;
166 self.ripples.press(
167 origin,
168 now,
169 RippleStart::Additive,
170 RippleStyle::material_patterned(),
171 );
172 self.now = Some(now);
173 }
174
175 fn release(&mut self, now: Instant) {
176 self.is_pressed = false;
177 self.touch_press_position = None;
178
179 self.ripples.release(now);
180
181 self.now = Some(now);
182 }
183
184 fn cancel(&mut self, now: Instant) {
185 self.release(now);
186 }
187
188 fn sync_hover(&mut self, is_hovered: bool, now: Instant) -> bool {
189 if self.is_hovered == is_hovered {
190 return false;
191 }
192
193 self.is_hovered = is_hovered;
194 self.animate_state_layer(now);
195
196 true
197 }
198
199 fn snap_state_layer_to_hover_target(&mut self) {
200 self.state_layer_opacity
201 .snap_to(button_hover_state_layer_target(self.is_hovered));
202 }
203
204 fn animate_state_layer(&mut self, now: Instant) {
205 self.state_layer_opacity.set_target(
206 button_hover_state_layer_target(self.is_hovered),
207 now,
208 duration_ms(tokens::state::STATE_LAYER_TRANSITION_DURATION_MS),
209 tokens::motion::EASING_LINEAR,
210 );
211 }
212
213 fn advance(&mut self, now: Instant) -> bool {
214 self.now = Some(now);
215 self.prune(now);
216
217 self.state_layer_opacity.advance(now) || self.has_visible_ripples(now)
218 }
219
220 fn state_layer_opacity(&self) -> f32 {
221 self.state_layer_opacity.value
222 }
223
224 fn prune(&mut self, now: Instant) {
225 self.ripples.prune(now);
226 }
227
228 fn has_visible_ripples(&self, now: Instant) -> bool {
229 self.ripples.has_visible_ripples(now)
230 }
231
232 #[cfg(test)]
233 fn ripple_opacity(&self, now: Instant) -> f32 {
234 self.ripples.ripple_opacity(now)
235 }
236}
237
238impl<Message, Renderer> Widget<Message, Theme, Renderer> for Button<'_, Message, Renderer>
239where
240 Message: Clone,
241 Renderer: geometry::Renderer + primitive::Renderer,
242{
243 fn tag(&self) -> tree::Tag {
244 tree::Tag::of::<ButtonState>()
245 }
246
247 fn state(&self) -> tree::State {
248 tree::State::new(ButtonState::default())
249 }
250
251 fn children(&self) -> Vec<Tree> {
252 vec![Tree::new(&self.content)]
253 }
254
255 fn diff(&self, tree: &mut Tree) {
256 tree.diff_children(std::slice::from_ref(&self.content));
257 }
258
259 fn size(&self) -> Size<Length> {
260 Size {
261 width: self.width,
262 height: self.height,
263 }
264 }
265
266 fn layout(
267 &mut self,
268 tree: &mut Tree,
269 renderer: &Renderer,
270 limits: &layout::Limits,
271 ) -> layout::Node {
272 layout::padded(limits, self.width, self.height, self.padding, |limits| {
273 self.content
274 .as_widget_mut()
275 .layout(&mut tree.children[0], renderer, limits)
276 })
277 }
278
279 fn operate(
280 &mut self,
281 tree: &mut Tree,
282 layout: Layout<'_>,
283 renderer: &Renderer,
284 operation: &mut dyn core_widget::Operation,
285 ) {
286 operation.container(None, layout.bounds());
287 operation.traverse(&mut |operation| {
288 self.content.as_widget_mut().operate(
289 &mut tree.children[0],
290 layout.children().next().unwrap(),
291 renderer,
292 operation,
293 );
294 });
295 }
296
297 fn update(
298 &mut self,
299 tree: &mut Tree,
300 event: &Event,
301 layout: Layout<'_>,
302 cursor: mouse::Cursor,
303 renderer: &Renderer,
304 clipboard: &mut dyn Clipboard,
305 shell: &mut Shell<'_, Message>,
306 viewport: &Rectangle,
307 ) {
308 self.content.as_widget_mut().update(
309 &mut tree.children[0],
310 event,
311 layout.children().next().unwrap(),
312 cursor,
313 renderer,
314 clipboard,
315 shell,
316 viewport,
317 );
318
319 if shell.is_event_captured() {
320 return;
321 }
322
323 let bounds = layout.bounds();
324 let now = match event {
325 Event::Window(window::Event::RedrawRequested(now)) => Some(*now),
326 _ => None,
327 };
328 let now_or_current = || now.unwrap_or_else(Instant::now);
329 let state = tree.state.downcast_mut::<ButtonState>();
330 let is_touch_event = matches!(event, Event::Touch(_));
331 let is_hovered = self.on_press.is_some() && !is_touch_event && cursor.is_over(bounds);
332 let should_snap_initial_redraw_hover =
333 button_should_snap_initial_redraw_hover(event, state, is_hovered);
334
335 if button_should_sync_hover(event, cursor) {
336 if state.sync_hover(is_hovered, now_or_current()) {
337 if should_snap_initial_redraw_hover {
338 state.snap_state_layer_to_hover_target();
339 }
340
341 shell.request_redraw();
342 }
343 }
344
345 match event {
346 Event::Mouse(mouse::Event::ButtonPressed(mouse::Button::Left))
347 | Event::Touch(touch::Event::FingerPressed { .. }) => {
348 if self.on_press.is_some() {
349 if let Some(origin) = press_origin(event, bounds, cursor) {
350 state.press(origin, now_or_current());
351 state.touch_press_position = touch_position(event, cursor);
352 shell.capture_event();
353 shell.request_redraw();
354 }
355 }
356 }
357 Event::Touch(touch::Event::FingerMoved { .. }) => {
358 if state.is_pressed
359 && touch_moved_beyond_click_slop(state.touch_press_position, event, cursor)
360 {
361 state.cancel(now_or_current());
362 shell.request_redraw();
363 }
364 }
365 Event::Mouse(mouse::Event::ButtonReleased(mouse::Button::Left))
366 | Event::Touch(touch::Event::FingerLifted { .. }) => {
367 if state.is_pressed {
368 state.release(now_or_current());
369
370 if release_is_over(event, bounds, cursor) {
371 if let Some(on_press) = &self.on_press {
372 shell.publish(on_press.get());
373 }
374 }
375
376 shell.capture_event();
377 shell.request_redraw();
378 }
379 }
380 Event::Touch(touch::Event::FingerLost { .. }) => {
381 if state.is_pressed {
382 state.cancel(now_or_current());
383 shell.request_redraw();
384 }
385 }
386 _ => {}
387 }
388
389 let current_status =
390 button_status(self.on_press.is_some(), state.is_pressed, bounds, cursor);
391
392 if let Some(now) = now {
393 if state.advance(now) {
394 shell.request_redraw();
395 }
396
397 self.status = Some(current_status);
398 state.last_status = Some(current_status);
399 } else if self.status.is_some_and(|status| status != current_status)
400 || state.state_layer_opacity.is_animating()
401 || state.has_visible_ripples(state.now.unwrap_or_else(Instant::now))
402 {
403 shell.request_redraw();
404 }
405 }
406
407 fn draw(
408 &self,
409 tree: &Tree,
410 renderer: &mut Renderer,
411 theme: &Theme,
412 _style: &renderer::Style,
413 layout: Layout<'_>,
414 cursor: mouse::Cursor,
415 viewport: &Rectangle,
416 ) {
417 let bounds = layout.bounds();
418
419 if bounds.width < 1.0 || bounds.height < 1.0 {
420 return;
421 }
422
423 let state = tree.state.downcast_ref::<ButtonState>();
424 let status = button_status(self.on_press.is_some(), state.is_pressed, bounds, cursor);
425 let now = state.now.unwrap_or_else(Instant::now);
426 let style = button_draw_style(theme, &self.class, status);
427 let content_layout = layout.children().next().unwrap();
428
429 if style.background.is_some() || style.border.width > 0.0 || style.shadow.color.a > 0.0 {
430 renderer.fill_quad(
431 renderer::Quad {
432 bounds,
433 border: style.border,
434 shadow: style.shadow,
435 snap: style.snap,
436 },
437 style
438 .background
439 .unwrap_or(Background::Color(Color::TRANSPARENT)),
440 );
441 }
442
443 let viewport = if self.clip {
444 bounds.intersection(viewport).unwrap_or(*viewport)
445 } else {
446 *viewport
447 };
448
449 self.content.as_widget().draw(
450 &tree.children[0],
451 renderer,
452 theme,
453 &renderer::Style {
454 text_color: style.text_color,
455 },
456 content_layout,
457 cursor,
458 &viewport,
459 );
460
461 draw_button_state_layer(
462 renderer,
463 bounds,
464 &style,
465 button_state_layer_opacity_for_draw(state, status),
466 );
467
468 draw_ripples(
469 renderer,
470 bounds,
471 &state.ripples,
472 style.text_color,
473 RippleConfig::bounded(style.border.radius),
474 now,
475 );
476 }
477
478 fn mouse_interaction(
479 &self,
480 _tree: &Tree,
481 layout: Layout<'_>,
482 cursor: mouse::Cursor,
483 _viewport: &Rectangle,
484 _renderer: &Renderer,
485 ) -> mouse::Interaction {
486 if cursor.is_over(layout.bounds()) && self.on_press.is_some() {
487 mouse::Interaction::Pointer
488 } else {
489 mouse::Interaction::default()
490 }
491 }
492
493 fn overlay<'b>(
494 &'b mut self,
495 tree: &'b mut Tree,
496 layout: Layout<'b>,
497 renderer: &Renderer,
498 viewport: &Rectangle,
499 translation: iced_widget::core::Vector,
500 ) -> Option<overlay::Element<'b, Message, Theme, Renderer>> {
501 self.content.as_widget_mut().overlay(
502 &mut tree.children[0],
503 layout.children().next().unwrap(),
504 renderer,
505 viewport,
506 translation,
507 )
508 }
509}
510
511impl<'a, Message, Renderer> From<Button<'a, Message, Renderer>>
512 for Element<'a, Message, Theme, Renderer>
513where
514 Message: Clone + 'a,
515 Renderer: geometry::Renderer + primitive::Renderer + 'a,
516{
517 fn from(button: Button<'a, Message, Renderer>) -> Self {
518 Element::new(button)
519 }
520}
521
522fn button_hover_state_layer_target(is_hovered: bool) -> f32 {
523 if is_hovered {
524 tokens::state::HOVER_STATE_LAYER_OPACITY
525 } else {
526 0.0
527 }
528}
529
530fn button_should_sync_hover(event: &Event, cursor: mouse::Cursor) -> bool {
531 match event {
532 Event::Window(window::Event::RedrawRequested(_)) => {
533 !matches!(cursor, mouse::Cursor::Unavailable)
534 }
535 Event::Mouse(_) | Event::Touch(_) => true,
536 _ => false,
537 }
538}
539
540fn button_should_snap_initial_redraw_hover(
541 event: &Event,
542 state: &ButtonState,
543 is_hovered: bool,
544) -> bool {
545 matches!(event, Event::Window(window::Event::RedrawRequested(_)))
546 && state.last_status.is_none()
547 && is_hovered
548}
549
550fn button_state_layer_opacity_for_draw(state: &ButtonState, status: Status) -> f32 {
551 if matches!(status, Status::Hovered) && state.last_status.is_none() {
552 button_hover_state_layer_target(true)
553 } else {
554 state.state_layer_opacity()
555 }
556}
557
558fn button_status(
559 is_enabled: bool,
560 is_pressed: bool,
561 bounds: Rectangle,
562 cursor: mouse::Cursor,
563) -> Status {
564 if !is_enabled {
565 Status::Disabled
566 } else if cursor.is_over(bounds) {
567 if is_pressed {
568 Status::Pressed
569 } else {
570 Status::Hovered
571 }
572 } else {
573 Status::Active
574 }
575}
576
577fn button_draw_style(
578 theme: &Theme,
579 class: &<Theme as Catalog>::Class<'_>,
580 status: Status,
581) -> Style {
582 if matches!(status, Status::Pressed | Status::Hovered) {
583 return theme.style(class, Status::Active);
584 }
585
586 theme.style(class, status)
587}
588
589fn draw_button_state_layer<Renderer>(
590 renderer: &mut Renderer,
591 bounds: Rectangle,
592 style: &Style,
593 opacity: f32,
594) where
595 Renderer: geometry::Renderer,
596{
597 if opacity <= 0.0 {
598 return;
599 }
600
601 renderer.fill_quad(
602 renderer::Quad {
603 bounds,
604 border: Border {
605 radius: style.border.radius,
606 ..Border::default()
607 },
608 snap: style.snap,
609 ..renderer::Quad::default()
610 },
611 state_layer(style.text_color, opacity),
612 );
613}
614
615fn press_origin(event: &Event, bounds: Rectangle, cursor: mouse::Cursor) -> Option<Point> {
616 if cursor.position().is_some() {
617 return cursor.position_in(bounds);
618 }
619
620 if cursor.is_levitating() {
621 return None;
622 }
623
624 match event {
625 Event::Touch(touch::Event::FingerPressed { position, .. }) => {
626 relative_position(*position, bounds)
627 }
628 _ => cursor.position_in(bounds),
629 }
630}
631
632fn release_is_over(event: &Event, bounds: Rectangle, cursor: mouse::Cursor) -> bool {
633 if cursor.position().is_some() {
634 return cursor.is_over(bounds);
635 }
636
637 if cursor.is_levitating() {
638 return false;
639 }
640
641 match event {
642 Event::Touch(touch::Event::FingerLifted { position, .. }) => bounds.contains(*position),
643 _ => cursor.is_over(bounds),
644 }
645}
646
647fn touch_position(event: &Event, cursor: mouse::Cursor) -> Option<Point> {
648 if cursor.position().is_some() {
649 return cursor.position();
650 }
651
652 if cursor.is_levitating() {
653 return None;
654 }
655
656 match event {
657 Event::Touch(
658 touch::Event::FingerPressed { position, .. }
659 | touch::Event::FingerMoved { position, .. }
660 | touch::Event::FingerLifted { position, .. }
661 | touch::Event::FingerLost { position, .. },
662 ) => Some(*position),
663 _ => None,
664 }
665}
666
667fn touch_moved_beyond_click_slop(
668 press_position: Option<Point>,
669 event: &Event,
670 cursor: mouse::Cursor,
671) -> bool {
672 let Some(press_position) = press_position else {
673 return false;
674 };
675 let Some(position) = touch_position(event, cursor) else {
676 return false;
677 };
678 let dx = position.x - press_position.x;
679 let dy = position.y - press_position.y;
680
681 dx * dx + dy * dy > TOUCH_CLICK_SLOP * TOUCH_CLICK_SLOP
682}
683
684fn relative_position(position: Point, bounds: Rectangle) -> Option<Point> {
685 bounds
686 .contains(position)
687 .then(|| position - iced_widget::core::Vector::new(bounds.x, bounds.y))
688}
689
690#[cfg(test)]
691#[path = "../../../tests/widget/component/button.rs"]
692mod tests;
693
694fn standard<'a, Message, Renderer>(
695 label: impl text::IntoFragment<'a>,
696 style: fn(&Theme, iced_widget::button::Status) -> iced_widget::button::Style,
697) -> Button<'a, Message, Renderer>
698where
699 Message: Clone + 'a,
700 Renderer: geometry::Renderer + core_text::Renderer + 'a,
701{
702 Button::new(text_button_content(
703 label,
704 tokens::component::button::LABEL_TEXT_SIZE,
705 tokens::component::button::LABEL_TEXT_LINE_HEIGHT,
706 tokens::component::button::CONTAINER_HEIGHT,
707 tokens::component::button::LEADING_SPACE,
708 ))
709 .height(Length::Fixed(tokens::component::button::CONTAINER_HEIGHT))
710 .padding(Padding::ZERO)
711 .style(style)
712}
713
714fn chip<'a, Message, Renderer>(
715 label: impl text::IntoFragment<'a>,
716 style: fn(&Theme, iced_widget::button::Status) -> iced_widget::button::Style,
717) -> Button<'a, Message, Renderer>
718where
719 Message: Clone + 'a,
720 Renderer: geometry::Renderer + core_text::Renderer + 'a,
721{
722 Button::new(text_button_content(
723 label,
724 tokens::component::chip::LABEL_TEXT_SIZE,
725 tokens::component::chip::LABEL_TEXT_LINE_HEIGHT,
726 tokens::component::chip::CONTAINER_HEIGHT,
727 tokens::component::chip::LEADING_SPACE,
728 ))
729 .height(Length::Fixed(tokens::component::chip::CONTAINER_HEIGHT))
730 .padding(Padding::ZERO)
731 .style(style)
732}
733
734fn icon<'a, Message, Renderer>(
735 icon: impl text::IntoFragment<'a>,
736 style: fn(&Theme, iced_widget::button::Status) -> iced_widget::button::Style,
737) -> Button<'a, Message, Renderer>
738where
739 Message: Clone + 'a,
740 Renderer: geometry::Renderer + core_text::Renderer + 'a,
741 iced_widget::core::Font: Into<Renderer::Font>,
742{
743 Button::new(icon_button_content(icon))
744 .width(Length::Fixed(
745 tokens::component::icon_button::CONTAINER_WIDTH,
746 ))
747 .height(Length::Fixed(
748 tokens::component::icon_button::CONTAINER_HEIGHT,
749 ))
750 .padding(Padding::ZERO)
751 .style(style)
752}
753
754fn sized_fab<'a, Message, Renderer>(
755 icon_content: impl text::IntoFragment<'a>,
756 width: f32,
757 height: f32,
758 icon_size: f32,
759 style: fn(&Theme, iced_widget::button::Status) -> iced_widget::button::Style,
760) -> Button<'a, Message, Renderer>
761where
762 Message: Clone + 'a,
763 Renderer: geometry::Renderer + core_text::Renderer + 'a,
764 iced_widget::core::Font: Into<Renderer::Font>,
765{
766 Button::new(sized_fab_content(icon_content, width, height, icon_size))
767 .width(Length::Fixed(width))
768 .height(Length::Fixed(height))
769 .padding(Padding::ZERO)
770 .style(style)
771}
772
773fn fab<'a, Message, Renderer>(
774 icon_content: impl text::IntoFragment<'a>,
775 style: fn(&Theme, iced_widget::button::Status) -> iced_widget::button::Style,
776) -> Button<'a, Message, Renderer>
777where
778 Message: Clone + 'a,
779 Renderer: geometry::Renderer + core_text::Renderer + 'a,
780 iced_widget::core::Font: Into<Renderer::Font>,
781{
782 Button::new(fab_content(icon_content))
783 .width(Length::Fixed(tokens::component::fab::CONTAINER_WIDTH))
784 .height(Length::Fixed(tokens::component::fab::CONTAINER_HEIGHT))
785 .padding(Padding::ZERO)
786 .style(style)
787}
788
789fn small_fab<'a, Message, Renderer>(
790 icon_content: impl text::IntoFragment<'a>,
791 style: fn(&Theme, iced_widget::button::Status) -> iced_widget::button::Style,
792) -> Button<'a, Message, Renderer>
793where
794 Message: Clone + 'a,
795 Renderer: geometry::Renderer + core_text::Renderer + 'a,
796 iced_widget::core::Font: Into<Renderer::Font>,
797{
798 sized_fab(
799 icon_content,
800 tokens::component::fab::SMALL_CONTAINER_WIDTH,
801 tokens::component::fab::SMALL_CONTAINER_HEIGHT,
802 tokens::component::fab::SMALL_ICON_SIZE,
803 style,
804 )
805}
806
807fn large_fab<'a, Message, Renderer>(
808 icon_content: impl text::IntoFragment<'a>,
809 style: fn(&Theme, iced_widget::button::Status) -> iced_widget::button::Style,
810) -> Button<'a, Message, Renderer>
811where
812 Message: Clone + 'a,
813 Renderer: geometry::Renderer + core_text::Renderer + 'a,
814 iced_widget::core::Font: Into<Renderer::Font>,
815{
816 sized_fab(
817 icon_content,
818 tokens::component::fab::LARGE_CONTAINER_WIDTH,
819 tokens::component::fab::LARGE_CONTAINER_HEIGHT,
820 tokens::component::fab::LARGE_ICON_SIZE,
821 style,
822 )
823}
824
825fn extended_fab<'a, Message, Renderer>(
826 label: impl text::IntoFragment<'a>,
827 style: fn(&Theme, iced_widget::button::Status) -> iced_widget::button::Style,
828) -> Button<'a, Message, Renderer>
829where
830 Message: Clone + 'a,
831 Renderer: geometry::Renderer + core_text::Renderer + 'a,
832{
833 Button::new(extended_fab_content(label))
834 .height(Length::Fixed(
835 tokens::component::fab::EXTENDED_CONTAINER_HEIGHT,
836 ))
837 .padding(Padding::ZERO)
838 .style(style)
839}
840
841fn extended_fab_with_icon<'a, Message, Renderer>(
842 icon_content: impl text::IntoFragment<'a>,
843 label: impl text::IntoFragment<'a>,
844 style: fn(&Theme, iced_widget::button::Status) -> iced_widget::button::Style,
845) -> Button<'a, Message, Renderer>
846where
847 Message: Clone + 'a,
848 Renderer: geometry::Renderer + core_text::Renderer + 'a,
849 iced_widget::core::Font: Into<Renderer::Font>,
850{
851 Button::new(extended_fab_icon_content(icon_content, label))
852 .height(Length::Fixed(
853 tokens::component::fab::EXTENDED_CONTAINER_HEIGHT,
854 ))
855 .padding(Padding::ZERO)
856 .style(style)
857}
858
859pub fn elevated<'a, Message, Renderer>(
860 label: impl text::IntoFragment<'a>,
861) -> Button<'a, Message, Renderer>
862where
863 Message: Clone + 'a,
864 Renderer: geometry::Renderer + core_text::Renderer + 'a,
865{
866 standard(label, button_style::elevated)
867}
868
869pub fn filled<'a, Message, Renderer>(
870 label: impl text::IntoFragment<'a>,
871) -> Button<'a, Message, Renderer>
872where
873 Message: Clone + 'a,
874 Renderer: geometry::Renderer + core_text::Renderer + 'a,
875{
876 standard(label, button_style::filled)
877}
878
879pub fn filled_action<'a, Message, Renderer>(
880 label: impl text::IntoFragment<'a>,
881 on_press: Message,
882) -> Element<'a, Message, Theme, Renderer>
883where
884 Message: Clone + 'a,
885 Renderer: geometry::Renderer + primitive::Renderer + core_text::Renderer + 'a,
886{
887 filled(label).on_press(on_press).into()
888}
889
890pub fn maybe_action<'a, Message, Renderer>(
892 button: Button<'a, Message, Renderer>,
893 enabled: bool,
894 on_press: Message,
895) -> Element<'a, Message, Theme, Renderer>
896where
897 Message: Clone + 'a,
898 Renderer: geometry::Renderer + primitive::Renderer + 'a,
899{
900 button.on_press_maybe(enabled.then_some(on_press)).into()
901}
902
903pub fn enabled_actions<'a, Message, Renderer>(
905 enabled: bool,
906 on_press: Message,
907 buttons: impl IntoIterator<Item = Button<'a, Message, Renderer>>,
908) -> Vec<Element<'a, Message, Theme, Renderer>>
909where
910 Message: Clone + 'a,
911 Renderer: geometry::Renderer + primitive::Renderer + 'a,
912{
913 buttons
914 .into_iter()
915 .map(|button| maybe_action(button, enabled, on_press.clone()))
916 .collect()
917}
918
919pub fn filled_tonal<'a, Message, Renderer>(
920 label: impl text::IntoFragment<'a>,
921) -> Button<'a, Message, Renderer>
922where
923 Message: Clone + 'a,
924 Renderer: geometry::Renderer + core_text::Renderer + 'a,
925{
926 standard(label, button_style::filled_tonal)
927}
928
929pub fn outlined<'a, Message, Renderer>(
930 label: impl text::IntoFragment<'a>,
931) -> Button<'a, Message, Renderer>
932where
933 Message: Clone + 'a,
934 Renderer: geometry::Renderer + core_text::Renderer + 'a,
935{
936 standard(label, button_style::outlined)
937}
938
939pub fn outlined_action<'a, Message, Renderer>(
940 label: impl text::IntoFragment<'a>,
941 on_press: Message,
942) -> Element<'a, Message, Theme, Renderer>
943where
944 Message: Clone + 'a,
945 Renderer: geometry::Renderer + primitive::Renderer + core_text::Renderer + 'a,
946{
947 outlined(label).on_press(on_press).into()
948}
949
950pub fn text<'a, Message, Renderer>(
951 label: impl text::IntoFragment<'a>,
952) -> Button<'a, Message, Renderer>
953where
954 Message: Clone + 'a,
955 Renderer: geometry::Renderer + core_text::Renderer + 'a,
956{
957 standard(label, button_style::text)
958}
959
960pub fn text_action<'a, Message, Renderer>(
961 label: impl text::IntoFragment<'a>,
962 on_press: Message,
963) -> Element<'a, Message, Theme, Renderer>
964where
965 Message: Clone + 'a,
966 Renderer: geometry::Renderer + primitive::Renderer + core_text::Renderer + 'a,
967{
968 text(label).on_press(on_press).into()
969}
970
971pub fn icon_button<'a, Message, Renderer>(
972 icon_content: impl text::IntoFragment<'a>,
973) -> Button<'a, Message, Renderer>
974where
975 Message: Clone + 'a,
976 Renderer: geometry::Renderer + core_text::Renderer + 'a,
977 iced_widget::core::Font: Into<Renderer::Font>,
978{
979 icon(icon_content, button_style::icon)
980}
981
982pub fn filled_icon<'a, Message, Renderer>(
983 icon_content: impl text::IntoFragment<'a>,
984) -> Button<'a, Message, Renderer>
985where
986 Message: Clone + 'a,
987 Renderer: geometry::Renderer + core_text::Renderer + 'a,
988 iced_widget::core::Font: Into<Renderer::Font>,
989{
990 icon(icon_content, button_style::filled_icon)
991}
992
993pub fn filled_tonal_icon<'a, Message, Renderer>(
994 icon_content: impl text::IntoFragment<'a>,
995) -> Button<'a, Message, Renderer>
996where
997 Message: Clone + 'a,
998 Renderer: geometry::Renderer + core_text::Renderer + 'a,
999 iced_widget::core::Font: Into<Renderer::Font>,
1000{
1001 icon(icon_content, button_style::filled_tonal_icon)
1002}
1003
1004pub fn outlined_icon<'a, Message, Renderer>(
1005 icon_content: impl text::IntoFragment<'a>,
1006) -> Button<'a, Message, Renderer>
1007where
1008 Message: Clone + 'a,
1009 Renderer: geometry::Renderer + core_text::Renderer + 'a,
1010 iced_widget::core::Font: Into<Renderer::Font>,
1011{
1012 icon(icon_content, button_style::outlined_icon)
1013}
1014
1015pub fn primary_fab<'a, Message, Renderer>(
1016 icon_content: impl text::IntoFragment<'a>,
1017) -> Button<'a, Message, Renderer>
1018where
1019 Message: Clone + 'a,
1020 Renderer: geometry::Renderer + core_text::Renderer + 'a,
1021 iced_widget::core::Font: Into<Renderer::Font>,
1022{
1023 fab(icon_content, button_style::fab_primary)
1024}
1025
1026pub fn primary_fab_action<'a, Message, Renderer>(
1027 icon_content: impl text::IntoFragment<'a>,
1028 on_press: Message,
1029) -> Element<'a, Message, Theme, Renderer>
1030where
1031 Message: Clone + 'a,
1032 Renderer: geometry::Renderer + primitive::Renderer + core_text::Renderer + 'a,
1033 iced_widget::core::Font: Into<Renderer::Font>,
1034{
1035 primary_fab(icon_content).on_press(on_press).into()
1036}
1037
1038pub fn primary_small_fab<'a, Message, Renderer>(
1039 icon_content: impl text::IntoFragment<'a>,
1040) -> Button<'a, Message, Renderer>
1041where
1042 Message: Clone + 'a,
1043 Renderer: geometry::Renderer + core_text::Renderer + 'a,
1044 iced_widget::core::Font: Into<Renderer::Font>,
1045{
1046 small_fab(icon_content, button_style::fab_primary_small)
1047}
1048
1049pub fn primary_large_fab<'a, Message, Renderer>(
1050 icon_content: impl text::IntoFragment<'a>,
1051) -> Button<'a, Message, Renderer>
1052where
1053 Message: Clone + 'a,
1054 Renderer: geometry::Renderer + core_text::Renderer + 'a,
1055 iced_widget::core::Font: Into<Renderer::Font>,
1056{
1057 large_fab(icon_content, button_style::fab_primary_large)
1058}
1059
1060pub fn secondary_fab<'a, Message, Renderer>(
1061 icon_content: impl text::IntoFragment<'a>,
1062) -> Button<'a, Message, Renderer>
1063where
1064 Message: Clone + 'a,
1065 Renderer: geometry::Renderer + core_text::Renderer + 'a,
1066 iced_widget::core::Font: Into<Renderer::Font>,
1067{
1068 fab(icon_content, button_style::fab_secondary)
1069}
1070
1071pub fn secondary_small_fab<'a, Message, Renderer>(
1072 icon_content: impl text::IntoFragment<'a>,
1073) -> Button<'a, Message, Renderer>
1074where
1075 Message: Clone + 'a,
1076 Renderer: geometry::Renderer + core_text::Renderer + 'a,
1077 iced_widget::core::Font: Into<Renderer::Font>,
1078{
1079 small_fab(icon_content, button_style::fab_secondary_small)
1080}
1081
1082pub fn secondary_large_fab<'a, Message, Renderer>(
1083 icon_content: impl text::IntoFragment<'a>,
1084) -> Button<'a, Message, Renderer>
1085where
1086 Message: Clone + 'a,
1087 Renderer: geometry::Renderer + core_text::Renderer + 'a,
1088 iced_widget::core::Font: Into<Renderer::Font>,
1089{
1090 large_fab(icon_content, button_style::fab_secondary_large)
1091}
1092
1093pub fn tertiary_fab<'a, Message, Renderer>(
1094 icon_content: impl text::IntoFragment<'a>,
1095) -> Button<'a, Message, Renderer>
1096where
1097 Message: Clone + 'a,
1098 Renderer: geometry::Renderer + core_text::Renderer + 'a,
1099 iced_widget::core::Font: Into<Renderer::Font>,
1100{
1101 fab(icon_content, button_style::fab_tertiary)
1102}
1103
1104pub fn tertiary_small_fab<'a, Message, Renderer>(
1105 icon_content: impl text::IntoFragment<'a>,
1106) -> Button<'a, Message, Renderer>
1107where
1108 Message: Clone + 'a,
1109 Renderer: geometry::Renderer + core_text::Renderer + 'a,
1110 iced_widget::core::Font: Into<Renderer::Font>,
1111{
1112 small_fab(icon_content, button_style::fab_tertiary_small)
1113}
1114
1115pub fn tertiary_large_fab<'a, Message, Renderer>(
1116 icon_content: impl text::IntoFragment<'a>,
1117) -> Button<'a, Message, Renderer>
1118where
1119 Message: Clone + 'a,
1120 Renderer: geometry::Renderer + core_text::Renderer + 'a,
1121 iced_widget::core::Font: Into<Renderer::Font>,
1122{
1123 large_fab(icon_content, button_style::fab_tertiary_large)
1124}
1125
1126pub fn surface_fab<'a, Message, Renderer>(
1127 icon_content: impl text::IntoFragment<'a>,
1128) -> Button<'a, Message, Renderer>
1129where
1130 Message: Clone + 'a,
1131 Renderer: geometry::Renderer + core_text::Renderer + 'a,
1132 iced_widget::core::Font: Into<Renderer::Font>,
1133{
1134 fab(icon_content, button_style::fab_surface)
1135}
1136
1137pub fn surface_small_fab<'a, Message, Renderer>(
1138 icon_content: impl text::IntoFragment<'a>,
1139) -> Button<'a, Message, Renderer>
1140where
1141 Message: Clone + 'a,
1142 Renderer: geometry::Renderer + core_text::Renderer + 'a,
1143 iced_widget::core::Font: Into<Renderer::Font>,
1144{
1145 small_fab(icon_content, button_style::fab_surface_small)
1146}
1147
1148pub fn surface_large_fab<'a, Message, Renderer>(
1149 icon_content: impl text::IntoFragment<'a>,
1150) -> Button<'a, Message, Renderer>
1151where
1152 Message: Clone + 'a,
1153 Renderer: geometry::Renderer + core_text::Renderer + 'a,
1154 iced_widget::core::Font: Into<Renderer::Font>,
1155{
1156 large_fab(icon_content, button_style::fab_surface_large)
1157}
1158
1159pub fn primary_extended_fab<'a, Message, Renderer>(
1160 label: impl text::IntoFragment<'a>,
1161) -> Button<'a, Message, Renderer>
1162where
1163 Message: Clone + 'a,
1164 Renderer: geometry::Renderer + core_text::Renderer + 'a,
1165{
1166 extended_fab(label, button_style::extended_fab_primary)
1167}
1168
1169pub fn primary_extended_fab_with_icon<'a, Message, Renderer>(
1170 icon_content: impl text::IntoFragment<'a>,
1171 label: impl text::IntoFragment<'a>,
1172) -> Button<'a, Message, Renderer>
1173where
1174 Message: Clone + 'a,
1175 Renderer: geometry::Renderer + core_text::Renderer + 'a,
1176 iced_widget::core::Font: Into<Renderer::Font>,
1177{
1178 extended_fab_with_icon(icon_content, label, button_style::extended_fab_primary)
1179}
1180
1181pub fn secondary_extended_fab<'a, Message, Renderer>(
1182 label: impl text::IntoFragment<'a>,
1183) -> Button<'a, Message, Renderer>
1184where
1185 Message: Clone + 'a,
1186 Renderer: geometry::Renderer + core_text::Renderer + 'a,
1187{
1188 extended_fab(label, button_style::extended_fab_secondary)
1189}
1190
1191pub fn secondary_extended_fab_with_icon<'a, Message, Renderer>(
1192 icon_content: impl text::IntoFragment<'a>,
1193 label: impl text::IntoFragment<'a>,
1194) -> Button<'a, Message, Renderer>
1195where
1196 Message: Clone + 'a,
1197 Renderer: geometry::Renderer + core_text::Renderer + 'a,
1198 iced_widget::core::Font: Into<Renderer::Font>,
1199{
1200 extended_fab_with_icon(icon_content, label, button_style::extended_fab_secondary)
1201}
1202
1203pub fn tertiary_extended_fab<'a, Message, Renderer>(
1204 label: impl text::IntoFragment<'a>,
1205) -> Button<'a, Message, Renderer>
1206where
1207 Message: Clone + 'a,
1208 Renderer: geometry::Renderer + core_text::Renderer + 'a,
1209{
1210 extended_fab(label, button_style::extended_fab_tertiary)
1211}
1212
1213pub fn tertiary_extended_fab_with_icon<'a, Message, Renderer>(
1214 icon_content: impl text::IntoFragment<'a>,
1215 label: impl text::IntoFragment<'a>,
1216) -> Button<'a, Message, Renderer>
1217where
1218 Message: Clone + 'a,
1219 Renderer: geometry::Renderer + core_text::Renderer + 'a,
1220 iced_widget::core::Font: Into<Renderer::Font>,
1221{
1222 extended_fab_with_icon(icon_content, label, button_style::extended_fab_tertiary)
1223}
1224
1225pub fn surface_extended_fab<'a, Message, Renderer>(
1226 label: impl text::IntoFragment<'a>,
1227) -> Button<'a, Message, Renderer>
1228where
1229 Message: Clone + 'a,
1230 Renderer: geometry::Renderer + core_text::Renderer + 'a,
1231{
1232 extended_fab(label, button_style::extended_fab_surface)
1233}
1234
1235pub fn surface_extended_fab_with_icon<'a, Message, Renderer>(
1236 icon_content: impl text::IntoFragment<'a>,
1237 label: impl text::IntoFragment<'a>,
1238) -> Button<'a, Message, Renderer>
1239where
1240 Message: Clone + 'a,
1241 Renderer: geometry::Renderer + core_text::Renderer + 'a,
1242 iced_widget::core::Font: Into<Renderer::Font>,
1243{
1244 extended_fab_with_icon(icon_content, label, button_style::extended_fab_surface)
1245}
1246
1247pub fn assist_chip<'a, Message, Renderer>(
1248 label: impl text::IntoFragment<'a>,
1249) -> Button<'a, Message, Renderer>
1250where
1251 Message: Clone + 'a,
1252 Renderer: geometry::Renderer + core_text::Renderer + 'a,
1253{
1254 chip(label, button_style::assist_chip)
1255}
1256
1257pub fn elevated_assist_chip<'a, Message, Renderer>(
1258 label: impl text::IntoFragment<'a>,
1259) -> Button<'a, Message, Renderer>
1260where
1261 Message: Clone + 'a,
1262 Renderer: geometry::Renderer + core_text::Renderer + 'a,
1263{
1264 chip(label, button_style::elevated_assist_chip)
1265}
1266
1267pub fn suggestion_chip<'a, Message, Renderer>(
1268 label: impl text::IntoFragment<'a>,
1269) -> Button<'a, Message, Renderer>
1270where
1271 Message: Clone + 'a,
1272 Renderer: geometry::Renderer + core_text::Renderer + 'a,
1273{
1274 chip(label, button_style::suggestion_chip)
1275}
1276
1277pub fn elevated_suggestion_chip<'a, Message, Renderer>(
1278 label: impl text::IntoFragment<'a>,
1279) -> Button<'a, Message, Renderer>
1280where
1281 Message: Clone + 'a,
1282 Renderer: geometry::Renderer + core_text::Renderer + 'a,
1283{
1284 chip(label, button_style::elevated_suggestion_chip)
1285}
1286
1287pub fn filter_chip<'a, Message, Renderer>(
1288 label: impl text::IntoFragment<'a>,
1289) -> Button<'a, Message, Renderer>
1290where
1291 Message: Clone + 'a,
1292 Renderer: geometry::Renderer + core_text::Renderer + 'a,
1293{
1294 chip(label, button_style::filter_chip)
1295}
1296
1297pub fn selected_filter_chip<'a, Message, Renderer>(
1298 label: impl text::IntoFragment<'a>,
1299) -> Button<'a, Message, Renderer>
1300where
1301 Message: Clone + 'a,
1302 Renderer: geometry::Renderer + core_text::Renderer + 'a,
1303{
1304 chip(label, button_style::selected_filter_chip)
1305}
1306
1307pub fn input_chip<'a, Message, Renderer>(
1308 label: impl text::IntoFragment<'a>,
1309) -> Button<'a, Message, Renderer>
1310where
1311 Message: Clone + 'a,
1312 Renderer: geometry::Renderer + core_text::Renderer + 'a,
1313{
1314 chip(label, button_style::input_chip)
1315}
1316
1317pub fn selected_input_chip<'a, Message, Renderer>(
1318 label: impl text::IntoFragment<'a>,
1319) -> Button<'a, Message, Renderer>
1320where
1321 Message: Clone + 'a,
1322 Renderer: geometry::Renderer + core_text::Renderer + 'a,
1323{
1324 chip(label, button_style::selected_input_chip)
1325}