overlay.rs

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