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