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    position_mode: OverlayPositionMode,
 18    hoverable: bool,
 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 Overlay {
 77    pub fn new(child: ElementBox) -> Self {
 78        Self {
 79            child,
 80            anchor_position: None,
 81            anchor_corner: AnchorCorner::TopLeft,
 82            fit_mode: OverlayFitMode::None,
 83            position_mode: OverlayPositionMode::Window,
 84            hoverable: false,
 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
114impl Element for Overlay {
115    type LayoutState = Vector2F;
116    type PaintState = ();
117
118    fn layout(
119        &mut self,
120        constraint: SizeConstraint,
121        cx: &mut LayoutContext,
122    ) -> (Vector2F, Self::LayoutState) {
123        let constraint = if self.anchor_position.is_some() {
124            SizeConstraint::new(Vector2F::zero(), cx.window_size)
125        } else {
126            constraint
127        };
128        let size = self.child.layout(constraint, cx);
129        (Vector2F::zero(), size)
130    }
131
132    fn paint(
133        &mut self,
134        bounds: RectF,
135        _: RectF,
136        size: &mut Self::LayoutState,
137        cx: &mut PaintContext,
138    ) {
139        let (anchor_position, mut bounds) = match self.position_mode {
140            OverlayPositionMode::Window => {
141                let anchor_position = self.anchor_position.unwrap_or_else(|| bounds.origin());
142                let bounds = self.anchor_corner.get_bounds(anchor_position, *size);
143                (anchor_position, bounds)
144            }
145            OverlayPositionMode::Local => {
146                let anchor_position = self.anchor_position.unwrap_or_default();
147                let bounds = self
148                    .anchor_corner
149                    .get_bounds(bounds.origin() + anchor_position, *size);
150                (anchor_position, bounds)
151            }
152        };
153
154        match self.fit_mode {
155            OverlayFitMode::SnapToWindow => {
156                // Snap the horizontal edges of the overlay to the horizontal edges of the window if
157                // its horizontal bounds overflow
158                if bounds.max_x() > cx.window_size.x() {
159                    let mut lower_right = bounds.lower_right();
160                    lower_right.set_x(cx.window_size.x());
161                    bounds = RectF::from_points(lower_right - *size, lower_right);
162                } else if bounds.min_x() < 0. {
163                    let mut upper_left = bounds.origin();
164                    upper_left.set_x(0.);
165                    bounds = RectF::from_points(upper_left, upper_left + *size);
166                }
167
168                // Snap the vertical edges of the overlay to the vertical edges of the window if
169                // its vertical bounds overflow.
170                if bounds.max_y() > cx.window_size.y() {
171                    let mut lower_right = bounds.lower_right();
172                    lower_right.set_y(cx.window_size.y());
173                    bounds = RectF::from_points(lower_right - *size, lower_right);
174                } else if bounds.min_y() < 0. {
175                    let mut upper_left = bounds.origin();
176                    upper_left.set_y(0.);
177                    bounds = RectF::from_points(upper_left, upper_left + *size);
178                }
179            }
180            OverlayFitMode::SwitchAnchor => {
181                let mut anchor_corner = self.anchor_corner;
182
183                if bounds.max_x() > cx.window_size.x() {
184                    anchor_corner = anchor_corner.switch_axis(Axis::Horizontal);
185                }
186
187                if bounds.max_y() > cx.window_size.y() {
188                    anchor_corner = anchor_corner.switch_axis(Axis::Vertical);
189                }
190
191                if bounds.min_x() < 0. {
192                    anchor_corner = anchor_corner.switch_axis(Axis::Horizontal)
193                }
194
195                if bounds.min_y() < 0. {
196                    anchor_corner = anchor_corner.switch_axis(Axis::Vertical)
197                }
198
199                // Update bounds if needed
200                if anchor_corner != self.anchor_corner {
201                    bounds = anchor_corner.get_bounds(anchor_position, *size)
202                }
203            }
204            OverlayFitMode::None => {}
205        }
206
207        cx.scene.push_stacking_context(None);
208
209        if self.hoverable {
210            enum OverlayHoverCapture {}
211            // Block hovers in lower stacking contexts
212            cx.scene
213                .push_mouse_region(MouseRegion::new::<OverlayHoverCapture>(
214                    cx.current_view_id(),
215                    cx.current_view_id(),
216                    bounds,
217                ));
218        }
219
220        self.child.paint(
221            bounds.origin(),
222            RectF::new(Vector2F::zero(), cx.window_size),
223            cx,
224        );
225        cx.scene.pop_stacking_context();
226    }
227
228    fn dispatch_event(
229        &mut self,
230        event: &Event,
231        _: RectF,
232        _: RectF,
233        _: &mut Self::LayoutState,
234        _: &mut Self::PaintState,
235        cx: &mut EventContext,
236    ) -> bool {
237        self.child.dispatch_event(event, cx)
238    }
239
240    fn rect_for_text_range(
241        &self,
242        range_utf16: Range<usize>,
243        _: RectF,
244        _: RectF,
245        _: &Self::LayoutState,
246        _: &Self::PaintState,
247        cx: &MeasurementContext,
248    ) -> Option<RectF> {
249        self.child.rect_for_text_range(range_utf16, cx)
250    }
251
252    fn debug(
253        &self,
254        _: RectF,
255        _: &Self::LayoutState,
256        _: &Self::PaintState,
257        cx: &DebugContext,
258    ) -> serde_json::Value {
259        json!({
260            "type": "Overlay",
261            "abs_position": self.anchor_position.to_json(),
262            "child": self.child.debug(cx),
263        })
264    }
265}