iced_widget/
button.rs

1//! Buttons allow your users to perform actions by pressing them.
2//!
3//! # Example
4//! ```no_run
5//! # mod iced { pub mod widget { pub use iced_widget::*; } }
6//! # pub type State = ();
7//! # pub type Element<'a, Message> = iced_widget::core::Element<'a, Message, iced_widget::Theme, iced_widget::Renderer>;
8//! use iced::widget::button;
9//!
10//! #[derive(Clone)]
11//! enum Message {
12//!     ButtonPressed,
13//! }
14//!
15//! fn view(state: &State) -> Element<'_, Message> {
16//!     button("Press me!").on_press(Message::ButtonPressed).into()
17//! }
18//! ```
19//! Allow your users to perform actions by pressing a button.
20use iced_runtime::core::border::Radius;
21use iced_runtime::core::widget::Id;
22use iced_runtime::{keyboard, task, Task};
23#[cfg(feature = "a11y")]
24use std::borrow::Cow;
25
26use crate::core::border::{self, Border};
27use crate::core::event::{self, Event};
28use crate::core::layout;
29use crate::core::mouse;
30use crate::core::overlay;
31use crate::core::renderer;
32use crate::core::theme::palette;
33use crate::core::touch;
34use crate::core::widget::tree::{self, Tree};
35use crate::core::widget::Operation;
36use crate::core::{
37    Background, Clipboard, Color, Element, Layout, Length, Padding, Rectangle,
38    Shadow, Shell, Size, Theme, Vector, Widget,
39};
40
41use iced_renderer::core::widget::operation;
42
43/// A generic widget that produces a message when pressed.
44///
45/// # Example
46/// ```no_run
47/// # mod iced { pub mod widget { pub use iced_widget::*; } }
48/// # pub type State = ();
49/// # pub type Element<'a, Message> = iced_widget::core::Element<'a, Message, iced_widget::Theme, iced_widget::Renderer>;
50/// use iced::widget::button;
51///
52/// #[derive(Clone)]
53/// enum Message {
54///     ButtonPressed,
55/// }
56///
57/// fn view(state: &State) -> Element<'_, Message> {
58///     button("Press me!").on_press(Message::ButtonPressed).into()
59/// }
60/// ```
61///
62/// If a [`Button::on_press`] handler is not set, the resulting [`Button`] will
63/// be disabled:
64///
65/// ```no_run
66/// # mod iced { pub mod widget { pub use iced_widget::*; } }
67/// # pub type State = ();
68/// # pub type Element<'a, Message> = iced_widget::core::Element<'a, Message, iced_widget::Theme, iced_widget::Renderer>;
69/// use iced::widget::button;
70///
71/// #[derive(Clone)]
72/// enum Message {
73///     ButtonPressed,
74/// }
75///
76/// fn view(state: &State) -> Element<'_, Message> {
77///     button("I am disabled!").into()
78/// }
79/// ```
80#[allow(missing_debug_implementations)]
81pub struct Button<'a, Message, Theme = crate::Theme, Renderer = crate::Renderer>
82where
83    Renderer: crate::core::Renderer,
84    Theme: Catalog,
85{
86    content: Element<'a, Message, Theme, Renderer>,
87    on_press: Option<OnPress<'a, Message>>,
88    id: Id,
89    #[cfg(feature = "a11y")]
90    name: Option<Cow<'a, str>>,
91    #[cfg(feature = "a11y")]
92    description: Option<iced_accessibility::Description<'a>>,
93    #[cfg(feature = "a11y")]
94    label: Option<Vec<iced_accessibility::accesskit::NodeId>>,
95    width: Length,
96    height: Length,
97    padding: Padding,
98    clip: bool,
99    class: Theme::Class<'a>,
100}
101
102enum OnPress<'a, Message> {
103    Direct(Message),
104    Closure(Box<dyn Fn() -> Message + 'a>),
105}
106
107impl<'a, Message: Clone> OnPress<'a, Message> {
108    fn get(&self) -> Message {
109        match self {
110            OnPress::Direct(message) => message.clone(),
111            OnPress::Closure(f) => f(),
112        }
113    }
114}
115
116impl<'a, Message, Theme, Renderer> Button<'a, Message, Theme, Renderer>
117where
118    Renderer: crate::core::Renderer,
119    Theme: Catalog,
120{
121    /// Creates a new [`Button`] with the given content.
122    pub fn new(
123        content: impl Into<Element<'a, Message, Theme, Renderer>>,
124    ) -> Self {
125        let content = content.into();
126        let size = content.as_widget().size_hint();
127
128        Button {
129            content,
130            id: Id::unique(),
131            #[cfg(feature = "a11y")]
132            name: None,
133            #[cfg(feature = "a11y")]
134            description: None,
135            #[cfg(feature = "a11y")]
136            label: None,
137            on_press: None,
138            width: size.width.fluid(),
139            height: size.height.fluid(),
140            padding: DEFAULT_PADDING,
141            clip: false,
142            class: Theme::default(),
143        }
144    }
145
146    /// Sets the width of the [`Button`].
147    pub fn width(mut self, width: impl Into<Length>) -> Self {
148        self.width = width.into();
149        self
150    }
151
152    /// Sets the height of the [`Button`].
153    pub fn height(mut self, height: impl Into<Length>) -> Self {
154        self.height = height.into();
155        self
156    }
157
158    /// Sets the [`Padding`] of the [`Button`].
159    pub fn padding<P: Into<Padding>>(mut self, padding: P) -> Self {
160        self.padding = padding.into();
161        self
162    }
163
164    /// Sets the message that will be produced when the [`Button`] is pressed.
165    ///
166    /// Unless `on_press` is called, the [`Button`] will be disabled.
167    pub fn on_press(mut self, on_press: Message) -> Self {
168        self.on_press = Some(OnPress::Direct(on_press));
169        self
170    }
171
172    /// Sets the message that will be produced when the [`Button`] is pressed.
173    ///
174    /// This is analogous to [`Button::on_press`], but using a closure to produce
175    /// the message.
176    ///
177    /// This closure will only be called when the [`Button`] is actually pressed and,
178    /// therefore, this method is useful to reduce overhead if creating the resulting
179    /// message is slow.
180    pub fn on_press_with(
181        mut self,
182        on_press: impl Fn() -> Message + 'a,
183    ) -> Self {
184        self.on_press = Some(OnPress::Closure(Box::new(on_press)));
185        self
186    }
187
188    /// Sets the message that will be produced when the [`Button`] is pressed,
189    /// if `Some`.
190    ///
191    /// If `None`, the [`Button`] will be disabled.
192    pub fn on_press_maybe(mut self, on_press: Option<Message>) -> Self {
193        self.on_press = on_press.map(OnPress::Direct);
194        self
195    }
196
197    /// Sets whether the contents of the [`Button`] should be clipped on
198    /// overflow.
199    pub fn clip(mut self, clip: bool) -> Self {
200        self.clip = clip;
201        self
202    }
203
204    /// Sets the style of the [`Button`].
205    #[must_use]
206    pub fn style(mut self, style: impl Fn(&Theme, Status) -> Style + 'a) -> Self
207    where
208        Theme::Class<'a>: From<StyleFn<'a, Theme>>,
209    {
210        self.class = (Box::new(style) as StyleFn<'a, Theme>).into();
211        self
212    }
213
214    /// Sets the style class of the [`Button`].
215    #[cfg(feature = "advanced")]
216    #[must_use]
217    pub fn class(mut self, class: impl Into<Theme::Class<'a>>) -> Self {
218        self.class = class.into();
219        self
220    }
221
222    /// Sets the [`Id`] of the [`Button`].
223    pub fn id(mut self, id: Id) -> Self {
224        self.id = id;
225        self
226    }
227
228    #[cfg(feature = "a11y")]
229    /// Sets the name of the [`Button`].
230    pub fn name(mut self, name: impl Into<Cow<'a, str>>) -> Self {
231        self.name = Some(name.into());
232        self
233    }
234
235    #[cfg(feature = "a11y")]
236    /// Sets the description of the [`Button`].
237    pub fn description_widget<T: iced_accessibility::Describes>(
238        mut self,
239        description: &T,
240    ) -> Self {
241        self.description = Some(iced_accessibility::Description::Id(
242            description.description(),
243        ));
244        self
245    }
246
247    #[cfg(feature = "a11y")]
248    /// Sets the description of the [`Button`].
249    pub fn description(mut self, description: impl Into<Cow<'a, str>>) -> Self {
250        self.description =
251            Some(iced_accessibility::Description::Text(description.into()));
252        self
253    }
254
255    #[cfg(feature = "a11y")]
256    /// Sets the label of the [`Button`].
257    pub fn label(mut self, label: &dyn iced_accessibility::Labels) -> Self {
258        self.label =
259            Some(label.label().into_iter().map(|l| l.into()).collect());
260        self
261    }
262}
263
264#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
265struct State {
266    is_hovered: bool,
267    is_pressed: bool,
268    is_focused: bool,
269}
270
271impl<'a, Message, Theme, Renderer> Widget<Message, Theme, Renderer>
272    for Button<'a, Message, Theme, Renderer>
273where
274    Message: 'a + Clone,
275    Renderer: 'a + crate::core::Renderer,
276    Theme: Catalog,
277{
278    fn tag(&self) -> tree::Tag {
279        tree::Tag::of::<State>()
280    }
281
282    fn state(&self) -> tree::State {
283        tree::State::new(State::default())
284    }
285
286    fn children(&self) -> Vec<Tree> {
287        vec![Tree::new(&self.content)]
288    }
289
290    fn diff(&mut self, tree: &mut Tree) {
291        tree.diff_children(std::slice::from_mut(&mut self.content));
292    }
293
294    fn size(&self) -> Size<Length> {
295        Size {
296            width: self.width,
297            height: self.height,
298        }
299    }
300
301    fn layout(
302        &self,
303        tree: &mut Tree,
304        renderer: &Renderer,
305        limits: &layout::Limits,
306    ) -> layout::Node {
307        layout::padded(
308            limits,
309            self.width,
310            self.height,
311            self.padding,
312            |limits| {
313                self.content.as_widget().layout(
314                    &mut tree.children[0],
315                    renderer,
316                    limits,
317                )
318            },
319        )
320    }
321
322    fn operate(
323        &self,
324        tree: &mut Tree,
325        layout: Layout<'_>,
326        renderer: &Renderer,
327        operation: &mut dyn Operation,
328    ) {
329        operation.container(None, layout.bounds(), &mut |operation| {
330            self.content.as_widget().operate(
331                &mut tree.children[0],
332                layout
333                    .children()
334                    .next()
335                    .unwrap()
336                    .with_virtual_offset(layout.virtual_offset()),
337                renderer,
338                operation,
339            );
340        });
341    }
342
343    fn on_event(
344        &mut self,
345        tree: &mut Tree,
346        event: Event,
347        layout: Layout<'_>,
348        cursor: mouse::Cursor,
349        renderer: &Renderer,
350        clipboard: &mut dyn Clipboard,
351        shell: &mut Shell<'_, Message>,
352        viewport: &Rectangle,
353    ) -> event::Status {
354        if let event::Status::Captured = self.content.as_widget_mut().on_event(
355            &mut tree.children[0],
356            event.clone(),
357            layout
358                .children()
359                .next()
360                .unwrap()
361                .with_virtual_offset(layout.virtual_offset()),
362            cursor,
363            renderer,
364            clipboard,
365            shell,
366            viewport,
367        ) {
368            return event::Status::Captured;
369        }
370
371        match event {
372            Event::Mouse(mouse::Event::ButtonPressed(mouse::Button::Left))
373            | Event::Touch(touch::Event::FingerPressed { .. }) => {
374                if self.on_press.is_some() {
375                    let bounds = layout.bounds();
376
377                    if cursor.is_over(bounds) {
378                        let state = tree.state.downcast_mut::<State>();
379
380                        state.is_pressed = true;
381
382                        return event::Status::Captured;
383                    }
384                }
385            }
386            Event::Mouse(mouse::Event::ButtonReleased(mouse::Button::Left))
387            | Event::Touch(touch::Event::FingerLifted { .. }) => {
388                if let Some(on_press) = self.on_press.as_ref().map(OnPress::get)
389                {
390                    let state = tree.state.downcast_mut::<State>();
391
392                    if state.is_pressed {
393                        state.is_pressed = false;
394
395                        let bounds = layout.bounds();
396
397                        if cursor.is_over(bounds) {
398                            shell.publish(on_press);
399                        }
400
401                        return event::Status::Captured;
402                    }
403                }
404            }
405            #[cfg(feature = "a11y")]
406            Event::A11y(
407                event_id,
408                iced_accessibility::accesskit::ActionRequest { action, .. },
409            ) => {
410                let state = tree.state.downcast_mut::<State>();
411                if let Some(Some(on_press)) = (self.id == event_id
412                    && matches!(
413                        action,
414                        iced_accessibility::accesskit::Action::Default
415                    ))
416                .then(|| self.on_press.as_ref())
417                {
418                    state.is_pressed = false;
419                    shell.publish(on_press.get());
420                }
421                return event::Status::Captured;
422            }
423            Event::Keyboard(keyboard::Event::KeyPressed { key, .. }) => {
424                if let Some(on_press) = self.on_press.as_ref() {
425                    let state = tree.state.downcast_mut::<State>();
426                    if state.is_focused
427                        && matches!(
428                            key,
429                            keyboard::Key::Named(keyboard::key::Named::Enter)
430                        )
431                    {
432                        state.is_pressed = true;
433                        shell.publish(on_press.get());
434                        return event::Status::Captured;
435                    }
436                }
437            }
438            Event::Touch(touch::Event::FingerLost { .. })
439            | Event::Mouse(mouse::Event::CursorLeft) => {
440                let state = tree.state.downcast_mut::<State>();
441                state.is_hovered = false;
442                state.is_pressed = false;
443            }
444            _ => {}
445        }
446
447        event::Status::Ignored
448    }
449
450    fn draw(
451        &self,
452        tree: &Tree,
453        renderer: &mut Renderer,
454        theme: &Theme,
455        renderer_style: &renderer::Style,
456        layout: Layout<'_>,
457        cursor: mouse::Cursor,
458        viewport: &Rectangle,
459    ) {
460        let bounds = layout.bounds();
461        let content_layout = layout
462            .children()
463            .next()
464            .unwrap()
465            .with_virtual_offset(layout.virtual_offset());
466        let is_mouse_over = cursor.is_over(bounds);
467
468        let status = if self.on_press.is_none() {
469            Status::Disabled
470        } else if is_mouse_over {
471            let state = tree.state.downcast_ref::<State>();
472
473            if state.is_pressed {
474                Status::Pressed
475            } else {
476                Status::Hovered
477            }
478        } else {
479            Status::Active
480        };
481
482        let style = theme.style(&self.class, status);
483
484        if style.background.is_some()
485            || style.border.width > 0.0
486            || style.shadow.color.a > 0.0
487        {
488            renderer.fill_quad(
489                renderer::Quad {
490                    bounds,
491                    border: style.border,
492                    shadow: style.shadow,
493                },
494                style
495                    .background
496                    .unwrap_or(Background::Color(Color::TRANSPARENT)),
497            );
498        }
499
500        let viewport = if self.clip {
501            bounds.intersection(viewport).unwrap_or(*viewport)
502        } else {
503            *viewport
504        };
505
506        self.content.as_widget().draw(
507            &tree.children[0],
508            renderer,
509            theme,
510            &renderer::Style {
511                text_color: style.text_color,
512                icon_color: style
513                    .icon_color
514                    .unwrap_or(renderer_style.icon_color),
515                scale_factor: renderer_style.scale_factor,
516            },
517            content_layout,
518            cursor,
519            &viewport,
520        );
521    }
522
523    fn mouse_interaction(
524        &self,
525        _tree: &Tree,
526        layout: Layout<'_>,
527        cursor: mouse::Cursor,
528        _viewport: &Rectangle,
529        _renderer: &Renderer,
530    ) -> mouse::Interaction {
531        let is_mouse_over = cursor.is_over(layout.bounds());
532
533        if is_mouse_over && self.on_press.is_some() {
534            mouse::Interaction::Pointer
535        } else {
536            mouse::Interaction::default()
537        }
538    }
539
540    fn overlay<'b>(
541        &'b mut self,
542        tree: &'b mut Tree,
543        layout: Layout<'_>,
544        renderer: &Renderer,
545        translation: Vector,
546    ) -> Option<overlay::Element<'b, Message, Theme, Renderer>> {
547        self.content.as_widget_mut().overlay(
548            &mut tree.children[0],
549            layout
550                .children()
551                .next()
552                .unwrap()
553                .with_virtual_offset(layout.virtual_offset()),
554            renderer,
555            translation,
556        )
557    }
558
559    #[cfg(feature = "a11y")]
560    /// get the a11y nodes for the widget
561    fn a11y_nodes(
562        &self,
563        layout: Layout<'_>,
564        state: &Tree,
565        p: mouse::Cursor,
566    ) -> iced_accessibility::A11yTree {
567        use iced_accessibility::{
568            accesskit::{
569                Action, DefaultActionVerb, NodeBuilder, NodeId, Rect, Role,
570            },
571            A11yNode, A11yTree,
572        };
573
574        let child_layout = layout.children().next().unwrap();
575        let child_tree = &state.children[0];
576        let child_tree = self.content.as_widget().a11y_nodes(
577            child_layout.with_virtual_offset(layout.virtual_offset()),
578            child_tree,
579            p,
580        );
581
582        let Rectangle {
583            x,
584            y,
585            width,
586            height,
587        } = layout.bounds();
588        let bounds = Rect::new(
589            x as f64,
590            y as f64,
591            (x + width) as f64,
592            (y + height) as f64,
593        );
594        let is_hovered = state.state.downcast_ref::<State>().is_hovered;
595
596        let mut node = NodeBuilder::new(Role::Button);
597        node.add_action(Action::Focus);
598        node.add_action(Action::Default);
599        node.set_bounds(bounds);
600        if let Some(name) = self.name.as_ref() {
601            node.set_name(name.clone());
602        }
603        match self.description.as_ref() {
604            Some(iced_accessibility::Description::Id(id)) => {
605                node.set_described_by(
606                    id.iter()
607                        .cloned()
608                        .map(|id| NodeId::from(id))
609                        .collect::<Vec<_>>(),
610                );
611            }
612            Some(iced_accessibility::Description::Text(text)) => {
613                node.set_description(text.clone());
614            }
615            None => {}
616        }
617
618        if let Some(label) = self.label.as_ref() {
619            node.set_labelled_by(label.clone());
620        }
621
622        if self.on_press.is_none() {
623            node.set_disabled()
624        }
625        if is_hovered {
626            node.set_hovered()
627        }
628        node.set_default_action_verb(DefaultActionVerb::Click);
629
630        A11yTree::node_with_child_tree(
631            A11yNode::new(node, self.id.clone()),
632            child_tree,
633        )
634    }
635
636    fn id(&self) -> Option<Id> {
637        Some(self.id.clone())
638    }
639
640    fn set_id(&mut self, id: Id) {
641        self.id = id;
642    }
643}
644
645impl<'a, Message, Theme, Renderer> From<Button<'a, Message, Theme, Renderer>>
646    for Element<'a, Message, Theme, Renderer>
647where
648    Message: Clone + 'a,
649    Theme: Catalog + 'a,
650    Renderer: crate::core::Renderer + 'a,
651{
652    fn from(button: Button<'a, Message, Theme, Renderer>) -> Self {
653        Self::new(button)
654    }
655}
656
657/// The default [`Padding`] of a [`Button`].
658pub(crate) const DEFAULT_PADDING: Padding = Padding {
659    top: 5.0,
660    bottom: 5.0,
661    right: 10.0,
662    left: 10.0,
663};
664
665/// The possible status of a [`Button`].
666#[derive(Debug, Clone, Copy, PartialEq, Eq)]
667pub enum Status {
668    /// The [`Button`] can be pressed.
669    Active,
670    /// The [`Button`] can be pressed and it is being hovered.
671    Hovered,
672    /// The [`Button`] is being pressed.
673    Pressed,
674    /// The [`Button`] cannot be pressed.
675    Disabled,
676}
677
678/// The style of a button.
679///
680/// If not specified with [`Button::style`]
681/// the theme will provide the style.
682#[derive(Debug, Clone, Copy, PartialEq)]
683pub struct Style {
684    /// The [`Background`] of the button.
685    pub background: Option<Background>,
686    /// The border radius of the button.
687    pub border_radius: Radius,
688    /// The border width of the button.
689    pub border_width: f32,
690    /// The border [`Color`] of the button.
691    pub border_color: Color,
692    /// The icon [`Color`] of the button.
693    pub icon_color: Option<Color>,
694    /// The text [`Color`] of the button.
695    pub text_color: Color,
696    /// The [`Border`] of the button.
697    pub border: Border,
698    /// The [`Shadow`] of the button.
699    pub shadow: Shadow,
700}
701
702impl Style {
703    /// Updates the [`Style`] with the given [`Background`].
704    pub fn with_background(self, background: impl Into<Background>) -> Self {
705        Self {
706            background: Some(background.into()),
707            ..self
708        }
709    }
710
711    // /// Returns whether the [`Button`] is currently focused or not.
712    // pub fn is_focused(&self) -> bool {
713    //     self.is_focused
714    // }
715
716    // /// Returns whether the [`Button`] is currently hovered or not.
717    // pub fn is_hovered(&self) -> bool {
718    //     self.is_hovered
719    // }
720
721    // /// Focuses the [`Button`].
722    // pub fn focus(&mut self) {
723    //     self.is_focused = true;
724    // }
725
726    // /// Unfocuses the [`Button`].
727    // pub fn unfocus(&mut self) {
728    //     self.is_focused = false;
729    // }
730}
731
732impl Default for Style {
733    fn default() -> Self {
734        Self {
735            background: None,
736            border_radius: 0.0.into(),
737            border_width: 0.0,
738            border_color: Color::TRANSPARENT,
739            icon_color: None,
740            text_color: Color::BLACK,
741            border: Border::default(),
742            shadow: Shadow::default(),
743        }
744    }
745}
746
747/// The theme catalog of a [`Button`].
748///
749/// All themes that can be used with [`Button`]
750/// must implement this trait.
751///
752/// # Example
753/// ```no_run
754/// # use iced_widget::core::{Color, Background};
755/// # use iced_widget::button::{Catalog, Status, Style};
756/// # struct MyTheme;
757/// #[derive(Debug, Default)]
758/// pub enum ButtonClass {
759///     #[default]
760///     Primary,
761///     Secondary,
762///     Danger
763/// }
764///
765/// impl Catalog for MyTheme {
766///     type Class<'a> = ButtonClass;
767///     
768///     fn default<'a>() -> Self::Class<'a> {
769///         ButtonClass::default()
770///     }
771///     
772///
773///     fn style(&self, class: &Self::Class<'_>, status: Status) -> Style {
774///         let mut style = Style::default();
775///
776///         match class {
777///             ButtonClass::Primary => {
778///                 style.background = Some(Background::Color(Color::from_rgb(0.529, 0.808, 0.921)));
779///             },
780///             ButtonClass::Secondary => {
781///                 style.background = Some(Background::Color(Color::WHITE));
782///             },
783///             ButtonClass::Danger => {
784///                 style.background = Some(Background::Color(Color::from_rgb(0.941, 0.502, 0.502)));
785///             },
786///         }
787///
788///         style
789///     }
790/// }
791/// ```
792///
793/// Although, in order to use [`Button::style`]
794/// with `MyTheme`, [`Catalog::Class`] must implement
795/// `From<StyleFn<'_, MyTheme>>`.
796pub trait Catalog {
797    /// The item class of the [`Catalog`].
798    type Class<'a>;
799
800    /// The default class produced by the [`Catalog`].
801    fn default<'a>() -> Self::Class<'a>;
802
803    /// The [`Style`] of a class with the given status.
804    fn style(&self, class: &Self::Class<'_>, status: Status) -> Style;
805}
806
807/// A styling function for a [`Button`].
808pub type StyleFn<'a, Theme> = Box<dyn Fn(&Theme, Status) -> Style + 'a>;
809
810impl Catalog for Theme {
811    type Class<'a> = StyleFn<'a, Self>;
812
813    fn default<'a>() -> Self::Class<'a> {
814        Box::new(primary)
815    }
816
817    fn style(&self, class: &Self::Class<'_>, status: Status) -> Style {
818        class(self, status)
819    }
820}
821
822/// A primary button; denoting a main action.
823pub fn primary(theme: &Theme, status: Status) -> Style {
824    let palette = theme.extended_palette();
825    let base = styled(palette.primary.strong);
826
827    match status {
828        Status::Active | Status::Pressed => base,
829        Status::Hovered => Style {
830            background: Some(Background::Color(palette.primary.base.color)),
831            ..base
832        },
833        Status::Disabled => disabled(base),
834    }
835}
836
837/// A secondary button; denoting a complementary action.
838pub fn secondary(theme: &Theme, status: Status) -> Style {
839    let palette = theme.extended_palette();
840    let base = styled(palette.secondary.base);
841
842    match status {
843        Status::Active | Status::Pressed => base,
844        Status::Hovered => Style {
845            background: Some(Background::Color(palette.secondary.strong.color)),
846            ..base
847        },
848        Status::Disabled => disabled(base),
849    }
850}
851
852/// A success button; denoting a good outcome.
853pub fn success(theme: &Theme, status: Status) -> Style {
854    let palette = theme.extended_palette();
855    let base = styled(palette.success.base);
856
857    match status {
858        Status::Active | Status::Pressed => base,
859        Status::Hovered => Style {
860            background: Some(Background::Color(palette.success.strong.color)),
861            ..base
862        },
863        Status::Disabled => disabled(base),
864    }
865}
866
867/// A danger button; denoting a destructive action.
868pub fn danger(theme: &Theme, status: Status) -> Style {
869    let palette = theme.extended_palette();
870    let base = styled(palette.danger.base);
871
872    match status {
873        Status::Active | Status::Pressed => base,
874        Status::Hovered => Style {
875            background: Some(Background::Color(palette.danger.strong.color)),
876            ..base
877        },
878        Status::Disabled => disabled(base),
879    }
880}
881
882/// A text button; useful for links.
883pub fn text(theme: &Theme, status: Status) -> Style {
884    let palette = theme.extended_palette();
885
886    let base = Style {
887        text_color: palette.background.base.text,
888        ..Style::default()
889    };
890
891    match status {
892        Status::Active | Status::Pressed => base,
893        Status::Hovered => Style {
894            text_color: palette.background.base.text.scale_alpha(0.8),
895            ..base
896        },
897        Status::Disabled => disabled(base),
898    }
899}
900
901fn styled(pair: palette::Pair) -> Style {
902    Style {
903        background: Some(Background::Color(pair.color)),
904        text_color: pair.text,
905        border: border::rounded(2),
906        ..Style::default()
907    }
908}
909
910fn disabled(style: Style) -> Style {
911    Style {
912        background: style
913            .background
914            .map(|background| background.scale_alpha(0.5)),
915        text_color: style.text_color.scale_alpha(0.5),
916        ..style
917    }
918}
919
920/// Produces a [`Task`] that focuses the [`Button`] with the given [`Id`].
921pub fn focus<Message: 'static + Send>(id: Id) -> Task<Message> {
922    task::widget(operation::focusable::focus(id))
923}
924
925impl operation::Focusable for State {
926    fn is_focused(&self) -> bool {
927        self.is_focused
928    }
929
930    fn focus(&mut self) {
931        self.is_focused = true;
932    }
933
934    fn unfocus(&mut self) {
935        self.is_focused = false;
936    }
937}