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(Clone, Debug, Default)]
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        fn probe(&self, _: Coord) -> Id {
54            self.id()
55        }
56    }
57
58    impl Self {
59        /// Construct a menu item with a given `label` and `msg`
60        ///
61        /// The message `msg` is emitted on activation. Any
62        /// type supporting `Clone` is valid, though it is recommended to use a
63        /// simple `Copy` type (e.g. an enum).
64        pub fn new_msg<S: Into<AccessString>>(label: S, msg: M) -> Self {
65            MenuEntry {
66                core: Default::default(),
67                label: AccessLabel::new(label).with_class(TextClass::MenuLabel),
68                msg,
69            }
70        }
71
72        /// Replace the message value
73        pub fn set_msg(&mut self, msg: M) {
74            self.msg = msg;
75        }
76
77        /// Get text contents
78        pub fn as_str(&self) -> &str {
79            self.label.as_str()
80        }
81    }
82
83    impl Events for Self {
84        type Data = ();
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        fn probe(&self, _: Coord) -> Id {
158            self.checkbox.id()
159        }
160    }
161
162    impl Events for Self {
163        type Data = A;
164
165        fn configure_recurse(&mut self, cx: &mut ConfigCx, data: &Self::Data) {
166            let id = self.make_child_id(widget_index!(self.checkbox));
167            if id.is_valid() {
168                cx.configure(self.checkbox.as_node(data), id);
169            }
170
171            let id = self.make_child_id(widget_index!(self.label));
172            if id.is_valid() {
173                cx.configure(self.label.as_node(&()), id);
174                self.label.set_target(self.checkbox.id());
175            }
176        }
177
178        fn handle_messages(&mut self, cx: &mut EventCx, data: &Self::Data) {
179            if let Some(kas::messages::Activate(code)) = cx.try_pop() {
180                self.checkbox.toggle(cx, data);
181                cx.depress_with_key(&self, code);
182            }
183        }
184    }
185
186    impl Menu for Self {
187        fn sub_items(&mut self) -> Option<SubItems<'_>> {
188            Some(SubItems {
189                label: Some(&mut self.label),
190                toggle: Some(&mut self.checkbox),
191                ..Default::default()
192            })
193        }
194    }
195
196    impl Self {
197        /// Construct a toggleable menu entry with the given `label`
198        ///
199        /// - `label` is displayed to the left or right (according to text direction)
200        /// - `state_fn` extracts the current state from input data
201        #[inline]
202        pub fn new(
203            label: impl Into<AccessString>,
204            state_fn: impl Fn(&ConfigCx, &A) -> bool + 'static,
205        ) -> Self {
206            MenuToggle {
207                core: Default::default(),
208                checkbox: CheckBox::new(state_fn),
209                label: AccessLabel::new(label).with_class(TextClass::MenuLabel),
210            }
211        }
212
213        /// Call the handler `f` on toggle
214        #[inline]
215        #[must_use]
216        pub fn with<F>(self, f: F) -> Self
217        where
218            F: Fn(&mut EventCx, &A, bool) + 'static,
219        {
220            MenuToggle {
221                core: self.core,
222                checkbox: self.checkbox.with(f),
223                label: self.label,
224            }
225        }
226
227        /// Send the message generated by `f` on toggle
228        #[inline]
229        #[must_use]
230        pub fn with_msg<M>(self, f: impl Fn(bool) -> M + 'static) -> Self
231        where
232            M: std::fmt::Debug + 'static,
233        {
234            self.with(move |cx, _, state| cx.push(f(state)))
235        }
236
237        /// Construct a toggleable menu entry with the given `label` and `msg_fn`
238        ///
239        /// - `label` is displayed to the left or right (according to text direction)
240        /// - `state_fn` extracts the current state from input data
241        /// - A message generated by `msg_fn` is emitted when toggled
242        #[inline]
243        pub fn new_msg<M: Debug + 'static>(
244            label: impl Into<AccessString>,
245            state_fn: impl Fn(&ConfigCx, &A) -> bool + 'static,
246            msg_fn: impl Fn(bool) -> M + 'static,
247        ) -> Self {
248            MenuToggle::new(label, state_fn).with_msg(msg_fn)
249        }
250    }
251}