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