floating_ui_core/
compute_position.rs

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
9/// Computes the `x` and `y` coordinates that will place the floating element next to a given reference element.
10///
11/// This export does not have any `platform` interface logic. You will need to write one for the platform you are using Floating UI with.
12///
13/// See [`Platform`][`crate::types::Platform`].
14pub 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}