1use smallvec::SmallVec;
2use taffy::style::{Display, Position};
3
4use crate::{
5 point, AnyElement, BorrowWindow, Bounds, Element, IntoElement, LayoutId, ParentElement, Pixels,
6 Point, Size, Style, WindowContext,
7};
8
9pub struct OverlayState {
10 child_layout_ids: SmallVec<[LayoutId; 4]>,
11}
12
13pub struct Overlay {
14 children: SmallVec<[AnyElement; 2]>,
15 anchor_corner: AnchorCorner,
16 fit_mode: OverlayFitMode,
17 anchor_position: Option<Point<Pixels>>,
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() -> Overlay {
24 Overlay {
25 children: SmallVec::new(),
26 anchor_corner: AnchorCorner::TopLeft,
27 fit_mode: OverlayFitMode::SwitchAnchor,
28 anchor_position: None,
29 position_mode: OverlayPositionMode::Window,
30 }
31}
32
33impl Overlay {
34 /// Sets which corner of the overlay should be anchored to the current position.
35 pub fn anchor(mut self, anchor: AnchorCorner) -> Self {
36 self.anchor_corner = anchor;
37 self
38 }
39
40 /// Sets the position in window co-ordinates
41 /// (otherwise the location the overlay is rendered is used)
42 pub fn position(mut self, anchor: Point<Pixels>) -> Self {
43 self.anchor_position = Some(anchor);
44 self
45 }
46
47 /// Sets the position mode for this overlay. Local will have this
48 /// interpret its [`Overlay::position`] as relative to the parent element.
49 /// While Window will have it interpret the position as relative to the window.
50 pub fn position_mode(mut self, mode: OverlayPositionMode) -> Self {
51 self.position_mode = mode;
52 self
53 }
54
55 /// Snap to window edge instead of switching anchor corner when an overflow would occur.
56 pub fn snap_to_window(mut self) -> Self {
57 self.fit_mode = OverlayFitMode::SnapToWindow;
58 self
59 }
60}
61
62impl ParentElement for Overlay {
63 fn children_mut(&mut self) -> &mut SmallVec<[AnyElement; 2]> {
64 &mut self.children
65 }
66}
67
68impl Element for Overlay {
69 type State = OverlayState;
70
71 fn request_layout(
72 &mut self,
73 _: Option<Self::State>,
74 cx: &mut WindowContext,
75 ) -> (crate::LayoutId, Self::State) {
76 let child_layout_ids = self
77 .children
78 .iter_mut()
79 .map(|child| child.request_layout(cx))
80 .collect::<SmallVec<_>>();
81
82 let overlay_style = Style {
83 position: Position::Absolute,
84 display: Display::Flex,
85 ..Style::default()
86 };
87
88 let layout_id = cx.request_layout(&overlay_style, child_layout_ids.iter().copied());
89
90 (layout_id, OverlayState { child_layout_ids })
91 }
92
93 fn paint(
94 &mut self,
95 bounds: crate::Bounds<crate::Pixels>,
96 element_state: &mut Self::State,
97 cx: &mut WindowContext,
98 ) {
99 if element_state.child_layout_ids.is_empty() {
100 return;
101 }
102
103 let mut child_min = point(Pixels::MAX, Pixels::MAX);
104 let mut child_max = Point::default();
105 for child_layout_id in &element_state.child_layout_ids {
106 let child_bounds = cx.layout_bounds(*child_layout_id);
107 child_min = child_min.min(&child_bounds.origin);
108 child_max = child_max.max(&child_bounds.lower_right());
109 }
110 let size: Size<Pixels> = (child_max - child_min).into();
111
112 let (origin, mut desired) = self.position_mode.get_position_and_bounds(
113 self.anchor_position,
114 self.anchor_corner,
115 size,
116 bounds,
117 );
118
119 let limits = Bounds {
120 origin: Point::default(),
121 size: cx.viewport_size(),
122 };
123
124 if self.fit_mode == OverlayFitMode::SwitchAnchor {
125 let mut anchor_corner = self.anchor_corner;
126
127 if desired.left() < limits.left() || desired.right() > limits.right() {
128 let switched = anchor_corner
129 .switch_axis(Axis::Horizontal)
130 .get_bounds(origin, size);
131 if !(switched.left() < limits.left() || switched.right() > limits.right()) {
132 anchor_corner = anchor_corner.switch_axis(Axis::Horizontal);
133 desired = switched
134 }
135 }
136
137 if desired.top() < limits.top() || desired.bottom() > limits.bottom() {
138 let switched = anchor_corner
139 .switch_axis(Axis::Vertical)
140 .get_bounds(origin, size);
141 if !(switched.top() < limits.top() || switched.bottom() > limits.bottom()) {
142 desired = switched;
143 }
144 }
145 }
146
147 // Snap the horizontal edges of the overlay to the horizontal edges of the window if
148 // its horizontal bounds overflow, aligning to the left if it is wider than the limits.
149 if desired.right() > limits.right() {
150 desired.origin.x -= desired.right() - limits.right();
151 }
152 if desired.left() < limits.left() {
153 desired.origin.x = limits.origin.x;
154 }
155
156 // Snap the vertical edges of the overlay to the vertical edges of the window if
157 // its vertical bounds overflow, aligning to the top if it is taller than the limits.
158 if desired.bottom() > limits.bottom() {
159 desired.origin.y -= desired.bottom() - limits.bottom();
160 }
161 if desired.top() < limits.top() {
162 desired.origin.y = limits.origin.y;
163 }
164
165 let mut offset = cx.element_offset() + desired.origin - bounds.origin;
166 offset = point(offset.x.round(), offset.y.round());
167 cx.with_absolute_element_offset(offset, |cx| {
168 cx.break_content_mask(|cx| {
169 for child in &mut self.children {
170 child.paint(cx);
171 }
172 })
173 })
174 }
175}
176
177impl IntoElement for Overlay {
178 type Element = Self;
179
180 fn element_id(&self) -> Option<crate::ElementId> {
181 None
182 }
183
184 fn into_element(self) -> Self::Element {
185 self
186 }
187}
188
189enum Axis {
190 Horizontal,
191 Vertical,
192}
193
194#[derive(Copy, Clone, PartialEq)]
195pub enum OverlayFitMode {
196 SnapToWindow,
197 SwitchAnchor,
198}
199
200#[derive(Copy, Clone, PartialEq)]
201pub enum OverlayPositionMode {
202 Window,
203 Local,
204}
205
206impl OverlayPositionMode {
207 fn get_position_and_bounds(
208 &self,
209 anchor_position: Option<Point<Pixels>>,
210 anchor_corner: AnchorCorner,
211 size: Size<Pixels>,
212 bounds: Bounds<Pixels>,
213 ) -> (Point<Pixels>, Bounds<Pixels>) {
214 match self {
215 OverlayPositionMode::Window => {
216 let anchor_position = anchor_position.unwrap_or_else(|| bounds.origin);
217 let bounds = anchor_corner.get_bounds(anchor_position, size);
218 (anchor_position, bounds)
219 }
220 OverlayPositionMode::Local => {
221 let anchor_position = anchor_position.unwrap_or_default();
222 let bounds = anchor_corner.get_bounds(bounds.origin + anchor_position, size);
223 (anchor_position, bounds)
224 }
225 }
226 }
227}
228
229#[derive(Clone, Copy, PartialEq, Eq)]
230pub enum AnchorCorner {
231 TopLeft,
232 TopRight,
233 BottomLeft,
234 BottomRight,
235}
236
237impl AnchorCorner {
238 fn get_bounds(&self, origin: Point<Pixels>, size: Size<Pixels>) -> Bounds<Pixels> {
239 let origin = match self {
240 Self::TopLeft => origin,
241 Self::TopRight => Point {
242 x: origin.x - size.width,
243 y: origin.y,
244 },
245 Self::BottomLeft => Point {
246 x: origin.x,
247 y: origin.y - size.height,
248 },
249 Self::BottomRight => Point {
250 x: origin.x - size.width,
251 y: origin.y - size.height,
252 },
253 };
254
255 Bounds { origin, size }
256 }
257
258 pub fn corner(&self, bounds: Bounds<Pixels>) -> Point<Pixels> {
259 match self {
260 Self::TopLeft => bounds.origin,
261 Self::TopRight => bounds.upper_right(),
262 Self::BottomLeft => bounds.lower_left(),
263 Self::BottomRight => bounds.lower_right(),
264 }
265 }
266
267 fn switch_axis(self, axis: Axis) -> Self {
268 match axis {
269 Axis::Vertical => match self {
270 AnchorCorner::TopLeft => AnchorCorner::BottomLeft,
271 AnchorCorner::TopRight => AnchorCorner::BottomRight,
272 AnchorCorner::BottomLeft => AnchorCorner::TopLeft,
273 AnchorCorner::BottomRight => AnchorCorner::TopRight,
274 },
275 Axis::Horizontal => match self {
276 AnchorCorner::TopLeft => AnchorCorner::TopRight,
277 AnchorCorner::TopRight => AnchorCorner::TopLeft,
278 AnchorCorner::BottomLeft => AnchorCorner::BottomRight,
279 AnchorCorner::BottomRight => AnchorCorner::BottomLeft,
280 },
281 }
282 }
283}