overlay.rs

  1use std::ops::Range;
  2
  3use crate::{
  4    geometry::{rect::RectF, vector::Vector2F},
  5    json::ToJson,
  6    AnyElement, Axis, Element, MouseRegion, SizeConstraint, ViewContext,
  7};
  8use serde_json::json;
  9
 10pub struct Overlay<V> {
 11    child: AnyElement<V>,
 12    anchor_position: Option<Vector2F>,
 13    anchor_corner: AnchorCorner,
 14    fit_mode: OverlayFitMode,
 15    position_mode: OverlayPositionMode,
 16    hoverable: bool,
 17    z_index: Option<usize>,
 18}
 19
 20#[derive(Copy, Clone)]
 21pub enum OverlayFitMode {
 22    SnapToWindow,
 23    SwitchAnchor,
 24    None,
 25}
 26
 27#[derive(Copy, Clone, PartialEq, Eq)]
 28pub enum OverlayPositionMode {
 29    Window,
 30    Local,
 31}
 32
 33#[derive(Clone, Copy, PartialEq, Eq)]
 34pub enum AnchorCorner {
 35    TopLeft,
 36    TopRight,
 37    BottomLeft,
 38    BottomRight,
 39}
 40
 41impl AnchorCorner {
 42    fn get_bounds(&self, anchor_position: Vector2F, size: Vector2F) -> RectF {
 43        match self {
 44            Self::TopLeft => RectF::from_points(anchor_position, anchor_position + size),
 45            Self::TopRight => RectF::from_points(
 46                anchor_position - Vector2F::new(size.x(), 0.),
 47                anchor_position + Vector2F::new(0., size.y()),
 48            ),
 49            Self::BottomLeft => RectF::from_points(
 50                anchor_position - Vector2F::new(0., size.y()),
 51                anchor_position + Vector2F::new(size.x(), 0.),
 52            ),
 53            Self::BottomRight => RectF::from_points(anchor_position - size, anchor_position),
 54        }
 55    }
 56
 57    fn switch_axis(self, axis: Axis) -> Self {
 58        match axis {
 59            Axis::Vertical => match self {
 60                AnchorCorner::TopLeft => AnchorCorner::BottomLeft,
 61                AnchorCorner::TopRight => AnchorCorner::BottomRight,
 62                AnchorCorner::BottomLeft => AnchorCorner::TopLeft,
 63                AnchorCorner::BottomRight => AnchorCorner::TopRight,
 64            },
 65            Axis::Horizontal => match self {
 66                AnchorCorner::TopLeft => AnchorCorner::TopRight,
 67                AnchorCorner::TopRight => AnchorCorner::TopLeft,
 68                AnchorCorner::BottomLeft => AnchorCorner::BottomRight,
 69                AnchorCorner::BottomRight => AnchorCorner::BottomLeft,
 70            },
 71        }
 72    }
 73}
 74
 75impl<V: 'static> Overlay<V> {
 76    pub fn new(child: impl Element<V>) -> Self {
 77        Self {
 78            child: child.into_any(),
 79            anchor_position: None,
 80            anchor_corner: AnchorCorner::TopLeft,
 81            fit_mode: OverlayFitMode::None,
 82            position_mode: OverlayPositionMode::Window,
 83            hoverable: false,
 84            z_index: None,
 85        }
 86    }
 87
 88    pub fn with_anchor_position(mut self, position: Vector2F) -> Self {
 89        self.anchor_position = Some(position);
 90        self
 91    }
 92
 93    pub fn with_anchor_corner(mut self, anchor_corner: AnchorCorner) -> Self {
 94        self.anchor_corner = anchor_corner;
 95        self
 96    }
 97
 98    pub fn with_fit_mode(mut self, fit_mode: OverlayFitMode) -> Self {
 99        self.fit_mode = fit_mode;
100        self
101    }
102
103    pub fn with_position_mode(mut self, position_mode: OverlayPositionMode) -> Self {
104        self.position_mode = position_mode;
105        self
106    }
107
108    pub fn with_hoverable(mut self, hoverable: bool) -> Self {
109        self.hoverable = hoverable;
110        self
111    }
112
113    pub fn with_z_index(mut self, z_index: usize) -> Self {
114        self.z_index = Some(z_index);
115        self
116    }
117}
118
119impl<V: 'static> Element<V> for Overlay<V> {
120    type LayoutState = Vector2F;
121    type PaintState = ();
122
123    fn layout(
124        &mut self,
125        constraint: SizeConstraint,
126        view: &mut V,
127        cx: &mut ViewContext<V>,
128    ) -> (Vector2F, Self::LayoutState) {
129        let constraint = if self.anchor_position.is_some() {
130            SizeConstraint::new(Vector2F::zero(), cx.window_size())
131        } else {
132            constraint
133        };
134        let size = self.child.layout(constraint, view, cx);
135        (Vector2F::zero(), size)
136    }
137
138    fn paint(
139        &mut self,
140        bounds: RectF,
141        _: RectF,
142        size: &mut Self::LayoutState,
143        view: &mut V,
144        cx: &mut ViewContext<V>,
145    ) {
146        let (anchor_position, mut bounds) = match self.position_mode {
147            OverlayPositionMode::Window => {
148                let anchor_position = self.anchor_position.unwrap_or_else(|| bounds.origin());
149                let bounds = self.anchor_corner.get_bounds(anchor_position, *size);
150                (anchor_position, bounds)
151            }
152            OverlayPositionMode::Local => {
153                let anchor_position = self.anchor_position.unwrap_or_default();
154                let bounds = self
155                    .anchor_corner
156                    .get_bounds(bounds.origin() + anchor_position, *size);
157                (anchor_position, bounds)
158            }
159        };
160
161        match self.fit_mode {
162            OverlayFitMode::SnapToWindow => {
163                // Snap the horizontal edges of the overlay to the horizontal edges of the window if
164                // its horizontal bounds overflow
165                if bounds.max_x() > cx.window_size().x() {
166                    let mut lower_right = bounds.lower_right();
167                    lower_right.set_x(cx.window_size().x());
168                    bounds = RectF::from_points(lower_right - *size, lower_right);
169                } else if bounds.min_x() < 0. {
170                    let mut upper_left = bounds.origin();
171                    upper_left.set_x(0.);
172                    bounds = RectF::from_points(upper_left, upper_left + *size);
173                }
174
175                // Snap the vertical edges of the overlay to the vertical edges of the window if
176                // its vertical bounds overflow.
177                if bounds.max_y() > cx.window_size().y() {
178                    let mut lower_right = bounds.lower_right();
179                    lower_right.set_y(cx.window_size().y());
180                    bounds = RectF::from_points(lower_right - *size, lower_right);
181                } else if bounds.min_y() < 0. {
182                    let mut upper_left = bounds.origin();
183                    upper_left.set_y(0.);
184                    bounds = RectF::from_points(upper_left, upper_left + *size);
185                }
186            }
187            OverlayFitMode::SwitchAnchor => {
188                let mut anchor_corner = self.anchor_corner;
189
190                if bounds.max_x() > cx.window_size().x() {
191                    anchor_corner = anchor_corner.switch_axis(Axis::Horizontal);
192                }
193
194                if bounds.max_y() > cx.window_size().y() {
195                    anchor_corner = anchor_corner.switch_axis(Axis::Vertical);
196                }
197
198                if bounds.min_x() < 0. {
199                    anchor_corner = anchor_corner.switch_axis(Axis::Horizontal)
200                }
201
202                if bounds.min_y() < 0. {
203                    anchor_corner = anchor_corner.switch_axis(Axis::Vertical)
204                }
205
206                // Update bounds if needed
207                if anchor_corner != self.anchor_corner {
208                    bounds = anchor_corner.get_bounds(anchor_position, *size)
209                }
210            }
211            OverlayFitMode::None => {}
212        }
213
214        cx.scene().push_stacking_context(None, self.z_index);
215        if self.hoverable {
216            enum OverlayHoverCapture {}
217            // Block hovers in lower stacking contexts
218            let view_id = cx.view_id();
219            cx.scene()
220                .push_mouse_region(MouseRegion::new::<OverlayHoverCapture>(
221                    view_id, view_id, bounds,
222                ));
223        }
224        self.child.paint(
225            bounds.origin(),
226            RectF::new(Vector2F::zero(), cx.window_size()),
227            view,
228            cx,
229        );
230        cx.scene().pop_stacking_context();
231    }
232
233    fn rect_for_text_range(
234        &self,
235        range_utf16: Range<usize>,
236        _: RectF,
237        _: RectF,
238        _: &Self::LayoutState,
239        _: &Self::PaintState,
240        view: &V,
241        cx: &ViewContext<V>,
242    ) -> Option<RectF> {
243        self.child.rect_for_text_range(range_utf16, view, cx)
244    }
245
246    fn debug(
247        &self,
248        _: RectF,
249        _: &Self::LayoutState,
250        _: &Self::PaintState,
251        view: &V,
252        cx: &ViewContext<V>,
253    ) -> serde_json::Value {
254        json!({
255            "type": "Overlay",
256            "abs_position": self.anchor_position.to_json(),
257            "child": self.child.debug(view, cx),
258        })
259    }
260}