floating_ui_core/middleware/
flip.rs1use floating_ui_utils::{
2 Alignment, Axis, Placement, get_alignment, get_alignment_sides, get_expanded_placements,
3 get_opposite_axis_placements, get_opposite_placement, get_side, get_side_axis,
4};
5use serde::{Deserialize, Serialize};
6
7use crate::{
8 detect_overflow::{DetectOverflowOptions, detect_overflow},
9 middleware::arrow::{ARROW_NAME, ArrowData},
10 types::{
11 Derivable, DerivableFn, Middleware, MiddlewareReturn, MiddlewareState,
12 MiddlewareWithOptions, Reset, ResetValue,
13 },
14};
15
16pub const FLIP_NAME: &str = "flip";
18
19#[derive(Copy, Clone, Debug, PartialEq)]
21pub enum CrossAxis {
22 True,
24 False,
26 Alignment,
28}
29
30#[derive(Copy, Clone, Debug, Default, PartialEq)]
32pub enum FallbackStrategy {
33 #[default]
34 BestFit,
35 InitialPlacement,
36}
37
38#[derive(Clone, Debug, PartialEq)]
40pub struct FlipOptions<Element: Clone> {
41 pub detect_overflow: Option<DetectOverflowOptions<Element>>,
45
46 pub main_axis: Option<bool>,
50
51 pub cross_axis: Option<CrossAxis>,
58
59 pub fallback_placements: Option<Vec<Placement>>,
63
64 pub fallback_strategy: Option<FallbackStrategy>,
68
69 pub fallback_axis_side_direction: Option<Alignment>,
73
74 pub flip_alignment: Option<bool>,
78}
79
80impl<Element: Clone> FlipOptions<Element> {
81 pub fn detect_overflow(mut self, value: DetectOverflowOptions<Element>) -> Self {
83 self.detect_overflow = Some(value);
84 self
85 }
86
87 pub fn main_axis(mut self, value: bool) -> Self {
89 self.main_axis = Some(value);
90 self
91 }
92
93 pub fn cross_axis(mut self, value: CrossAxis) -> Self {
95 self.cross_axis = Some(value);
96 self
97 }
98
99 pub fn fallback_placements(mut self, value: Vec<Placement>) -> Self {
101 self.fallback_placements = Some(value);
102 self
103 }
104
105 pub fn fallback_strategy(mut self, value: FallbackStrategy) -> Self {
107 self.fallback_strategy = Some(value);
108 self
109 }
110
111 pub fn fallback_axis_side_direction(mut self, value: Alignment) -> Self {
113 self.fallback_axis_side_direction = Some(value);
114 self
115 }
116
117 pub fn flip_alignment(mut self, value: bool) -> Self {
119 self.flip_alignment = Some(value);
120 self
121 }
122}
123
124impl<Element: Clone> Default for FlipOptions<Element> {
125 fn default() -> Self {
126 Self {
127 detect_overflow: Default::default(),
128 main_axis: Default::default(),
129 cross_axis: Default::default(),
130 fallback_placements: Default::default(),
131 fallback_strategy: Default::default(),
132 fallback_axis_side_direction: Default::default(),
133 flip_alignment: Default::default(),
134 }
135 }
136}
137
138#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
140pub struct FlipDataOverflow {
141 pub placement: Placement,
142 pub overflows: Vec<f64>,
143}
144
145#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
147pub struct FlipData {
148 pub index: usize,
149 pub overflows: Vec<FlipDataOverflow>,
150}
151
152#[derive(PartialEq)]
159pub struct Flip<'a, Element: Clone + 'static, Window: Clone> {
160 options: Derivable<'a, Element, Window, FlipOptions<Element>>,
161}
162
163impl<'a, Element: Clone + 'static, Window: Clone> Flip<'a, Element, Window> {
164 pub fn new(options: FlipOptions<Element>) -> Self {
166 Flip {
167 options: options.into(),
168 }
169 }
170
171 pub fn new_derivable(options: Derivable<'a, Element, Window, FlipOptions<Element>>) -> Self {
173 Flip { options }
174 }
175
176 pub fn new_derivable_fn(
178 options: DerivableFn<'a, Element, Window, FlipOptions<Element>>,
179 ) -> Self {
180 Flip {
181 options: options.into(),
182 }
183 }
184}
185
186impl<Element: Clone + 'static, Window: Clone> Clone for Flip<'_, Element, Window> {
187 fn clone(&self) -> Self {
188 Self {
189 options: self.options.clone(),
190 }
191 }
192}
193
194impl<Element: Clone + PartialEq, Window: Clone + PartialEq> Middleware<Element, Window>
195 for Flip<'static, Element, Window>
196{
197 fn name(&self) -> &'static str {
198 FLIP_NAME
199 }
200
201 fn compute(&self, state: MiddlewareState<Element, Window>) -> MiddlewareReturn {
202 let options = self.options.evaluate(state.clone());
203
204 let MiddlewareState {
205 placement,
206 initial_placement,
207 middleware_data,
208 elements,
209 rects,
210 platform,
211 ..
212 } = state;
213
214 let data: FlipData = middleware_data.get_as(self.name()).unwrap_or(FlipData {
215 index: 0,
216 overflows: vec![],
217 });
218
219 let check_main_axis = options.main_axis.unwrap_or(true);
220 let check_cross_axis = options.cross_axis.unwrap_or(CrossAxis::True);
221 let specified_fallback_placements = options.fallback_placements.clone();
222 let fallback_strategy = options.fallback_strategy.unwrap_or_default();
223 let fallback_axis_side_direction = options.fallback_axis_side_direction;
224 let flip_alignment = options.flip_alignment.unwrap_or(true);
225
226 let arrow_data: Option<ArrowData> = middleware_data.get_as(ARROW_NAME);
229 if arrow_data
230 .and_then(|arrow_data| arrow_data.alignment_offset)
231 .is_some()
232 {
233 return MiddlewareReturn {
234 x: None,
235 y: None,
236 data: None,
237 reset: None,
238 };
239 }
240
241 let side = get_side(placement);
242 let initial_side_axis = get_side_axis(initial_placement);
243 let is_base_placement = get_alignment(initial_placement).is_none();
244 let rtl = platform.is_rtl(elements.floating);
245
246 let has_specified_fallback_placements = specified_fallback_placements.is_some();
247 let mut placements =
248 specified_fallback_placements.unwrap_or(if is_base_placement || !flip_alignment {
249 vec![get_opposite_placement(initial_placement)]
250 } else {
251 get_expanded_placements(initial_placement)
252 });
253
254 let has_fallback_axis_side_direction = fallback_axis_side_direction.is_some();
255
256 if !has_specified_fallback_placements && has_fallback_axis_side_direction {
257 placements.append(&mut get_opposite_axis_placements(
258 initial_placement,
259 flip_alignment,
260 fallback_axis_side_direction,
261 rtl,
262 ));
263 }
264
265 placements.insert(0, initial_placement);
266
267 let overflow = detect_overflow(
268 MiddlewareState {
269 elements: elements.clone(),
270 ..state
271 },
272 options.detect_overflow.unwrap_or_default(),
273 );
274
275 let mut overflows: Vec<f64> = Vec::new();
276 let mut overflows_data = data.overflows;
277
278 if check_main_axis {
279 overflows.push(overflow.side(side));
280 }
281 if check_cross_axis == CrossAxis::True || check_cross_axis == CrossAxis::Alignment {
282 let sides = get_alignment_sides(placement, rects, rtl);
283 overflows.push(overflow.side(sides.0));
284 overflows.push(overflow.side(sides.1));
285 }
286
287 overflows_data.push(FlipDataOverflow {
288 placement,
289 overflows: overflows.clone(),
290 });
291
292 if !overflows.into_iter().all(|side| side <= 0.0) {
294 let next_index = data.index + 1;
295 let next_placement = placements.get(next_index);
296
297 if let Some(next_placement) = next_placement {
298 let ignore_cross_axis_overflow = if check_cross_axis == CrossAxis::Alignment {
299 initial_side_axis != get_side_axis(*next_placement)
300 } else {
301 false
302 };
303
304 if !ignore_cross_axis_overflow ||
305 overflows_data.iter().all(|d| {
307 if get_side_axis(d.placement) == initial_side_axis {
308 d.overflows.first().is_some_and(|overflow| *overflow > 0.0)
309 } else {
310 true
311 }
312 })
313 {
314 return MiddlewareReturn {
316 x: None,
317 y: None,
318 data: Some(
319 serde_json::to_value(FlipData {
320 index: next_index,
321 overflows: overflows_data,
322 })
323 .expect("Data should be valid JSON."),
324 ),
325 reset: Some(Reset::Value(ResetValue {
326 placement: Some(*next_placement),
327 rects: None,
328 })),
329 };
330 }
331 }
332
333 let mut reset_placement: Vec<&FlipDataOverflow> = overflows_data
335 .iter()
336 .filter(|overflow| overflow.overflows[0] <= 0.0)
337 .collect();
338 reset_placement.sort_by(|a, b| a.overflows[1].total_cmp(&b.overflows[1]));
339
340 let mut reset_placement = reset_placement.first().map(|overflow| overflow.placement);
341
342 if reset_placement.is_none() {
344 match fallback_strategy {
345 FallbackStrategy::BestFit => {
346 let mut placement: Vec<(Placement, f64)> = overflows_data
347 .into_iter()
348 .filter(|overflow| {
349 if has_fallback_axis_side_direction {
350 let current_side_axis = get_side_axis(overflow.placement);
351
352 current_side_axis == initial_side_axis
354 || current_side_axis == Axis::Y
355 } else {
356 true
357 }
358 })
359 .map(|overflow| {
360 (
361 overflow.placement,
362 overflow
363 .overflows
364 .into_iter()
365 .filter(|overflow| *overflow > 0.0)
366 .sum::<f64>(),
367 )
368 })
369 .collect();
370 placement.sort_by(|a, b| a.1.total_cmp(&b.1));
371
372 let placement = placement.first().map(|v| v.0);
373 if placement.is_some() {
374 reset_placement = placement;
375 }
376 }
377 FallbackStrategy::InitialPlacement => {
378 reset_placement = Some(initial_placement);
379 }
380 }
381 }
382
383 if placement != reset_placement.expect("Reset placement is not none.") {
384 return MiddlewareReturn {
385 x: None,
386 y: None,
387 data: None,
388 reset: Some(Reset::Value(ResetValue {
389 placement: reset_placement,
390 rects: None,
391 })),
392 };
393 }
394 }
395
396 MiddlewareReturn {
397 x: None,
398 y: None,
399 data: None,
400 reset: None,
401 }
402 }
403}
404
405impl<Element: Clone, Window: Clone> MiddlewareWithOptions<Element, Window, FlipOptions<Element>>
406 for Flip<'_, Element, Window>
407{
408 fn options(&self) -> &Derivable<'_, Element, Window, FlipOptions<Element>> {
409 &self.options
410 }
411}