overlay.rs

  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}