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}