1use std::ops::Range;
2
3use crate::{
4 geometry::{rect::RectF, vector::Vector2F},
5 json::ToJson,
6 presenter::MeasurementContext,
7 Axis, DebugContext, Element, ElementBox, LayoutContext, MouseRegion, PaintContext,
8 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.paint_stacking_context(None, |cx| {
208 if self.hoverable {
209 enum OverlayHoverCapture {}
210 // Block hovers in lower stacking contexts
211 cx.scene
212 .push_mouse_region(MouseRegion::new::<OverlayHoverCapture>(
213 cx.current_view_id(),
214 cx.current_view_id(),
215 bounds,
216 ));
217 }
218
219 self.child.paint(
220 bounds.origin(),
221 RectF::new(Vector2F::zero(), cx.window_size),
222 cx,
223 );
224 });
225 }
226
227 fn rect_for_text_range(
228 &self,
229 range_utf16: Range<usize>,
230 _: RectF,
231 _: RectF,
232 _: &Self::LayoutState,
233 _: &Self::PaintState,
234 cx: &MeasurementContext,
235 ) -> Option<RectF> {
236 self.child.rect_for_text_range(range_utf16, cx)
237 }
238
239 fn debug(
240 &self,
241 _: RectF,
242 _: &Self::LayoutState,
243 _: &Self::PaintState,
244 cx: &DebugContext,
245 ) -> serde_json::Value {
246 json!({
247 "type": "Overlay",
248 "abs_position": self.anchor_position.to_json(),
249 "child": self.child.debug(cx),
250 })
251 }
252}