floating_ui_core/middleware/
inline.rs1use std::rc::Rc;
2
3use floating_ui_utils::{
4 Axis, ClientRectObject, Coords, DefaultVirtualElement, ElementOrVirtual, Padding, Rect, Side,
5 get_padding_object, get_side_axis, rect_to_client_rect,
6};
7
8use crate::types::{
9 Derivable, DerivableFn, GetElementRectsArgs, Middleware, MiddlewareReturn, MiddlewareState,
10 MiddlewareWithOptions, Reset, ResetRects, ResetValue,
11};
12
13fn get_bounding_rect(rects: Vec<ClientRectObject>) -> Rect {
14 let min_x = rects
15 .iter()
16 .map(|rect| rect.left)
17 .reduce(f64::min)
18 .unwrap_or(f64::INFINITY);
19 let min_y = rects
20 .iter()
21 .map(|rect| rect.top)
22 .reduce(f64::min)
23 .unwrap_or(f64::INFINITY);
24 let max_x = rects
25 .iter()
26 .map(|rect| rect.right)
27 .reduce(f64::max)
28 .unwrap_or(f64::NEG_INFINITY);
29 let max_y = rects
30 .iter()
31 .map(|rect| rect.bottom)
32 .reduce(f64::max)
33 .unwrap_or(f64::NEG_INFINITY);
34 Rect {
35 x: min_x,
36 y: min_y,
37 width: max_x - min_x,
38 height: max_y - min_y,
39 }
40}
41
42fn get_rects_by_line(rects: Vec<ClientRectObject>) -> Vec<ClientRectObject> {
43 let mut sorted_rects = rects.clone();
44 sorted_rects.sort_by(|a, b| a.y.total_cmp(&b.y));
45
46 let mut groups: Vec<Vec<ClientRectObject>> = vec![];
47 let mut prev_rect: Option<ClientRectObject> = None;
48 for rect in sorted_rects {
49 if prev_rect.is_none()
50 || prev_rect.is_some_and(|prev_rect| rect.y - prev_rect.y > prev_rect.height / 2.0)
51 {
52 groups.push(vec![rect.clone()]);
53 } else {
54 groups
55 .last_mut()
56 .expect("Last group should exist.")
57 .push(rect.clone());
58 }
59 prev_rect = Some(rect);
60 }
61
62 groups
63 .into_iter()
64 .map(|rects| rect_to_client_rect(get_bounding_rect(rects)))
65 .collect()
66}
67
68pub const INLINE_NAME: &str = "inline";
70
71#[derive(Clone, Debug, Default, PartialEq)]
73pub struct InlineOptions {
74 pub x: Option<f64>,
78
79 pub y: Option<f64>,
83
84 pub padding: Option<Padding>,
88}
89
90impl InlineOptions {
91 pub fn x(mut self, value: f64) -> Self {
93 self.x = Some(value);
94 self
95 }
96
97 pub fn y(mut self, value: f64) -> Self {
99 self.y = Some(value);
100 self
101 }
102
103 pub fn coords(mut self, value: Coords) -> Self {
105 self.x = Some(value.x);
106 self.y = Some(value.y);
107 self
108 }
109
110 pub fn padding(mut self, value: Padding) -> Self {
112 self.padding = Some(value);
113 self
114 }
115}
116
117#[derive(PartialEq)]
123pub struct Inline<'a, Element: Clone + 'static, Window: Clone> {
124 options: Derivable<'a, Element, Window, InlineOptions>,
125}
126
127impl<'a, Element: Clone + 'static, Window: Clone> Inline<'a, Element, Window> {
128 pub fn new(options: InlineOptions) -> Self {
130 Inline {
131 options: options.into(),
132 }
133 }
134
135 pub fn new_derivable(options: Derivable<'a, Element, Window, InlineOptions>) -> Self {
137 Inline { options }
138 }
139
140 pub fn new_derivable_fn(options: DerivableFn<'a, Element, Window, InlineOptions>) -> Self {
142 Inline {
143 options: options.into(),
144 }
145 }
146}
147
148impl<Element: Clone, Window: Clone> Clone for Inline<'_, Element, Window> {
149 fn clone(&self) -> Self {
150 Self {
151 options: self.options.clone(),
152 }
153 }
154}
155
156impl<Element: Clone + PartialEq + 'static, Window: Clone + PartialEq + 'static>
157 Middleware<Element, Window> for Inline<'static, Element, Window>
158{
159 fn name(&self) -> &'static str {
160 INLINE_NAME
161 }
162
163 fn compute(&self, state: MiddlewareState<Element, Window>) -> MiddlewareReturn {
164 let options = self.options.evaluate(state.clone());
165
166 let MiddlewareState {
167 placement,
168 strategy,
169 elements,
170 rects,
171 platform,
172 ..
173 } = state;
174
175 let padding = options.padding.unwrap_or(Padding::All(2.0));
178
179 let native_client_rects = platform
180 .get_client_rects(elements.reference)
181 .unwrap_or(vec![]);
182
183 let client_rects = get_rects_by_line(native_client_rects.clone());
184 let fallback = rect_to_client_rect(get_bounding_rect(native_client_rects));
185 let padding_object = get_padding_object(padding);
186
187 let get_bounding_client_rect = move || {
188 if client_rects.len() == 2
190 && client_rects[0].left > client_rects[1].right
191 && let Some(x) = options.x
192 && let Some(y) = options.y
193 {
194 return client_rects
195 .clone()
196 .into_iter()
197 .find(|rect| {
198 x > rect.left - padding_object.left
199 && x < rect.right + padding_object.right
200 && y > rect.top - padding_object.top
201 && rect.y < rect.bottom + padding_object.bottom
202 })
203 .unwrap_or(fallback.clone());
204 }
205
206 if client_rects.len() >= 2 {
208 if get_side_axis(placement) == Axis::Y {
209 let first_rect = client_rects.first().expect("Enough elements exist.");
210 let last_rect = client_rects.last().expect("Enough elements exist.");
211 let is_top = placement.side() == Side::Top;
212
213 let top = first_rect.top;
214 let bottom = last_rect.bottom;
215 let left = if is_top {
216 first_rect.left
217 } else {
218 last_rect.left
219 };
220 let right = if is_top {
221 first_rect.right
222 } else {
223 last_rect.right
224 };
225 let width = right - left;
226 let height = bottom - top;
227
228 return ClientRectObject {
229 x: left,
230 y: top,
231 width,
232 height,
233 top,
234 right,
235 bottom,
236 left,
237 };
238 }
239
240 let is_left_side = placement.side() == Side::Left;
241 let max_right = client_rects
242 .iter()
243 .map(|rect| rect.right)
244 .reduce(f64::max)
245 .expect("Enough elements exist.");
246 let min_left = client_rects
247 .iter()
248 .map(|rect| rect.left)
249 .reduce(f64::min)
250 .expect("Enough elements exist.");
251 let measure_rects: Vec<&ClientRectObject> = client_rects
252 .iter()
253 .filter(|rect| {
254 if is_left_side {
255 rect.left == min_left
256 } else {
257 rect.right == max_right
258 }
259 })
260 .collect();
261
262 let top = measure_rects.first().expect("Enough elements exist.").top;
263 let bottom = measure_rects.last().expect("Enough elements exist.").bottom;
264 let left = min_left;
265 let right = max_right;
266 let width = right - left;
267 let height = bottom - top;
268
269 return ClientRectObject {
270 x: left,
271 y: top,
272 width,
273 height,
274 top,
275 right,
276 bottom,
277 left,
278 };
279 }
280
281 fallback.clone()
282 };
283
284 let reset_rects = platform.get_element_rects(GetElementRectsArgs {
285 reference: ElementOrVirtual::VirtualElement(Box::new(DefaultVirtualElement::new(
286 Rc::new(get_bounding_client_rect),
287 ))),
288 floating: elements.floating,
289 strategy,
290 });
291
292 if rects.reference.x != reset_rects.reference.x
293 || rects.reference.y != reset_rects.reference.y
294 || rects.reference.width != reset_rects.reference.width
295 || rects.reference.height != reset_rects.reference.height
296 {
297 MiddlewareReturn {
298 x: None,
299 y: None,
300 data: None,
301 reset: Some(Reset::Value(ResetValue {
302 placement: None,
303 rects: Some(ResetRects::Value(reset_rects)),
304 })),
305 }
306 } else {
307 MiddlewareReturn {
308 x: None,
309 y: None,
310 data: None,
311 reset: None,
312 }
313 }
314 }
315}
316
317impl<Element: Clone, Window: Clone> MiddlewareWithOptions<Element, Window, InlineOptions>
318 for Inline<'_, Element, Window>
319{
320 fn options(&self) -> &Derivable<'_, Element, Window, InlineOptions> {
321 &self.options
322 }
323}