1use crate::modifiers::ModalEvent;
2use crate::{icons::ICON_CHEVRON_RIGHT, prelude::*};
3
4#[derive(Lens)]
6pub struct MenuBar {
7 is_open: bool,
8}
9
10impl MenuBar {
11 pub fn new(cx: &mut Context, content: impl Fn(&mut Context)) -> Handle<Self> {
13 Self { is_open: false }
14 .build(cx, |cx| {
15 cx.add_listener(move |menu_bar: &mut Self, cx, event| {
16 let flag: bool = menu_bar.is_open;
17 event.map(
18 |window_event, meta: &mut crate::events::EventMeta| match window_event {
19 WindowEvent::MouseDown(_) => {
20 if flag && meta.origin != cx.current() {
21 if !cx.hovered.is_descendant_of(cx.tree, cx.current) {
23 cx.emit(MenuEvent::CloseAll);
24 }
25 }
26 }
27
28 _ => {}
29 },
30 );
31 });
32
33 (content)(cx);
34 })
35 .layout_type(LayoutType::Row)
36 }
37}
38
39impl View for MenuBar {
40 fn element(&self) -> Option<&'static str> {
41 Some("menubar")
42 }
43
44 fn event(&mut self, cx: &mut EventContext, event: &mut Event) {
45 event.map(|menu_event, _| match menu_event {
46 MenuEvent::MenuIsOpen => {
47 self.is_open = true;
48 }
49
50 MenuEvent::CloseAll => {
51 self.is_open = false;
52 cx.emit_custom(
53 Event::new(MenuEvent::Close).target(cx.current).propagate(Propagation::Subtree),
54 );
55 }
56
57 _ => {}
58 });
59 }
60}
61
62pub enum MenuEvent {
64 ToggleOpen,
66 Open,
68 Close,
70 CloseAll,
72 MenuIsOpen,
74}
75
76#[derive(Lens)]
78pub struct Submenu {
79 is_open: bool,
80 open_on_hover: bool,
81 is_submenu: bool,
82}
83
84impl Submenu {
85 pub fn new<V: View>(
87 cx: &mut Context,
88 content: impl Fn(&mut Context) -> Handle<V> + 'static,
89 menu: impl Fn(&mut Context) + 'static,
90 ) -> Handle<Self> {
91 let is_submenu = cx.data::<Submenu>().is_some();
92
93 let handle = Self { is_open: false, open_on_hover: is_submenu, is_submenu }
94 .build(cx, |cx| {
95 cx.add_listener(move |menu_button: &mut Self, cx, event| {
96 let flag: bool = menu_button.is_open;
97 event.map(
98 |window_event, meta: &mut crate::events::EventMeta| match window_event {
99 WindowEvent::MouseDown(_) => {
100 if flag && meta.origin != cx.current() {
101 if !cx.hovered.is_descendant_of(cx.tree, cx.current) {
103 cx.emit(MenuEvent::CloseAll);
104 cx.emit(MenuEvent::Close);
105 }
108 }
109 }
110
111 _ => {}
112 },
113 );
114 });
115 (content)(cx).hoverable(false);
117 Svg::new(cx, ICON_CHEVRON_RIGHT).class("arrow").hoverable(false);
118 Binding::new(cx, Submenu::is_open, move |cx, is_open| {
120 if is_open.get(cx) {
121 Popup::new(cx, |cx| {
122 (menu)(cx);
123 })
124 .placement(Submenu::is_submenu.map(|is_submenu| {
125 if *is_submenu { Placement::RightStart } else { Placement::BottomStart }
126 }))
127 .arrow_size(Pixels(0.0))
128 .checked(Submenu::is_open)
129 .on_hover(|cx| {
130 cx.emit_custom(
131 Event::new(MenuEvent::Close)
132 .target(cx.current)
133 .propagate(Propagation::Subtree),
134 )
135 });
136 }
137 });
138 })
141 .navigable(true)
142 .checked(Submenu::is_open)
143 .layout_type(LayoutType::Row)
144 .on_press(|cx| cx.emit(MenuEvent::ToggleOpen));
145
146 if handle.data::<MenuBar>().is_some() {
147 handle.bind(MenuBar::is_open, |handle, is_open| {
148 let is_open = is_open.get(&handle);
149 handle.modify(|menu_button| menu_button.open_on_hover = is_open);
150 })
151 } else {
152 handle
153 }
154 }
155}
156
157impl View for Submenu {
158 fn element(&self) -> Option<&'static str> {
159 Some("submenu")
160 }
161
162 fn event(&mut self, cx: &mut EventContext, event: &mut Event) {
163 event.map(|window_event, meta| match window_event {
164 WindowEvent::MouseEnter => {
165 if meta.target == cx.current {
166 if self.open_on_hover {
170 let parent = cx.tree.get_parent(cx.current).unwrap();
172 cx.emit_custom(
173 Event::new(MenuEvent::Close)
174 .target(parent)
175 .propagate(Propagation::Subtree),
176 );
177 cx.emit(MenuEvent::Open);
179 }
180 }
181 }
182
183 WindowEvent::KeyDown(code, _) => match code {
184 Code::ArrowLeft => {
185 if self.is_open {
187 self.is_open = false;
188 cx.focus();
189 meta.consume();
190 }
191 }
193
194 Code::ArrowRight => {
195 if !self.is_open {
196 self.is_open = true;
197 }
198 }
199
200 _ => {}
201 },
202
203 _ => {}
204 });
205
206 event.map(|menu_event, meta| match menu_event {
207 MenuEvent::Open => {
208 self.is_open = true;
209 meta.consume();
210 }
211
212 MenuEvent::Close => {
213 self.is_open = false;
214 }
216
217 MenuEvent::ToggleOpen => {
218 self.is_open ^= true;
219 if self.is_open {
220 cx.emit(MenuEvent::MenuIsOpen);
221 } else {
222 let parent = cx.tree.get_parent(cx.current).unwrap();
224 cx.emit_custom(
225 Event::new(MenuEvent::CloseAll)
226 .target(parent)
227 .propagate(Propagation::Direct),
228 );
229 }
230 meta.consume();
231 }
232
233 _ => {}
234 });
235 }
236}
237
238#[derive(Lens)]
240pub struct MenuButton {}
241
242impl MenuButton {
243 pub fn new<V: View>(
245 cx: &mut Context,
246 action: impl Fn(&mut EventContext) + Send + Sync + 'static,
247 content: impl Fn(&mut Context) -> Handle<V> + 'static,
248 ) -> Handle<Self> {
249 Self {}
250 .build(cx, |cx| {
251 (content)(cx).hoverable(false);
252 })
253 .on_press(move |cx| {
254 (action)(cx);
255 cx.emit(MenuEvent::CloseAll);
256 cx.emit(ModalEvent::HideMenu);
257 cx.emit(MenuEvent::Close);
258 })
259 .role(Role::MenuItem)
260 .navigable(true)
261 }
262}
263
264impl View for MenuButton {
265 fn element(&self) -> Option<&'static str> {
266 Some("menubutton")
267 }
268
269 fn event(&mut self, cx: &mut EventContext, event: &mut Event) {
270 event.map(|window_event, meta| match window_event {
271 WindowEvent::MouseEnter => {
272 if meta.target == cx.current {
273 let parent = cx.tree.get_parent(cx.current).unwrap();
274 cx.emit_custom(
275 Event::new(MenuEvent::Close).target(parent).propagate(Propagation::Subtree),
276 );
277 }
278 }
279
280 _ => {}
281 });
282 }
283}