1use smallvec::SmallVec;
2
3use crate::{
4 point, AnyElement, BorrowWindow, Bounds, Element, LayoutId, ParentComponent, Pixels, Point,
5 Size, Style,
6};
7
8pub struct OverlayState {
9 child_layout_ids: SmallVec<[LayoutId; 4]>,
10}
11
12pub struct Overlay<V> {
13 children: SmallVec<[AnyElement<V>; 2]>,
14 anchor_corner: AnchorCorner,
15 fit_mode: OverlayFitMode,
16 // todo!();
17 // anchor_position: Option<Vector2F>,
18 // position_mode: OverlayPositionMode,
19}
20
21/// overlay gives you a floating element that will avoid overflowing the window bounds.
22/// Its children should have no margin to avoid measurement issues.
23pub fn overlay<V: 'static>() -> Overlay<V> {
24 Overlay {
25 children: SmallVec::new(),
26 anchor_corner: AnchorCorner::TopLeft,
27 fit_mode: OverlayFitMode::SwitchAnchor,
28 }
29}
30
31impl<V> Overlay<V> {
32 /// Sets which corner of the overlay should be anchored to the current position.
33 pub fn anchor(mut self, anchor: AnchorCorner) -> Self {
34 self.anchor_corner = anchor;
35 self
36 }
37
38 /// Snap to window edge instead of switching anchor corner when an overflow would occur.
39 pub fn snap_to_window(mut self) -> Self {
40 self.fit_mode = OverlayFitMode::SnapToWindow;
41 self
42 }
43}
44
45impl<V: 'static> ParentComponent<V> for Overlay<V> {
46 fn children_mut(&mut self) -> &mut SmallVec<[AnyElement<V>; 2]> {
47 &mut self.children
48 }
49}
50
51impl<V: 'static> Element<V> for Overlay<V> {
52 type ElementState = OverlayState;
53
54 fn element_id(&self) -> Option<crate::ElementId> {
55 None
56 }
57
58 fn layout(
59 &mut self,
60 view_state: &mut V,
61 _: Option<Self::ElementState>,
62 cx: &mut crate::ViewContext<V>,
63 ) -> (crate::LayoutId, Self::ElementState) {
64 let child_layout_ids = self
65 .children
66 .iter_mut()
67 .map(|child| child.layout(view_state, cx))
68 .collect::<SmallVec<_>>();
69 let layout_id = cx.request_layout(&Style::default(), child_layout_ids.iter().copied());
70
71 (layout_id, OverlayState { child_layout_ids })
72 }
73
74 fn paint(
75 &mut self,
76 bounds: crate::Bounds<crate::Pixels>,
77 view_state: &mut V,
78 element_state: &mut Self::ElementState,
79 cx: &mut crate::ViewContext<V>,
80 ) {
81 if element_state.child_layout_ids.is_empty() {
82 return;
83 }
84
85 let mut child_min = point(Pixels::MAX, Pixels::MAX);
86 let mut child_max = Point::default();
87 for child_layout_id in &element_state.child_layout_ids {
88 let child_bounds = cx.layout_bounds(*child_layout_id);
89 child_min = child_min.min(&child_bounds.origin);
90 child_max = child_max.max(&child_bounds.lower_right());
91 }
92 let size: Size<Pixels> = (child_max - child_min).into();
93 let origin = bounds.origin;
94
95 let mut desired = self.anchor_corner.get_bounds(origin, size);
96 let limits = Bounds {
97 origin: Point::zero(),
98 size: cx.viewport_size(),
99 };
100
101 match self.fit_mode {
102 OverlayFitMode::SnapToWindow => {
103 // Snap the horizontal edges of the overlay to the horizontal edges of the window if
104 // its horizontal bounds overflow
105 if desired.right() > limits.right() {
106 desired.origin.x -= desired.right() - limits.right();
107 } else if desired.left() < limits.left() {
108 desired.origin.x = limits.origin.x;
109 }
110
111 // Snap the vertical edges of the overlay to the vertical edges of the window if
112 // its vertical bounds overflow.
113 if desired.bottom() > limits.bottom() {
114 desired.origin.y -= desired.bottom() - limits.bottom();
115 } else if desired.top() < limits.top() {
116 desired.origin.y = limits.origin.y;
117 }
118 }
119 OverlayFitMode::SwitchAnchor => {
120 let mut anchor_corner = self.anchor_corner;
121
122 if desired.left() < limits.left() || desired.right() > limits.right() {
123 anchor_corner = anchor_corner.switch_axis(Axis::Horizontal);
124 }
125
126 if bounds.top() < limits.top() || bounds.bottom() > limits.bottom() {
127 anchor_corner = anchor_corner.switch_axis(Axis::Vertical);
128 }
129
130 // Update bounds if needed
131 if anchor_corner != self.anchor_corner {
132 desired = anchor_corner.get_bounds(origin, size)
133 }
134 }
135 OverlayFitMode::None => {}
136 }
137
138 cx.with_element_offset(desired.origin - bounds.origin, |cx| {
139 for child in &mut self.children {
140 child.paint(view_state, cx);
141 }
142 })
143 }
144}
145
146enum Axis {
147 Horizontal,
148 Vertical,
149}
150
151#[derive(Copy, Clone)]
152pub enum OverlayFitMode {
153 SnapToWindow,
154 SwitchAnchor,
155 None,
156}
157
158#[derive(Clone, Copy, PartialEq, Eq)]
159pub enum AnchorCorner {
160 TopLeft,
161 TopRight,
162 BottomLeft,
163 BottomRight,
164}
165
166impl AnchorCorner {
167 fn get_bounds(&self, origin: Point<Pixels>, size: Size<Pixels>) -> Bounds<Pixels> {
168 let origin = match self {
169 Self::TopLeft => origin,
170 Self::TopRight => Point {
171 x: origin.x - size.width,
172 y: origin.y,
173 },
174 Self::BottomLeft => Point {
175 x: origin.x,
176 y: origin.y - size.height,
177 },
178 Self::BottomRight => Point {
179 x: origin.x - size.width,
180 y: origin.y - size.height,
181 },
182 };
183
184 Bounds { origin, size }
185 }
186
187 fn switch_axis(self, axis: Axis) -> Self {
188 match axis {
189 Axis::Vertical => match self {
190 AnchorCorner::TopLeft => AnchorCorner::BottomLeft,
191 AnchorCorner::TopRight => AnchorCorner::BottomRight,
192 AnchorCorner::BottomLeft => AnchorCorner::TopLeft,
193 AnchorCorner::BottomRight => AnchorCorner::TopRight,
194 },
195 Axis::Horizontal => match self {
196 AnchorCorner::TopLeft => AnchorCorner::TopRight,
197 AnchorCorner::TopRight => AnchorCorner::TopLeft,
198 AnchorCorner::BottomLeft => AnchorCorner::BottomRight,
199 AnchorCorner::BottomRight => AnchorCorner::BottomLeft,
200 },
201 }
202 }
203}