1use std::borrow::Cow;
5use std::sync::Arc;
6
7use super::menu_bar::MenuBarState;
8use super::menu_tree::MenuTree;
9#[cfg(wayland_platform)]
10use crate::app::cosmic::{WINDOWING_SYSTEM, WindowingSystem};
11use crate::style::menu_bar::StyleSheet;
12
13use iced::{Alignment, window};
14use iced_core::{Border, Renderer as IcedRenderer, Shadow, Widget};
15use iced_widget::core::layout::{Limits, Node};
16use iced_widget::core::mouse::{self, Cursor};
17use iced_widget::core::widget::Tree;
18use iced_widget::core::{
19 Clipboard, Layout, Length, Padding, Point, Rectangle, Shell, Size, Vector, event, overlay,
20 renderer, touch,
21};
22
23#[derive(Debug, Clone, Copy)]
25pub struct CloseCondition {
26 pub leave: bool,
28
29 pub click_outside: bool,
31
32 pub click_inside: bool,
34}
35
36#[derive(Debug, Clone, Copy)]
38pub enum ItemWidth {
39 Uniform(u16),
41 Static(u16),
47}
48
49#[derive(Debug, Clone, Copy)]
51pub enum ItemHeight {
52 Uniform(u16),
54 Static(u16),
57 Dynamic(u16),
66}
67
68#[derive(Debug, Clone, Copy)]
70pub enum PathHighlight {
71 Full,
73 OmitActive,
75 MenuActive,
77}
78
79#[derive(Debug, Clone, Copy)]
81pub(crate) enum Direction {
82 Positive,
83 Negative,
84}
85
86#[derive(Debug)]
88#[allow(clippy::struct_excessive_bools)]
89struct Aod {
90 horizontal: bool,
92 vertical: bool,
93
94 horizontal_overlap: bool,
96 vertical_overlap: bool,
97
98 horizontal_direction: Direction,
100 vertical_direction: Direction,
101
102 horizontal_offset: f32,
104 vertical_offset: f32,
105}
106impl Aod {
107 #[allow(clippy::too_many_arguments)]
109 fn adaptive(
110 parent_pos: f32,
111 parent_size: f32,
112 child_size: f32,
113 max_size: f32,
114 offset: f32,
115 on: bool,
116 overlap: bool,
117 direction: Direction,
118 ) -> (f32, f32) {
119 match direction {
149 Direction::Positive => {
150 let space_negative = parent_pos;
151 let space_positive = max_size - parent_pos - parent_size;
152
153 if overlap {
154 let overshoot = child_size - parent_size;
155 if on && space_negative > space_positive && overshoot > space_positive {
156 (parent_pos - overshoot, parent_pos - overshoot)
157 } else {
158 (parent_pos, parent_pos)
159 }
160 } else {
161 let overshoot = child_size + offset;
162 if on && space_negative > space_positive && overshoot > space_positive {
163 (parent_pos - overshoot, parent_pos - offset)
164 } else {
165 (parent_pos + parent_size + offset, parent_pos + parent_size)
166 }
167 }
168 }
169 Direction::Negative => {
170 let space_positive = parent_pos;
171 let space_negative = max_size - parent_pos - parent_size;
172
173 if overlap {
174 let overshoot = child_size - parent_size;
175 if on && space_negative > space_positive && overshoot > space_positive {
176 (parent_pos, parent_pos)
177 } else {
178 (parent_pos - overshoot, parent_pos - overshoot)
179 }
180 } else {
181 let overshoot = child_size + offset;
182 if on && space_negative > space_positive && overshoot > space_positive {
183 (parent_pos + parent_size + offset, parent_pos + parent_size)
184 } else {
185 (parent_pos - overshoot, parent_pos - offset)
186 }
187 }
188 }
189 }
190 }
191
192 fn resolve(
194 &self,
195 parent_bounds: Rectangle,
196 children_size: Size,
197 viewport_size: Size,
198 ) -> (Point, Point) {
199 let (x, ox) = Self::adaptive(
200 parent_bounds.x,
201 parent_bounds.width,
202 children_size.width,
203 viewport_size.width,
204 self.horizontal_offset,
205 self.horizontal,
206 self.horizontal_overlap,
207 self.horizontal_direction,
208 );
209 let (y, oy) = Self::adaptive(
210 parent_bounds.y,
211 parent_bounds.height,
212 children_size.height,
213 viewport_size.height,
214 self.vertical_offset,
215 self.vertical,
216 self.vertical_overlap,
217 self.vertical_direction,
218 );
219
220 ([x, y].into(), [ox, oy].into())
221 }
222}
223
224#[derive(Debug, Clone, Copy)]
230pub(super) struct MenuSlice {
231 pub(super) start_index: usize,
232 pub(super) end_index: usize,
233 pub(super) lower_bound_rel: f32,
234 pub(super) upper_bound_rel: f32,
235}
236
237#[derive(Debug, Clone)]
238pub struct MenuBounds {
240 pub(crate) child_positions: Vec<f32>,
241 child_sizes: Vec<Size>,
242 children_bounds: Rectangle,
243 pub parent_bounds: Rectangle,
244 check_bounds: Rectangle,
245 offset_bounds: Rectangle,
246}
247impl MenuBounds {
248 #[allow(clippy::too_many_arguments)]
249 fn new<Message>(
250 menu_tree: &MenuTree<Message>,
251 renderer: &crate::Renderer,
252 item_width: ItemWidth,
253 item_height: ItemHeight,
254 viewport_size: Size,
255 overlay_offset: Vector,
256 aod: &Aod,
257 bounds_expand: u16,
258 parent_bounds: Rectangle,
259 tree: &mut [Tree],
260 is_overlay: bool,
261 ) -> Self {
262 let (children_size, child_positions, child_sizes) =
263 get_children_layout(menu_tree, renderer, item_width, item_height, tree);
264
265 let view_parent_bounds = parent_bounds + overlay_offset;
267
268 let (children_position, offset_position) = {
270 let (cp, op) = aod.resolve(view_parent_bounds, children_size, viewport_size);
271 if is_overlay {
272 (cp - overlay_offset, op - overlay_offset)
273 } else {
274 (Point::ORIGIN, op - overlay_offset)
275 }
276 };
277
278 let delta = children_position - offset_position;
280 let offset_size = if delta.x.abs() > delta.y.abs() {
281 Size::new(delta.x, children_size.height)
282 } else {
283 Size::new(children_size.width, delta.y)
284 };
285 let offset_bounds = Rectangle::new(offset_position, offset_size);
286
287 let children_bounds = Rectangle::new(children_position, children_size);
288 let check_bounds = pad_rectangle(children_bounds, bounds_expand.into());
289
290 Self {
291 child_positions,
292 child_sizes,
293 children_bounds,
294 parent_bounds,
295 check_bounds,
296 offset_bounds,
297 }
298 }
299}
300
301#[derive(Clone)]
302pub(crate) struct MenuState {
303 pub(crate) index: Option<usize>,
305 scroll_offset: f32,
306 pub menu_bounds: MenuBounds,
307}
308impl MenuState {
309 pub(super) fn layout<Message>(
310 &mut self,
311 overlay_offset: Vector,
312 slice: MenuSlice,
313 renderer: &crate::Renderer,
314 menu_tree: &[MenuTree<Message>],
315 tree: &mut [Tree],
316 ) -> Node {
317 let MenuSlice {
318 start_index,
319 end_index,
320 lower_bound_rel,
321 upper_bound_rel,
322 } = slice;
323
324 debug_assert_eq!(menu_tree.len(), self.menu_bounds.child_positions.len());
325
326 let children_bounds = self.menu_bounds.children_bounds + overlay_offset;
328 let child_nodes = self.menu_bounds.child_positions[start_index..=end_index]
329 .iter_mut()
330 .zip(self.menu_bounds.child_sizes[start_index..=end_index].iter_mut())
331 .zip(menu_tree[start_index..=end_index].iter())
332 .map(|((cp, size), mt)| {
333 let mut position = *cp;
334 let mut size = *size;
335
336 if position < lower_bound_rel && (position + size.height) > lower_bound_rel {
337 size.height = position + size.height - lower_bound_rel;
338 position = lower_bound_rel;
339 } else if position <= upper_bound_rel && (position + size.height) > upper_bound_rel
340 {
341 size.height = upper_bound_rel - position;
342 }
343
344 let limits = Limits::new(size, size);
345
346 mt.item
347 .element
348 .with_data_mut(|e| {
349 e.as_widget_mut()
350 .layout(&mut tree[mt.index], renderer, &limits)
351 })
352 .move_to(Point::new(0.0, position + self.scroll_offset))
353 })
354 .collect::<Vec<_>>();
355
356 Node::with_children(children_bounds.size(), child_nodes).move_to(children_bounds.position())
357 }
358
359 fn layout_single<Message>(
360 &self,
361 overlay_offset: Vector,
362 index: usize,
363 renderer: &crate::Renderer,
364 menu_tree: &mut MenuTree<Message>,
365 tree: &mut Tree,
366 ) -> Node {
367 let children_bounds = self.menu_bounds.children_bounds + overlay_offset;
369
370 let position = self.menu_bounds.child_positions[index];
371 let limits = Limits::new(Size::ZERO, self.menu_bounds.child_sizes[index]);
372 let parent_offset = children_bounds.position() - Point::ORIGIN;
373 let node = menu_tree.item.layout(tree, renderer, &limits);
374 node.move_to(Point::new(
375 parent_offset.x,
376 parent_offset.y + position + self.scroll_offset,
377 ))
378 }
379
380 pub(super) fn slice(
382 &self,
383 viewport_size: Size,
384 overlay_offset: Vector,
385 item_height: ItemHeight,
386 ) -> MenuSlice {
387 let children_bounds = self.menu_bounds.children_bounds + overlay_offset;
389
390 let max_index = self.menu_bounds.child_positions.len().saturating_sub(1);
391
392 let lower_bound = children_bounds.y.max(0.0);
394 let upper_bound = (children_bounds.y + children_bounds.height).min(viewport_size.height);
395
396 let lower_bound_rel = lower_bound - (children_bounds.y + self.scroll_offset);
398 let upper_bound_rel = upper_bound - (children_bounds.y + self.scroll_offset);
399
400 let (start_index, end_index) = match item_height {
402 ItemHeight::Uniform(u) => {
403 let start_index = (lower_bound_rel / f32::from(u)).floor() as usize;
404 let end_index = ((upper_bound_rel / f32::from(u)).floor() as usize).min(max_index);
405 (start_index, end_index)
406 }
407 ItemHeight::Static(_) | ItemHeight::Dynamic(_) => {
408 let positions = &self.menu_bounds.child_positions;
409 let sizes = &self.menu_bounds.child_sizes;
410
411 let start_index = search_bound(0, 0, max_index, lower_bound_rel, positions, sizes);
412 let end_index = search_bound(
413 max_index,
414 start_index,
415 max_index,
416 upper_bound_rel,
417 positions,
418 sizes,
419 )
420 .min(max_index);
421
422 (start_index, end_index)
423 }
424 };
425
426 MenuSlice {
427 start_index,
428 end_index,
429 lower_bound_rel,
430 upper_bound_rel,
431 }
432 }
433}
434
435#[derive(Clone)]
436pub(crate) struct Menu<'b, Message: std::clone::Clone> {
437 pub(crate) tree: MenuBarState,
438 pub(crate) menu_roots: Cow<'b, [MenuTree<Message>]>,
440 pub(crate) bounds_expand: u16,
441 pub(crate) menu_overlays_parent: bool,
443 pub(crate) close_condition: CloseCondition,
444 pub(crate) item_width: ItemWidth,
445 pub(crate) item_height: ItemHeight,
446 pub(crate) bar_bounds: Rectangle,
447 pub(crate) main_offset: i32,
448 pub(crate) cross_offset: i32,
449 pub(crate) root_bounds_list: Vec<Rectangle>,
450 pub(crate) path_highlight: Option<PathHighlight>,
451 pub(crate) style: Cow<'b, <crate::Theme as StyleSheet>::Style>,
452 pub(crate) position: Point,
453 pub(crate) is_overlay: bool,
454 pub(crate) window_id: window::Id,
456 pub(crate) depth: usize,
457 pub(crate) on_surface_action:
458 Option<Arc<dyn Fn(crate::surface::Action) -> Message + Send + Sync + 'static>>,
459}
460impl<'b, Message: Clone + 'static> Menu<'b, Message> {
461 pub(crate) fn overlay(self) -> overlay::Element<'b, Message, crate::Theme, crate::Renderer> {
462 overlay::Element::new(Box::new(self))
463 }
464
465 pub(crate) fn layout(&self, renderer: &crate::Renderer, limits: Limits) -> Node {
466 let position = self.position;
468 let mut intrinsic_size = Size::ZERO;
469
470 let empty = Vec::new();
471 self.tree.inner.with_data_mut(|data| {
472 if data.active_root.len() < self.depth + 1 || data.menu_states.len() < self.depth + 1 {
473 return Node::new(limits.min());
474 }
475
476 let overlay_offset = Point::ORIGIN - position;
477 let tree_children: &mut Vec<Tree> = &mut data.tree.children;
478
479 let children = (if self.is_overlay { 0 } else { self.depth }..=if self.is_overlay {
480 data.active_root.len() - 1
481 } else {
482 self.depth
483 })
484 .map(|active_root| {
485 if self.menu_roots.is_empty() {
486 return (&empty, vec![]);
487 }
488 let (active_tree, roots) =
489 data.active_root[..=active_root].iter().skip(1).fold(
490 (
491 &mut tree_children[data.active_root[0]].children,
492 &self.menu_roots[data.active_root[0]].children,
493 ),
494 |(tree, mt), next_active_root| (tree, &mt[*next_active_root].children),
495 );
496
497 data.menu_states[if self.is_overlay { 0 } else { self.depth }
498 ..=if self.is_overlay {
499 data.active_root.len() - 1
500 } else {
501 self.depth
502 }]
503 .iter_mut()
504 .enumerate()
505 .filter(|ms| self.is_overlay || ms.0 < 1)
506 .fold(
507 (roots, Vec::new()),
508 |(menu_root, mut nodes), (_i, ms)| {
509 let slice =
510 ms.slice(limits.max(), overlay_offset, self.item_height);
511 let _start_index = slice.start_index;
512 let _end_index = slice.end_index;
513 let children_node = ms.layout(
514 overlay_offset,
515 slice,
516 renderer,
517 menu_root,
518 active_tree,
519 );
520 let node_size = children_node.size();
521 intrinsic_size.height += node_size.height;
522
523 intrinsic_size.width = intrinsic_size.width.max(node_size.width);
524
525 nodes.push(children_node);
526 (
529 ms.index
530 .map_or(menu_root, |active| &menu_root[active].children),
531 nodes,
532 )
533 },
534 )
535 })
536 .map(|(_, l)| l)
537 .next()
538 .unwrap_or_default();
539
540 Node::with_children(
542 limits.resolve(Length::Shrink, Length::Shrink, intrinsic_size),
543 children,
544 )
545 .translate(Point::ORIGIN - position)
546 })
547 }
548
549 #[allow(clippy::too_many_lines)]
550 fn update(
551 &mut self,
552 event: &event::Event,
553 layout: Layout<'_>,
554 view_cursor: Cursor,
555 renderer: &crate::Renderer,
556 clipboard: &mut dyn Clipboard,
557 shell: &mut Shell<'_, Message>,
558 ) -> Option<(usize, MenuState)> {
559 use event::Event::{Mouse, Touch};
560 use event::Status::{Captured, Ignored};
561 use mouse::Button::Left;
562 use mouse::Event::{ButtonPressed, ButtonReleased, CursorMoved, WheelScrolled};
563 use touch::Event::{FingerLifted, FingerMoved, FingerPressed};
564
565 if !self
566 .tree
567 .inner
568 .with_data(|data| data.open || data.active_root.len() <= self.depth)
569 {
570 return None;
571 }
572
573 let viewport = layout.bounds();
574
575 let viewport_size = viewport.size();
576 let overlay_offset = Point::ORIGIN - viewport.position();
577 let overlay_cursor = view_cursor.position().unwrap_or_default() - overlay_offset;
578 let menu_roots = match &mut self.menu_roots {
579 Cow::Borrowed(_) => panic!(),
580 Cow::Owned(o) => o.as_mut_slice(),
581 };
582 process_menu_events(
583 self,
584 event,
585 view_cursor,
586 renderer,
587 clipboard,
588 shell,
589 overlay_offset,
590 );
591
592 init_root_menu(
593 self,
594 renderer,
595 shell,
596 overlay_cursor,
597 viewport_size,
598 overlay_offset,
599 self.bar_bounds,
600 self.main_offset as f32,
601 );
602
603 match event {
604 Mouse(WheelScrolled { delta }) => process_scroll_events(
605 self,
606 shell,
607 *delta,
608 overlay_cursor,
609 viewport_size,
610 overlay_offset,
611 ),
612
613 Mouse(ButtonPressed(Left)) | Touch(FingerPressed { .. }) => {
614 self.tree.inner.with_data_mut(|data| {
615 data.pressed = true;
616 data.view_cursor = view_cursor;
617 });
618 }
619
620 Mouse(CursorMoved { position }) | Touch(FingerMoved { position, .. }) => {
621 let view_cursor = Cursor::Available(*position);
622 let overlay_cursor = view_cursor.position().unwrap_or_default() - overlay_offset;
623 if !self.is_overlay && !view_cursor.is_over(viewport) {
624 return None;
625 }
626 let new_root = process_overlay_events(
627 self,
628 renderer,
629 viewport_size,
630 overlay_offset,
631 view_cursor,
632 overlay_cursor,
633 self.cross_offset as f32,
634 shell,
635 );
636
637 if view_cursor.is_over(viewport) {
638 shell.request_redraw();
639 }
640
641 if self.is_overlay && view_cursor.is_over(viewport) {
642 shell.capture_event();
643 }
644
645 return new_root;
646 }
647
648 Mouse(ButtonReleased(_)) | Touch(FingerLifted { .. }) => {
649 self.tree.inner.with_data_mut(|state| {
650 state.pressed = false;
651
652 if state.open
654 && state
655 .view_cursor
656 .position()
657 .unwrap_or_default()
658 .distance(view_cursor.position().unwrap_or_default())
659 < 2.0
660 {
661 let is_inside = state.menu_states[..=if self.is_overlay {
662 state.active_root.len().saturating_sub(1)
663 } else {
664 self.depth
665 }]
666 .iter()
667 .any(|ms| ms.menu_bounds.check_bounds.contains(overlay_cursor));
668 let mut needs_reset = false;
669 needs_reset |= self.close_condition.click_inside
670 && is_inside
671 && matches!(
672 event,
673 Mouse(ButtonReleased(Left)) | Touch(FingerLifted { .. })
674 );
675
676 needs_reset |= self.close_condition.click_outside && !is_inside;
677
678 if needs_reset {
679 #[cfg(wayland_platform)]
680 if matches!(WINDOWING_SYSTEM.get(), Some(WindowingSystem::Wayland))
681 && let Some(handler) = self.on_surface_action.as_ref()
682 {
683 let mut root = self.window_id;
684 let mut depth = self.depth;
685 while let Some(parent) =
686 state.popup_id.iter().find(|(_, v)| **v == root)
687 {
688 if depth == 0 {
690 break;
691 }
692 root = *parent.0;
693 depth = depth.saturating_sub(1);
694 }
695 shell
696 .publish((handler)(crate::surface::Action::DestroyPopup(root)));
697 }
698
699 state.reset();
700 }
701 }
702
703 if self.bar_bounds.contains(overlay_cursor) {
705 state.reset();
706 }
707 });
708 }
709
710 _ => {}
711 }
712 None
713 }
714
715 #[allow(unused_results, clippy::too_many_lines)]
716 fn draw(
717 &self,
718 renderer: &mut crate::Renderer,
719 theme: &crate::Theme,
720 style: &renderer::Style,
721 layout: Layout<'_>,
722 view_cursor: Cursor,
723 ) {
724 self.tree.inner.with_data(|state| {
725 if !state.open || state.active_root.len() <= self.depth {
726 return;
727 }
728 let active_root = &state.active_root[..=if self.is_overlay { 0 } else { self.depth }];
729 let viewport = layout.bounds();
730 let viewport_size = viewport.size();
731 let overlay_offset = Point::ORIGIN - viewport.position();
732
733 let render_bounds = if self.is_overlay {
734 Rectangle::new(Point::ORIGIN, viewport.size())
735 } else {
736 Rectangle::new(Point::ORIGIN, Size::INFINITE)
737 };
738
739 let styling = theme.appearance(&self.style, self.is_overlay);
740 let roots = active_root.iter().skip(1).fold(
741 &self.menu_roots[active_root[0]].children,
742 |mt, next_active_root| &mt[*next_active_root].children,
743 );
744 let indices = state.get_trimmed_indices(self.depth).collect::<Vec<_>>();
745 state.menu_states[if self.is_overlay { 0 } else { self.depth }..=if self.is_overlay {
746 state.menu_states.len() - 1
747 } else {
748 self.depth
749 }]
750 .iter()
751 .zip(layout.children())
752 .enumerate()
753 .filter(|ms: &(usize, (&MenuState, Layout<'_>))| self.is_overlay || ms.0 < 1)
754 .fold(
755 roots,
756 |menu_roots: &Vec<MenuTree<Message>>, (i, (ms, children_layout))| {
757 let draw_path = self.path_highlight.as_ref().is_some_and(|ph| match ph {
758 PathHighlight::Full => true,
759 PathHighlight::OmitActive => {
760 !indices.is_empty() && i < indices.len() - 1
761 }
762 PathHighlight::MenuActive => {
763 !indices.is_empty()
764 && i < indices.len()
765 && menu_roots.len() > indices[i]
766 && (i < indices.len() - 1
767 || !menu_roots[indices[i]].children.is_empty())
768 }
769 });
770
771 let view_cursor = if self.depth == state.active_root.len() - 1
773 || i == state.menu_states.len() - 1
774 {
775 view_cursor
776 } else {
777 Cursor::Available([-1.0; 2].into())
778 };
779
780 let draw_menu = |r: &mut crate::Renderer| {
781 let slice = ms.slice(viewport_size, overlay_offset, self.item_height);
783 let start_index = slice.start_index;
784 let end_index = slice.end_index;
785
786 let children_bounds = children_layout.bounds();
787
788 let menu_quad = renderer::Quad {
794 bounds: pad_rectangle(
795 children_bounds.intersection(&viewport).unwrap_or_default(),
796 styling.background_expand.into(),
797 ),
798 border: Border {
799 radius: styling.menu_border_radius.into(),
800 width: styling.border_width,
801 color: styling.border_color,
802 },
803 shadow: Shadow::default(),
804 snap: true,
805 };
806 let menu_color = styling.background;
807 r.fill_quad(menu_quad, menu_color);
808 if let (true, Some(active)) = (draw_path, ms.index)
810 && let Some(active_layout) = children_layout
811 .children()
812 .nth(active.saturating_sub(start_index))
813 {
814 let i = active.saturating_sub(start_index);
815 let mut rad = styling.menu_border_radius;
816 let rad_0 = theme.cosmic().radius_0();
817 if start_index != end_index {
818 if 0 == i {
819 rad[2] = rad_0[0];
820 rad[3] = rad_0[1];
821 } else if i == end_index - start_index {
822 rad[0] = rad_0[2];
823 rad[1] = rad_0[3];
824 } else {
825 rad = rad_0;
826 }
827 }
828 let path_quad = renderer::Quad {
829 bounds: active_layout
830 .bounds()
831 .intersection(&viewport)
832 .unwrap_or_default(),
833 border: Border {
834 radius: rad.into(),
835 ..Default::default()
836 },
837 shadow: Shadow::default(),
838 snap: true,
839 };
840
841 r.fill_quad(path_quad, styling.path);
842 }
843 if start_index < menu_roots.len() {
844 menu_roots[start_index..=end_index]
846 .iter()
847 .enumerate()
848 .zip(children_layout.children())
849 .for_each(|((i, mt), clo)| {
850 let t = theme.with_list_item_position(
851 if start_index == end_index {
852 Some((Alignment::Center, i))
853 } else if 0 == i {
854 Some((Alignment::Start, i))
855 } else if i == end_index - start_index {
856 Some((Alignment::End, i))
857 } else {
858 None
859 },
860 );
861
862 mt.item.draw(
863 &state.tree.children[active_root[0]].children[mt.index],
864 r,
865 &t,
866 style,
867 clo,
868 view_cursor,
869 &children_layout
870 .bounds()
871 .intersection(&viewport)
872 .unwrap_or_default(),
873 );
874 });
875 }
876 };
877
878 renderer.with_layer(render_bounds, draw_menu);
879
880 ms.index
882 .map_or(menu_roots, |active| &menu_roots[active].children)
883 },
884 );
885 });
886 }
887}
888impl<Message: Clone + 'static> overlay::Overlay<Message, crate::Theme, crate::Renderer>
889 for Menu<'_, Message>
890{
891 fn layout(&mut self, renderer: &crate::Renderer, bounds: Size) -> iced_core::layout::Node {
892 Menu::layout(
893 self,
894 renderer,
895 Limits::NONE
896 .min_width(bounds.width)
897 .max_width(bounds.width)
898 .min_height(bounds.height)
899 .max_height(bounds.height),
900 )
901 }
902
903 fn update(
904 &mut self,
905 event: &iced::Event,
906 layout: Layout<'_>,
907 cursor: mouse::Cursor,
908 renderer: &crate::Renderer,
909 clipboard: &mut dyn Clipboard,
910 shell: &mut Shell<'_, Message>,
911 ) {
912 self.update(event, layout, cursor, renderer, clipboard, shell);
913 }
914
915 fn draw(
916 &self,
917 renderer: &mut crate::Renderer,
918 theme: &crate::Theme,
919 style: &renderer::Style,
920 layout: Layout<'_>,
921 cursor: mouse::Cursor,
922 ) {
923 self.draw(renderer, theme, style, layout, cursor);
924 }
925
926 fn mouse_interaction(
927 &self,
928 layout: Layout<'_>,
929 cursor: mouse::Cursor,
930 _renderer: &crate::Renderer,
931 ) -> mouse::Interaction {
932 if cursor.is_over(layout.bounds()) {
933 mouse::Interaction::Idle
934 } else {
935 mouse::Interaction::None
936 }
937 }
938}
939
940impl<Message: std::clone::Clone + 'static> Widget<Message, crate::Theme, crate::Renderer>
941 for Menu<'_, Message>
942{
943 fn size(&self) -> Size<Length> {
944 Size {
945 width: Length::Shrink,
946 height: Length::Shrink,
947 }
948 }
949
950 fn layout(
951 &mut self,
952 _tree: &mut Tree,
953 renderer: &crate::Renderer,
954 limits: &iced_core::layout::Limits,
955 ) -> iced_core::layout::Node {
956 Menu::layout(self, renderer, *limits)
957 }
958
959 fn draw(
960 &self,
961 _tree: &Tree,
962 renderer: &mut crate::Renderer,
963 theme: &crate::Theme,
964 style: &renderer::Style,
965 layout: Layout<'_>,
966 cursor: mouse::Cursor,
967 _viewport: &Rectangle,
968 ) {
969 Menu::draw(self, renderer, theme, style, layout, cursor);
970 }
971
972 #[allow(clippy::too_many_lines)]
973 fn update(
974 &mut self,
975 tree: &mut Tree,
976 event: &iced::Event,
977 layout: Layout<'_>,
978 cursor: mouse::Cursor,
979 renderer: &crate::Renderer,
980 clipboard: &mut dyn Clipboard,
981 shell: &mut Shell<'_, Message>,
982 viewport: &Rectangle,
983 ) {
984 let new_root = self.update(event, layout, cursor, renderer, clipboard, shell);
985
986 #[cfg(wayland_platform)]
987 if matches!(WINDOWING_SYSTEM.get(), Some(WindowingSystem::Wayland))
988 && let Some((new_root, new_ms)) = new_root
989 {
990 use iced_runtime::platform_specific::wayland::CornerRadius;
991 use iced_runtime::platform_specific::wayland::popup::{
992 SctkPopupSettings, SctkPositioner,
993 };
994
995 use crate::surface::action::LiveSettings;
996 use crate::theme::THEME;
997 let overlay_offset = Point::ORIGIN - viewport.position();
998
999 let overlay_cursor = cursor.position().unwrap_or_default() - overlay_offset;
1000
1001 let Some((mut menu, popup_id)) = self.tree.inner.with_data_mut(|state| {
1002 let popup_id = *state
1003 .popup_id
1004 .entry(self.window_id)
1005 .or_insert_with(window::Id::unique);
1006 let active_roots = state
1007 .active_root
1008 .get(self.depth)
1009 .cloned()
1010 .unwrap_or_default();
1011
1012 let root_bounds_list = layout
1013 .children()
1014 .next()
1015 .unwrap()
1016 .children()
1017 .map(|lo| lo.bounds())
1018 .collect();
1019
1020 let mut popup_menu = Menu {
1021 tree: self.tree.clone(),
1022 menu_roots: Cow::Owned(Cow::into_owned(self.menu_roots.clone())),
1023 bounds_expand: self.bounds_expand,
1024 menu_overlays_parent: false,
1025 close_condition: self.close_condition,
1026 item_width: self.item_width,
1027 item_height: self.item_height,
1028 bar_bounds: layout.bounds(),
1029 main_offset: self.main_offset,
1030 cross_offset: self.cross_offset,
1031 root_bounds_list,
1032 path_highlight: self.path_highlight,
1033 style: Cow::Owned(Cow::into_owned(self.style.clone())),
1034 position: Point::new(0., 0.),
1035 is_overlay: false,
1036 window_id: popup_id,
1037 depth: self.depth + 1,
1038 on_surface_action: self.on_surface_action.clone(),
1039 };
1040
1041 state.active_root.push(new_root);
1042
1043 Some((popup_menu, popup_id))
1044 }) else {
1045 return;
1046 };
1047 init_root_popup_menu(
1049 &mut menu,
1050 renderer,
1051 shell,
1052 cursor.position().unwrap_or_default(),
1053 layout.bounds().size(),
1054 Vector::new(0., 0.),
1055 layout.bounds(),
1056 self.main_offset as f32,
1057 );
1058 let (anchor_rect, gravity) = self.tree.inner.with_data_mut(|state| {
1059 (state
1060 .menu_states
1061 .get(self.depth + 1)
1062 .map(|s| s.menu_bounds.parent_bounds)
1063 .map_or_else(
1064 || {
1065 let bounds = layout.bounds();
1066 Rectangle {
1067 x: bounds.x as i32,
1068 y: bounds.y as i32,
1069 width: bounds.width as i32,
1070 height: bounds.height as i32,
1071 }
1072 },
1073 |r| Rectangle {
1074 x: r.x as i32,
1075 y: r.y as i32,
1076 width: r.width as i32,
1077 height: r.height as i32,
1078 },
1079 ), match (state.horizontal_direction, state.vertical_direction) {
1080 (Direction::Positive, Direction::Positive) => cctk::wayland_protocols::xdg::shell::client::xdg_positioner::Gravity::BottomRight,
1081 (Direction::Positive, Direction::Negative) => cctk::wayland_protocols::xdg::shell::client::xdg_positioner::Gravity::TopRight,
1082 (Direction::Negative, Direction::Positive) => cctk::wayland_protocols::xdg::shell::client::xdg_positioner::Gravity::BottomLeft,
1083 (Direction::Negative, Direction::Negative) => cctk::wayland_protocols::xdg::shell::client::xdg_positioner::Gravity::TopLeft,
1084 })
1085 });
1086
1087 let menu_node = Widget::layout(
1088 &mut menu,
1089 &mut Tree::empty(),
1090 renderer,
1091 &Limits::NONE.min_width(1.).min_height(1.),
1092 );
1093
1094 let popup_size = menu_node.size();
1095 let mut positioner = SctkPositioner {
1096 size: Some((
1097 popup_size.width.ceil() as u32 + 2,
1098 popup_size.height.ceil() as u32 + 2,
1099 )),
1100 anchor_rect,
1101 anchor:
1102 cctk::wayland_protocols::xdg::shell::client::xdg_positioner::Anchor::TopRight,
1103 gravity,
1104 reactive: true,
1105 ..Default::default()
1106 };
1107 positioner.constraint_adjustment &= !(1 << 0);
1109 let parent = self.window_id;
1110
1111 let t = THEME.lock().unwrap();
1112 let styling = t.appearance(&crate::theme::menu_bar::MenuBarStyle::Default, false);
1113 drop(t);
1114 let rad = styling.menu_border_radius;
1115
1116 shell.publish((self.on_surface_action.as_ref().unwrap())(
1117 crate::surface::action::simple_popup(
1118 move || LiveSettings {
1119 corners: Some(CornerRadius {
1120 top_left: rad[0] as u32,
1121 top_right: rad[1] as u32,
1122 bottom_left: rad[2] as u32,
1123 bottom_right: rad[3] as u32,
1124 }),
1125 ..Default::default()
1126 },
1127 move || SctkPopupSettings {
1128 parent,
1129 id: popup_id,
1130 positioner: positioner.clone(),
1131 parent_size: None,
1132 grab: true,
1133 close_with_children: false,
1134 input_zone: None,
1135 },
1136 Some(move || {
1137 crate::Element::from(
1138 crate::widget::container(menu.clone()).center(Length::Fill),
1139 )
1140 .map(crate::action::app)
1141 }),
1142 ),
1143 ));
1144 }
1145 }
1146
1147 fn mouse_interaction(
1148 &self,
1149 _tree: &Tree,
1150 layout: Layout<'_>,
1151 cursor: mouse::Cursor,
1152 _viewport: &Rectangle,
1153 _renderer: &crate::Renderer,
1154 ) -> mouse::Interaction {
1155 if cursor.is_over(layout.bounds()) {
1156 mouse::Interaction::Idle
1157 } else {
1158 mouse::Interaction::None
1159 }
1160 }
1161}
1162
1163impl<'a, Message> From<Menu<'a, Message>>
1164 for iced::Element<'a, Message, crate::Theme, crate::Renderer>
1165where
1166 Message: std::clone::Clone + 'static,
1167{
1168 fn from(value: Menu<'a, Message>) -> Self {
1169 Self::new(value)
1170 }
1171}
1172
1173fn pad_rectangle(rect: Rectangle, padding: Padding) -> Rectangle {
1174 Rectangle {
1175 x: rect.x - padding.left,
1176 y: rect.y - padding.top,
1177 width: rect.width + padding.x(),
1178 height: rect.height + padding.y(),
1179 }
1180}
1181
1182#[allow(clippy::too_many_arguments)]
1183pub(crate) fn init_root_menu<Message: Clone>(
1184 menu: &mut Menu<'_, Message>,
1185 renderer: &crate::Renderer,
1186 shell: &mut Shell<'_, Message>,
1187 overlay_cursor: Point,
1188 viewport_size: Size,
1189 overlay_offset: Vector,
1190 bar_bounds: Rectangle,
1191 main_offset: f32,
1192) {
1193 menu.tree.inner.with_data_mut(|state| {
1194 if !(state.menu_states.get(menu.depth).is_none()
1195 && (!menu.is_overlay || bar_bounds.contains(overlay_cursor)))
1196 || menu.depth > 0
1197 || !state.open
1198 {
1199 return;
1200 }
1201
1202 for (i, (&root_bounds, mt)) in menu
1203 .root_bounds_list
1204 .iter()
1205 .zip(menu.menu_roots.iter())
1206 .enumerate()
1207 {
1208 if mt.children.is_empty() {
1209 continue;
1210 }
1211
1212 if root_bounds.contains(overlay_cursor) {
1213 let view_center = viewport_size.width * 0.5;
1214 let rb_center = root_bounds.center_x();
1215
1216 state.horizontal_direction = if menu.is_overlay && rb_center > view_center {
1217 Direction::Negative
1218 } else {
1219 Direction::Positive
1220 };
1221
1222 let aod = Aod {
1223 horizontal: true,
1224 vertical: true,
1225 horizontal_overlap: true,
1226 vertical_overlap: false,
1227 horizontal_direction: state.horizontal_direction,
1228 vertical_direction: state.vertical_direction,
1229 horizontal_offset: 0.0,
1230 vertical_offset: main_offset,
1231 };
1232 let menu_bounds = MenuBounds::new(
1233 mt,
1234 renderer,
1235 menu.item_width,
1236 menu.item_height,
1237 viewport_size,
1238 overlay_offset,
1239 &aod,
1240 menu.bounds_expand,
1241 root_bounds,
1242 &mut state.tree.children[0].children,
1243 menu.is_overlay,
1244 );
1245 state.active_root.push(i);
1246 let ms = MenuState {
1247 index: None,
1248 scroll_offset: 0.0,
1249 menu_bounds,
1250 };
1251 state.menu_states.push(ms);
1252 shell.invalidate_layout();
1254
1255 break;
1256 }
1257 }
1258 });
1259}
1260
1261#[cfg(wayland_platform)]
1262pub(super) fn init_root_popup_menu<Message>(
1263 menu: &mut Menu<'_, Message>,
1264 renderer: &crate::Renderer,
1265 shell: &mut Shell<'_, Message>,
1266 overlay_cursor: Point,
1267 viewport_size: Size,
1268 overlay_offset: Vector,
1269 bar_bounds: Rectangle,
1270 main_offset: f32,
1271) where
1272 Message: std::clone::Clone,
1273{
1274 menu.tree.inner.with_data_mut(|state| {
1275 if !(state.menu_states.get(menu.depth).is_none()
1276 && (!menu.is_overlay || bar_bounds.contains(overlay_cursor)))
1277 {
1278 return;
1279 }
1280
1281 let active_roots = &state.active_root[..=menu.depth];
1282
1283 let mt = active_roots
1284 .iter()
1285 .skip(1)
1286 .fold(&menu.menu_roots[active_roots[0]], |mt, next_active_root| {
1287 &mt.children[*next_active_root]
1288 });
1289 let i = active_roots.last().unwrap();
1290 let root_bounds = menu.root_bounds_list[*i];
1291
1292 assert!(!mt.children.is_empty(), "skipping menu with no children");
1293 let aod = Aod {
1294 horizontal: true,
1295 vertical: true,
1296 horizontal_overlap: true,
1297 vertical_overlap: false,
1298 horizontal_direction: state.horizontal_direction,
1299 vertical_direction: state.vertical_direction,
1300 horizontal_offset: 0.0,
1301 vertical_offset: main_offset,
1302 };
1303 let menu_bounds = MenuBounds::new(
1304 mt,
1305 renderer,
1306 menu.item_width,
1307 menu.item_height,
1308 viewport_size,
1309 overlay_offset,
1310 &aod,
1311 menu.bounds_expand,
1312 root_bounds,
1313 &mut state.tree.children[0].children,
1315 menu.is_overlay,
1316 );
1317
1318 let view_center = viewport_size.width * 0.5;
1319 let rb_center = root_bounds.center_x();
1320
1321 state.horizontal_direction = if rb_center > view_center {
1322 Direction::Negative
1323 } else {
1324 Direction::Positive
1325 };
1326
1327 let ms = MenuState {
1328 index: None,
1329 scroll_offset: 0.0,
1330 menu_bounds,
1331 };
1332 state.menu_states.push(ms);
1333
1334 shell.invalidate_layout();
1336 });
1337}
1338
1339#[allow(clippy::too_many_arguments)]
1340fn process_menu_events<Message: std::clone::Clone>(
1341 menu: &mut Menu<Message>,
1342 event: &event::Event,
1343 view_cursor: Cursor,
1344 renderer: &crate::Renderer,
1345 clipboard: &mut dyn Clipboard,
1346 shell: &mut Shell<'_, Message>,
1347 overlay_offset: Vector,
1348) {
1349 let my_state = &mut menu.tree;
1350 let menu_roots = match &mut menu.menu_roots {
1351 Cow::Borrowed(_) => panic!(),
1352 Cow::Owned(o) => o.as_mut_slice(),
1353 };
1354 my_state.inner.with_data_mut(|state| {
1355 if state.active_root.len() <= menu.depth {
1356 return;
1357 }
1358
1359 let Some(hover) = state.menu_states.last_mut() else {
1360 return;
1361 };
1362
1363 let Some(hover_index) = hover.index else {
1364 return;
1365 };
1366
1367 let mt = state.active_root.iter().skip(1).fold(
1368 &mut menu_roots[state.active_root[0]],
1370 |mt, next_active_root| &mut mt.children[*next_active_root],
1371 );
1372
1373 let mt = &mut mt.children[hover_index];
1374 let tree = &mut state.tree.children[state.active_root[0]].children[mt.index];
1375
1376 let child_node = hover.layout_single(
1378 overlay_offset,
1379 hover.index.expect("missing index within menu state."),
1380 renderer,
1381 mt,
1382 tree,
1383 );
1384 let child_layout = Layout::new(&child_node);
1385
1386 mt.item.update(
1388 tree,
1389 event,
1390 child_layout,
1391 view_cursor,
1392 renderer,
1393 clipboard,
1394 shell,
1395 &Rectangle::default(),
1396 );
1397 });
1398}
1399
1400#[allow(unused_results, clippy::too_many_lines, clippy::too_many_arguments)]
1401fn process_overlay_events<Message>(
1402 menu: &mut Menu<Message>,
1403 renderer: &crate::Renderer,
1404 viewport_size: Size,
1405 overlay_offset: Vector,
1406 view_cursor: Cursor,
1407 overlay_cursor: Point,
1408 cross_offset: f32,
1409 shell: &mut Shell<'_, Message>,
1410) -> Option<(usize, MenuState)>
1411where
1412 Message: std::clone::Clone,
1413{
1414 let mut new_menu_root = None;
1424
1425 menu.tree.inner.with_data_mut(|state| {
1426
1427 state.view_cursor = view_cursor;
1430
1431 if state.open {
1433 let mut prev_bounds = std::iter::once(menu.bar_bounds)
1434 .chain(
1435 if menu.is_overlay {
1436 state.menu_states[..state.menu_states.len().saturating_sub(1)].iter()
1437 } else {
1438 state.menu_states[..menu.depth].iter()
1439 }
1440 .map(|s| s.menu_bounds.children_bounds),
1441 )
1442 .collect::<Vec<_>>();
1443
1444 if menu.is_overlay && menu.close_condition.leave {
1445 for i in (0..state.menu_states.len()).rev() {
1446 let mb = &state.menu_states[i].menu_bounds;
1447
1448 if mb.parent_bounds.contains(overlay_cursor)
1449 || menu.is_overlay && mb.children_bounds.contains(overlay_cursor)
1450 || mb.offset_bounds.contains(overlay_cursor)
1451 || (mb.check_bounds.contains(overlay_cursor)
1452 && prev_bounds.iter().all(|pvb| !pvb.contains(overlay_cursor)))
1453 {
1454 break;
1455 }
1456 prev_bounds.pop();
1457 state.active_root.pop();
1458 state.menu_states.pop();
1459 }
1460 } else if menu.is_overlay {
1461 for i in (0..state.menu_states.len()).rev() {
1462 let mb = &state.menu_states[i].menu_bounds;
1463
1464 if mb.parent_bounds.contains(overlay_cursor)
1465 || mb.children_bounds.contains(overlay_cursor)
1466 || prev_bounds.iter().all(|pvb| !pvb.contains(overlay_cursor))
1467 {
1468 break;
1469 }
1470 prev_bounds.pop();
1471 state.active_root.pop();
1472 state.menu_states.pop();
1473 }
1474 }
1475 }
1476
1477 let menu_states_len = state.menu_states.len();
1479
1480 let Some(last_menu_state) = state.menu_states.get_mut(if menu.is_overlay {
1481 menu_states_len.saturating_sub(1)
1482 } else {
1483 menu.depth
1484 }) else {
1485 if menu.is_overlay {
1486 if !menu.bar_bounds.contains(overlay_cursor) {
1494 state.open = false;
1495 }
1496 }
1497 shell.capture_event();
1498 return new_menu_root;
1499 };
1500
1501 let last_menu_bounds = &last_menu_state.menu_bounds;
1502 let last_parent_bounds = last_menu_bounds.parent_bounds;
1503 let last_children_bounds = last_menu_bounds.children_bounds;
1504
1505 if (menu.is_overlay && !menu.menu_overlays_parent && last_parent_bounds.contains(overlay_cursor))
1506 || menu.is_overlay && !last_children_bounds.contains(overlay_cursor)
1508 {
1510
1511 if last_menu_state.index.take().is_some() {
1512 shell.request_redraw();
1513 }
1514 shell.capture_event();
1515 return new_menu_root;
1516 }
1517
1518 let height_diff = (overlay_cursor.y
1520 - (last_children_bounds.y + last_menu_state.scroll_offset))
1521 .clamp(0.0, last_children_bounds.height - 0.001);
1522
1523 let active_root = if menu.is_overlay {
1524 &state.active_root
1525 } else {
1526 &state.active_root[..=menu.depth]
1527 };
1528
1529 if state.pressed {
1530 return new_menu_root;
1531 }
1532 let roots = active_root.iter().skip(1).fold(
1533 &menu.menu_roots[active_root[0]].children,
1534 |mt, next_active_root| &mt[*next_active_root].children,
1535 );
1536 let tree = &mut state.tree.children[active_root[0]].children;
1537
1538 let active_menu: &Vec<MenuTree<Message>> = roots;
1539 let new_index = match menu.item_height {
1540 ItemHeight::Uniform(u) => (height_diff / f32::from(u)).floor() as usize,
1541 ItemHeight::Static(_) | ItemHeight::Dynamic(_) => {
1542 let max_index = active_menu.len() - 1;
1543 search_bound(
1544 0,
1545 0,
1546 max_index,
1547 height_diff,
1548 &last_menu_bounds.child_positions,
1549 &last_menu_bounds.child_sizes,
1550 )
1551 }
1552 };
1553
1554 let remove = last_menu_state
1555 .index
1556 .as_ref()
1557 .is_some_and(|i| *i != new_index && !active_menu[*i].children.is_empty());
1558
1559 #[cfg(wayland_platform)]
1560 if matches!(WINDOWING_SYSTEM.get(), Some(WindowingSystem::Wayland)) && remove {
1561 if let Some(id) = state.popup_id.remove(&menu.window_id) {
1562 state.active_root.truncate(menu.depth + 1);
1563 shell.publish((menu.on_surface_action.as_ref().unwrap())({
1564 crate::surface::action::destroy_popup(id)
1565 }));
1566 }
1567 }
1568 let item = &active_menu[new_index];
1569 let old_index = last_menu_state.index.replace(new_index);
1571
1572 if old_index != Some(new_index) {
1573 shell.request_redraw();
1574 }
1575
1576 if !item.children.is_empty() && old_index.is_none_or(|i| i != new_index) {
1579 let item_position = Point::new(
1580 0.0,
1581 last_menu_bounds.child_positions[new_index] + last_menu_state.scroll_offset,
1582 );
1583 let item_size = last_menu_bounds.child_sizes[new_index];
1584
1585 let item_bounds = Rectangle::new(item_position, item_size)
1587 + (last_menu_bounds.children_bounds.position() - Point::ORIGIN);
1588
1589 let aod = Aod {
1590 horizontal: true,
1591 vertical: true,
1592 horizontal_overlap: false,
1593 vertical_overlap: true,
1594 horizontal_direction: state.horizontal_direction,
1595 vertical_direction: state.vertical_direction,
1596 horizontal_offset: cross_offset,
1597 vertical_offset: 0.0,
1598 };
1599 let ms = MenuState {
1600 index: None,
1601 scroll_offset: 0.0,
1602 menu_bounds: MenuBounds::new(
1603 item,
1604 renderer,
1605 menu.item_width,
1606 menu.item_height,
1607 viewport_size,
1608 overlay_offset,
1609 &aod,
1610 menu.bounds_expand,
1611 item_bounds,
1612 tree,
1613 menu.is_overlay,
1614 ),
1615 };
1616
1617 new_menu_root = Some((new_index, ms.clone()));
1618 if menu.is_overlay {
1619 state.active_root.push(new_index);
1620 } else {
1621 state.menu_states.truncate(menu.depth + 1);
1622 }
1623 state.menu_states.push(ms);
1624 } else if !menu.is_overlay && remove {
1625 state.menu_states.truncate(menu.depth + 1);
1626 }
1627
1628 shell.capture_event();
1629 new_menu_root
1630 })
1631}
1632
1633fn process_scroll_events<Message>(
1634 menu: &mut Menu<'_, Message>,
1635 shell: &mut Shell<'_, Message>,
1636 delta: mouse::ScrollDelta,
1637 overlay_cursor: Point,
1638 viewport_size: Size,
1639 overlay_offset: Vector,
1640) where
1641 Message: Clone,
1642{
1643 use event::Status::{Captured, Ignored};
1644 use mouse::ScrollDelta;
1645
1646 menu.tree.inner.with_data_mut(|state| {
1647 let delta_y = match delta {
1648 ScrollDelta::Lines { y, .. } => y * 60.0,
1649 ScrollDelta::Pixels { y, .. } => y,
1650 };
1651
1652 let calc_offset_bounds = |menu_state: &MenuState, viewport_size: Size| -> (f32, f32) {
1653 let children_bounds = menu_state.menu_bounds.children_bounds + overlay_offset;
1655
1656 let max_offset = (0.0 - children_bounds.y).max(0.0);
1657 let min_offset =
1658 (viewport_size.height - (children_bounds.y + children_bounds.height)).min(0.0);
1659 (max_offset, min_offset)
1660 };
1661
1662 if state.menu_states.is_empty() {
1664 return;
1665 } else if state.menu_states.len() == 1 {
1666 let last_ms = &mut state.menu_states[0];
1667
1668 if last_ms.index.is_none() {
1669 return;
1670 }
1671
1672 let (max_offset, min_offset) = calc_offset_bounds(last_ms, viewport_size);
1673 last_ms.scroll_offset = (last_ms.scroll_offset + delta_y).clamp(min_offset, max_offset);
1674 } else {
1675 let max_index = state.menu_states.len() - 1;
1677 let last_two = &mut state.menu_states[max_index - 1..=max_index];
1678
1679 if last_two[1].index.is_some() {
1680 let (max_offset, min_offset) = calc_offset_bounds(&last_two[1], viewport_size);
1682 last_two[1].scroll_offset =
1683 (last_two[1].scroll_offset + delta_y).clamp(min_offset, max_offset);
1684 } else {
1685 if !last_two[0]
1686 .menu_bounds
1687 .children_bounds
1688 .contains(overlay_cursor)
1689 {
1690 shell.capture_event();
1691 return;
1692 }
1693
1694 let (max_offset, min_offset) = calc_offset_bounds(&last_two[0], viewport_size);
1696 let scroll_offset =
1697 (last_two[0].scroll_offset + delta_y).clamp(min_offset, max_offset);
1698 let clamped_delta_y = scroll_offset - last_two[0].scroll_offset;
1699 last_two[0].scroll_offset = scroll_offset;
1700
1701 last_two[1].menu_bounds.parent_bounds.y += clamped_delta_y;
1703 last_two[1].menu_bounds.children_bounds.y += clamped_delta_y;
1704 last_two[1].menu_bounds.check_bounds.y += clamped_delta_y;
1705 }
1706 }
1707 shell.capture_event();
1708 shell.request_redraw();
1709 });
1710}
1711
1712#[allow(clippy::pedantic)]
1713fn get_children_layout<Message>(
1715 menu_tree: &MenuTree<Message>,
1716 renderer: &crate::Renderer,
1717 item_width: ItemWidth,
1718 item_height: ItemHeight,
1719 tree: &mut [Tree],
1720) -> (Size, Vec<f32>, Vec<Size>) {
1721 let width = match item_width {
1722 ItemWidth::Uniform(u) => f32::from(u),
1723 ItemWidth::Static(s) => f32::from(menu_tree.width.unwrap_or(s)),
1724 };
1725
1726 let child_sizes: Vec<Size> = match item_height {
1727 ItemHeight::Uniform(u) => {
1728 let count = menu_tree.children.len();
1729 vec![Size::new(width, f32::from(u)); count]
1730 }
1731 ItemHeight::Static(s) => menu_tree
1732 .children
1733 .iter()
1734 .map(|mt| Size::new(width, f32::from(mt.height.unwrap_or(s))))
1735 .collect(),
1736 ItemHeight::Dynamic(d) => menu_tree
1737 .children
1738 .iter()
1739 .map(|mt| {
1740 mt.item
1741 .element
1742 .with_data_mut(|w| match w.as_widget_mut().size().height {
1743 Length::Fixed(f) => Size::new(width, f),
1744 Length::Shrink => {
1745 let l_height = w
1746 .as_widget_mut()
1747 .layout(
1748 &mut tree[mt.index],
1749 renderer,
1750 &Limits::new(Size::ZERO, Size::new(width, f32::MAX)),
1751 )
1752 .size()
1753 .height;
1754
1755 let height = if (f32::MAX - l_height) < 0.001 {
1756 f32::from(d)
1757 } else {
1758 l_height
1759 };
1760
1761 Size::new(width, height)
1762 }
1763 _ => mt.height.map_or_else(
1764 || Size::new(width, f32::from(d)),
1765 |h| Size::new(width, f32::from(h)),
1766 ),
1767 })
1768 })
1769 .collect(),
1770 };
1771
1772 let max_index = menu_tree.children.len().saturating_sub(1);
1773 let child_positions: Vec<f32> = std::iter::once(0.0)
1774 .chain(child_sizes[0..max_index].iter().scan(0.0, |acc, x| {
1775 *acc += x.height;
1776 Some(*acc)
1777 }))
1778 .collect();
1779
1780 let height = child_sizes.iter().fold(0.0, |acc, x| acc + x.height);
1781
1782 (Size::new(width, height), child_positions, child_sizes)
1783}
1784
1785fn search_bound(
1786 default: usize,
1787 default_left: usize,
1788 default_right: usize,
1789 bound: f32,
1790 positions: &[f32],
1791 sizes: &[Size],
1792) -> usize {
1793 let mut left = default_left;
1795 let mut right = default_right;
1796
1797 let mut index = default;
1798 while left != right {
1799 let m = ((left + right) / 2) + 1;
1800 if positions[m] > bound {
1801 right = m - 1;
1802 } else {
1803 left = m;
1804 }
1805 }
1806 let height = sizes[left].height;
1808 if positions[left] + height > bound {
1809 index = left;
1810 }
1811 index
1812}