anchored.rs

  1use smallvec::SmallVec;
  2use taffy::style::{Display, Position};
  3
  4use crate::{
  5    point, AnyElement, Axis, Bounds, Corner, Edges, Element, GlobalElementId, IntoElement,
  6    LayoutId, 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: Corner,
 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: Corner::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: Corner) -> 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.bottom_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 = Bounds::from_corner_and_size(
144                    anchor_corner.other_side_corner_along(Axis::Horizontal),
145                    origin,
146                    size,
147                );
148                if !(switched.left() < limits.left() || switched.right() > limits.right()) {
149                    anchor_corner = anchor_corner.other_side_corner_along(Axis::Horizontal);
150                    desired = switched
151                }
152            }
153
154            if desired.top() < limits.top() || desired.bottom() > limits.bottom() {
155                let switched = Bounds::from_corner_and_size(
156                    anchor_corner.other_side_corner_along(Axis::Vertical),
157                    origin,
158                    size,
159                );
160                if !(switched.top() < limits.top() || switched.bottom() > limits.bottom()) {
161                    desired = switched;
162                }
163            }
164        }
165
166        let edges = match self.fit_mode {
167            AnchoredFitMode::SnapToWindowWithMargin(edges) => edges,
168            _ => Edges::default(),
169        };
170
171        // Snap the horizontal edges of the anchored element to the horizontal edges of the window if
172        // its horizontal bounds overflow, aligning to the left if it is wider than the limits.
173        if desired.right() > limits.right() {
174            desired.origin.x -= desired.right() - limits.right() + edges.right;
175        }
176        if desired.left() < limits.left() {
177            desired.origin.x = limits.origin.x + edges.left;
178        }
179
180        // Snap the vertical edges of the anchored element to the vertical edges of the window if
181        // its vertical bounds overflow, aligning to the top if it is taller than the limits.
182        if desired.bottom() > limits.bottom() {
183            desired.origin.y -= desired.bottom() - limits.bottom() + edges.bottom;
184        }
185        if desired.top() < limits.top() {
186            desired.origin.y = limits.origin.y + edges.top;
187        }
188
189        let offset = desired.origin - bounds.origin;
190        let offset = point(offset.x.round(), offset.y.round());
191
192        cx.with_element_offset(offset, |cx| {
193            for child in &mut self.children {
194                child.prepaint(cx);
195            }
196        })
197    }
198
199    fn paint(
200        &mut self,
201        _id: Option<&GlobalElementId>,
202        _bounds: crate::Bounds<crate::Pixels>,
203        _request_layout: &mut Self::RequestLayoutState,
204        _prepaint: &mut Self::PrepaintState,
205        cx: &mut WindowContext,
206    ) {
207        for child in &mut self.children {
208            child.paint(cx);
209        }
210    }
211}
212
213impl IntoElement for Anchored {
214    type Element = Self;
215
216    fn into_element(self) -> Self::Element {
217        self
218    }
219}
220
221/// Which algorithm to use when fitting the anchored element to be inside the window.
222#[derive(Copy, Clone, PartialEq)]
223pub enum AnchoredFitMode {
224    /// Snap the anchored element to the window edge.
225    SnapToWindow,
226    /// Snap to window edge and leave some margins.
227    SnapToWindowWithMargin(Edges<Pixels>),
228    /// Switch which corner anchor this anchored element is attached to.
229    SwitchAnchor,
230}
231
232/// Which algorithm to use when positioning the anchored element.
233#[derive(Copy, Clone, PartialEq)]
234pub enum AnchoredPositionMode {
235    /// Position the anchored element relative to the window.
236    Window,
237    /// Position the anchored element relative to its parent.
238    Local,
239}
240
241impl AnchoredPositionMode {
242    fn get_position_and_bounds(
243        &self,
244        anchor_position: Option<Point<Pixels>>,
245        anchor_corner: Corner,
246        size: Size<Pixels>,
247        bounds: Bounds<Pixels>,
248    ) -> (Point<Pixels>, Bounds<Pixels>) {
249        match self {
250            AnchoredPositionMode::Window => {
251                let anchor_position = anchor_position.unwrap_or(bounds.origin);
252                let bounds = Bounds::from_corner_and_size(anchor_corner, anchor_position, size);
253                (anchor_position, bounds)
254            }
255            AnchoredPositionMode::Local => {
256                let anchor_position = anchor_position.unwrap_or_default();
257                let bounds = Bounds::from_corner_and_size(
258                    anchor_corner,
259                    bounds.origin + anchor_position,
260                    size,
261                );
262                (anchor_position, bounds)
263            }
264        }
265    }
266}