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 z_index: Option<usize>,
20}
21
22#[derive(Copy, Clone)]
23pub enum OverlayFitMode {
24 SnapToWindow,
25 SwitchAnchor,
26 None,
27}
28
29#[derive(Copy, Clone, PartialEq, Eq)]
30pub enum OverlayPositionMode {
31 Window,
32 Local,
33}
34
35#[derive(Clone, Copy, PartialEq, Eq)]
36pub enum AnchorCorner {
37 TopLeft,
38 TopRight,
39 BottomLeft,
40 BottomRight,
41}
42
43impl AnchorCorner {
44 fn get_bounds(&self, anchor_position: Vector2F, size: Vector2F) -> RectF {
45 match self {
46 Self::TopLeft => RectF::from_points(anchor_position, anchor_position + size),
47 Self::TopRight => RectF::from_points(
48 anchor_position - Vector2F::new(size.x(), 0.),
49 anchor_position + Vector2F::new(0., size.y()),
50 ),
51 Self::BottomLeft => RectF::from_points(
52 anchor_position - Vector2F::new(0., size.y()),
53 anchor_position + Vector2F::new(size.x(), 0.),
54 ),
55 Self::BottomRight => RectF::from_points(anchor_position - size, anchor_position),
56 }
57 }
58
59 fn switch_axis(self, axis: Axis) -> Self {
60 match axis {
61 Axis::Vertical => match self {
62 AnchorCorner::TopLeft => AnchorCorner::BottomLeft,
63 AnchorCorner::TopRight => AnchorCorner::BottomRight,
64 AnchorCorner::BottomLeft => AnchorCorner::TopLeft,
65 AnchorCorner::BottomRight => AnchorCorner::TopRight,
66 },
67 Axis::Horizontal => match self {
68 AnchorCorner::TopLeft => AnchorCorner::TopRight,
69 AnchorCorner::TopRight => AnchorCorner::TopLeft,
70 AnchorCorner::BottomLeft => AnchorCorner::BottomRight,
71 AnchorCorner::BottomRight => AnchorCorner::BottomLeft,
72 },
73 }
74 }
75}
76
77impl Overlay {
78 pub fn new(child: ElementBox) -> Self {
79 Self {
80 child,
81 anchor_position: None,
82 anchor_corner: AnchorCorner::TopLeft,
83 fit_mode: OverlayFitMode::None,
84 position_mode: OverlayPositionMode::Window,
85 hoverable: false,
86 z_index: None,
87 }
88 }
89
90 pub fn with_anchor_position(mut self, position: Vector2F) -> Self {
91 self.anchor_position = Some(position);
92 self
93 }
94
95 pub fn with_anchor_corner(mut self, anchor_corner: AnchorCorner) -> Self {
96 self.anchor_corner = anchor_corner;
97 self
98 }
99
100 pub fn with_fit_mode(mut self, fit_mode: OverlayFitMode) -> Self {
101 self.fit_mode = fit_mode;
102 self
103 }
104
105 pub fn with_position_mode(mut self, position_mode: OverlayPositionMode) -> Self {
106 self.position_mode = position_mode;
107 self
108 }
109
110 pub fn with_hoverable(mut self, hoverable: bool) -> Self {
111 self.hoverable = hoverable;
112 self
113 }
114
115 pub fn with_z_index(mut self, z_index: usize) -> Self {
116 self.z_index = Some(z_index);
117 self
118 }
119}
120
121impl Element for Overlay {
122 type LayoutState = Vector2F;
123 type PaintState = ();
124
125 fn layout(
126 &mut self,
127 constraint: SizeConstraint,
128 cx: &mut LayoutContext,
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, cx);
136 (Vector2F::zero(), size)
137 }
138
139 fn paint(
140 &mut self,
141 bounds: RectF,
142 _: RectF,
143 size: &mut Self::LayoutState,
144 cx: &mut PaintContext,
145 ) {
146 let (anchor_position, mut bounds) = match self.position_mode {
147 OverlayPositionMode::Window => {
148 let anchor_position = self.anchor_position.unwrap_or_else(|| bounds.origin());
149 let bounds = self.anchor_corner.get_bounds(anchor_position, *size);
150 (anchor_position, bounds)
151 }
152 OverlayPositionMode::Local => {
153 let anchor_position = self.anchor_position.unwrap_or_default();
154 let bounds = self
155 .anchor_corner
156 .get_bounds(bounds.origin() + anchor_position, *size);
157 (anchor_position, bounds)
158 }
159 };
160
161 match self.fit_mode {
162 OverlayFitMode::SnapToWindow => {
163 // Snap the horizontal edges of the overlay to the horizontal edges of the window if
164 // its horizontal bounds overflow
165 if bounds.max_x() > cx.window_size.x() {
166 let mut lower_right = bounds.lower_right();
167 lower_right.set_x(cx.window_size.x());
168 bounds = RectF::from_points(lower_right - *size, lower_right);
169 } else if bounds.min_x() < 0. {
170 let mut upper_left = bounds.origin();
171 upper_left.set_x(0.);
172 bounds = RectF::from_points(upper_left, upper_left + *size);
173 }
174
175 // Snap the vertical edges of the overlay to the vertical edges of the window if
176 // its vertical bounds overflow.
177 if bounds.max_y() > cx.window_size.y() {
178 let mut lower_right = bounds.lower_right();
179 lower_right.set_y(cx.window_size.y());
180 bounds = RectF::from_points(lower_right - *size, lower_right);
181 } else if bounds.min_y() < 0. {
182 let mut upper_left = bounds.origin();
183 upper_left.set_y(0.);
184 bounds = RectF::from_points(upper_left, upper_left + *size);
185 }
186 }
187 OverlayFitMode::SwitchAnchor => {
188 let mut anchor_corner = self.anchor_corner;
189
190 if bounds.max_x() > cx.window_size.x() {
191 anchor_corner = anchor_corner.switch_axis(Axis::Horizontal);
192 }
193
194 if bounds.max_y() > cx.window_size.y() {
195 anchor_corner = anchor_corner.switch_axis(Axis::Vertical);
196 }
197
198 if bounds.min_x() < 0. {
199 anchor_corner = anchor_corner.switch_axis(Axis::Horizontal)
200 }
201
202 if bounds.min_y() < 0. {
203 anchor_corner = anchor_corner.switch_axis(Axis::Vertical)
204 }
205
206 // Update bounds if needed
207 if anchor_corner != self.anchor_corner {
208 bounds = anchor_corner.get_bounds(anchor_position, *size)
209 }
210 }
211 OverlayFitMode::None => {}
212 }
213
214 cx.paint_stacking_context(None, self.z_index, |cx| {
215 if self.hoverable {
216 enum OverlayHoverCapture {}
217 // Block hovers in lower stacking contexts
218 cx.scene
219 .push_mouse_region(MouseRegion::new::<OverlayHoverCapture>(
220 cx.current_view_id(),
221 cx.current_view_id(),
222 bounds,
223 ));
224 }
225
226 self.child.paint(
227 bounds.origin(),
228 RectF::new(Vector2F::zero(), cx.window_size),
229 cx,
230 );
231 });
232 }
233
234 fn rect_for_text_range(
235 &self,
236 range_utf16: Range<usize>,
237 _: RectF,
238 _: RectF,
239 _: &Self::LayoutState,
240 _: &Self::PaintState,
241 cx: &MeasurementContext,
242 ) -> Option<RectF> {
243 self.child.rect_for_text_range(range_utf16, cx)
244 }
245
246 fn debug(
247 &self,
248 _: RectF,
249 _: &Self::LayoutState,
250 _: &Self::PaintState,
251 cx: &DebugContext,
252 ) -> serde_json::Value {
253 json!({
254 "type": "Overlay",
255 "abs_position": self.anchor_position.to_json(),
256 "child": self.child.debug(cx),
257 })
258 }
259}