1#![allow(clippy::unwrap_used)]
4#![allow(clippy::doc_markdown)]
5#![allow(clippy::wildcard_imports)]
6#![allow(clippy::enum_glob_use)]
7#![allow(clippy::too_many_arguments)]
8#![allow(clippy::unused_self)]
9#![allow(clippy::return_self_not_must_use)]
10#![allow(clippy::pedantic)]
11#![allow(clippy::similar_names)]
12
13use super::common::*;
14use super::flex;
15use iced_core::{
16 Clipboard, Element, Event, Length, Padding, Pixels, Point, Rectangle, Shell, Size, Vector,
17 alignment,
18 layout::{Layout, Limits, Node},
19 mouse, renderer,
20 time::Instant,
21 widget::Operation,
22 widget::tree::{self, Tree},
23 window,
24};
25use std::iter::once;
26
27use super::menu_bar::*;
28use crate::style::menu_bar::*;
29
30#[cfg(feature = "debug_log")]
31use log::{debug, warn};
32
33#[derive(Debug)]
65pub(super) struct MenuState {
66 pub(super) scroll_offset: f32,
67 pub(super) active: Index,
68 pub(super) slice: MenuSlice,
69}
70impl MenuState {
71 pub(super) fn open_new_menu<'a, Message, Theme: Catalog, Renderer: renderer::Renderer>(
73 &mut self,
74 active_index: usize,
75 item: &Item<'a, Message, Theme, Renderer>,
76 item_tree: &mut Tree,
77 ) {
78 #[cfg(feature = "debug_log")]
79 debug!(target:"menu::MenuState::open_new_menu", "");
80 let Some(menu) = item.menu.as_ref() else {
81 #[cfg(feature = "debug_log")]
82 debug!(target:"menu::MenuState::open_new_menu", "item.menu is None");
83 return;
84 };
85
86 self.active = Some(active_index);
87
88 let menu_tree = menu.tree();
90
91 #[cfg(feature = "debug_log")]
92 {
93 if item_tree.children.len() > 2 {
94 warn!(
95 target:"menu::MenuState::open_new_menu",
96 "item_tree.children.len() > 2 | len: {:?}",
97 item_tree.children.len()
98 );
99 }
100 }
101
102 if item_tree.children.len() == 1 {
103 item_tree.children.push(menu_tree);
104 } else {
105 item_tree.children[1] = menu_tree;
106 }
107 }
108}
109impl Default for MenuState {
110 fn default() -> Self {
111 Self {
112 scroll_offset: 0.0,
113 active: None,
114 slice: MenuSlice {
115 start_index: 0,
116 end_index: usize::MAX - 1,
117 lower_bound_rel: 0.0,
118 upper_bound_rel: f32::MAX,
119 },
120 }
121 }
122}
123
124#[must_use]
126pub struct Menu<'a, Message, Theme, Renderer>
127where
128 Theme: Catalog,
129 Renderer: renderer::Renderer,
130{
131 pub(super) items: Vec<Item<'a, Message, Theme, Renderer>>,
132 pub(super) spacing: Pixels,
133 pub(super) max_width: f32,
134 pub(super) width: Length,
135 pub(super) height: Length,
136 pub(super) axis: Axis,
137 pub(super) offset: f32,
138 pub(super) padding: Padding,
139 pub(super) close_on_item_click: Option<bool>,
140 pub(super) close_on_background_click: Option<bool>,
141}
142impl<'a, Message, Theme, Renderer> Menu<'a, Message, Theme, Renderer>
143where
144 Theme: Catalog,
145 Renderer: renderer::Renderer,
146{
147 pub fn new(items: Vec<Item<'a, Message, Theme, Renderer>>) -> Self {
149 Self {
150 items,
151 spacing: Pixels::ZERO,
152 max_width: f32::MAX,
153 width: Length::Fill,
154 height: Length::Shrink,
155 axis: Axis::Horizontal,
156 offset: 0.0,
157 padding: Padding::new(5.0),
158 close_on_item_click: None,
159 close_on_background_click: None,
160 }
161 }
162
163 pub fn max_width(mut self, max_width: f32) -> Self {
165 self.max_width = max_width;
166 self
167 }
168
169 pub fn width(mut self, width: impl Into<Length>) -> Self {
171 self.width = width.into();
172 self
173 }
174
175 pub fn spacing(mut self, spacing: impl Into<Pixels>) -> Self {
177 self.spacing = spacing.into();
178 self
179 }
180
181 pub fn offset(mut self, offset: f32) -> Self {
183 self.offset = offset;
184 self
185 }
186
187 pub fn padding(mut self, padding: impl Into<Padding>) -> Self {
189 self.padding = padding.into();
190 self
191 }
192
193 pub fn close_on_item_click(mut self, value: bool) -> Self {
195 self.close_on_item_click = Some(value);
196 self
197 }
198
199 pub fn close_on_background_click(mut self, value: bool) -> Self {
201 self.close_on_background_click = Some(value);
202 self
203 }
204
205 pub(super) fn tree(&self) -> Tree {
207 Tree {
208 tag: self.tag(),
209 state: self.state(),
210 children: self.children(),
211 }
212 }
213}
214impl<Message, Theme, Renderer> Menu<'_, Message, Theme, Renderer>
215where
216 Theme: Catalog,
217 Renderer: renderer::Renderer,
218{
219 pub(super) fn tag(&self) -> tree::Tag {
220 tree::Tag::of::<MenuState>()
221 }
222
223 pub(super) fn state(&self) -> tree::State {
224 tree::State::Some(Box::<MenuState>::default())
225 }
226
227 pub(super) fn children(&self) -> Vec<Tree> {
229 self.items.iter().map(Item::tree).collect()
230 }
231
232 pub(super) fn diff(&self, tree: &mut Tree) {
234 tree.diff_children_custom(&self.items, |tree, item| item.diff(tree), Item::tree);
235 }
236
237 pub(super) fn layout(
241 &mut self,
242 tree: &mut Tree,
243 renderer: &Renderer,
244 limits: &Limits,
245 parent_bounds: Rectangle,
246 parent_direction: (Direction, Direction),
247 viewport: &Rectangle,
248 ) -> (Node, (Direction, Direction)) {
249 #[cfg(feature = "debug_log")]
250 debug!(target:"menu::Menu::layout", "");
251 let limits = limits.max_width(self.max_width);
252
253 let items_node = flex::resolve(
254 flex::Axis::Vertical,
255 renderer,
256 &limits,
257 self.width,
258 self.height,
259 Padding::ZERO,
260 self.spacing,
261 alignment::Alignment::Center,
262 &mut self
263 .items
264 .iter_mut()
265 .map(|i| &mut i.item)
266 .collect::<Vec<_>>(),
267 &mut tree
268 .children
269 .iter_mut()
270 .map(|t| &mut t.children[0])
271 .collect::<Vec<_>>(),
272 );
273
274 let aod = Aod::new(
275 self.axis,
276 viewport.size(),
277 parent_bounds,
278 parent_direction,
279 self.offset,
280 );
281
282 let children_size = items_node.bounds().size();
283 let (children_position, offset_position, child_direction) =
284 aod.resolve(parent_bounds, children_size, viewport.size());
285
286 let delta = children_position - offset_position;
288 let offset_size = if delta.x.abs() > delta.y.abs() {
289 Size::new(self.offset, children_size.height)
290 } else {
291 Size::new(children_size.width, self.offset)
292 };
293
294 let offset_bounds = Rectangle::new(offset_position, offset_size);
295
296 let menu_state = tree.state.downcast_mut::<MenuState>();
297
298 let (lower_bound_rel, upper_bound_rel) = cal_bounds_rel_menu(
300 &items_node,
301 children_position - Point::ORIGIN,
302 viewport.size(),
303 menu_state.scroll_offset,
304 );
305 let slice =
306 MenuSlice::from_bounds_rel(lower_bound_rel, upper_bound_rel, &items_node, |n| {
307 n.bounds().y
308 });
309 menu_state.slice = slice;
310 #[cfg(feature = "debug_log")]
311 debug!(target:"menu::Menu::layout", "slice: {:?}", slice);
312
313 let slice_node = if slice.start_index == slice.end_index {
314 let node = &items_node.children()[slice.start_index];
315 let bounds = node.bounds();
316 let start_offset = slice.lower_bound_rel - bounds.y;
317 let height = slice.upper_bound_rel - slice.lower_bound_rel;
318
319 Node::with_children(
320 Size::new(items_node.bounds().width, height),
321 once(clip_node_y(node, height, start_offset)).collect(),
322 )
323 } else {
324 let start_node = {
325 let node = &items_node.children()[slice.start_index];
326 let bounds = node.bounds();
327 let start_offset = slice.lower_bound_rel - bounds.y;
328 let height = bounds.height - start_offset;
329 clip_node_y(node, height, start_offset)
330 };
331
332 let end_node = {
333 let node = &items_node.children()[slice.end_index];
334 let bounds = node.bounds();
335 let height = slice.upper_bound_rel - bounds.y;
336 clip_node_y(node, height, 0.0)
337 };
338
339 Node::with_children(
340 Size::new(
341 items_node.bounds().width,
342 slice.upper_bound_rel - slice.lower_bound_rel,
343 ),
344 once(start_node)
345 .chain(
346 items_node.children()[slice.start_index + 1..slice.end_index]
347 .iter()
348 .map(Clone::clone),
349 )
350 .chain(once(end_node))
351 .collect(),
352 )
353 };
354
355 (
356 Node::with_children(
357 Size::INFINITE,
358 [
359 slice_node
360 .move_to(children_position)
361 .translate([0.0, menu_state.scroll_offset]), Node::new(children_size).move_to(children_position), Node::new(offset_bounds.size()).move_to(offset_bounds.position()), ]
365 .into(),
366 ),
367 child_direction,
368 )
369 }
370
371 pub(super) fn update(
375 &mut self,
376 global_state: &mut GlobalState,
377 global_parameters: &GlobalParameters<'_, Theme>,
378 rec_event: RecEvent,
379 tree: &mut Tree,
380 event: &Event,
381 layout: Layout<'_>,
382 cursor: mouse::Cursor,
383 renderer: &Renderer,
384 clipboard: &mut dyn Clipboard,
385 shell: &mut Shell<'_, Message>,
386 viewport: &Rectangle,
387 parent_bounds: Rectangle,
388 prev_bounds_list: &[Rectangle],
389 prev_active: &mut Index,
390 ) -> RecEvent {
391 let mut lc = layout.children();
392 let slice_layout = lc.next().unwrap();
393 let items_bounds = lc.next().unwrap().bounds();
394 let offset_bounds = lc.next().unwrap().bounds();
395 let background_bounds = pad_rectangle(items_bounds, self.padding);
396 let safe_bounds = pad_rectangle(
397 background_bounds,
398 Padding::new(global_parameters.safe_bounds_margin),
399 );
400
401 enum Op {
402 UpdateItems,
403 OpenEvent,
404 LeftPress,
405 ScrollEvent,
406 RedrawUpdate,
407 }
408
409 let mut run_op = |global_state: &mut GlobalState, tree: &mut Tree, op: &Op| {
410 let Tree {
411 state,
412 children: item_trees,
413 ..
414 } = tree;
415 let menu_state = state.downcast_mut::<MenuState>();
416
417 match op {
418 Op::UpdateItems => {
419 itl_iter_slice!(
420 menu_state.slice,
421 self.items;iter_mut,
422 item_trees;iter_mut,
423 slice_layout.children()
424 )
425 .for_each(|((item, tree), layout)| {
426 item.update(
427 tree, event, layout, cursor, renderer, clipboard, shell, viewport,
428 );
429 });
430 }
431 Op::RedrawUpdate => {
432 let cursor = if let Some(active) = menu_state.active {
433 match &global_parameters.draw_path {
434 DrawPath::FakeHovering => {
435 let active_in_slice = active - menu_state.slice.start_index;
436 let center = slice_layout
437 .children()
438 .nth(active_in_slice)
439 .unwrap_or_else(|| panic!(" Index {:?} (in slice space) is not within the slice layout \
440 | slice_layout.children().count(): {:?} \
441 | This should not happen, please report this issue
442 ",
443 active_in_slice,
444 slice_layout.children().count()))
445 .bounds()
446 .center();
447 mouse::Cursor::Available(center)
448 }
449 DrawPath::Backdrop => mouse::Cursor::Unavailable,
450 }
451 } else {
452 cursor
453 };
454
455 let mut temp_messages = vec![];
456 let mut temp_shell = Shell::new(&mut temp_messages);
457
458 let redraw_event =
459 Event::Window(window::Event::RedrawRequested(Instant::now()));
460
461 itl_iter_slice!(
462 menu_state.slice,
463 self.items;iter_mut,
464 item_trees;iter_mut,
465 slice_layout.children()
466 )
467 .for_each(|((item, tree), layout)| {
468 item.update(
469 tree,
470 &redraw_event,
471 layout,
472 cursor,
473 renderer,
474 clipboard,
475 &mut temp_shell,
476 viewport,
477 );
478 });
479 shell.merge(temp_shell, |message| message);
480 }
481 Op::LeftPress => {
482 if matches!(
483 event,
484 Event::Mouse(mouse::Event::ButtonPressed(mouse::Button::Left))
485 ) {
486 schedule_close_on_click(
487 global_state,
488 global_parameters,
489 menu_state.slice,
490 &mut self.items,
491 slice_layout.children(),
492 cursor,
493 self.close_on_item_click,
494 self.close_on_background_click,
495 );
496 }
497 }
498 Op::ScrollEvent => {
499 if let Event::Mouse(mouse::Event::WheelScrolled { delta }) = event {
500 if cursor.is_over(background_bounds) {
501 let delta_y = match delta {
502 mouse::ScrollDelta::Lines { y, .. } => {
503 y * global_parameters.scroll_speed.line
504 }
505 mouse::ScrollDelta::Pixels { y, .. } => {
506 y * global_parameters.scroll_speed.pixel
507 }
508 };
509
510 let max_offset = (0.0 - items_bounds.y).max(0.0);
511 let min_offset = (viewport.size().height
512 - (items_bounds.y + items_bounds.height))
513 .min(0.0);
514 menu_state.scroll_offset =
515 (menu_state.scroll_offset + delta_y).clamp(min_offset, max_offset);
516 }
517 shell.request_redraw();
518 }
519 }
520 Op::OpenEvent => {
521 if !global_state.pressed {
522 assert!(
523 menu_state.active.is_none(),
524 "
525 Menu::open_event() is called only when RecEvent::Close is returned, \
526 which means no child menu should be open (menu_state.active must be None). \
527 If this assert fails, please report this issue.
528 "
529 );
530
531 try_open_menu(
532 &mut self.items,
533 menu_state,
534 item_trees,
535 slice_layout.children(),
536 cursor,
537 shell,
538 );
539 }
540 }
541 }
542 };
543
544 let mut update = |global_state: &mut GlobalState, tree: &mut Tree, ops: &[Op]| {
545 for op in ops.iter() {
546 run_op(global_state, tree, op);
547 }
548 };
549
550 match rec_event {
551 RecEvent::Event => {
552 update(global_state, tree, &[Op::RedrawUpdate]);
554 shell.capture_event();
555 RecEvent::Event
556 }
557 RecEvent::Close => {
558 if cursor.is_over(background_bounds) || cursor.is_over(offset_bounds) {
559 update(
561 global_state,
562 tree,
563 &[
564 Op::UpdateItems,
565 Op::LeftPress,
566 Op::ScrollEvent,
567 Op::OpenEvent,
568 ],
569 );
570 shell.capture_event();
571 RecEvent::Event
572 } else if cursor.is_over(parent_bounds) {
573 update(global_state, tree, &[Op::RedrawUpdate]);
575 assert!(!shell.is_event_captured(), "Returning RecEvent::None");
578 RecEvent::None
579 } else {
580 let open = {
581 if global_state.pressed {
582 true
583 } else if prev_bounds_list.iter().any(|r| cursor.is_over(*r)) {
584 false
585 } else {
586 cursor.is_over(safe_bounds)
587 }
588 };
589
590 if open {
591 update(global_state, tree, &[Op::UpdateItems, Op::LeftPress]);
593 shell.capture_event();
594 RecEvent::Event
595 } else {
596 #[cfg(feature = "debug_log")]
598 debug!(target:"menu::Menu::update", "close menu");
599 assert!(!shell.is_event_captured(), "Returning RecEvent::Close");
600 *prev_active = None;
601 if tree.children.len() == 2 {
602 let _ = tree.children.pop();
604 }
605 shell.invalidate_layout();
606 shell.request_redraw();
607 RecEvent::Close
608 }
609 }
610 }
611 RecEvent::None => {
612 update(
613 global_state,
614 tree,
615 &[Op::UpdateItems, Op::LeftPress, Op::ScrollEvent],
616 );
617 shell.capture_event();
618 RecEvent::Event
619 }
620 }
621 }
622
623 pub(super) fn operate(
624 &mut self,
625 tree: &mut Tree,
626 layout: Layout<'_>,
627 renderer: &Renderer,
628 operation: &mut dyn Operation<()>,
629 ) {
630 let mut lc = layout.children();
631 let slice_layout = lc.next().unwrap();
632
633 let menu_state = tree.state.downcast_mut::<MenuState>();
634 let slice = menu_state.slice;
635
636 operation.container(None, layout.bounds());
637 operation.traverse(&mut |operation| {
638 itl_iter_slice!(slice, self.items;iter_mut, tree.children;iter_mut, slice_layout.children())
639 .for_each(|((child, state), layout)| {
640 child.operate(state, layout, renderer, operation);
641 });
642 });
643 }
644
645 pub(super) fn mouse_interaction(
649 &self,
650 tree: &Tree,
651 layout: Layout<'_>,
652 cursor: mouse::Cursor,
653 renderer: &Renderer,
654 ) -> mouse::Interaction {
655 let mut lc = layout.children();
656 let slice_layout = lc.next().unwrap();
657
658 let menu_state = tree.state.downcast_ref::<MenuState>();
659 let slice = menu_state.slice;
660
661 itl_iter_slice!(slice, self.items;iter, tree.children;iter, slice_layout.children())
662 .map(|((item, tree), layout)| item.mouse_interaction(tree, layout, cursor, renderer))
663 .max()
664 .unwrap_or_default()
665 }
666
667 pub(super) fn draw(
671 &self,
672 draw_path: &DrawPath,
673 tree: &Tree,
674 renderer: &mut Renderer,
675 theme: &Theme,
676 style: &renderer::Style,
677 theme_style: &Style,
678 layout: Layout<'_>,
679 cursor: mouse::Cursor,
680 viewport: &Rectangle,
681 ) {
682 let mut lc = layout.children();
683 let slice_layout = lc.next().unwrap();
684 let items_bounds = lc.next().unwrap().bounds();
685
686 let menu_state = tree.state.downcast_ref::<MenuState>();
687 let slice = menu_state.slice;
688
689 let pad_rectangle = pad_rectangle(items_bounds, self.padding);
691 if pad_rectangle.intersects(viewport) {
692 renderer.fill_quad(
693 renderer::Quad {
694 bounds: pad_rectangle,
695 border: theme_style.menu_border,
696 shadow: theme_style.menu_shadow,
697 ..Default::default()
698 },
699 theme_style.menu_background,
700 );
701 }
702
703 if let (DrawPath::Backdrop, Some(active)) = (draw_path, menu_state.active) {
704 let active_in_slice = active - menu_state.slice.start_index;
705 let active_bounds = slice_layout
706 .children()
707 .nth(active_in_slice)
708 .unwrap_or_else(|| {
709 panic!(
710 "Index {:?} (in slice space) is not within the slice layout \
711 | slice_layout.children().count(): {:?} \
712 | This should not happen, please report this issue
713 ",
714 active_in_slice,
715 slice_layout.children().count()
716 )
717 })
718 .bounds();
719
720 renderer.fill_quad(
721 renderer::Quad {
722 bounds: active_bounds,
723 border: theme_style.path_border,
724 ..Default::default()
725 },
726 theme_style.path,
727 );
728 }
729
730 renderer.with_layer(items_bounds, |r| {
731 itl_iter_slice!(slice, self.items;iter, tree.children;iter, slice_layout.children())
732 .for_each(|((item, tree), layout)| {
733 item.draw(tree, r, theme, style, layout, cursor, viewport);
734 });
735 });
736 }
737}
738
739#[must_use]
741pub struct Item<'a, Message, Theme, Renderer>
742where
743 Theme: Catalog,
744 Renderer: renderer::Renderer,
745{
746 pub(super) item: Element<'a, Message, Theme, Renderer>,
747 pub(super) menu: Option<Box<Menu<'a, Message, Theme, Renderer>>>,
748 pub(super) close_on_click: Option<bool>,
749}
750impl<'a, Message, Theme, Renderer> Item<'a, Message, Theme, Renderer>
751where
752 Theme: Catalog,
753 Renderer: renderer::Renderer,
754{
755 pub fn new(item: impl Into<Element<'a, Message, Theme, Renderer>>) -> Self {
757 Self {
758 item: item.into(),
759 menu: None,
760 close_on_click: None,
761 }
762 }
763
764 pub fn with_menu(
766 item: impl Into<Element<'a, Message, Theme, Renderer>>,
767 menu: Menu<'a, Message, Theme, Renderer>,
768 ) -> Self {
769 Self {
770 item: item.into(),
771 menu: Some(Box::new(menu)),
772 close_on_click: None,
773 }
774 }
775
776 pub fn close_on_click(mut self, close_on_click: bool) -> Self {
778 self.close_on_click = Some(close_on_click);
779 self
780 }
781
782 pub(super) fn tree(&self) -> Tree {
784 Tree {
785 tag: self.tag(),
786 state: self.state(),
787 children: self.children(),
788 }
789 }
790}
791impl<Message, Theme, Renderer> Item<'_, Message, Theme, Renderer>
792where
793 Theme: Catalog,
794 Renderer: renderer::Renderer,
795{
796 pub(super) fn tag(&self) -> tree::Tag {
801 tree::Tag::stateless()
802 }
803
804 pub(super) fn state(&self) -> tree::State {
805 tree::State::None
806 }
807
808 pub(super) fn children(&self) -> Vec<Tree> {
810 vec![Tree::new(&self.item)]
815 }
816
817 #[allow(clippy::option_if_let_else)]
819 pub(super) fn diff(&self, tree: &mut Tree) {
820 if let Some(t0) = tree.children.get_mut(0) {
821 t0.diff(&self.item);
822 if let Some(m) = self.menu.as_ref() {
823 if let Some(t1) = tree.children.get_mut(1) {
824 m.diff(t1);
825 } else {
826 *tree = self.tree();
827 }
828 }
829 } else {
830 *tree = self.tree();
831 }
832 }
833
834 pub(super) fn update(
837 &mut self,
838 tree: &mut Tree,
839 event: &Event,
840 layout: Layout<'_>,
841 cursor: mouse::Cursor,
842 renderer: &Renderer,
843 clipboard: &mut dyn Clipboard,
844 shell: &mut Shell<'_, Message>,
845 viewport: &Rectangle,
846 ) {
847 #[cfg(feature = "debug_log")]
848 debug!(target:"Item::update", "");
849 self.item.as_widget_mut().update(
850 &mut tree.children[0],
851 event,
852 layout,
853 cursor,
854 renderer,
855 clipboard,
856 shell,
857 viewport,
858 )
859 }
860
861 pub(super) fn mouse_interaction(
864 &self,
865 tree: &Tree,
866 layout: Layout<'_>,
867 cursor: mouse::Cursor,
868 renderer: &Renderer,
869 ) -> mouse::Interaction {
870 self.item.as_widget().mouse_interaction(
871 &tree.children[0],
872 layout,
873 cursor,
874 &layout.bounds(),
875 renderer,
876 )
877 }
878
879 pub(super) fn draw(
882 &self,
883 tree: &Tree,
884 renderer: &mut Renderer,
885 theme: &Theme,
886 style: &renderer::Style,
887 layout: Layout<'_>,
888 cursor: mouse::Cursor,
889 viewport: &Rectangle,
890 ) {
891 self.item.as_widget().draw(
892 &tree.children[0],
893 renderer,
894 theme,
895 style,
896 layout,
897 cursor,
898 viewport,
899 );
900 }
901
902 pub(super) fn operate(
903 &mut self,
904 tree: &mut Tree,
905 layout: Layout<'_>,
906 renderer: &Renderer,
907 operation: &mut dyn Operation<()>,
908 ) {
909 self.item
910 .as_widget_mut()
911 .operate(&mut tree.children[0], layout, renderer, operation);
912 }
913}
914
915#[derive(Debug)]
917#[allow(clippy::struct_excessive_bools)]
918struct Aod {
919 horizontal_overlap: bool,
921 vertical_overlap: bool,
922
923 horizontal_direction: Direction,
925 vertical_direction: Direction,
926
927 horizontal_offset: f32,
929 vertical_offset: f32,
930}
931impl Aod {
932 fn adaptive(
934 parent_pos: f32,
935 parent_size: f32,
936 child_size: f32,
937 max_size: f32,
938 offset: f32,
939 overlap: bool,
940 direction: Direction,
941 ) -> (f32, f32, Direction) {
942 match direction {
972 Direction::Positive => {
973 let space_negative = parent_pos;
974 let space_positive = max_size - parent_pos - parent_size;
975
976 if overlap {
977 let overshoot = child_size - parent_size;
978 if space_negative > space_positive && overshoot > space_positive {
979 (
980 parent_pos - overshoot,
981 parent_pos - overshoot,
982 direction.flip(),
983 )
984 } else {
985 (parent_pos, parent_pos, direction)
986 }
987 } else {
988 let overshoot = child_size + offset;
989 if space_negative > space_positive && overshoot > space_positive {
990 (
991 parent_pos - overshoot,
992 parent_pos - offset,
993 direction.flip(),
994 )
995 } else {
996 (
997 parent_pos + parent_size + offset,
998 parent_pos + parent_size,
999 direction,
1000 )
1001 }
1002 }
1003 }
1004 Direction::Negative => {
1005 let space_positive = parent_pos;
1006 let space_negative = max_size - parent_pos - parent_size;
1007
1008 if overlap {
1009 let overshoot = child_size - parent_size;
1010 if space_negative > space_positive && overshoot > space_positive {
1011 (parent_pos, parent_pos, direction.flip())
1012 } else {
1013 (parent_pos - overshoot, parent_pos - overshoot, direction)
1014 }
1015 } else {
1016 let overshoot = child_size + offset;
1017 if space_negative > space_positive && overshoot > space_positive {
1018 (
1019 parent_pos + parent_size + offset,
1020 parent_pos + parent_size,
1021 direction.flip(),
1022 )
1023 } else {
1024 (parent_pos - overshoot, parent_pos - offset, direction)
1025 }
1026 }
1027 }
1028 }
1029 }
1030
1031 fn resolve(
1033 &self,
1034 parent_bounds: Rectangle,
1035 children_size: Size,
1036 viewport_size: Size,
1037 ) -> (Point, Point, (Direction, Direction)) {
1038 let (x, ox, dx) = Self::adaptive(
1039 parent_bounds.x,
1040 parent_bounds.width,
1041 children_size.width,
1042 viewport_size.width,
1043 self.horizontal_offset,
1044 self.horizontal_overlap,
1045 self.horizontal_direction,
1046 );
1047 let (y, oy, dy) = Self::adaptive(
1048 parent_bounds.y,
1049 parent_bounds.height,
1050 children_size.height,
1051 viewport_size.height,
1052 self.vertical_offset,
1053 self.vertical_overlap,
1054 self.vertical_direction,
1055 );
1056
1057 ([x, y].into(), [ox, oy].into(), (dx, dy))
1058 }
1059
1060 fn new(
1061 axis: Axis,
1062 viewport: Size,
1063 parent_bounds: Rectangle,
1064 parent_direction: (Direction, Direction),
1065 offset: f32,
1066 ) -> Self {
1067 let hcenter = viewport.width / 2.0;
1068 let vcenter = viewport.height / 2.0;
1069
1070 let phcenter = parent_bounds.x + parent_bounds.width / 2.0;
1071 let pvcenter = parent_bounds.y + parent_bounds.height / 2.0;
1072
1073 let (pdx, pdy) = parent_direction;
1074 match axis {
1075 Axis::Horizontal => {
1076 let horizontal_direction = pdx;
1077 let vertical_direction = if pvcenter < vcenter {
1078 Direction::Positive
1079 } else {
1080 Direction::Negative
1081 };
1082 Self {
1083 horizontal_overlap: false,
1084 vertical_overlap: true,
1085 horizontal_direction,
1086 vertical_direction,
1087 horizontal_offset: offset,
1088 vertical_offset: 0.0,
1089 }
1090 }
1091 Axis::Vertical => {
1092 let horizontal_direction = if phcenter < hcenter {
1093 Direction::Positive
1094 } else {
1095 Direction::Negative
1096 };
1097 let vertical_direction = pdy;
1098 Self {
1099 horizontal_overlap: true,
1100 vertical_overlap: false,
1101 horizontal_direction,
1102 vertical_direction,
1103 horizontal_offset: 0.0,
1104 vertical_offset: offset,
1105 }
1106 }
1107 }
1108 }
1109}
1110
1111fn cal_bounds_rel_menu(
1112 items_node: &Node,
1113 translation: Vector,
1114 viewport: Size,
1115 scroll_offset: f32,
1116) -> (f32, f32) {
1117 let items_bounds = items_node.bounds() + translation; let lower_bound = items_bounds.y.max(0.0);
1121 let upper_bound = (items_bounds.y + items_bounds.height).min(viewport.height);
1122
1123 let lower_bound_rel = lower_bound - (items_bounds.y + scroll_offset);
1125 let upper_bound_rel = upper_bound - (items_bounds.y + scroll_offset);
1126
1127 (lower_bound_rel, upper_bound_rel)
1128}