1use crate::{
2 geometry::{rect::RectF, vector::Vector2F},
3 json::ToJson,
4 DebugContext, Element, ElementBox, Event, EventContext, LayoutContext, MouseRegion,
5 PaintContext, SizeConstraint,
6};
7use serde_json::json;
8
9pub struct Overlay {
10 child: ElementBox,
11 abs_position: Option<Vector2F>,
12 fit_mode: OverlayFitMode,
13 hoverable: bool,
14}
15
16#[derive(Copy, Clone)]
17pub enum OverlayFitMode {
18 SnapToWindow,
19 FlipAlignment,
20 None,
21}
22
23impl Overlay {
24 pub fn new(child: ElementBox) -> Self {
25 Self {
26 child,
27 abs_position: None,
28 fit_mode: OverlayFitMode::None,
29 hoverable: false,
30 }
31 }
32
33 pub fn with_abs_position(mut self, position: Vector2F) -> Self {
34 self.abs_position = Some(position);
35 self
36 }
37
38 pub fn fit_mode(mut self, fit_mode: OverlayFitMode) -> Self {
39 self.fit_mode = fit_mode;
40 self
41 }
42
43 pub fn hoverable(mut self, hoverable: bool) -> Self {
44 self.hoverable = hoverable;
45 self
46 }
47}
48
49impl Element for Overlay {
50 type LayoutState = Vector2F;
51 type PaintState = ();
52
53 fn layout(
54 &mut self,
55 constraint: SizeConstraint,
56 cx: &mut LayoutContext,
57 ) -> (Vector2F, Self::LayoutState) {
58 let constraint = if self.abs_position.is_some() {
59 SizeConstraint::new(Vector2F::zero(), cx.window_size)
60 } else {
61 constraint
62 };
63 let size = self.child.layout(constraint, cx);
64 (Vector2F::zero(), size)
65 }
66
67 fn paint(
68 &mut self,
69 bounds: RectF,
70 _: RectF,
71 size: &mut Self::LayoutState,
72 cx: &mut PaintContext,
73 ) {
74 let mut bounds = RectF::new(self.abs_position.unwrap_or(bounds.origin()), *size);
75 cx.scene.push_stacking_context(None);
76
77 if self.hoverable {
78 cx.scene.push_mouse_region(MouseRegion {
79 view_id: cx.current_view_id(),
80 bounds,
81 ..Default::default()
82 });
83 }
84
85 match self.fit_mode {
86 OverlayFitMode::SnapToWindow => {
87 // Snap the right edge of the overlay to the right edge of the window if
88 // its horizontal bounds overflow.
89 if bounds.lower_right().x() > cx.window_size.x() {
90 bounds.set_origin_x((cx.window_size.x() - bounds.width()).max(0.));
91 }
92
93 // Snap the bottom edge of the overlay to the bottom edge of the window if
94 // its vertical bounds overflow.
95 if bounds.lower_right().y() > cx.window_size.y() {
96 bounds.set_origin_y((cx.window_size.y() - bounds.height()).max(0.));
97 }
98 }
99 OverlayFitMode::FlipAlignment => {
100 // Right-align overlay if its horizontal bounds overflow.
101 if bounds.lower_right().x() > cx.window_size.x() {
102 bounds.set_origin_x(bounds.origin_x() - bounds.width());
103 }
104
105 // Bottom-align overlay if its vertical bounds overflow.
106 if bounds.lower_right().y() > cx.window_size.y() {
107 bounds.set_origin_y(bounds.origin_y() - bounds.height());
108 }
109 }
110 OverlayFitMode::None => {}
111 }
112
113 self.child.paint(bounds.origin(), bounds, cx);
114 cx.scene.pop_stacking_context();
115 }
116
117 fn dispatch_event(
118 &mut self,
119 event: &Event,
120 _: RectF,
121 _: RectF,
122 _: &mut Self::LayoutState,
123 _: &mut Self::PaintState,
124 cx: &mut EventContext,
125 ) -> bool {
126 self.child.dispatch_event(event, cx)
127 }
128
129 fn debug(
130 &self,
131 _: RectF,
132 _: &Self::LayoutState,
133 _: &Self::PaintState,
134 cx: &DebugContext,
135 ) -> serde_json::Value {
136 json!({
137 "type": "Overlay",
138 "abs_position": self.abs_position.to_json(),
139 "child": self.child.debug(cx),
140 })
141 }
142}