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