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