use floating_ui_utils::{
clamp, get_alignment, get_alignment_axis, get_axis_length, get_padding_object, Axis, Coords,
OwnedElementOrWindow, Padding, Side,
};
use serde::{Deserialize, Serialize};
use crate::types::{
Derivable, DerivableFn, Middleware, MiddlewareReturn, MiddlewareState, MiddlewareWithOptions,
};
pub const ARROW_NAME: &str = "arrow";
#[derive(Clone, Debug)]
pub struct ArrowOptions<Element: Clone> {
pub element: Element,
pub padding: Option<Padding>,
}
impl<Element: Clone> ArrowOptions<Element> {
pub fn new(element: Element) -> Self {
ArrowOptions {
element,
padding: None,
}
}
pub fn element(mut self, value: Element) -> Self {
self.element = value;
self
}
pub fn padding(mut self, value: Padding) -> Self {
self.padding = Some(value);
self
}
}
#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
pub struct ArrowData {
pub x: Option<f64>,
pub y: Option<f64>,
pub center_offset: f64,
pub alignment_offset: Option<f64>,
}
pub struct Arrow<'a, Element: Clone, Window: Clone> {
options: Derivable<'a, Element, Window, ArrowOptions<Element>>,
}
impl<'a, Element: Clone, Window: Clone> Arrow<'a, Element, Window> {
pub fn new(options: ArrowOptions<Element>) -> Self {
Arrow {
options: options.into(),
}
}
pub fn new_derivable(options: Derivable<'a, Element, Window, ArrowOptions<Element>>) -> Self {
Arrow { options }
}
pub fn new_derivable_fn(
options: DerivableFn<'a, Element, Window, ArrowOptions<Element>>,
) -> Self {
Arrow {
options: options.into(),
}
}
}
impl<'a, Element: Clone, Window: Clone> Clone for Arrow<'a, Element, Window> {
fn clone(&self) -> Self {
Self {
options: self.options.clone(),
}
}
}
impl<'a, Element: Clone, Window: Clone> Middleware<Element, Window> for Arrow<'a, Element, Window> {
fn name(&self) -> &'static str {
ARROW_NAME
}
fn compute(&self, state: MiddlewareState<Element, Window>) -> MiddlewareReturn {
let options = self.options.evaluate(state.clone());
let MiddlewareState {
x,
y,
placement,
middleware_data,
elements,
rects,
platform,
..
} = state;
let data: Option<ArrowData> = middleware_data.get_as(self.name());
let padding_object = get_padding_object(options.padding.unwrap_or(Padding::All(0.0)));
let coords = Coords { x, y };
let axis = get_alignment_axis(placement);
let length = get_axis_length(axis);
let arrow_dimensions = platform.get_dimensions(&options.element);
let min_prop = match axis {
Axis::X => Side::Left,
Axis::Y => Side::Top,
};
let max_prop = match axis {
Axis::X => Side::Right,
Axis::Y => Side::Bottom,
};
let start_diff = coords.axis(axis) - rects.reference.axis(axis);
let end_diff = rects.reference.length(length) + rects.reference.axis(axis)
- coords.axis(axis)
- rects.floating.length(length);
let arrow_offset_parent = platform.get_offset_parent(&options.element);
let client_size = arrow_offset_parent
.and_then(|arrow_offset_parent| match arrow_offset_parent {
OwnedElementOrWindow::Element(element) => {
platform.get_client_length(&element, length)
}
OwnedElementOrWindow::Window(_) => {
platform.get_client_length(elements.floating, length)
}
})
.unwrap_or(rects.floating.length(length));
let center_to_reference = end_diff / 2.0 - start_diff / 2.0;
let largest_possible_padding =
client_size / 2.0 - arrow_dimensions.length(length) / 2.0 - 1.0;
let min_padding = padding_object.side(min_prop).min(largest_possible_padding);
let max_padding = padding_object.side(max_prop).min(largest_possible_padding);
let min = min_padding;
let max = client_size - arrow_dimensions.length(length) - max_padding;
let center =
client_size / 2.0 - arrow_dimensions.length(length) / 2.0 + center_to_reference;
let offset = clamp(min, center, max);
let should_add_offset = data.is_none()
&& get_alignment(placement).is_some()
&& center != offset
&& rects.reference.length(length) / 2.0
- (match center < min {
true => min_padding,
false => max_padding,
})
- arrow_dimensions.length(length) / 2.0
< 0.0;
let alignment_offset = match should_add_offset {
true => match center < min {
true => center - min,
false => center - max,
},
false => 0.0,
};
MiddlewareReturn {
x: match axis {
Axis::X => Some(coords.axis(axis) + alignment_offset),
Axis::Y => None,
},
y: match axis {
Axis::X => None,
Axis::Y => Some(coords.axis(axis) + alignment_offset),
},
data: Some(
serde_json::to_value(ArrowData {
x: match axis {
Axis::X => Some(offset),
Axis::Y => None,
},
y: match axis {
Axis::X => None,
Axis::Y => Some(offset),
},
center_offset: center - offset - alignment_offset,
alignment_offset: match should_add_offset {
true => Some(alignment_offset),
false => None,
},
})
.expect("Data should be valid JSON."),
),
reset: None,
}
}
}
impl<'a, Element: Clone, Window: Clone>
MiddlewareWithOptions<Element, Window, ArrowOptions<Element>> for Arrow<'a, Element, Window>
{
fn options(&self) -> &Derivable<Element, Window, ArrowOptions<Element>> {
&self.options
}
}