use floating_ui_utils::{
get_alignment, get_alignment_sides, get_opposite_alignment_placement, get_side, Alignment,
Placement, ALL_PLACEMENTS,
};
use serde::{Deserialize, Serialize};
use crate::{
detect_overflow::{detect_overflow, DetectOverflowOptions},
types::{
Derivable, DerivableFn, Middleware, MiddlewareReturn, MiddlewareState,
MiddlewareWithOptions, Reset, ResetValue,
},
};
fn get_placement_list(
alignment: Option<Alignment>,
auto_alignment: bool,
allowed_placements: Vec<Placement>,
) -> Vec<Placement> {
let allowed_placements_sorted_by_alignment: Vec<Placement> = match alignment {
Some(alignment) => {
let mut list = vec![];
list.append(
&mut allowed_placements
.clone()
.into_iter()
.filter(|placement| get_alignment(*placement) == Some(alignment))
.collect(),
);
list.append(
&mut allowed_placements
.clone()
.into_iter()
.filter(|placement| get_alignment(*placement) != Some(alignment))
.collect(),
);
list
}
None => allowed_placements
.into_iter()
.filter(|placement| get_alignment(*placement).is_none())
.collect(),
};
allowed_placements_sorted_by_alignment
.into_iter()
.filter(|placement| match alignment {
Some(alignment) => {
get_alignment(*placement) == Some(alignment)
|| (match auto_alignment {
true => get_opposite_alignment_placement(*placement) != *placement,
false => false,
})
}
None => true,
})
.collect()
}
pub const AUTO_PLACEMENT_NAME: &str = "autoPlacement";
#[derive(Clone, Debug)]
pub struct AutoPlacementOptions<Element: Clone> {
pub detect_overflow: Option<DetectOverflowOptions<Element>>,
pub cross_axis: Option<bool>,
pub alignment: Option<Alignment>,
pub auto_alignment: Option<bool>,
pub allowed_placements: Option<Vec<Placement>>,
}
impl<Element: Clone> AutoPlacementOptions<Element> {
pub fn detect_overflow(mut self, value: DetectOverflowOptions<Element>) -> Self {
self.detect_overflow = Some(value);
self
}
pub fn cross_axis(mut self, value: bool) -> Self {
self.cross_axis = Some(value);
self
}
pub fn alignment(mut self, value: Alignment) -> Self {
self.alignment = Some(value);
self
}
pub fn auto_alignment(mut self, value: bool) -> Self {
self.auto_alignment = Some(value);
self
}
pub fn allowed_placements(mut self, value: Vec<Placement>) -> Self {
self.allowed_placements = Some(value);
self
}
}
impl<Element: Clone> Default for AutoPlacementOptions<Element> {
fn default() -> Self {
Self {
detect_overflow: Default::default(),
cross_axis: Default::default(),
alignment: Default::default(),
auto_alignment: Default::default(),
allowed_placements: Default::default(),
}
}
}
#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
pub struct AutoPlacementDataOverflow {
pub placement: Placement,
pub overflows: Vec<f64>,
}
#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
pub struct AutoPlacementData {
pub index: usize,
pub overflows: Vec<AutoPlacementDataOverflow>,
}
pub struct AutoPlacement<'a, Element: Clone, Window: Clone> {
options: Derivable<'a, Element, Window, AutoPlacementOptions<Element>>,
}
impl<'a, Element: Clone, Window: Clone> Clone for AutoPlacement<'a, Element, Window> {
fn clone(&self) -> Self {
Self {
options: self.options.clone(),
}
}
}
impl<'a, Element: Clone, Window: Clone> AutoPlacement<'a, Element, Window> {
pub fn new(options: AutoPlacementOptions<Element>) -> Self {
AutoPlacement {
options: options.into(),
}
}
pub fn new_derivable(
options: Derivable<'a, Element, Window, AutoPlacementOptions<Element>>,
) -> Self {
AutoPlacement { options }
}
pub fn new_derivable_fn(
options: DerivableFn<'a, Element, Window, AutoPlacementOptions<Element>>,
) -> Self {
AutoPlacement {
options: options.into(),
}
}
}
impl<'a, Element: Clone, Window: Clone> Middleware<Element, Window>
for AutoPlacement<'a, Element, Window>
{
fn name(&self) -> &'static str {
AUTO_PLACEMENT_NAME
}
fn compute(&self, state: MiddlewareState<Element, Window>) -> MiddlewareReturn {
let options = self.options.evaluate(state.clone());
let MiddlewareState {
rects,
middleware_data,
placement,
platform,
elements,
..
} = state;
let data: AutoPlacementData =
middleware_data
.get_as(self.name())
.unwrap_or(AutoPlacementData {
index: 0,
overflows: vec![],
});
let cross_axis = options.cross_axis.unwrap_or(false);
let alignment = options.alignment;
let has_allowed_placements = options.allowed_placements.is_some();
let allowed_placements = options
.allowed_placements
.unwrap_or(Vec::from(ALL_PLACEMENTS));
let auto_alignment = options.auto_alignment.unwrap_or(true);
let placements = match alignment.is_some() || !has_allowed_placements {
true => get_placement_list(alignment, auto_alignment, allowed_placements),
false => allowed_placements,
};
let overflow = detect_overflow(
MiddlewareState {
elements: elements.clone(),
..state
},
options.detect_overflow.unwrap_or_default(),
);
let current_index = data.index;
let current_placement = placements.get(current_index);
if let Some(current_placement) = current_placement {
let current_placement = *current_placement;
let alignment_sides =
get_alignment_sides(current_placement, rects, platform.is_rtl(elements.floating));
if placement != current_placement {
return MiddlewareReturn {
x: None,
y: None,
data: None,
reset: Some(Reset::Value(ResetValue {
placement: Some(placements[0]),
rects: None,
})),
};
}
let current_overflows = vec![
overflow.side(get_side(current_placement)),
overflow.side(alignment_sides.0),
overflow.side(alignment_sides.1),
];
let mut all_overflows = data.overflows.clone();
all_overflows.push(AutoPlacementDataOverflow {
placement,
overflows: current_overflows,
});
let next_placement = placements.get(current_index + 1);
if let Some(next_placement) = next_placement {
return MiddlewareReturn {
x: None,
y: None,
data: Some(
serde_json::to_value(AutoPlacementData {
index: current_index + 1,
overflows: all_overflows.clone(),
})
.expect("Data should be valid JSON."),
),
reset: Some(Reset::Value(ResetValue {
placement: Some(*next_placement),
rects: None,
})),
};
}
let mut placements_sorted_by_most_space: Vec<_> = all_overflows
.clone()
.into_iter()
.map(|overflow| {
let alignment = get_alignment(overflow.placement);
(
overflow.placement,
match alignment.is_some() && cross_axis {
true => overflow.overflows[0..2].iter().sum(),
false => overflow.overflows[0],
},
overflow.overflows,
)
})
.collect();
placements_sorted_by_most_space.sort_by(|a, b| a.1.total_cmp(&b.1));
let placements_that_fit_on_each_side: Vec<_> = placements_sorted_by_most_space
.clone()
.into_iter()
.filter(|overflow| {
overflow.2[0..match get_alignment(overflow.0) {
Some(_) => 2,
None => 3,
}]
.iter()
.all(|v| *v <= 0.0)
})
.collect();
let reset_placement = placements_that_fit_on_each_side
.first()
.map(|v| v.0)
.unwrap_or(placements_sorted_by_most_space[0].0);
if reset_placement != placement {
return MiddlewareReturn {
x: None,
y: None,
data: Some(
serde_json::to_value(AutoPlacementData {
index: current_index + 1,
overflows: all_overflows,
})
.expect("Data should be valid JSON."),
),
reset: Some(Reset::Value(ResetValue {
placement: Some(reset_placement),
rects: None,
})),
};
}
}
MiddlewareReturn {
x: None,
y: None,
data: None,
reset: None,
}
}
}
impl<'a, Element: Clone, Window: Clone>
MiddlewareWithOptions<Element, Window, AutoPlacementOptions<Element>>
for AutoPlacement<'a, Element, Window>
{
fn options(&self) -> &Derivable<Element, Window, AutoPlacementOptions<Element>> {
&self.options
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_base_placement() {
assert_eq!(
get_placement_list(
None,
false,
vec![
Placement::Top,
Placement::Bottom,
Placement::Left,
Placement::Right,
Placement::TopStart,
Placement::RightEnd,
]
),
vec![
Placement::Top,
Placement::Bottom,
Placement::Left,
Placement::Right,
]
)
}
#[test]
fn test_start_alignment_without_auto_alignment() {
assert_eq!(
get_placement_list(
Some(Alignment::Start),
false,
vec![
Placement::Top,
Placement::Bottom,
Placement::Left,
Placement::Right,
Placement::TopStart,
Placement::RightEnd,
Placement::LeftStart,
]
),
vec![Placement::TopStart, Placement::LeftStart]
)
}
#[test]
fn test_start_alignment_with_auto_alignment() {
assert_eq!(
get_placement_list(
Some(Alignment::Start),
true,
vec![
Placement::Top,
Placement::Bottom,
Placement::Left,
Placement::Right,
Placement::TopStart,
Placement::RightEnd,
Placement::LeftStart,
]
),
vec![
Placement::TopStart,
Placement::LeftStart,
Placement::RightEnd,
]
)
}
#[test]
fn test_end_alignment_without_auto_alignment() {
assert_eq!(
get_placement_list(
Some(Alignment::End),
false,
vec![
Placement::Top,
Placement::Bottom,
Placement::Left,
Placement::Right,
Placement::TopStart,
Placement::RightEnd,
Placement::LeftStart,
]
),
vec![Placement::RightEnd,]
)
}
#[test]
fn test_end_alignment_with_auto_alignment() {
assert_eq!(
get_placement_list(
Some(Alignment::End),
true,
vec![
Placement::Top,
Placement::Bottom,
Placement::Left,
Placement::Right,
Placement::TopStart,
Placement::RightEnd,
Placement::LeftStart,
]
),
vec![
Placement::RightEnd,
Placement::TopStart,
Placement::LeftStart
]
)
}
}