1use smallvec::SmallVec;
2use taffy::style::{Display, Position};
3
4use crate::{
5 point, AnyElement, Bounds, Element, GlobalElementId, IntoElement, LayoutId, ParentElement,
6 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
65impl ParentElement for Anchored {
66 fn extend(&mut self, elements: impl IntoIterator<Item = AnyElement>) {
67 self.children.extend(elements)
68 }
69}
70
71impl Element for Anchored {
72 type RequestLayoutState = AnchoredState;
73 type PrepaintState = ();
74
75 fn id(&self) -> Option<crate::ElementId> {
76 None
77 }
78
79 fn request_layout(
80 &mut self,
81 _id: Option<&GlobalElementId>,
82 cx: &mut WindowContext,
83 ) -> (crate::LayoutId, Self::RequestLayoutState) {
84 let child_layout_ids = self
85 .children
86 .iter_mut()
87 .map(|child| child.request_layout(cx))
88 .collect::<SmallVec<_>>();
89
90 let anchored_style = Style {
91 position: Position::Absolute,
92 display: Display::Flex,
93 ..Style::default()
94 };
95
96 let layout_id = cx.request_layout(anchored_style, child_layout_ids.iter().copied());
97
98 (layout_id, AnchoredState { child_layout_ids })
99 }
100
101 fn prepaint(
102 &mut self,
103 _id: Option<&GlobalElementId>,
104 bounds: Bounds<Pixels>,
105 request_layout: &mut Self::RequestLayoutState,
106 cx: &mut WindowContext,
107 ) {
108 if request_layout.child_layout_ids.is_empty() {
109 return;
110 }
111
112 let mut child_min = point(Pixels::MAX, Pixels::MAX);
113 let mut child_max = Point::default();
114 for child_layout_id in &request_layout.child_layout_ids {
115 let child_bounds = cx.layout_bounds(*child_layout_id);
116 child_min = child_min.min(&child_bounds.origin);
117 child_max = child_max.max(&child_bounds.lower_right());
118 }
119 let size: Size<Pixels> = (child_max - child_min).into();
120
121 let (origin, mut desired) = self.position_mode.get_position_and_bounds(
122 self.anchor_position,
123 self.anchor_corner,
124 size,
125 bounds,
126 );
127
128 let limits = Bounds {
129 origin: Point::default(),
130 size: cx.viewport_size(),
131 };
132
133 if self.fit_mode == AnchoredFitMode::SwitchAnchor {
134 let mut anchor_corner = self.anchor_corner;
135
136 if desired.left() < limits.left() || desired.right() > limits.right() {
137 let switched = anchor_corner
138 .switch_axis(Axis::Horizontal)
139 .get_bounds(origin, size);
140 if !(switched.left() < limits.left() || switched.right() > limits.right()) {
141 anchor_corner = anchor_corner.switch_axis(Axis::Horizontal);
142 desired = switched
143 }
144 }
145
146 if desired.top() < limits.top() || desired.bottom() > limits.bottom() {
147 let switched = anchor_corner
148 .switch_axis(Axis::Vertical)
149 .get_bounds(origin, size);
150 if !(switched.top() < limits.top() || switched.bottom() > limits.bottom()) {
151 desired = switched;
152 }
153 }
154 }
155
156 // Snap the horizontal edges of the anchored element to the horizontal edges of the window if
157 // its horizontal bounds overflow, aligning to the left if it is wider than the limits.
158 if desired.right() > limits.right() {
159 desired.origin.x -= desired.right() - limits.right();
160 }
161 if desired.left() < limits.left() {
162 desired.origin.x = limits.origin.x;
163 }
164
165 // Snap the vertical edges of the anchored element to the vertical edges of the window if
166 // its vertical bounds overflow, aligning to the top if it is taller than the limits.
167 if desired.bottom() > limits.bottom() {
168 desired.origin.y -= desired.bottom() - limits.bottom();
169 }
170 if desired.top() < limits.top() {
171 desired.origin.y = limits.origin.y;
172 }
173
174 let offset = desired.origin - bounds.origin;
175 let offset = point(offset.x.round(), offset.y.round());
176
177 cx.with_element_offset(offset, |cx| {
178 for child in &mut self.children {
179 child.prepaint(cx);
180 }
181 })
182 }
183
184 fn paint(
185 &mut self,
186 _id: Option<&GlobalElementId>,
187 _bounds: crate::Bounds<crate::Pixels>,
188 _request_layout: &mut Self::RequestLayoutState,
189 _prepaint: &mut Self::PrepaintState,
190 cx: &mut WindowContext,
191 ) {
192 for child in &mut self.children {
193 child.paint(cx);
194 }
195 }
196}
197
198impl IntoElement for Anchored {
199 type Element = Self;
200
201 fn into_element(self) -> Self::Element {
202 self
203 }
204}
205
206enum Axis {
207 Horizontal,
208 Vertical,
209}
210
211/// Which algorithm to use when fitting the anchored element to be inside the window.
212#[derive(Copy, Clone, PartialEq)]
213pub enum AnchoredFitMode {
214 /// Snap the anchored element to the window edge
215 SnapToWindow,
216 /// Switch which corner anchor this anchored element is attached to
217 SwitchAnchor,
218}
219
220/// Which algorithm to use when positioning the anchored element.
221#[derive(Copy, Clone, PartialEq)]
222pub enum AnchoredPositionMode {
223 /// Position the anchored element relative to the window
224 Window,
225 /// Position the anchored element relative to its parent
226 Local,
227}
228
229impl AnchoredPositionMode {
230 fn get_position_and_bounds(
231 &self,
232 anchor_position: Option<Point<Pixels>>,
233 anchor_corner: AnchorCorner,
234 size: Size<Pixels>,
235 bounds: Bounds<Pixels>,
236 ) -> (Point<Pixels>, Bounds<Pixels>) {
237 match self {
238 AnchoredPositionMode::Window => {
239 let anchor_position = anchor_position.unwrap_or(bounds.origin);
240 let bounds = anchor_corner.get_bounds(anchor_position, size);
241 (anchor_position, bounds)
242 }
243 AnchoredPositionMode::Local => {
244 let anchor_position = anchor_position.unwrap_or_default();
245 let bounds = anchor_corner.get_bounds(bounds.origin + anchor_position, size);
246 (anchor_position, bounds)
247 }
248 }
249 }
250}
251
252/// Which corner of the anchored element should be considered the anchor.
253#[derive(Clone, Copy, PartialEq, Eq)]
254pub enum AnchorCorner {
255 /// The top left corner
256 TopLeft,
257 /// The top right corner
258 TopRight,
259 /// The bottom left corner
260 BottomLeft,
261 /// The bottom right corner
262 BottomRight,
263}
264
265impl AnchorCorner {
266 fn get_bounds(&self, origin: Point<Pixels>, size: Size<Pixels>) -> Bounds<Pixels> {
267 let origin = match self {
268 Self::TopLeft => origin,
269 Self::TopRight => Point {
270 x: origin.x - size.width,
271 y: origin.y,
272 },
273 Self::BottomLeft => Point {
274 x: origin.x,
275 y: origin.y - size.height,
276 },
277 Self::BottomRight => Point {
278 x: origin.x - size.width,
279 y: origin.y - size.height,
280 },
281 };
282
283 Bounds { origin, size }
284 }
285
286 /// Get the point corresponding to this anchor corner in `bounds`.
287 pub fn corner(&self, bounds: Bounds<Pixels>) -> Point<Pixels> {
288 match self {
289 Self::TopLeft => bounds.origin,
290 Self::TopRight => bounds.upper_right(),
291 Self::BottomLeft => bounds.lower_left(),
292 Self::BottomRight => bounds.lower_right(),
293 }
294 }
295
296 fn switch_axis(self, axis: Axis) -> Self {
297 match axis {
298 Axis::Vertical => match self {
299 AnchorCorner::TopLeft => AnchorCorner::BottomLeft,
300 AnchorCorner::TopRight => AnchorCorner::BottomRight,
301 AnchorCorner::BottomLeft => AnchorCorner::TopLeft,
302 AnchorCorner::BottomRight => AnchorCorner::TopRight,
303 },
304 Axis::Horizontal => match self {
305 AnchorCorner::TopLeft => AnchorCorner::TopRight,
306 AnchorCorner::TopRight => AnchorCorner::TopLeft,
307 AnchorCorner::BottomLeft => AnchorCorner::BottomRight,
308 AnchorCorner::BottomRight => AnchorCorner::BottomLeft,
309 },
310 }
311 }
312}