1use fret_core::{Edges, Point, Px, Rect, Size};
7use fret_ui::overlay_placement::{
8 AnchoredPanelLayout, AnchoredPanelLayoutTrace, AnchoredPanelOptions, CollisionOptions,
9 anchored_panel_layout, anchored_panel_layout_sized, anchored_panel_layout_sized_with_trace,
10 anchored_panel_layout_with_trace, inset_rect, intersect_rect,
11};
12
13pub use fret_ui::overlay_placement::{
14 Align, ArrowLayout, ArrowOptions, LayoutDirection, Offset, ShiftOptions, Side, StickyMode,
15};
16
17#[derive(Debug, Clone, Copy, PartialEq)]
18pub struct PopperAvailableMetrics {
19 pub available_width: Px,
20 pub available_height: Px,
21 pub anchor_width: Px,
22 pub anchor_height: Px,
23}
24
25pub fn anchored_panel_options_for_popper_content(
33 direction: LayoutDirection,
34 arrow_protrusion: Px,
35 align_offset: Px,
36 arrow: Option<ArrowOptions>,
37) -> AnchoredPanelOptions {
38 AnchoredPanelOptions {
39 direction,
40 offset: Offset {
41 main_axis: arrow_protrusion,
42 cross_axis: align_offset,
43 alignment_axis: Some(align_offset),
46 },
47 shift: ShiftOptions {
49 main_axis: true,
50 cross_axis: false,
51 },
52 arrow,
53 collision: Default::default(),
54 sticky: Default::default(),
55 }
56}
57
58#[derive(Debug, Clone, Copy, PartialEq)]
60pub struct PopperContentPlacement {
61 pub direction: LayoutDirection,
62 pub side: Side,
63 pub align: Align,
64 pub side_offset: Px,
65 pub align_offset: Px,
66 pub arrow: Option<ArrowOptions>,
67 pub arrow_protrusion: Px,
68 pub shift_cross_axis: bool,
73 pub collision_padding: Edges,
74 pub collision_boundary: Option<Rect>,
75 pub hide_when_detached: bool,
76 pub sticky: StickyMode,
77}
78
79impl PopperContentPlacement {
80 pub fn new(direction: LayoutDirection, side: Side, align: Align, side_offset: Px) -> Self {
81 Self {
82 direction,
83 side,
84 align,
85 side_offset,
86 align_offset: Px(0.0),
87 arrow: None,
88 arrow_protrusion: Px(0.0),
89 shift_cross_axis: false,
90 collision_padding: Edges::all(Px(0.0)),
91 collision_boundary: None,
92 hide_when_detached: false,
93 sticky: StickyMode::Partial,
94 }
95 }
96
97 pub fn with_align_offset(mut self, align_offset: Px) -> Self {
98 self.align_offset = align_offset;
99 self
100 }
101
102 pub fn with_arrow(mut self, arrow: Option<ArrowOptions>, arrow_protrusion: Px) -> Self {
103 self.arrow = arrow;
104 self.arrow_protrusion = arrow_protrusion;
105 self
106 }
107
108 pub fn with_shift_cross_axis(mut self, cross_axis: bool) -> Self {
109 self.shift_cross_axis = cross_axis;
110 self
111 }
112
113 pub fn with_collision_padding(mut self, collision_padding: Edges) -> Self {
114 self.collision_padding = collision_padding;
115 self
116 }
117
118 pub fn with_collision_boundary(mut self, collision_boundary: Option<Rect>) -> Self {
119 self.collision_boundary = collision_boundary;
120 self
121 }
122
123 pub fn with_hide_when_detached(mut self, hide_when_detached: bool) -> Self {
124 self.hide_when_detached = hide_when_detached;
125 self
126 }
127
128 pub fn with_sticky(mut self, sticky: StickyMode) -> Self {
129 self.sticky = sticky;
130 self
131 }
132
133 pub fn reference_hidden(self, outer: Rect, anchor: Rect) -> bool {
138 if !self.hide_when_detached {
139 return false;
140 }
141
142 let mut boundary = outer;
143 if let Some(extra_boundary) = self.collision_boundary {
144 boundary = fret_ui::overlay_placement::intersect_rect(boundary, extra_boundary);
145 }
146 boundary = fret_ui::overlay_placement::inset_rect(boundary, self.collision_padding);
147
148 let intersection = fret_ui::overlay_placement::intersect_rect(boundary, anchor);
149 intersection.size.width.0 <= 0.0 || intersection.size.height.0 <= 0.0
150 }
151
152 pub fn options(self) -> AnchoredPanelOptions {
153 let mut options = anchored_panel_options_for_popper_content(
154 self.direction,
155 self.arrow_protrusion,
156 self.align_offset,
157 self.arrow,
158 );
159 options.shift.cross_axis = self.shift_cross_axis;
160 options.collision = CollisionOptions {
161 padding: self.collision_padding,
162 boundary: self.collision_boundary,
163 };
164 options.sticky = self.sticky;
165 options
166 }
167}
168
169pub fn popper_content_layout_sized(
171 outer: Rect,
172 anchor: Rect,
173 desired: Size,
174 placement: PopperContentPlacement,
175) -> AnchoredPanelLayout {
176 popper_layout_sized(
177 outer,
178 anchor,
179 desired,
180 placement.side_offset,
181 placement.side,
182 placement.align,
183 placement.options(),
184 )
185}
186
187pub fn popper_content_layout_size_unclamped(
194 outer: Rect,
195 anchor: Rect,
196 desired: Size,
197 placement: PopperContentPlacement,
198) -> AnchoredPanelLayout {
199 anchored_panel_layout(
200 outer,
201 anchor,
202 desired,
203 placement.side_offset,
204 placement.side,
205 placement.align,
206 placement.options(),
207 )
208}
209
210pub fn popper_content_layout_size_unclamped_with_trace(
212 outer: Rect,
213 anchor: Rect,
214 desired: Size,
215 placement: PopperContentPlacement,
216) -> (AnchoredPanelLayout, AnchoredPanelLayoutTrace) {
217 anchored_panel_layout_with_trace(
218 outer,
219 anchor,
220 desired,
221 placement.side_offset,
222 placement.side,
223 placement.align,
224 placement.options(),
225 )
226}
227
228pub fn popper_content_layout_unclamped(
233 outer: Rect,
234 anchor: Rect,
235 desired: Size,
236 placement: PopperContentPlacement,
237) -> AnchoredPanelLayout {
238 let mut options = placement.options();
239 options.shift.main_axis = false;
241 anchored_panel_layout(
242 outer,
243 anchor,
244 desired,
245 placement.side_offset,
246 placement.side,
247 placement.align,
248 options,
249 )
250}
251
252pub fn popper_content_layout_unclamped_with_trace(
254 outer: Rect,
255 anchor: Rect,
256 desired: Size,
257 placement: PopperContentPlacement,
258) -> (AnchoredPanelLayout, AnchoredPanelLayoutTrace) {
259 let mut options = placement.options();
260 options.shift.main_axis = false;
261 anchored_panel_layout_with_trace(
262 outer,
263 anchor,
264 desired,
265 placement.side_offset,
266 placement.side,
267 placement.align,
268 options,
269 )
270}
271
272pub fn popper_available_metrics(
282 outer: Rect,
283 anchor: Rect,
284 layout: &AnchoredPanelLayout,
285 direction: LayoutDirection,
286) -> PopperAvailableMetrics {
287 let rect = layout.rect;
288 let width = rect.size.width.0.max(0.0);
289 let height = rect.size.height.0.max(0.0);
290
291 let outer_left = outer.origin.x.0;
292 let outer_top = outer.origin.y.0;
293 let outer_right = outer_left + outer.size.width.0.max(0.0);
294 let outer_bottom = outer_top + outer.size.height.0.max(0.0);
295
296 let rect_left = rect.origin.x.0;
297 let rect_top = rect.origin.y.0;
298 let rect_right = rect_left + rect.size.width.0.max(0.0);
299 let rect_bottom = rect_top + rect.size.height.0.max(0.0);
300
301 let overflow_left = outer_left - rect_left;
305 let overflow_top = outer_top - rect_top;
306 let overflow_right = rect_right - outer_right;
307 let overflow_bottom = rect_bottom - outer_bottom;
308
309 let maximum_clipping_width = (width - overflow_left - overflow_right).max(0.0);
310 let maximum_clipping_height = (height - overflow_top - overflow_bottom).max(0.0);
311
312 let alignment = match layout.align {
313 Align::Center => None,
314 other => Some(other),
315 };
316
317 let side = layout.side;
318
319 let (height_side, width_side) = match side {
320 Side::Top | Side::Bottom => {
321 let height_side = side;
322 let width_side = match alignment {
323 Some(Align::Start) => {
324 if direction == LayoutDirection::Rtl {
325 Side::Right
326 } else {
327 Side::Left
328 }
329 }
330 Some(Align::End) => {
331 if direction == LayoutDirection::Rtl {
332 Side::Left
333 } else {
334 Side::Right
335 }
336 }
337 _ => Side::Right,
338 };
339 (height_side, width_side)
340 }
341 Side::Left | Side::Right => {
342 let width_side = side;
343 let height_side = match alignment {
344 Some(Align::End) => Side::Top,
345 _ => Side::Bottom,
346 };
347 (height_side, width_side)
348 }
349 };
350
351 let overflow_for_side = |side: Side| match side {
352 Side::Top => overflow_top,
353 Side::Bottom => overflow_bottom,
354 Side::Left => overflow_left,
355 Side::Right => overflow_right,
356 };
357
358 let overflow_available_height = (height - overflow_for_side(height_side))
359 .min(maximum_clipping_height)
360 .max(0.0);
361 let overflow_available_width = (width - overflow_for_side(width_side))
362 .min(maximum_clipping_width)
363 .max(0.0);
364
365 let shift_enabled_x = matches!(side, Side::Top | Side::Bottom);
370 let shift_enabled_y = matches!(side, Side::Left | Side::Right);
371
372 let mut available_height = overflow_available_height;
373 let mut available_width = overflow_available_width;
374
375 if shift_enabled_x {
376 available_width = maximum_clipping_width;
377 }
378 if shift_enabled_y {
379 available_height = maximum_clipping_height;
380 }
381
382 PopperAvailableMetrics {
383 available_width: Px(available_width),
384 available_height: Px(available_height),
385 anchor_width: anchor.size.width,
386 anchor_height: anchor.size.height,
387 }
388}
389
390pub fn popper_desired_width(outer: Rect, anchor: Rect, min_width: Px) -> Px {
391 Px(anchor.size.width.0.max(min_width.0).min(outer.size.width.0))
392}
393
394pub fn popper_available_metrics_for_placement(
405 outer: Rect,
406 anchor: Rect,
407 min_width: Px,
408 placement: PopperContentPlacement,
409) -> PopperAvailableMetrics {
410 let desired_w = popper_desired_width(outer, anchor, min_width);
411 let probe_desired = Size::new(desired_w, outer.size.height);
412 let layout = popper_content_layout_sized(outer, anchor, probe_desired, placement);
413 let mut boundary = outer;
418 if let Some(extra_boundary) = placement.collision_boundary {
419 boundary = intersect_rect(boundary, extra_boundary);
420 }
421 boundary = inset_rect(boundary, placement.collision_padding);
422 popper_available_metrics(boundary, anchor, &layout, placement.direction)
423}
424
425pub fn popper_layout_sized(
427 outer: Rect,
428 anchor: Rect,
429 desired: Size,
430 side_offset: Px,
431 side: Side,
432 align: Align,
433 options: AnchoredPanelOptions,
434) -> AnchoredPanelLayout {
435 anchored_panel_layout_sized(outer, anchor, desired, side_offset, side, align, options)
436}
437
438pub fn popper_layout_sized_with_trace(
440 outer: Rect,
441 anchor: Rect,
442 desired: Size,
443 side_offset: Px,
444 side: Side,
445 align: Align,
446 options: AnchoredPanelOptions,
447) -> (AnchoredPanelLayout, AnchoredPanelLayoutTrace) {
448 anchored_panel_layout_sized_with_trace(
449 outer,
450 anchor,
451 desired,
452 side_offset,
453 side,
454 align,
455 options,
456 )
457}
458
459fn opposite_side(side: Side) -> Side {
460 match side {
461 Side::Top => Side::Bottom,
462 Side::Bottom => Side::Top,
463 Side::Left => Side::Right,
464 Side::Right => Side::Left,
465 }
466}
467
468pub fn popper_content_transform_origin(
476 layout: &AnchoredPanelLayout,
477 anchor: Rect,
478 arrow_size: Option<Px>,
479) -> Point {
480 let rect = layout.rect;
481 let anchor_center = Point::new(
482 Px(anchor.origin.x.0 + anchor.size.width.0 * 0.5),
483 Px(anchor.origin.y.0 + anchor.size.height.0 * 0.5),
484 );
485
486 let face = layout
487 .arrow
488 .map(|a| a.side)
489 .unwrap_or_else(|| opposite_side(layout.side));
490
491 let arrow_hidden = should_hide_arrow(layout);
492
493 let (mut x, mut y) = match face {
494 Side::Top => (Px(rect.size.width.0 * 0.5), Px(0.0)),
495 Side::Bottom => (Px(rect.size.width.0 * 0.5), rect.size.height),
496 Side::Left => (Px(0.0), Px(rect.size.height.0 * 0.5)),
497 Side::Right => (rect.size.width, Px(rect.size.height.0 * 0.5)),
498 };
499
500 if let (Some(arrow), Some(arrow_size)) = (layout.arrow, arrow_size) {
501 if !arrow_hidden {
502 let cross_x = Px((arrow.offset.0 + arrow_size.0 * 0.5).clamp(0.0, rect.size.width.0));
503 let cross_y = Px((arrow.offset.0 + arrow_size.0 * 0.5).clamp(0.0, rect.size.height.0));
504 match face {
505 Side::Top | Side::Bottom => x = cross_x,
506 Side::Left | Side::Right => y = cross_y,
507 }
508 } else {
509 let align_x = match layout.align {
513 Align::Start => Px(0.0),
514 Align::Center => Px(rect.size.width.0 * 0.5),
515 Align::End => rect.size.width,
516 };
517 let align_y = match layout.align {
518 Align::Start => Px(0.0),
519 Align::Center => Px(rect.size.height.0 * 0.5),
520 Align::End => rect.size.height,
521 };
522 match face {
523 Side::Top | Side::Bottom => x = align_x,
524 Side::Left | Side::Right => y = align_y,
525 }
526 }
527 } else {
528 match face {
529 Side::Top | Side::Bottom => {
530 x = Px((anchor_center.x.0 - rect.origin.x.0).clamp(0.0, rect.size.width.0));
531 }
532 Side::Left | Side::Right => {
533 y = Px((anchor_center.y.0 - rect.origin.y.0).clamp(0.0, rect.size.height.0));
534 }
535 }
536 }
537
538 Point::new(Px(rect.origin.x.0 + x.0), Px(rect.origin.y.0 + y.0))
539}
540
541pub fn should_hide_arrow(layout: &AnchoredPanelLayout) -> bool {
542 layout
543 .arrow
544 .is_some_and(|arrow| arrow.center_offset.0.abs() > 0.01)
545}
546
547pub fn default_arrow_protrusion(arrow_size: Px) -> Px {
552 Px(arrow_size.0 * 0.75)
553}
554
555pub fn diamond_arrow_options(
559 enabled: bool,
560 arrow_size: Px,
561 arrow_padding: Px,
562) -> (Option<ArrowOptions>, Px) {
563 if !enabled {
564 return (None, Px(0.0));
565 }
566
567 (
568 Some(ArrowOptions {
569 size: Size::new(arrow_size, arrow_size),
570 padding: Edges::all(arrow_padding),
571 }),
572 default_arrow_protrusion(arrow_size),
573 )
574}
575
576pub fn wrapper_insets_for_arrow(layout: &AnchoredPanelLayout, protrusion: Px) -> Edges {
581 if should_hide_arrow(layout) {
582 return Edges::all(Px(0.0));
583 }
584
585 let Some(arrow) = layout.arrow else {
586 return Edges::all(Px(0.0));
587 };
588
589 match arrow.side {
590 Side::Top => Edges {
591 top: protrusion,
592 ..Edges::all(Px(0.0))
593 },
594 Side::Bottom => Edges {
595 bottom: protrusion,
596 ..Edges::all(Px(0.0))
597 },
598 Side::Left => Edges {
599 left: protrusion,
600 ..Edges::all(Px(0.0))
601 },
602 Side::Right => Edges {
603 right: protrusion,
604 ..Edges::all(Px(0.0))
605 },
606 }
607}
608
609#[cfg(test)]
610mod tests {
611 use fret_core::{Point, Rect, Size};
612
613 use super::*;
614
615 #[test]
616 fn wrapper_insets_are_zero_without_arrow() {
617 let layout = AnchoredPanelLayout {
618 rect: Rect::new(Point::new(Px(0.0), Px(0.0)), Size::new(Px(10.0), Px(10.0))),
619 side: Side::Bottom,
620 align: Align::Center,
621 arrow: None,
622 };
623 assert_eq!(
624 wrapper_insets_for_arrow(&layout, Px(9.0)),
625 Edges::all(Px(0.0))
626 );
627 }
628
629 #[test]
630 fn wrapper_insets_follow_arrow_side() {
631 let mut layout = AnchoredPanelLayout {
632 rect: Rect::new(Point::new(Px(0.0), Px(0.0)), Size::new(Px(10.0), Px(10.0))),
633 side: Side::Bottom,
634 align: Align::Center,
635 arrow: Some(ArrowLayout {
636 side: Side::Top,
637 offset: Px(1.0),
638 alignment_offset: Px(0.0),
639 center_offset: Px(0.0),
640 }),
641 };
642
643 assert_eq!(wrapper_insets_for_arrow(&layout, Px(7.0)).top, Px(7.0));
644
645 layout.arrow = Some(ArrowLayout {
646 side: Side::Left,
647 offset: Px(1.0),
648 alignment_offset: Px(0.0),
649 center_offset: Px(0.0),
650 });
651 assert_eq!(wrapper_insets_for_arrow(&layout, Px(7.0)).left, Px(7.0));
652 }
653
654 #[test]
655 fn wrapper_insets_are_zero_when_arrow_is_hidden() {
656 let layout = AnchoredPanelLayout {
657 rect: Rect::new(Point::new(Px(0.0), Px(0.0)), Size::new(Px(10.0), Px(10.0))),
658 side: Side::Bottom,
659 align: Align::Center,
660 arrow: Some(ArrowLayout {
661 side: Side::Top,
662 offset: Px(1.0),
663 alignment_offset: Px(0.0),
664 center_offset: Px(10.0),
665 }),
666 };
667
668 assert_eq!(
669 wrapper_insets_for_arrow(&layout, Px(7.0)),
670 Edges::all(Px(0.0))
671 );
672 }
673
674 #[test]
675 fn popper_layout_sized_returns_arrow_layout_when_configured() {
676 let outer = Rect::new(
677 Point::new(Px(0.0), Px(0.0)),
678 Size::new(Px(200.0), Px(200.0)),
679 );
680 let anchor = Rect::new(
681 Point::new(Px(50.0), Px(60.0)),
682 Size::new(Px(40.0), Px(10.0)),
683 );
684 let desired = Size::new(Px(120.0), Px(80.0));
685
686 let layout = popper_layout_sized(
687 outer,
688 anchor,
689 desired,
690 Px(8.0),
691 Side::Bottom,
692 Align::Center,
693 AnchoredPanelOptions {
694 direction: LayoutDirection::Ltr,
695 offset: Offset::default(),
696 shift: Default::default(),
697 arrow: Some(ArrowOptions {
698 size: Size::new(Px(12.0), Px(12.0)),
699 padding: Edges::all(Px(8.0)),
700 }),
701 collision: Default::default(),
702 sticky: Default::default(),
703 },
704 );
705
706 let arrow = layout.arrow.expect("arrow layout");
707 assert_eq!(arrow.side, Side::Top);
708 }
709
710 #[test]
711 fn transform_origin_tracks_arrow_on_anchor_edge() {
712 let outer = Rect::new(
713 Point::new(Px(0.0), Px(0.0)),
714 Size::new(Px(200.0), Px(200.0)),
715 );
716 let anchor = Rect::new(
717 Point::new(Px(50.0), Px(60.0)),
718 Size::new(Px(40.0), Px(10.0)),
719 );
720 let desired = Size::new(Px(120.0), Px(80.0));
721 let arrow_size = Px(12.0);
722
723 let layout = popper_layout_sized(
724 outer,
725 anchor,
726 desired,
727 Px(8.0),
728 Side::Bottom,
729 Align::Center,
730 AnchoredPanelOptions {
731 direction: LayoutDirection::Ltr,
732 offset: Offset::default(),
733 shift: Default::default(),
734 arrow: Some(ArrowOptions {
735 size: Size::new(arrow_size, arrow_size),
736 padding: Edges::all(Px(8.0)),
737 }),
738 collision: Default::default(),
739 sticky: Default::default(),
740 },
741 );
742
743 let origin = popper_content_transform_origin(&layout, anchor, Some(arrow_size));
744 let arrow = layout.arrow.expect("expected arrow layout");
745 assert_eq!(origin.y, layout.rect.origin.y);
746 assert_eq!(
747 origin.x,
748 Px(layout.rect.origin.x.0 + arrow.offset.0 + arrow_size.0 * 0.5)
749 );
750 }
751
752 #[test]
753 fn transform_origin_tracks_anchor_center_without_arrow() {
754 let outer = Rect::new(
755 Point::new(Px(0.0), Px(0.0)),
756 Size::new(Px(200.0), Px(200.0)),
757 );
758 let anchor = Rect::new(
759 Point::new(Px(50.0), Px(60.0)),
760 Size::new(Px(40.0), Px(10.0)),
761 );
762 let desired = Size::new(Px(120.0), Px(80.0));
763
764 let layout = popper_layout_sized(
765 outer,
766 anchor,
767 desired,
768 Px(8.0),
769 Side::Bottom,
770 Align::Center,
771 AnchoredPanelOptions {
772 direction: LayoutDirection::Ltr,
773 offset: Offset::default(),
774 shift: Default::default(),
775 arrow: None,
776 collision: Default::default(),
777 sticky: Default::default(),
778 },
779 );
780
781 let origin = popper_content_transform_origin(&layout, anchor, None);
782 assert_eq!(origin.y, layout.rect.origin.y);
783
784 let anchor_center_x = anchor.origin.x.0 + anchor.size.width.0 * 0.5;
785 let x_in_panel =
786 (anchor_center_x - layout.rect.origin.x.0).clamp(0.0, layout.rect.size.width.0);
787 assert_eq!(origin.x, Px(layout.rect.origin.x.0 + x_in_panel));
788 }
789
790 #[test]
791 fn transform_origin_uses_alignment_when_arrow_is_hidden() {
792 let layout = AnchoredPanelLayout {
793 rect: Rect::new(
794 Point::new(Px(10.0), Px(20.0)),
795 Size::new(Px(100.0), Px(50.0)),
796 ),
797 side: Side::Bottom,
798 align: Align::End,
799 arrow: Some(ArrowLayout {
800 side: Side::Top,
801 offset: Px(1.0),
802 alignment_offset: Px(0.0),
803 center_offset: Px(10.0),
804 }),
805 };
806
807 let origin = popper_content_transform_origin(&layout, Rect::default(), Some(Px(12.0)));
808 assert_eq!(origin.y, Px(20.0));
809 assert_eq!(origin.x, Px(110.0));
810 }
811
812 #[test]
813 fn popper_content_placement_passes_collision_padding_to_solver() {
814 let outer = Rect::new(
815 Point::new(Px(0.0), Px(0.0)),
816 Size::new(Px(200.0), Px(100.0)),
817 );
818 let anchor = Rect::new(
819 Point::new(Px(10.0), Px(40.0)),
820 Size::new(Px(40.0), Px(10.0)),
821 );
822 let desired = Size::new(Px(120.0), Px(40.0));
823
824 let layout = popper_content_layout_sized(
825 outer,
826 anchor,
827 desired,
828 PopperContentPlacement::new(LayoutDirection::Ltr, Side::Bottom, Align::Start, Px(0.0))
829 .with_collision_padding(Edges {
830 bottom: Px(20.0),
831 ..Edges::all(Px(0.0))
832 }),
833 );
834
835 assert_eq!(layout.side, Side::Top);
836 }
837
838 #[test]
839 fn popper_content_reference_hidden_false_when_disabled() {
840 let outer = Rect::new(
841 Point::new(Px(0.0), Px(0.0)),
842 Size::new(Px(100.0), Px(100.0)),
843 );
844 let anchor_outside = Rect::new(
845 Point::new(Px(200.0), Px(200.0)),
846 Size::new(Px(10.0), Px(10.0)),
847 );
848 let placement =
849 PopperContentPlacement::new(LayoutDirection::Ltr, Side::Bottom, Align::Start, Px(0.0));
850
851 assert!(!placement.reference_hidden(outer, anchor_outside));
852 }
853
854 #[test]
855 fn popper_content_reference_hidden_true_when_anchor_outside_boundary() {
856 let outer = Rect::new(
857 Point::new(Px(0.0), Px(0.0)),
858 Size::new(Px(100.0), Px(100.0)),
859 );
860 let anchor_outside = Rect::new(
861 Point::new(Px(200.0), Px(200.0)),
862 Size::new(Px(10.0), Px(10.0)),
863 );
864 let placement =
865 PopperContentPlacement::new(LayoutDirection::Ltr, Side::Bottom, Align::Start, Px(0.0))
866 .with_hide_when_detached(true);
867
868 assert!(placement.reference_hidden(outer, anchor_outside));
869 }
870
871 #[test]
872 fn available_metrics_track_anchor_and_available_space() {
873 let outer = Rect::new(
874 Point::new(Px(0.0), Px(0.0)),
875 Size::new(Px(100.0), Px(100.0)),
876 );
877 let anchor = Rect::new(
878 Point::new(Px(10.0), Px(20.0)),
879 Size::new(Px(30.0), Px(40.0)),
880 );
881 let layout = AnchoredPanelLayout {
882 rect: Rect::new(
883 Point::new(Px(40.0), Px(40.0)),
884 Size::new(Px(20.0), Px(10.0)),
885 ),
886 side: Side::Bottom,
887 align: Align::Center,
888 arrow: None,
889 };
890
891 let m = popper_available_metrics(outer, anchor, &layout, LayoutDirection::Ltr);
892 assert_eq!(m.anchor_width, Px(30.0));
893 assert_eq!(m.anchor_height, Px(40.0));
894 assert_eq!(m.available_width, Px(100.0));
895 assert_eq!(m.available_height, Px(60.0));
896 }
897}