use floating_ui_utils::{
get_padding_object, get_side_axis, rect_to_client_rect, Axis, ClientRectObject, Coords,
DefaultVirtualElement, ElementOrVirtual, Padding, Rect, Side,
};
use crate::types::{
Derivable, DerivableFn, GetElementRectsArgs, Middleware, MiddlewareReturn, MiddlewareState,
MiddlewareWithOptions, Reset, ResetRects, ResetValue,
};
fn get_bounding_rect(rects: Vec<ClientRectObject>) -> Rect {
let min_x = rects
.iter()
.map(|rect| rect.left)
.reduce(f64::min)
.unwrap_or(f64::INFINITY);
let min_y = rects
.iter()
.map(|rect| rect.top)
.reduce(f64::min)
.unwrap_or(f64::INFINITY);
let max_x = rects
.iter()
.map(|rect| rect.right)
.reduce(f64::max)
.unwrap_or(f64::NEG_INFINITY);
let max_y = rects
.iter()
.map(|rect| rect.bottom)
.reduce(f64::max)
.unwrap_or(f64::NEG_INFINITY);
Rect {
x: min_x,
y: min_y,
width: max_x - min_x,
height: max_y - min_y,
}
}
fn get_rects_by_line(rects: Vec<ClientRectObject>) -> Vec<ClientRectObject> {
let mut sorted_rects = rects.clone();
sorted_rects.sort_by(|a, b| a.y.total_cmp(&b.y));
let mut groups: Vec<Vec<ClientRectObject>> = vec![];
let mut prev_rect: Option<ClientRectObject> = None;
for rect in sorted_rects {
if prev_rect.is_none()
|| prev_rect.is_some_and(|prev_rect| rect.y - prev_rect.y > prev_rect.height / 2.0)
{
groups.push(vec![rect.clone()]);
} else {
groups
.last_mut()
.expect("Last group should exist.")
.push(rect.clone());
}
prev_rect = Some(rect);
}
groups
.into_iter()
.map(|rects| rect_to_client_rect(get_bounding_rect(rects)))
.collect()
}
pub const INLINE_NAME: &str = "inline";
#[derive(Clone, Debug, Default)]
pub struct InlineOptions {
pub x: Option<f64>,
pub y: Option<f64>,
pub padding: Option<Padding>,
}
impl InlineOptions {
pub fn x(mut self, value: f64) -> Self {
self.x = Some(value);
self
}
pub fn y(mut self, value: f64) -> Self {
self.y = Some(value);
self
}
pub fn coords(mut self, value: Coords) -> Self {
self.x = Some(value.x);
self.y = Some(value.y);
self
}
pub fn padding(mut self, value: Padding) -> Self {
self.padding = Some(value);
self
}
}
pub struct Inline<'a, Element: Clone, Window: Clone> {
options: Derivable<'a, Element, Window, InlineOptions>,
}
impl<'a, Element: Clone, Window: Clone> Inline<'a, Element, Window> {
pub fn new(options: InlineOptions) -> Self {
Inline {
options: options.into(),
}
}
pub fn new_derivable(options: Derivable<'a, Element, Window, InlineOptions>) -> Self {
Inline { options }
}
pub fn new_derivable_fn(options: DerivableFn<'a, Element, Window, InlineOptions>) -> Self {
Inline {
options: options.into(),
}
}
}
impl<'a, Element: Clone, Window: Clone> Clone for Inline<'a, Element, Window> {
fn clone(&self) -> Self {
Self {
options: self.options.clone(),
}
}
}
impl<'a, Element: Clone + 'static, Window: Clone> Middleware<Element, Window>
for Inline<'a, Element, Window>
{
fn name(&self) -> &'static str {
INLINE_NAME
}
fn compute(&self, state: MiddlewareState<Element, Window>) -> MiddlewareReturn {
let options = self.options.evaluate(state.clone());
let MiddlewareState {
placement,
strategy,
elements,
rects,
platform,
..
} = state;
let padding = options.padding.unwrap_or(Padding::All(2.0));
let native_client_rects = platform
.get_client_rects(elements.reference)
.unwrap_or(vec![]);
let client_rects = get_rects_by_line(native_client_rects.clone());
let fallback = rect_to_client_rect(get_bounding_rect(native_client_rects));
let padding_object = get_padding_object(padding);
let get_bounding_client_rect = move || {
if client_rects.len() == 2 && client_rects[0].left > client_rects[1].right {
if let Some(x) = options.x {
if let Some(y) = options.y {
return client_rects
.clone()
.into_iter()
.find(|rect| {
x > rect.left - padding_object.left
&& x < rect.right + padding_object.right
&& y > rect.top - padding_object.top
&& rect.y < rect.bottom + padding_object.bottom
})
.unwrap_or(fallback.clone());
}
}
}
if client_rects.len() >= 2 {
if get_side_axis(placement) == Axis::Y {
let first_rect = client_rects.first().expect("Enough elements exist.");
let last_rect = client_rects.last().expect("Enough elements exist.");
let is_top = placement.side() == Side::Top;
let top = first_rect.top;
let bottom = last_rect.bottom;
let left = match is_top {
true => first_rect.left,
false => last_rect.left,
};
let right = match is_top {
true => first_rect.right,
false => last_rect.right,
};
let width = right - left;
let height = bottom - top;
return ClientRectObject {
x: left,
y: top,
width,
height,
top,
right,
bottom,
left,
};
}
let is_left_side = placement.side() == Side::Left;
let max_right = client_rects
.iter()
.map(|rect| rect.right)
.reduce(f64::max)
.expect("Enough elements exist.");
let min_left = client_rects
.iter()
.map(|rect| rect.left)
.reduce(f64::min)
.expect("Enough elements exist.");
let measure_rects: Vec<&ClientRectObject> = client_rects
.iter()
.filter(|rect| match is_left_side {
true => rect.left == min_left,
false => rect.right == max_right,
})
.collect();
let top = measure_rects.first().expect("Enough elements exist.").top;
let bottom = measure_rects.last().expect("Enough elements exist.").bottom;
let left = min_left;
let right = max_right;
let width = right - left;
let height = bottom - top;
return ClientRectObject {
x: left,
y: top,
width,
height,
top,
right,
bottom,
left,
};
}
fallback.clone()
};
let reset_rects = platform.get_element_rects(GetElementRectsArgs {
reference: ElementOrVirtual::VirtualElement(Box::new(DefaultVirtualElement::new(
Box::new(get_bounding_client_rect),
))),
floating: elements.floating,
strategy,
});
if rects.reference.x != reset_rects.reference.x
|| rects.reference.y != reset_rects.reference.y
|| rects.reference.width != reset_rects.reference.width
|| rects.reference.height != reset_rects.reference.height
{
MiddlewareReturn {
x: None,
y: None,
data: None,
reset: Some(Reset::Value(ResetValue {
placement: None,
rects: Some(ResetRects::Value(reset_rects)),
})),
}
} else {
MiddlewareReturn {
x: None,
y: None,
data: None,
reset: None,
}
}
}
}
impl<'a, Element: Clone, Window: Clone> MiddlewareWithOptions<Element, Window, InlineOptions>
for Inline<'a, Element, Window>
{
fn options(&self) -> &Derivable<Element, Window, InlineOptions> {
&self.options
}
}