use floating_ui_utils::{
get_alignment, get_side, get_side_axis, Alignment, Axis, Coords, Placement, Side,
};
use serde::{Deserialize, Serialize};
use crate::{
middleware::{ArrowData, ARROW_NAME},
types::{
Derivable, DerivableFn, Middleware, MiddlewareReturn, MiddlewareState,
MiddlewareWithOptions,
},
};
fn convert_value_to_coords<Element: Clone, Window: Clone>(
state: MiddlewareState<Element, Window>,
options: &OffsetOptions,
) -> Coords {
let MiddlewareState {
placement,
platform,
elements,
..
} = state;
let rtl = platform.is_rtl(elements.floating).unwrap_or(false);
let side = get_side(placement);
let alignment = get_alignment(placement);
let is_vertical = get_side_axis(placement) == Axis::Y;
let main_axis_multi = match side {
Side::Left | Side::Top => -1.0,
Side::Right | Side::Bottom => 1.0,
};
let cross_axis_multi = match rtl && is_vertical {
true => -1.0,
false => 1.0,
};
let (main_axis, mut cross_axis, alignment_axis): (f64, f64, Option<f64>) = match options {
OffsetOptions::Value(value) => (*value, 0.0, None),
OffsetOptions::Values(values) => (
values.main_axis.unwrap_or(0.0),
values.cross_axis.unwrap_or(0.0),
values.alignment_axis,
),
};
if let Some(alignment) = alignment {
if let Some(alignment_axis) = alignment_axis {
cross_axis = match alignment {
Alignment::Start => alignment_axis,
Alignment::End => alignment_axis * -1.0,
};
}
}
match is_vertical {
true => Coords {
x: cross_axis * cross_axis_multi,
y: main_axis * main_axis_multi,
},
false => Coords {
x: main_axis * main_axis_multi,
y: cross_axis * cross_axis_multi,
},
}
}
pub const OFFSET_NAME: &str = "offset";
#[derive(Clone, Default, Debug)]
pub struct OffsetOptionsValues {
pub main_axis: Option<f64>,
pub cross_axis: Option<f64>,
pub alignment_axis: Option<f64>,
}
impl OffsetOptionsValues {
pub fn main_axis(mut self, value: f64) -> Self {
self.main_axis = Some(value);
self
}
pub fn cross_axis(mut self, value: f64) -> Self {
self.cross_axis = Some(value);
self
}
pub fn alignment_axis(mut self, value: f64) -> Self {
self.alignment_axis = Some(value);
self
}
}
#[derive(Clone, Debug)]
pub enum OffsetOptions {
Value(f64),
Values(OffsetOptionsValues),
}
impl Default for OffsetOptions {
fn default() -> Self {
OffsetOptions::Value(0.0)
}
}
#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
pub struct OffsetData {
pub diff_coords: Coords,
pub placement: Placement,
}
pub struct Offset<'a, Element: Clone, Window: Clone> {
options: Derivable<'a, Element, Window, OffsetOptions>,
}
impl<'a, Element: Clone, Window: Clone> Offset<'a, Element, Window> {
pub fn new(options: OffsetOptions) -> Self {
Offset {
options: options.into(),
}
}
pub fn new_derivable(options: Derivable<'a, Element, Window, OffsetOptions>) -> Self {
Offset { options }
}
pub fn new_derivable_fn(options: DerivableFn<'a, Element, Window, OffsetOptions>) -> Self {
Offset {
options: options.into(),
}
}
}
impl<'a, Element: Clone, Window: Clone> Clone for Offset<'a, Element, Window> {
fn clone(&self) -> Self {
Self {
options: self.options.clone(),
}
}
}
impl<'a, Element: Clone, Window: Clone> Middleware<Element, Window>
for Offset<'a, Element, Window>
{
fn name(&self) -> &'static str {
OFFSET_NAME
}
fn compute(&self, state: MiddlewareState<Element, Window>) -> MiddlewareReturn {
let options = self.options.evaluate(state.clone());
let MiddlewareState {
x,
y,
placement,
middleware_data,
..
} = state;
let data: Option<OffsetData> = middleware_data.get_as(self.name());
let diff_coords = convert_value_to_coords(state, &options);
if let Some(data_placement) = data.map(|data| data.placement) {
if placement == data_placement {
let arrow_data: Option<ArrowData> = middleware_data.get_as(ARROW_NAME);
if arrow_data.map_or(false, |arrow_data| arrow_data.alignment_offset.is_some()) {
return MiddlewareReturn {
x: None,
y: None,
data: None,
reset: None,
};
}
}
}
MiddlewareReturn {
x: Some(x + diff_coords.x),
y: Some(y + diff_coords.y),
data: Some(
serde_json::to_value(OffsetData {
diff_coords,
placement,
})
.expect("Data should be valid JSON."),
),
reset: None,
}
}
}
impl<'a, Element: Clone, Window: Clone> MiddlewareWithOptions<Element, Window, OffsetOptions>
for Offset<'a, Element, Window>
{
fn options(&self) -> &Derivable<Element, Window, OffsetOptions> {
&self.options
}
}