Skip to main content

kas_widgets/menu/
menu_entry.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//! Menu Entries
7
8use super::{Menu, SubItems};
9use crate::{AccessLabel, CheckBox};
10use kas::prelude::*;
11use kas::theme::{FrameStyle, TextClass};
12use std::fmt::Debug;
13
14#[impl_self]
15mod MenuEntry {
16    /// A standard menu entry
17    ///
18    /// # Messages
19    ///
20    /// A `MenuEntry` has an associated message value of type `M`. A clone of
21    /// this value is pushed when the entry is activated.
22    ///
23    /// # Messages
24    ///
25    /// [`kas::messages::Activate`] may be used to trigger the menu entry.
26    #[derive(Debug)]
27    #[widget]
28    #[layout(self.label)]
29    pub struct MenuEntry<M: Clone + Debug + 'static> {
30        core: widget_core!(),
31        #[widget]
32        label: AccessLabel,
33        msg: M,
34    }
35
36    impl Layout for Self {
37        fn draw(&self, mut draw: DrawCx) {
38            draw.frame(self.rect(), FrameStyle::MenuEntry, Default::default());
39            self.label.draw(draw.re());
40        }
41    }
42
43    impl Tile for Self {
44        fn navigable(&self) -> bool {
45            true
46        }
47
48        fn role(&self, cx: &mut dyn RoleCx) -> Role<'_> {
49            cx.set_label(self.label.id());
50            Role::Button
51        }
52    }
53
54    impl Self {
55        /// Construct a menu item with a given `label` and `msg`
56        ///
57        /// The message `msg` is emitted on activation. Any
58        /// type supporting `Clone` is valid, though it is recommended to use a
59        /// simple `Copy` type (e.g. an enum).
60        pub fn new_msg<S: Into<AccessString>>(label: S, msg: M) -> Self {
61            MenuEntry {
62                core: Default::default(),
63                label: AccessLabel::new(label).with_class(TextClass::Label),
64                msg,
65            }
66        }
67
68        /// Replace the message value
69        pub fn set_msg(&mut self, msg: M) {
70            self.msg = msg;
71        }
72
73        /// Get text contents
74        pub fn as_str(&self) -> &str {
75            self.label.as_str()
76        }
77    }
78
79    impl Events for Self {
80        type Data = ();
81
82        fn probe(&self, _: Coord) -> Id {
83            self.id()
84        }
85
86        fn handle_event(&mut self, cx: &mut EventCx, _: &Self::Data, event: Event) -> IsUsed {
87            match event {
88                Event::Command(cmd, code) if cmd.is_activate() => {
89                    cx.push(self.msg.clone());
90                    cx.depress_with_key(&self, code);
91                    Used
92                }
93                _ => Unused,
94            }
95        }
96
97        fn handle_messages(&mut self, cx: &mut EventCx, _: &Self::Data) {
98            if let Some(kas::messages::Activate(code)) = cx.try_pop() {
99                cx.push(self.msg.clone());
100                cx.depress_with_key(&self, code);
101            }
102        }
103    }
104
105    impl Menu for Self {
106        fn sub_items(&mut self) -> Option<SubItems<'_>> {
107            Some(SubItems {
108                label: Some(&mut self.label),
109                ..Default::default()
110            })
111        }
112    }
113
114    impl PartialEq<M> for Self
115    where
116        M: PartialEq,
117    {
118        #[inline]
119        fn eq(&self, rhs: &M) -> bool {
120            self.msg == *rhs
121        }
122    }
123}
124
125#[impl_self]
126mod MenuToggle {
127    /// A menu entry which can be toggled
128    ///
129    /// # Messages
130    ///
131    /// [`kas::messages::Activate`] may be used to toggle the menu entry.
132    #[widget]
133    #[layout(row! [self.checkbox, self.label])]
134    pub struct MenuToggle<A> {
135        core: widget_core!(),
136        #[widget]
137        checkbox: CheckBox<A>,
138        #[widget(&())]
139        label: AccessLabel,
140    }
141
142    impl Layout for Self {
143        fn draw(&self, mut draw: DrawCx) {
144            draw.set_id(self.checkbox.id());
145            draw.frame(self.rect(), FrameStyle::MenuEntry, Default::default());
146            kas::MacroDefinedLayout::draw(self, draw);
147        }
148    }
149
150    impl Tile for Self {
151        fn role_child_properties(&self, cx: &mut dyn RoleCx, index: usize) {
152            if index == widget_index!(self.checkbox) {
153                cx.set_label(self.label.id());
154            }
155        }
156    }
157
158    impl Events for Self {
159        type Data = A;
160
161        fn probe(&self, _: Coord) -> Id {
162            self.checkbox.id()
163        }
164
165        fn post_configure(&mut self, _: &mut ConfigCx) {
166            self.label.set_target(self.checkbox.id());
167        }
168
169        fn handle_messages(&mut self, cx: &mut EventCx, data: &Self::Data) {
170            if let Some(kas::messages::Activate(code)) = cx.try_pop() {
171                self.checkbox.toggle(cx, data);
172                cx.depress_with_key(&self, code);
173            }
174        }
175    }
176
177    impl Menu for Self {
178        fn sub_items(&mut self) -> Option<SubItems<'_>> {
179            Some(SubItems {
180                label: Some(&mut self.label),
181                toggle: Some(&mut self.checkbox),
182                ..Default::default()
183            })
184        }
185    }
186
187    impl Self {
188        /// Construct a toggleable menu entry with the given `label`
189        ///
190        /// - `label` is displayed to the left or right (according to text direction)
191        /// - `state_fn` extracts the current state from input data
192        #[inline]
193        pub fn new(
194            label: impl Into<AccessString>,
195            state_fn: impl Fn(&ConfigCx, &A) -> bool + 'static,
196        ) -> Self {
197            MenuToggle {
198                core: Default::default(),
199                checkbox: CheckBox::new(state_fn),
200                label: AccessLabel::new(label).with_class(TextClass::Label),
201            }
202        }
203
204        /// Call the handler `f` on toggle
205        #[inline]
206        #[must_use]
207        pub fn with<F>(self, f: F) -> Self
208        where
209            F: Fn(&mut EventCx, &A, bool) + 'static,
210        {
211            MenuToggle {
212                core: self.core,
213                checkbox: self.checkbox.with(f),
214                label: self.label,
215            }
216        }
217
218        /// Send the message generated by `f` on toggle
219        #[inline]
220        #[must_use]
221        pub fn with_msg<M>(self, f: impl Fn(bool) -> M + 'static) -> Self
222        where
223            M: std::fmt::Debug + 'static,
224        {
225            self.with(move |cx, _, state| cx.push(f(state)))
226        }
227
228        /// Construct a toggleable menu entry with the given `label` and `msg_fn`
229        ///
230        /// - `label` is displayed to the left or right (according to text direction)
231        /// - `state_fn` extracts the current state from input data
232        /// - A message generated by `msg_fn` is emitted when toggled
233        #[inline]
234        pub fn new_msg<M: Debug + 'static>(
235            label: impl Into<AccessString>,
236            state_fn: impl Fn(&ConfigCx, &A) -> bool + 'static,
237            msg_fn: impl Fn(bool) -> M + 'static,
238        ) -> Self {
239            MenuToggle::new(label, state_fn).with_msg(msg_fn)
240        }
241    }
242}