vizia_core/views/
menu.rs
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 {
126 Placement::RightStart
127 } else {
128 Placement::BottomStart
129 }
130 }))
131 .arrow_size(Pixels(0.0))
132 .checked(Submenu::is_open)
133 .on_hover(|cx| {
134 cx.emit_custom(
135 Event::new(MenuEvent::Close)
136 .target(cx.current)
137 .propagate(Propagation::Subtree),
138 )
139 });
140 }
141 });
142 })
145 .navigable(true)
146 .checked(Submenu::is_open)
147 .layout_type(LayoutType::Row)
148 .on_press(|cx| cx.emit(MenuEvent::ToggleOpen));
149
150 if handle.data::<MenuBar>().is_some() {
151 handle.bind(MenuBar::is_open, |handle, is_open| {
152 let is_open = is_open.get(&handle);
153 handle.modify(|menu_button| menu_button.open_on_hover = is_open);
154 })
155 } else {
156 handle
157 }
158 }
159}
160
161impl View for Submenu {
162 fn element(&self) -> Option<&'static str> {
163 Some("submenu")
164 }
165
166 fn event(&mut self, cx: &mut EventContext, event: &mut Event) {
167 event.map(|window_event, meta| match window_event {
168 WindowEvent::MouseEnter => {
169 if meta.target == cx.current {
170 if self.open_on_hover {
174 let parent = cx.tree.get_parent(cx.current).unwrap();
176 cx.emit_custom(
177 Event::new(MenuEvent::Close)
178 .target(parent)
179 .propagate(Propagation::Subtree),
180 );
181 cx.emit(MenuEvent::Open);
183 }
184 }
185 }
186
187 WindowEvent::KeyDown(code, _) => match code {
188 Code::ArrowLeft => {
189 if self.is_open {
191 self.is_open = false;
192 cx.focus();
193 meta.consume();
194 }
195 }
197
198 Code::ArrowRight => {
199 if !self.is_open {
200 self.is_open = true;
201 }
202 }
203
204 _ => {}
205 },
206
207 _ => {}
208 });
209
210 event.map(|menu_event, meta| match menu_event {
211 MenuEvent::Open => {
212 self.is_open = true;
213 meta.consume();
214 }
215
216 MenuEvent::Close => {
217 self.is_open = false;
218 }
220
221 MenuEvent::ToggleOpen => {
222 self.is_open ^= true;
223 if self.is_open {
224 cx.emit(MenuEvent::MenuIsOpen);
225 } else {
226 let parent = cx.tree.get_parent(cx.current).unwrap();
228 cx.emit_custom(
229 Event::new(MenuEvent::CloseAll)
230 .target(parent)
231 .propagate(Propagation::Direct),
232 );
233 }
234 meta.consume();
235 }
236
237 _ => {}
238 });
239 }
240}
241
242#[derive(Lens)]
244pub struct MenuButton {}
245
246impl MenuButton {
247 pub fn new<V: View>(
249 cx: &mut Context,
250 action: impl Fn(&mut EventContext) + Send + Sync + 'static,
251 content: impl Fn(&mut Context) -> Handle<V> + 'static,
252 ) -> Handle<Self> {
253 Self {}
254 .build(cx, |cx| {
255 (content)(cx).hoverable(false);
256 })
257 .on_press(move |cx| {
258 (action)(cx);
259 cx.emit(MenuEvent::CloseAll);
260 cx.emit(ModalEvent::HideMenu);
261 cx.emit(MenuEvent::Close);
262 })
263 .role(Role::MenuItem)
264 .navigable(true)
265 }
266}
267
268impl View for MenuButton {
269 fn element(&self) -> Option<&'static str> {
270 Some("menubutton")
271 }
272
273 fn event(&mut self, cx: &mut EventContext, event: &mut Event) {
274 event.map(|window_event, meta| match window_event {
275 WindowEvent::MouseEnter => {
276 if meta.target == cx.current {
277 let parent = cx.tree.get_parent(cx.current).unwrap();
278 cx.emit_custom(
279 Event::new(MenuEvent::Close).target(parent).propagate(Propagation::Subtree),
280 );
281 }
282 }
283
284 _ => {}
285 });
286 }
287}