kas_widgets/
button.rs

1// Licensed under the Apache License, Version 2.0 (the "License");
2// you may not use this file except in compliance with the License.
3// You may obtain a copy of the License in the LICENSE-APACHE file or at:
4//     https://www.apache.org/licenses/LICENSE-2.0
5
6//! Push-buttons
7
8use super::AccessLabel;
9use kas::event::Key;
10use kas::prelude::*;
11use kas::theme::{Background, FrameStyle};
12use std::fmt::Debug;
13
14#[impl_self]
15mod Button {
16    /// A push-button with a generic label
17    ///
18    /// Default alignment of content is centered.
19    ///
20    /// ### Messages
21    ///
22    /// [`kas::messages::Activate`] may be used to trigger the button.
23    #[widget]
24    #[layout(
25        frame!(self.inner)
26            .with_style(FrameStyle::Button)
27            .with_background(self.bg)
28            .align(AlignHints::CENTER)
29    )]
30    pub struct Button<W: Widget> {
31        core: widget_core!(),
32        key: Option<Key>,
33        bg: Background,
34        #[widget]
35        pub inner: W,
36        on_press: Option<Box<dyn Fn(&mut EventCx, &W::Data)>>,
37    }
38
39    impl Self {
40        /// Construct a button with given `inner` widget
41        #[inline]
42        pub fn new(inner: W) -> Self {
43            Button {
44                core: Default::default(),
45                key: Default::default(),
46                bg: Background::Default,
47                inner,
48                on_press: None,
49            }
50        }
51
52        /// Call the handler `f` on press / activation
53        #[inline]
54        #[must_use]
55        pub fn with(mut self, f: impl Fn(&mut EventCx, &W::Data) + 'static) -> Self {
56            debug_assert!(self.on_press.is_none());
57            self.on_press = Some(Box::new(f));
58            self
59        }
60
61        /// Send the message `msg` on press / activation
62        #[inline]
63        #[must_use]
64        pub fn with_msg<M>(self, msg: M) -> Self
65        where
66            M: Clone + Debug + 'static,
67        {
68            self.with(move |cx, _| cx.push(msg.clone()))
69        }
70
71        /// Construct a button with a given `inner` and payload `msg`
72        ///
73        /// When the button is activated, a clone of `msg` is sent to the
74        /// parent widget. The parent (or an ancestor) should handle this using
75        /// [`Events::handle_messages`].
76        #[inline]
77        pub fn new_msg<M: Clone + Debug + 'static>(inner: W, msg: M) -> Self {
78            Self::new(inner).with_msg(msg)
79        }
80
81        /// Add access key (chain style)
82        #[must_use]
83        pub fn with_access_key(mut self, key: Key) -> Self {
84            debug_assert!(self.key.is_none());
85            self.key = Some(key);
86            self
87        }
88
89        /// Set the frame background color (inline)
90        ///
91        /// The default background is [`Background::Default`].
92        #[inline]
93        #[must_use]
94        pub fn with_background(mut self, bg: Background) -> Self {
95            self.bg = bg;
96            self
97        }
98    }
99
100    impl Layout for Self {
101        fn draw(&self, mut draw: DrawCx) {
102            if let Some(key) = self.key.as_ref() {
103                let _ = draw.access_key(self.id_ref(), key);
104            }
105            kas::MacroDefinedLayout::draw(self, draw);
106        }
107    }
108
109    impl Tile for Self {
110        fn navigable(&self) -> bool {
111            true
112        }
113
114        fn role(&self, _: &mut dyn RoleCx) -> Role<'_> {
115            Role::Button
116        }
117
118        fn probe(&self, _: Coord) -> Id {
119            self.id()
120        }
121    }
122
123    impl Events for Self {
124        const REDRAW_ON_MOUSE_OVER: bool = true;
125
126        type Data = W::Data;
127
128        fn handle_event(&mut self, cx: &mut EventCx, data: &W::Data, event: Event) -> IsUsed {
129            event.on_click(cx, self.id(), |cx| {
130                if let Some(f) = self.on_press.as_ref() {
131                    f(cx, data);
132                }
133            })
134        }
135
136        fn handle_messages(&mut self, cx: &mut EventCx, data: &W::Data) {
137            if let Some(kas::messages::Activate(code)) = cx.try_pop() {
138                if let Some(f) = self.on_press.as_ref() {
139                    f(cx, data);
140                }
141                cx.depress_with_key(&self, code);
142            }
143        }
144    }
145
146    impl Button<AccessLabel> {
147        /// Construct a button with the given `label`
148        ///
149        /// This is a convenience method. It may be possible to merge this
150        /// functionality into [`Button::new`] once Rust has support for
151        /// overlapping trait implementations (not specialisation).
152        pub fn label(label: impl Into<AccessString>) -> Self {
153            Button::new(AccessLabel::new(label))
154        }
155
156        /// Construct a button with the given `label` and payload `msg`
157        ///
158        /// This is a convenience method. It may be possible to merge this
159        /// functionality into [`Button::new_msg`] once Rust has support for
160        /// overlapping trait implementations (not specialisation).
161        pub fn label_msg<M>(label: impl Into<AccessString>, msg: M) -> Self
162        where
163            M: Clone + Debug + 'static,
164        {
165            Button::new_msg(AccessLabel::new(label), msg)
166        }
167    }
168}