1use floating_ui_utils::{Coords, ElementOrVirtual, Placement, Strategy};
2
3use crate::compute_coords_from_placement::compute_coords_from_placement;
4use crate::types::{
5 ComputePositionConfig, ComputePositionReturn, Elements, GetElementRectsArgs, MiddlewareData,
6 MiddlewareReturn, MiddlewareState, Reset, ResetRects,
7};
8
9pub fn compute_position<Element: Clone, Window: Clone>(
15 reference: ElementOrVirtual<Element>,
16 floating: &Element,
17 config: ComputePositionConfig<Element, Window>,
18) -> ComputePositionReturn {
19 let placement = config.placement.unwrap_or(Placement::Bottom);
20 let strategy = config.strategy.unwrap_or(Strategy::Absolute);
21 let platform = config.platform;
22 let middlewares = config.middleware.unwrap_or_default();
23
24 let rtl = platform.is_rtl(floating);
25
26 let mut rects = platform.get_element_rects(GetElementRectsArgs {
27 reference: reference.clone(),
28 floating,
29 strategy,
30 });
31 let Coords { mut x, mut y } = compute_coords_from_placement(&rects, placement, rtl);
32 let mut stateful_placement = placement;
33 let mut middleware_data = MiddlewareData::default();
34 let mut reset_count = 0;
35
36 let mut i = 0;
37 while i < middlewares.len() {
38 let middleware = &middlewares[i];
39
40 let MiddlewareReturn {
41 x: next_x,
42 y: next_y,
43 data,
44 reset,
45 } = middleware.compute(MiddlewareState {
46 x,
47 y,
48 initial_placement: placement,
49 placement: stateful_placement,
50 strategy,
51 middleware_data: &middleware_data,
52 rects: &rects,
53 platform,
54 elements: Elements {
55 reference: reference.clone(),
56 floating,
57 },
58 });
59
60 x = next_x.unwrap_or(x);
61 y = next_y.unwrap_or(y);
62
63 if let Some(data) = data {
64 let existing_data = middleware_data.get(middleware.name());
65
66 let new_data = match existing_data {
67 Some(existing_data) => {
68 let mut a = existing_data
69 .as_object()
70 .expect("Existing data should be an object.")
71 .to_owned();
72
73 let mut b = data
74 .as_object()
75 .expect("New data should be an object.")
76 .to_owned();
77
78 b.retain(|_, v| !v.is_null());
79 a.extend(b);
80
81 serde_json::Value::Object(a)
82 }
83 None => data,
84 };
85
86 middleware_data.set(middleware.name(), new_data);
87 }
88
89 if let Some(reset) = reset {
90 if reset_count <= 50 {
91 reset_count += 1;
92
93 match reset {
94 Reset::True => {}
95 Reset::Value(value) => {
96 if let Some(reset_placement) = value.placement {
97 stateful_placement = reset_placement;
98 }
99
100 if let Some(reset_rects) = value.rects {
101 rects = match reset_rects {
102 ResetRects::True => {
103 platform.get_element_rects(GetElementRectsArgs {
104 reference: reference.clone(),
105 floating,
106 strategy,
107 })
108 }
109 ResetRects::Value(element_rects) => element_rects,
110 }
111 }
112
113 let Coords {
114 x: next_x,
115 y: next_y,
116 } = compute_coords_from_placement(&rects, stateful_placement, rtl);
117 x = next_x;
118 y = next_y;
119 }
120 }
121
122 i = 0;
123 continue;
124 }
125 }
126
127 i += 1;
128 }
129
130 ComputePositionReturn {
131 x,
132 y,
133 placement: stateful_placement,
134 strategy,
135 middleware_data,
136 }
137}
138
139#[cfg(test)]
140mod tests {
141 use serde_json::json;
142
143 use crate::test_utils::{FLOATING, PLATFORM, REFERENCE};
144 use crate::types::Middleware;
145
146 use super::*;
147
148 #[test]
149 fn test_returned_data() {
150 #[derive(Clone, PartialEq)]
151 struct CustomMiddleware {}
152
153 impl<Element: Clone + 'static, Window: Clone + 'static> Middleware<Element, Window>
154 for CustomMiddleware
155 {
156 fn name(&self) -> &'static str {
157 "custom"
158 }
159
160 fn compute(&self, _state: MiddlewareState<Element, Window>) -> MiddlewareReturn {
161 MiddlewareReturn {
162 x: None,
163 y: None,
164 data: Some(json!({"property": true})),
165 reset: None,
166 }
167 }
168 }
169
170 let ComputePositionReturn {
171 x,
172 y,
173 placement,
174 strategy,
175 middleware_data,
176 } = compute_position(
177 (&REFERENCE).into(),
178 &FLOATING,
179 ComputePositionConfig {
180 platform: &PLATFORM,
181 placement: Some(Placement::Top),
182 strategy: None,
183 middleware: Some(vec![Box::new(CustomMiddleware {})]),
184 },
185 );
186
187 assert_eq!(x, 25.0);
188 assert_eq!(y, -50.0);
189 assert_eq!(placement, Placement::Top);
190 assert_eq!(strategy, Strategy::Absolute);
191 assert_eq!(
192 middleware_data.get("custom"),
193 Some(&json!({"property": true}))
194 );
195 }
196
197 #[test]
198 fn test_middleware() {
199 #[derive(Clone, PartialEq)]
200 struct TestMiddleware {}
201
202 impl<Element: Clone + 'static, Window: Clone + 'static> Middleware<Element, Window>
203 for TestMiddleware
204 {
205 fn name(&self) -> &'static str {
206 "test"
207 }
208
209 fn compute(
210 &self,
211 MiddlewareState { x, y, .. }: MiddlewareState<Element, Window>,
212 ) -> MiddlewareReturn {
213 MiddlewareReturn {
214 x: Some(x + 1.0),
215 y: Some(y + 1.0),
216 data: None,
217 reset: None,
218 }
219 }
220 }
221
222 let ComputePositionReturn { x, y, .. } = compute_position(
223 (&REFERENCE).into(),
224 &FLOATING,
225 ComputePositionConfig {
226 platform: &PLATFORM,
227 placement: None,
228 strategy: None,
229 middleware: None,
230 },
231 );
232
233 let ComputePositionReturn { x: x2, y: y2, .. } = compute_position(
234 (&REFERENCE).into(),
235 &FLOATING,
236 ComputePositionConfig {
237 platform: &PLATFORM,
238 placement: None,
239 strategy: None,
240 middleware: Some(vec![Box::new(TestMiddleware {})]),
241 },
242 );
243
244 assert_eq!((x2, y2), (x + 1.0, y + 1.0));
245 }
246
247 #[test]
248 fn test_middleware_data() {
249 #[derive(Clone, PartialEq)]
250 struct TestMiddleware {}
251
252 impl<Element: Clone + 'static, Window: Clone + 'static> Middleware<Element, Window>
253 for TestMiddleware
254 {
255 fn name(&self) -> &'static str {
256 "test"
257 }
258
259 fn compute(&self, _state: MiddlewareState<Element, Window>) -> MiddlewareReturn {
260 MiddlewareReturn {
261 x: None,
262 y: None,
263 data: Some(json!({"hello": true})),
264 reset: None,
265 }
266 }
267 }
268
269 let ComputePositionReturn {
270 middleware_data, ..
271 } = compute_position(
272 (&REFERENCE).into(),
273 &FLOATING,
274 ComputePositionConfig {
275 platform: &PLATFORM,
276 placement: None,
277 strategy: None,
278 middleware: Some(vec![Box::new(TestMiddleware {})]),
279 },
280 );
281
282 assert_eq!(middleware_data.get("test"), Some(&json!({"hello": true})));
283 }
284}