overlay.rs

  1use smallvec::SmallVec;
  2
  3use crate::{
  4    point, AnyElement, BorrowWindow, Bounds, Element, LayoutId, ParentComponent, Pixels, Point,
  5    Size, Style,
  6};
  7
  8pub struct OverlayState {
  9    child_layout_ids: SmallVec<[LayoutId; 4]>,
 10}
 11
 12pub struct Overlay<V> {
 13    children: SmallVec<[AnyElement<V>; 2]>,
 14    anchor_corner: AnchorCorner,
 15    fit_mode: OverlayFitMode,
 16    // todo!();
 17    // anchor_position: Option<Vector2F>,
 18    // position_mode: OverlayPositionMode,
 19}
 20
 21/// overlay gives you a floating element that will avoid overflowing the window bounds.
 22/// Its children should have no margin to avoid measurement issues.
 23pub fn overlay<V: 'static>() -> Overlay<V> {
 24    Overlay {
 25        children: SmallVec::new(),
 26        anchor_corner: AnchorCorner::TopLeft,
 27        fit_mode: OverlayFitMode::SwitchAnchor,
 28    }
 29}
 30
 31impl<V> Overlay<V> {
 32    /// Sets which corner of the overlay should be anchored to the current position.
 33    pub fn anchor(mut self, anchor: AnchorCorner) -> Self {
 34        self.anchor_corner = anchor;
 35        self
 36    }
 37
 38    /// Snap to window edge instead of switching anchor corner when an overflow would occur.
 39    pub fn snap_to_window(mut self) -> Self {
 40        self.fit_mode = OverlayFitMode::SnapToWindow;
 41        self
 42    }
 43}
 44
 45impl<V: 'static> ParentComponent<V> for Overlay<V> {
 46    fn children_mut(&mut self) -> &mut SmallVec<[AnyElement<V>; 2]> {
 47        &mut self.children
 48    }
 49}
 50
 51impl<V: 'static> Element<V> for Overlay<V> {
 52    type ElementState = OverlayState;
 53
 54    fn element_id(&self) -> Option<crate::ElementId> {
 55        None
 56    }
 57
 58    fn layout(
 59        &mut self,
 60        view_state: &mut V,
 61        _: Option<Self::ElementState>,
 62        cx: &mut crate::ViewContext<V>,
 63    ) -> (crate::LayoutId, Self::ElementState) {
 64        let child_layout_ids = self
 65            .children
 66            .iter_mut()
 67            .map(|child| child.layout(view_state, cx))
 68            .collect::<SmallVec<_>>();
 69        let layout_id = cx.request_layout(&Style::default(), child_layout_ids.iter().copied());
 70
 71        (layout_id, OverlayState { child_layout_ids })
 72    }
 73
 74    fn paint(
 75        &mut self,
 76        bounds: crate::Bounds<crate::Pixels>,
 77        view_state: &mut V,
 78        element_state: &mut Self::ElementState,
 79        cx: &mut crate::ViewContext<V>,
 80    ) {
 81        if element_state.child_layout_ids.is_empty() {
 82            return;
 83        }
 84
 85        let mut child_min = point(Pixels::MAX, Pixels::MAX);
 86        let mut child_max = Point::default();
 87        for child_layout_id in &element_state.child_layout_ids {
 88            let child_bounds = cx.layout_bounds(*child_layout_id);
 89            child_min = child_min.min(&child_bounds.origin);
 90            child_max = child_max.max(&child_bounds.lower_right());
 91        }
 92        let size: Size<Pixels> = (child_max - child_min).into();
 93        let origin = bounds.origin;
 94
 95        let mut desired = self.anchor_corner.get_bounds(origin, size);
 96        let limits = Bounds {
 97            origin: Point::zero(),
 98            size: cx.viewport_size(),
 99        };
100
101        match self.fit_mode {
102            OverlayFitMode::SnapToWindow => {
103                // Snap the horizontal edges of the overlay to the horizontal edges of the window if
104                // its horizontal bounds overflow
105                if desired.right() > limits.right() {
106                    desired.origin.x -= desired.right() - limits.right();
107                } else if desired.left() < limits.left() {
108                    desired.origin.x = limits.origin.x;
109                }
110
111                // Snap the vertical edges of the overlay to the vertical edges of the window if
112                // its vertical bounds overflow.
113                if desired.bottom() > limits.bottom() {
114                    desired.origin.y -= desired.bottom() - limits.bottom();
115                } else if desired.top() < limits.top() {
116                    desired.origin.y = limits.origin.y;
117                }
118            }
119            OverlayFitMode::SwitchAnchor => {
120                let mut anchor_corner = self.anchor_corner;
121
122                if desired.left() < limits.left() || desired.right() > limits.right() {
123                    anchor_corner = anchor_corner.switch_axis(Axis::Horizontal);
124                }
125
126                if bounds.top() < limits.top() || bounds.bottom() > limits.bottom() {
127                    anchor_corner = anchor_corner.switch_axis(Axis::Vertical);
128                }
129
130                // Update bounds if needed
131                if anchor_corner != self.anchor_corner {
132                    desired = anchor_corner.get_bounds(origin, size)
133                }
134            }
135            OverlayFitMode::None => {}
136        }
137
138        cx.with_element_offset(desired.origin - bounds.origin, |cx| {
139            for child in &mut self.children {
140                child.paint(view_state, cx);
141            }
142        })
143    }
144}
145
146enum Axis {
147    Horizontal,
148    Vertical,
149}
150
151#[derive(Copy, Clone)]
152pub enum OverlayFitMode {
153    SnapToWindow,
154    SwitchAnchor,
155    None,
156}
157
158#[derive(Clone, Copy, PartialEq, Eq)]
159pub enum AnchorCorner {
160    TopLeft,
161    TopRight,
162    BottomLeft,
163    BottomRight,
164}
165
166impl AnchorCorner {
167    fn get_bounds(&self, origin: Point<Pixels>, size: Size<Pixels>) -> Bounds<Pixels> {
168        let origin = match self {
169            Self::TopLeft => origin,
170            Self::TopRight => Point {
171                x: origin.x - size.width,
172                y: origin.y,
173            },
174            Self::BottomLeft => Point {
175                x: origin.x,
176                y: origin.y - size.height,
177            },
178            Self::BottomRight => Point {
179                x: origin.x - size.width,
180                y: origin.y - size.height,
181            },
182        };
183
184        Bounds { origin, size }
185    }
186
187    fn switch_axis(self, axis: Axis) -> Self {
188        match axis {
189            Axis::Vertical => match self {
190                AnchorCorner::TopLeft => AnchorCorner::BottomLeft,
191                AnchorCorner::TopRight => AnchorCorner::BottomRight,
192                AnchorCorner::BottomLeft => AnchorCorner::TopLeft,
193                AnchorCorner::BottomRight => AnchorCorner::TopRight,
194            },
195            Axis::Horizontal => match self {
196                AnchorCorner::TopLeft => AnchorCorner::TopRight,
197                AnchorCorner::TopRight => AnchorCorner::TopLeft,
198                AnchorCorner::BottomLeft => AnchorCorner::BottomRight,
199                AnchorCorner::BottomRight => AnchorCorner::BottomLeft,
200            },
201        }
202    }
203}