overlay.rs

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