Skip to main content

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(self.frame_style)
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        frame_style: FrameStyle,
35        #[widget]
36        pub inner: W,
37        on_press: Option<Box<dyn Fn(&mut EventCx, &W::Data)>>,
38    }
39
40    impl Self {
41        /// Construct a button with given `inner` widget
42        #[inline]
43        pub fn new(inner: W) -> Self {
44            Button {
45                core: Default::default(),
46                key: Default::default(),
47                frame_style: FrameStyle::Button,
48                bg: Background::Default,
49                inner,
50                on_press: None,
51            }
52        }
53
54        /// Call the handler `f` on press / activation
55        #[inline]
56        #[must_use]
57        pub fn with(mut self, f: impl Fn(&mut EventCx, &W::Data) + 'static) -> Self {
58            debug_assert!(self.on_press.is_none());
59            self.on_press = Some(Box::new(f));
60            self
61        }
62
63        /// Send the message `msg` on press / activation
64        #[inline]
65        #[must_use]
66        pub fn with_msg<M>(self, msg: M) -> Self
67        where
68            M: Clone + Debug + 'static,
69        {
70            self.with(move |cx, _| cx.push(msg.clone()))
71        }
72
73        /// Construct a button with a given `inner` and payload `msg`
74        ///
75        /// When the button is activated, a clone of `msg` is sent to the
76        /// parent widget. The parent (or an ancestor) should handle this using
77        /// [`Events::handle_messages`].
78        #[inline]
79        pub fn new_msg<M: Clone + Debug + 'static>(inner: W, msg: M) -> Self {
80            Self::new(inner).with_msg(msg)
81        }
82
83        /// Add access key (chain style)
84        #[must_use]
85        pub fn with_access_key(mut self, key: Key) -> Self {
86            debug_assert!(self.key.is_none());
87            self.key = Some(key);
88            self
89        }
90
91        /// Set the frame background color (inline)
92        ///
93        /// The default background is [`Background::Default`].
94        #[inline]
95        #[must_use]
96        pub fn with_background(mut self, bg: Background) -> Self {
97            self.bg = bg;
98            self
99        }
100
101        /// Set the frame style (inline)
102        ///
103        /// The default style is [`FrameStyle::Button`].
104        #[inline]
105        #[must_use]
106        pub fn with_frame_style(mut self, style: FrameStyle) -> Self {
107            self.frame_style = style;
108            self
109        }
110    }
111
112    impl Layout for Self {
113        fn draw(&self, mut draw: DrawCx) {
114            if let Some(key) = self.key.as_ref() {
115                let _ = draw.access_key(self.id_ref(), key);
116            }
117            kas::MacroDefinedLayout::draw(self, draw);
118        }
119    }
120
121    impl Tile for Self {
122        fn navigable(&self) -> bool {
123            true
124        }
125
126        fn role(&self, _: &mut dyn RoleCx) -> Role<'_> {
127            Role::Button
128        }
129    }
130
131    impl Events for Self {
132        const REDRAW_ON_MOUSE_OVER: bool = true;
133
134        type Data = W::Data;
135
136        fn probe(&self, _: Coord) -> Id {
137            self.id()
138        }
139
140        fn handle_event(&mut self, cx: &mut EventCx, data: &W::Data, event: Event) -> IsUsed {
141            event.on_click(cx, self.id(), |cx| {
142                if let Some(f) = self.on_press.as_ref() {
143                    f(cx, data);
144                }
145            })
146        }
147
148        fn handle_messages(&mut self, cx: &mut EventCx, data: &W::Data) {
149            if let Some(kas::messages::Activate(code)) = cx.try_pop() {
150                if let Some(f) = self.on_press.as_ref() {
151                    f(cx, data);
152                }
153                cx.depress_with_key(&self, code);
154            }
155        }
156    }
157
158    impl Button<AccessLabel> {
159        /// Construct a button with the given `label`
160        ///
161        /// This is a convenience method. It may be possible to merge this
162        /// functionality into [`Button::new`] once Rust has support for
163        /// overlapping trait implementations (not specialisation).
164        pub fn label(label: impl Into<AccessString>) -> Self {
165            Button::new(AccessLabel::new(label))
166        }
167
168        /// Construct a button with the given `label` and payload `msg`
169        ///
170        /// This is a convenience method. It may be possible to merge this
171        /// functionality into [`Button::new_msg`] once Rust has support for
172        /// overlapping trait implementations (not specialisation).
173        pub fn label_msg<M>(label: impl Into<AccessString>, msg: M) -> Self
174        where
175            M: Clone + Debug + 'static,
176        {
177            Button::new_msg(AccessLabel::new(label), msg)
178        }
179    }
180}