1use super::{
5 menu_inner::{
6 CloseCondition, Direction, ItemHeight, ItemWidth, Menu, MenuState, PathHighlight,
7 },
8 menu_tree::MenuTree,
9};
10use crate::style::menu_bar::StyleSheet;
11
12use iced::{Point, Vector};
13use iced_core::Border;
14use iced_widget::core::{
15 Alignment, Clipboard, Element, Layout, Length, Padding, Rectangle, Shell, Widget, event,
16 layout::{Limits, Node},
17 mouse::{self, Cursor},
18 overlay, renderer, touch,
19 widget::{Tree, tree},
20};
21
22pub fn menu_bar<Message, Renderer: iced_core::Renderer>(
24 menu_roots: Vec<MenuTree<Message, Renderer>>,
25) -> MenuBar<Message, Renderer> {
26 MenuBar::new(menu_roots)
27}
28
29pub(crate) struct MenuBarState {
30 pub(crate) pressed: bool,
31 pub(crate) view_cursor: Cursor,
32 pub(crate) open: bool,
33 pub(crate) active_root: Option<usize>,
34 pub(crate) horizontal_direction: Direction,
35 pub(crate) vertical_direction: Direction,
36 pub(crate) menu_states: Vec<MenuState>,
37}
38impl MenuBarState {
39 pub(super) fn get_trimmed_indices(&self) -> impl Iterator<Item = usize> + '_ {
40 self.menu_states
41 .iter()
42 .take_while(|ms| ms.index.is_some())
43 .map(|ms| ms.index.expect("No indices were found in the menu state."))
44 }
45
46 pub(super) fn reset(&mut self) {
47 self.open = false;
48 self.active_root = None;
49 self.menu_states.clear();
50 }
51}
52impl Default for MenuBarState {
53 fn default() -> Self {
54 Self {
55 pressed: false,
56 view_cursor: Cursor::Available([-0.5, -0.5].into()),
57 open: false,
58 active_root: None,
59 horizontal_direction: Direction::Positive,
60 vertical_direction: Direction::Positive,
61 menu_states: Vec::new(),
62 }
63 }
64}
65
66pub(crate) fn menu_roots_children<Message, Renderer>(
67 menu_roots: &Vec<MenuTree<'_, Message, Renderer>>,
68) -> Vec<Tree>
69where
70 Renderer: renderer::Renderer,
71{
72 menu_roots
82 .iter()
83 .map(|root| {
84 let mut tree = Tree::empty();
85 let flat = root
86 .flattern()
87 .iter()
88 .map(|mt| Tree::new(mt.item.as_widget()))
89 .collect();
90 tree.children = flat;
91 tree
92 })
93 .collect()
94}
95
96#[allow(invalid_reference_casting)]
97pub(crate) fn menu_roots_diff<Message, Renderer>(
98 menu_roots: &mut Vec<MenuTree<'_, Message, Renderer>>,
99 tree: &mut Tree,
100) where
101 Renderer: renderer::Renderer,
102{
103 if tree.children.len() > menu_roots.len() {
104 tree.children.truncate(menu_roots.len());
105 }
106
107 tree.children
108 .iter_mut()
109 .zip(menu_roots.iter())
110 .for_each(|(t, root)| {
111 let mut flat = root
112 .flattern()
113 .iter()
114 .map(|mt| {
115 let widget = mt.item.as_widget();
116 let widget_ptr = widget as *const dyn Widget<Message, crate::Theme, Renderer>;
117 let widget_ptr_mut =
118 widget_ptr as *mut dyn Widget<Message, crate::Theme, Renderer>;
119 unsafe { &mut *widget_ptr_mut }
121 })
122 .collect::<Vec<_>>();
123
124 t.diff_children(flat.as_mut_slice());
125 });
126
127 if tree.children.len() < menu_roots.len() {
128 let extended = menu_roots[tree.children.len()..].iter().map(|root| {
129 let mut tree = Tree::empty();
130 let flat = root
131 .flattern()
132 .iter()
133 .map(|mt| Tree::new(mt.item.as_widget()))
134 .collect();
135 tree.children = flat;
136 tree
137 });
138 tree.children.extend(extended);
139 }
140}
141
142#[allow(missing_debug_implementations)]
144pub struct MenuBar<'a, Message, Renderer = crate::Renderer>
145where
146 Renderer: renderer::Renderer,
147{
148 width: Length,
149 height: Length,
150 spacing: f32,
151 padding: Padding,
152 bounds_expand: u16,
153 main_offset: i32,
154 cross_offset: i32,
155 close_condition: CloseCondition,
156 item_width: ItemWidth,
157 item_height: ItemHeight,
158 path_highlight: Option<PathHighlight>,
159 menu_roots: Vec<MenuTree<'a, Message, Renderer>>,
160 style: <crate::Theme as StyleSheet>::Style,
161}
162
163impl<'a, Message, Renderer> MenuBar<'a, Message, Renderer>
164where
165 Renderer: renderer::Renderer,
166{
167 #[must_use]
169 pub fn new(menu_roots: Vec<MenuTree<'a, Message, Renderer>>) -> Self {
170 let mut menu_roots = menu_roots;
171 menu_roots.iter_mut().for_each(MenuTree::set_index);
172
173 Self {
174 width: Length::Shrink,
175 height: Length::Shrink,
176 spacing: 0.0,
177 padding: Padding::ZERO,
178 bounds_expand: 16,
179 main_offset: 0,
180 cross_offset: 0,
181 close_condition: CloseCondition {
182 leave: false,
183 click_outside: true,
184 click_inside: true,
185 },
186 item_width: ItemWidth::Uniform(150),
187 item_height: ItemHeight::Uniform(30),
188 path_highlight: Some(PathHighlight::MenuActive),
189 menu_roots,
190 style: <crate::Theme as StyleSheet>::Style::default(),
191 }
192 }
193
194 #[must_use]
200 pub fn bounds_expand(mut self, value: u16) -> Self {
201 self.bounds_expand = value;
202 self
203 }
204
205 #[must_use]
207 pub fn close_condition(mut self, close_condition: CloseCondition) -> Self {
208 self.close_condition = close_condition;
209 self
210 }
211
212 #[must_use]
214 pub fn cross_offset(mut self, value: i32) -> Self {
215 self.cross_offset = value;
216 self
217 }
218
219 #[must_use]
221 pub fn height(mut self, height: Length) -> Self {
222 self.height = height;
223 self
224 }
225
226 #[must_use]
228 pub fn item_height(mut self, item_height: ItemHeight) -> Self {
229 self.item_height = item_height;
230 self
231 }
232
233 #[must_use]
235 pub fn item_width(mut self, item_width: ItemWidth) -> Self {
236 self.item_width = item_width;
237 self
238 }
239
240 #[must_use]
242 pub fn main_offset(mut self, value: i32) -> Self {
243 self.main_offset = value;
244 self
245 }
246
247 #[must_use]
249 pub fn padding<P: Into<Padding>>(mut self, padding: P) -> Self {
250 self.padding = padding.into();
251 self
252 }
253
254 #[must_use]
256 pub fn path_highlight(mut self, path_highlight: Option<PathHighlight>) -> Self {
257 self.path_highlight = path_highlight;
258 self
259 }
260
261 #[must_use]
263 pub fn spacing(mut self, units: f32) -> Self {
264 self.spacing = units;
265 self
266 }
267
268 #[must_use]
270 pub fn style(mut self, style: impl Into<<crate::Theme as StyleSheet>::Style>) -> Self {
271 self.style = style.into();
272 self
273 }
274
275 #[must_use]
277 pub fn width(mut self, width: Length) -> Self {
278 self.width = width;
279 self
280 }
281}
282impl<Message, Renderer> Widget<Message, crate::Theme, Renderer> for MenuBar<'_, Message, Renderer>
283where
284 Renderer: renderer::Renderer,
285{
286 fn size(&self) -> iced_core::Size<Length> {
287 iced_core::Size::new(self.width, self.height)
288 }
289
290 fn diff(&mut self, tree: &mut Tree) {
291 menu_roots_diff(&mut self.menu_roots, tree);
292 }
293
294 fn tag(&self) -> tree::Tag {
295 tree::Tag::of::<MenuBarState>()
296 }
297
298 fn state(&self) -> tree::State {
299 tree::State::new(MenuBarState::default())
300 }
301
302 fn children(&self) -> Vec<Tree> {
303 menu_roots_children(&self.menu_roots)
304 }
305
306 fn layout(&self, tree: &mut Tree, renderer: &Renderer, limits: &Limits) -> Node {
307 use super::flex;
308
309 let limits = limits.width(self.width).height(self.height);
310 let children = self
311 .menu_roots
312 .iter()
313 .map(|root| &root.item)
314 .collect::<Vec<_>>();
315 let mut tree_children = tree
317 .children
318 .iter_mut()
319 .map(|t| &mut t.children[0])
320 .collect::<Vec<_>>();
321 flex::resolve(
322 &flex::Axis::Horizontal,
323 renderer,
324 &limits,
325 self.padding,
326 self.spacing,
327 Alignment::Center,
328 &children,
329 &mut tree_children,
330 )
331 }
332
333 fn on_event(
334 &mut self,
335 tree: &mut Tree,
336 event: event::Event,
337 layout: Layout<'_>,
338 view_cursor: Cursor,
339 renderer: &Renderer,
340 clipboard: &mut dyn Clipboard,
341 shell: &mut Shell<'_, Message>,
342 viewport: &Rectangle,
343 ) -> event::Status {
344 use event::Event::{Mouse, Touch};
345 use mouse::{Button::Left, Event::ButtonReleased};
346 use touch::Event::{FingerLifted, FingerLost};
347
348 let root_status = process_root_events(
349 &mut self.menu_roots,
350 view_cursor,
351 tree,
352 &event,
353 layout,
354 renderer,
355 clipboard,
356 shell,
357 viewport,
358 );
359
360 let state = tree.state.downcast_mut::<MenuBarState>();
361
362 match event {
363 Mouse(ButtonReleased(Left)) | Touch(FingerLifted { .. } | FingerLost { .. }) => {
364 if state.menu_states.is_empty() && view_cursor.is_over(layout.bounds()) {
365 state.view_cursor = view_cursor;
366 state.open = true;
367 }
370 }
371 _ => (),
372 }
373 root_status
374 }
375
376 fn draw(
377 &self,
378 tree: &Tree,
379 renderer: &mut Renderer,
380 theme: &crate::Theme,
381 style: &renderer::Style,
382 layout: Layout<'_>,
383 view_cursor: Cursor,
384 viewport: &Rectangle,
385 ) {
386 let state = tree.state.downcast_ref::<MenuBarState>();
387 let cursor_pos = view_cursor.position().unwrap_or_default();
388 let position = if state.open && (cursor_pos.x < 0.0 || cursor_pos.y < 0.0) {
389 state.view_cursor
390 } else {
391 view_cursor
392 };
393
394 if self.path_highlight.is_some() {
396 let styling = theme.appearance(&self.style);
397 if let Some(active) = state.active_root {
398 let active_bounds = layout
399 .children()
400 .nth(active)
401 .expect("Active child not found in menu?")
402 .bounds();
403 let path_quad = renderer::Quad {
404 bounds: active_bounds,
405 border: Border {
406 radius: styling.bar_border_radius.into(),
407 ..Default::default()
408 },
409 shadow: Default::default(),
410 };
411
412 renderer.fill_quad(path_quad, styling.path);
413 }
414 }
415
416 self.menu_roots
417 .iter()
418 .zip(&tree.children)
419 .zip(layout.children())
420 .for_each(|((root, t), lo)| {
421 root.item.as_widget().draw(
422 &t.children[root.index],
423 renderer,
424 theme,
425 style,
426 lo,
427 position,
428 viewport,
429 );
430 });
431 }
432
433 fn overlay<'b>(
434 &'b mut self,
435 tree: &'b mut Tree,
436 layout: Layout<'_>,
437 _renderer: &Renderer,
438 translation: Vector,
439 ) -> Option<overlay::Element<'b, Message, crate::Theme, Renderer>> {
440 let state = tree.state.downcast_ref::<MenuBarState>();
444 if !state.open {
445 return None;
446 }
447
448 Some(
449 Menu {
450 tree,
451 menu_roots: &mut self.menu_roots,
452 bounds_expand: self.bounds_expand,
453 menu_overlays_parent: false,
454 close_condition: self.close_condition,
455 item_width: self.item_width,
456 item_height: self.item_height,
457 bar_bounds: layout.bounds(),
458 main_offset: self.main_offset,
459 cross_offset: self.cross_offset,
460 root_bounds_list: layout.children().map(|lo| lo.bounds()).collect(),
461 path_highlight: self.path_highlight,
462 style: &self.style,
463 position: Point::new(translation.x, translation.y),
464 }
465 .overlay(),
466 )
467 }
468}
469impl<'a, Message, Renderer> From<MenuBar<'a, Message, Renderer>>
470 for Element<'a, Message, crate::Theme, Renderer>
471where
472 Message: 'a,
473 Renderer: 'a + renderer::Renderer,
474{
475 fn from(value: MenuBar<'a, Message, Renderer>) -> Self {
476 Self::new(value)
477 }
478}
479
480#[allow(unused_results, clippy::too_many_arguments)]
481fn process_root_events<Message, Renderer>(
482 menu_roots: &mut [MenuTree<'_, Message, Renderer>],
483 view_cursor: Cursor,
484 tree: &mut Tree,
485 event: &event::Event,
486 layout: Layout<'_>,
487 renderer: &Renderer,
488 clipboard: &mut dyn Clipboard,
489 shell: &mut Shell<'_, Message>,
490 viewport: &Rectangle,
491) -> event::Status
492where
493 Renderer: renderer::Renderer,
494{
495 menu_roots
496 .iter_mut()
497 .zip(&mut tree.children)
498 .zip(layout.children())
499 .map(|((root, t), lo)| {
500 root.item.as_widget_mut().on_event(
502 &mut t.children[root.index],
503 event.clone(),
504 lo,
505 view_cursor,
506 renderer,
507 clipboard,
508 shell,
509 viewport,
510 )
511 })
512 .fold(event::Status::Ignored, event::Status::merge)
513}