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