1use std::ops::Range;
2
3use crate::{
4 geometry::{rect::RectF, vector::Vector2F},
5 json::ToJson,
6 AnyElement, Axis, Element, MouseRegion, SceneBuilder, SizeConstraint, View, ViewContext,
7};
8use serde_json::json;
9
10pub struct Overlay<V: View> {
11 child: AnyElement<V>,
12 anchor_position: Option<Vector2F>,
13 anchor_corner: AnchorCorner,
14 fit_mode: OverlayFitMode,
15 position_mode: OverlayPositionMode,
16 hoverable: bool,
17 z_index: Option<usize>,
18}
19
20#[derive(Copy, Clone)]
21pub enum OverlayFitMode {
22 SnapToWindow,
23 SwitchAnchor,
24 None,
25}
26
27#[derive(Copy, Clone, PartialEq, Eq)]
28pub enum OverlayPositionMode {
29 Window,
30 Local,
31}
32
33#[derive(Clone, Copy, PartialEq, Eq)]
34pub enum AnchorCorner {
35 TopLeft,
36 TopRight,
37 BottomLeft,
38 BottomRight,
39}
40
41impl AnchorCorner {
42 fn get_bounds(&self, anchor_position: Vector2F, size: Vector2F) -> RectF {
43 match self {
44 Self::TopLeft => RectF::from_points(anchor_position, anchor_position + size),
45 Self::TopRight => RectF::from_points(
46 anchor_position - Vector2F::new(size.x(), 0.),
47 anchor_position + Vector2F::new(0., size.y()),
48 ),
49 Self::BottomLeft => RectF::from_points(
50 anchor_position - Vector2F::new(0., size.y()),
51 anchor_position + Vector2F::new(size.x(), 0.),
52 ),
53 Self::BottomRight => RectF::from_points(anchor_position - size, anchor_position),
54 }
55 }
56
57 fn switch_axis(self, axis: Axis) -> Self {
58 match axis {
59 Axis::Vertical => match self {
60 AnchorCorner::TopLeft => AnchorCorner::BottomLeft,
61 AnchorCorner::TopRight => AnchorCorner::BottomRight,
62 AnchorCorner::BottomLeft => AnchorCorner::TopLeft,
63 AnchorCorner::BottomRight => AnchorCorner::TopRight,
64 },
65 Axis::Horizontal => match self {
66 AnchorCorner::TopLeft => AnchorCorner::TopRight,
67 AnchorCorner::TopRight => AnchorCorner::TopLeft,
68 AnchorCorner::BottomLeft => AnchorCorner::BottomRight,
69 AnchorCorner::BottomRight => AnchorCorner::BottomLeft,
70 },
71 }
72 }
73}
74
75impl<V: View> Overlay<V> {
76 pub fn new(child: impl Element<V>) -> Self {
77 Self {
78 child: child.into_any(),
79 anchor_position: None,
80 anchor_corner: AnchorCorner::TopLeft,
81 fit_mode: OverlayFitMode::None,
82 position_mode: OverlayPositionMode::Window,
83 hoverable: false,
84 z_index: None,
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 pub fn with_z_index(mut self, z_index: usize) -> Self {
114 self.z_index = Some(z_index);
115 self
116 }
117}
118
119impl<V: View> Element<V> for Overlay<V> {
120 type LayoutState = Vector2F;
121 type PaintState = ();
122
123 fn layout(
124 &mut self,
125 constraint: SizeConstraint,
126 view: &mut V,
127 cx: &mut ViewContext<V>,
128 ) -> (Vector2F, Self::LayoutState) {
129 let constraint = if self.anchor_position.is_some() {
130 SizeConstraint::new(Vector2F::zero(), cx.window_size())
131 } else {
132 constraint
133 };
134 let size = self.child.layout(constraint, view, cx);
135 (Vector2F::zero(), size)
136 }
137
138 fn paint(
139 &mut self,
140 scene: &mut SceneBuilder,
141 bounds: RectF,
142 _: RectF,
143 size: &mut Self::LayoutState,
144 view: &mut V,
145 cx: &mut ViewContext<V>,
146 ) {
147 let (anchor_position, mut bounds) = match self.position_mode {
148 OverlayPositionMode::Window => {
149 let anchor_position = self.anchor_position.unwrap_or_else(|| bounds.origin());
150 let bounds = self.anchor_corner.get_bounds(anchor_position, *size);
151 (anchor_position, bounds)
152 }
153 OverlayPositionMode::Local => {
154 let anchor_position = self.anchor_position.unwrap_or_default();
155 let bounds = self
156 .anchor_corner
157 .get_bounds(bounds.origin() + anchor_position, *size);
158 (anchor_position, bounds)
159 }
160 };
161
162 match self.fit_mode {
163 OverlayFitMode::SnapToWindow => {
164 // Snap the horizontal edges of the overlay to the horizontal edges of the window if
165 // its horizontal bounds overflow
166 if bounds.max_x() > cx.window_size().x() {
167 let mut lower_right = bounds.lower_right();
168 lower_right.set_x(cx.window_size().x());
169 bounds = RectF::from_points(lower_right - *size, lower_right);
170 } else if bounds.min_x() < 0. {
171 let mut upper_left = bounds.origin();
172 upper_left.set_x(0.);
173 bounds = RectF::from_points(upper_left, upper_left + *size);
174 }
175
176 // Snap the vertical edges of the overlay to the vertical edges of the window if
177 // its vertical bounds overflow.
178 if bounds.max_y() > cx.window_size().y() {
179 let mut lower_right = bounds.lower_right();
180 lower_right.set_y(cx.window_size().y());
181 bounds = RectF::from_points(lower_right - *size, lower_right);
182 } else if bounds.min_y() < 0. {
183 let mut upper_left = bounds.origin();
184 upper_left.set_y(0.);
185 bounds = RectF::from_points(upper_left, upper_left + *size);
186 }
187 }
188 OverlayFitMode::SwitchAnchor => {
189 let mut anchor_corner = self.anchor_corner;
190
191 if bounds.max_x() > cx.window_size().x() {
192 anchor_corner = anchor_corner.switch_axis(Axis::Horizontal);
193 }
194
195 if bounds.max_y() > cx.window_size().y() {
196 anchor_corner = anchor_corner.switch_axis(Axis::Vertical);
197 }
198
199 if bounds.min_x() < 0. {
200 anchor_corner = anchor_corner.switch_axis(Axis::Horizontal)
201 }
202
203 if bounds.min_y() < 0. {
204 anchor_corner = anchor_corner.switch_axis(Axis::Vertical)
205 }
206
207 // Update bounds if needed
208 if anchor_corner != self.anchor_corner {
209 bounds = anchor_corner.get_bounds(anchor_position, *size)
210 }
211 }
212 OverlayFitMode::None => {}
213 }
214
215 scene.paint_stacking_context(None, self.z_index, |scene| {
216 if self.hoverable {
217 enum OverlayHoverCapture {}
218 // Block hovers in lower stacking contexts
219 scene.push_mouse_region(MouseRegion::new::<OverlayHoverCapture>(
220 cx.view_id(),
221 cx.view_id(),
222 bounds,
223 ));
224 }
225
226 self.child.paint(
227 scene,
228 bounds.origin(),
229 RectF::new(Vector2F::zero(), cx.window_size()),
230 view,
231 cx,
232 );
233 });
234 }
235
236 fn rect_for_text_range(
237 &self,
238 range_utf16: Range<usize>,
239 _: RectF,
240 _: RectF,
241 _: &Self::LayoutState,
242 _: &Self::PaintState,
243 view: &V,
244 cx: &ViewContext<V>,
245 ) -> Option<RectF> {
246 self.child.rect_for_text_range(range_utf16, view, cx)
247 }
248
249 fn debug(
250 &self,
251 _: RectF,
252 _: &Self::LayoutState,
253 _: &Self::PaintState,
254 view: &V,
255 cx: &ViewContext<V>,
256 ) -> serde_json::Value {
257 json!({
258 "type": "Overlay",
259 "abs_position": self.anchor_position.to_json(),
260 "child": self.child.debug(view, cx),
261 })
262 }
263}