Skip to main content

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