floating_ui_core/
detect_overflow.rs

1use floating_ui_utils::{
2    Coords, ElementOrVirtual, OwnedElementOrWindow, Padding, Rect, SideObject, get_padding_object,
3    rect_to_client_rect,
4};
5
6use crate::types::{
7    Boundary, ConvertOffsetParentRelativeRectToViewportRelativeRectArgs, ElementContext, Elements,
8    GetClippingRectArgs, MiddlewareState, RootBoundary,
9};
10
11/// Options for [`detect_overflow`].
12#[derive(Clone, Debug, PartialEq)]
13pub struct DetectOverflowOptions<Element> {
14    /// The clipping element(s) or area in which overflow will be checked.
15    ///
16    /// Defaults to [`Boundary::ClippingAncestors`].
17    pub boundary: Option<Boundary<Element>>,
18
19    /// The root clipping area in which overflow will be checked.
20    ///
21    /// Defaults to [`RootBoundary::Viewport`].
22    pub root_boundary: Option<RootBoundary>,
23
24    /// The element in which overflow is being checked relative to a boundary.
25    ///
26    /// Defaults to [`ElementContext::Floating`].
27    pub element_context: Option<ElementContext>,
28
29    /// Whether to check for overflow using the alternate element's boundary (only when [`boundary`][`Self::boundary`] is [`Boundary::ClippingAncestors`]).
30    ///
31    /// Defaults to `false`.
32    pub alt_boundary: Option<bool>,
33
34    /// Virtual padding for the resolved overflow detection offsets.
35    ///
36    /// Defaults to `0` on all sides.
37    pub padding: Option<Padding>,
38}
39
40impl<Element> DetectOverflowOptions<Element> {
41    /// Set `boundary` option.
42    pub fn boundary(mut self, value: Boundary<Element>) -> Self {
43        self.boundary = Some(value);
44        self
45    }
46
47    /// Set `root_boundary` option.
48    pub fn root_boundary(mut self, value: RootBoundary) -> Self {
49        self.root_boundary = Some(value);
50        self
51    }
52
53    /// Set `element_context` option.
54    pub fn element_context(mut self, value: ElementContext) -> Self {
55        self.element_context = Some(value);
56        self
57    }
58
59    /// Set `alt_boundary` option.
60    pub fn alt_boundary(mut self, value: bool) -> Self {
61        self.alt_boundary = Some(value);
62        self
63    }
64
65    /// Set `padding` option.
66    pub fn padding(mut self, value: Padding) -> Self {
67        self.padding = Some(value);
68        self
69    }
70}
71
72impl<Element> Default for DetectOverflowOptions<Element> {
73    fn default() -> Self {
74        Self {
75            boundary: Default::default(),
76            root_boundary: Default::default(),
77            element_context: Default::default(),
78            alt_boundary: Default::default(),
79            padding: Default::default(),
80        }
81    }
82}
83
84/// Resolves with an object of overflow side offsets that determine how much the element is overflowing a given clipping boundary on each side.
85/// - positive = overflowing the boundary by that number of pixels
86/// - negative = how many pixels left before it will overflow
87/// - `0` = lies flush with the boundary
88///
89/// See [the Rust Floating UI book](https://floating-ui.rustforweb.org/detect-overflow.html) for more documentation.
90pub fn detect_overflow<Element: Clone + 'static, Window: Clone + 'static>(
91    state: MiddlewareState<Element, Window>,
92    options: DetectOverflowOptions<Element>,
93) -> SideObject {
94    let MiddlewareState {
95        x,
96        y,
97        platform,
98        rects,
99        elements,
100        strategy,
101        ..
102    } = state;
103
104    let boundary = options.boundary.unwrap_or(Boundary::ClippingAncestors);
105    let root_boundary = options.root_boundary.unwrap_or(RootBoundary::Viewport);
106    let element_context = options.element_context.unwrap_or(ElementContext::Floating);
107    let alt_boundary = options.alt_boundary.unwrap_or(false);
108    let padding = options.padding.unwrap_or(Padding::All(0.0));
109
110    let padding_object = get_padding_object(padding);
111    let alt_context = match element_context {
112        ElementContext::Reference => ElementContext::Floating,
113        ElementContext::Floating => ElementContext::Reference,
114    };
115    let element = if alt_boundary {
116        elements.get_element_context(alt_context)
117    } else {
118        elements.get_element_context(element_context)
119    };
120
121    let document_element = platform.get_document_element(elements.floating);
122    let context_element: Option<Element>;
123
124    let element = match element {
125        ElementOrVirtual::Element(element) => element,
126        ElementOrVirtual::VirtualElement(virtual_element) => {
127            context_element = virtual_element.context_element();
128
129            context_element
130                .as_ref()
131                .or(document_element.as_ref())
132                .expect("Element should exist.")
133        }
134    };
135
136    let clipping_client_rect =
137        rect_to_client_rect(platform.get_clipping_rect(GetClippingRectArgs {
138            element,
139            boundary,
140            root_boundary,
141            strategy,
142        }));
143
144    let rect = match element_context {
145        ElementContext::Reference => rects.reference.clone(),
146        ElementContext::Floating => Rect {
147            x,
148            y,
149            width: rects.floating.width,
150            height: rects.floating.height,
151        },
152    };
153
154    let offset_parent = platform.get_offset_parent(elements.floating);
155    let offset_scale = match offset_parent.as_ref() {
156        Some(offset_parent) => match offset_parent {
157            OwnedElementOrWindow::Element(element) => {
158                platform.get_scale(element).unwrap_or(Coords::new(1.0))
159            }
160            OwnedElementOrWindow::Window(_) => Coords::new(1.0),
161        },
162        None => Coords::new(1.0),
163    };
164
165    let element_client_rect = rect_to_client_rect(
166        platform
167            .convert_offset_parent_relative_rect_to_viewport_relative_rect(
168                ConvertOffsetParentRelativeRectToViewportRelativeRectArgs {
169                    elements: Some(Elements {
170                        reference: elements.reference,
171                        floating: elements.floating,
172                    }),
173                    rect: rect.clone(),
174                    offset_parent: offset_parent
175                        .as_ref()
176                        .map(|offset_parent| offset_parent.into()),
177                    strategy,
178                },
179            )
180            .unwrap_or(rect),
181    );
182
183    SideObject {
184        top: (clipping_client_rect.top - element_client_rect.top + padding_object.top)
185            / offset_scale.y,
186        right: (element_client_rect.right - clipping_client_rect.right + padding_object.right)
187            / offset_scale.x,
188        bottom: (element_client_rect.bottom - clipping_client_rect.bottom + padding_object.bottom)
189            / offset_scale.y,
190        left: (clipping_client_rect.left - element_client_rect.left + padding_object.left)
191            / offset_scale.x,
192    }
193}