overlay.rs

  1use std::ops::Range;
  2
  3use crate::{
  4    geometry::{rect::RectF, vector::Vector2F},
  5    json::ToJson,
  6    presenter::MeasurementContext,
  7    DebugContext, Element, ElementBox, Event, EventContext, LayoutContext, MouseRegion,
  8    PaintContext, SizeConstraint,
  9};
 10use serde_json::json;
 11
 12pub struct Overlay {
 13    child: ElementBox,
 14    abs_position: Option<Vector2F>,
 15    fit_mode: OverlayFitMode,
 16    hoverable: bool,
 17}
 18
 19#[derive(Copy, Clone)]
 20pub enum OverlayFitMode {
 21    SnapToWindow,
 22    FlipAlignment,
 23    None,
 24}
 25
 26impl Overlay {
 27    pub fn new(child: ElementBox) -> Self {
 28        Self {
 29            child,
 30            abs_position: None,
 31            fit_mode: OverlayFitMode::None,
 32            hoverable: false,
 33        }
 34    }
 35
 36    pub fn with_abs_position(mut self, position: Vector2F) -> Self {
 37        self.abs_position = Some(position);
 38        self
 39    }
 40
 41    pub fn fit_mode(mut self, fit_mode: OverlayFitMode) -> Self {
 42        self.fit_mode = fit_mode;
 43        self
 44    }
 45
 46    pub fn hoverable(mut self, hoverable: bool) -> Self {
 47        self.hoverable = hoverable;
 48        self
 49    }
 50}
 51
 52impl Element for Overlay {
 53    type LayoutState = Vector2F;
 54    type PaintState = ();
 55
 56    fn layout(
 57        &mut self,
 58        constraint: SizeConstraint,
 59        cx: &mut LayoutContext,
 60    ) -> (Vector2F, Self::LayoutState) {
 61        let constraint = if self.abs_position.is_some() {
 62            SizeConstraint::new(Vector2F::zero(), cx.window_size)
 63        } else {
 64            constraint
 65        };
 66        let size = self.child.layout(constraint, cx);
 67        (Vector2F::zero(), size)
 68    }
 69
 70    fn paint(
 71        &mut self,
 72        bounds: RectF,
 73        _: RectF,
 74        size: &mut Self::LayoutState,
 75        cx: &mut PaintContext,
 76    ) {
 77        let mut bounds = RectF::new(self.abs_position.unwrap_or(bounds.origin()), *size);
 78        cx.scene.push_stacking_context(None);
 79
 80        if self.hoverable {
 81            cx.scene.push_mouse_region(MouseRegion {
 82                view_id: cx.current_view_id(),
 83                bounds,
 84                ..Default::default()
 85            });
 86        }
 87
 88        match self.fit_mode {
 89            OverlayFitMode::SnapToWindow => {
 90                // Snap the right edge of the overlay to the right edge of the window if
 91                // its horizontal bounds overflow.
 92                if bounds.lower_right().x() > cx.window_size.x() {
 93                    bounds.set_origin_x((cx.window_size.x() - bounds.width()).max(0.));
 94                }
 95
 96                // Snap the bottom edge of the overlay to the bottom edge of the window if
 97                // its vertical bounds overflow.
 98                if bounds.lower_right().y() > cx.window_size.y() {
 99                    bounds.set_origin_y((cx.window_size.y() - bounds.height()).max(0.));
100                }
101            }
102            OverlayFitMode::FlipAlignment => {
103                // Right-align overlay if its horizontal bounds overflow.
104                if bounds.lower_right().x() > cx.window_size.x() {
105                    bounds.set_origin_x(bounds.origin_x() - bounds.width());
106                }
107
108                // Bottom-align overlay if its vertical bounds overflow.
109                if bounds.lower_right().y() > cx.window_size.y() {
110                    bounds.set_origin_y(bounds.origin_y() - bounds.height());
111                }
112            }
113            OverlayFitMode::None => {}
114        }
115
116        self.child.paint(bounds.origin(), bounds, cx);
117        cx.scene.pop_stacking_context();
118    }
119
120    fn dispatch_event(
121        &mut self,
122        event: &Event,
123        _: RectF,
124        _: RectF,
125        _: &mut Self::LayoutState,
126        _: &mut Self::PaintState,
127        cx: &mut EventContext,
128    ) -> bool {
129        self.child.dispatch_event(event, cx)
130    }
131
132    fn rect_for_text_range(
133        &self,
134        range_utf16: Range<usize>,
135        _: RectF,
136        _: RectF,
137        _: &Self::LayoutState,
138        _: &Self::PaintState,
139        cx: &MeasurementContext,
140    ) -> Option<RectF> {
141        self.child.rect_for_text_range(range_utf16, cx)
142    }
143
144    fn debug(
145        &self,
146        _: RectF,
147        _: &Self::LayoutState,
148        _: &Self::PaintState,
149        cx: &DebugContext,
150    ) -> serde_json::Value {
151        json!({
152            "type": "Overlay",
153            "abs_position": self.abs_position.to_json(),
154            "child": self.child.debug(cx),
155        })
156    }
157}