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