use floating_ui_utils::{get_side_axis, Alignment, Axis, Rect, Side};
use crate::{
detect_overflow::{detect_overflow, DetectOverflowOptions},
types::{
Derivable, DerivableFn, Middleware, MiddlewareReturn, MiddlewareState,
MiddlewareWithOptions, ResetRects, ResetValue,
},
};
use super::SHIFT_NAME;
pub const SIZE_NAME: &str = "size";
#[derive(Clone)]
pub struct ApplyState<'a, Element: Clone, Window: Clone> {
pub state: MiddlewareState<'a, Element, Window>,
pub available_width: f64,
pub available_height: f64,
}
pub type ApplyFn<Element, Window> = dyn Fn(ApplyState<Element, Window>);
#[derive(Clone)]
pub struct SizeOptions<'a, Element: Clone, Window: Clone> {
pub detect_overflow: Option<DetectOverflowOptions<Element>>,
pub apply: Option<&'a ApplyFn<Element, Window>>,
}
impl<'a, Element: Clone, Window: Clone> SizeOptions<'a, Element, Window> {
pub fn new() -> Self {
SizeOptions {
detect_overflow: None,
apply: None,
}
}
pub fn detect_overflow(mut self, value: DetectOverflowOptions<Element>) -> Self {
self.detect_overflow = Some(value);
self
}
pub fn apply(mut self, value: &'a ApplyFn<Element, Window>) -> Self {
self.apply = Some(value);
self
}
}
impl<'a, Element: Clone, Window: Clone> Default for SizeOptions<'a, Element, Window> {
fn default() -> Self {
Self {
detect_overflow: Default::default(),
apply: Default::default(),
}
}
}
pub struct Size<'a, Element: Clone, Window: Clone> {
options: Derivable<'a, Element, Window, SizeOptions<'a, Element, Window>>,
}
impl<'a, Element: Clone, Window: Clone> Size<'a, Element, Window> {
pub fn new(options: SizeOptions<'a, Element, Window>) -> Self {
Size {
options: options.into(),
}
}
pub fn new_derivable(
options: Derivable<'a, Element, Window, SizeOptions<'a, Element, Window>>,
) -> Self {
Size { options }
}
pub fn new_derivable_fn(
options: DerivableFn<'a, Element, Window, SizeOptions<'a, Element, Window>>,
) -> Self {
Size {
options: options.into(),
}
}
}
impl<'a, Element: Clone, Window: Clone> Clone for Size<'a, Element, Window> {
fn clone(&self) -> Self {
Self {
options: self.options.clone(),
}
}
}
impl<'a, Element: Clone, Window: Clone> Middleware<Element, Window> for Size<'a, Element, Window> {
fn name(&self) -> &'static str {
SIZE_NAME
}
fn compute(&self, state: MiddlewareState<Element, Window>) -> MiddlewareReturn {
let options = self.options.evaluate(state.clone());
let MiddlewareState {
placement,
elements,
rects,
platform,
..
} = state;
let overflow = detect_overflow(
MiddlewareState {
elements: elements.clone(),
..state
},
options.detect_overflow.unwrap_or_default(),
);
let side = placement.side();
let alignment = placement.alignment();
let is_y_axis = get_side_axis(placement) == Axis::Y;
let Rect { width, height, .. } = rects.floating;
let height_side;
let width_side;
match side {
Side::Top | Side::Bottom => {
height_side = side;
width_side = match alignment {
Some(alignment) => match alignment
== match platform.is_rtl(elements.floating) {
Some(true) => Alignment::Start,
_ => Alignment::End,
} {
true => Side::Left,
false => Side::Right,
},
None => Side::Right,
};
}
Side::Right | Side::Left => {
width_side = side;
height_side = match alignment {
Some(Alignment::End) => Side::Top,
_ => Side::Bottom,
};
}
}
let overflow_available_height = height - overflow.side(height_side);
let overflow_available_width = width - overflow.side(width_side);
let no_shift = state.middleware_data.get(SHIFT_NAME).is_none();
let mut available_height = overflow_available_height;
let mut available_width = overflow_available_width;
if is_y_axis {
let maximum_clipping_width = width - overflow.left - overflow.right;
available_width = match alignment.is_some() || no_shift {
true => overflow_available_width.min(maximum_clipping_width),
false => maximum_clipping_width,
};
} else {
let maximum_clipping_height = height - overflow.top - overflow.bottom;
available_height = match alignment.is_some() || no_shift {
true => overflow_available_height.min(maximum_clipping_height),
false => maximum_clipping_height,
}
}
if no_shift && alignment.is_none() {
let x_min = overflow.left.max(0.0);
let x_max = overflow.right.max(0.0);
let y_min = overflow.top.max(0.0);
let y_max = overflow.bottom.max(0.0);
if is_y_axis {
available_width = width
- 2.0
* (match x_min != 0.0 || x_max != 0.0 {
true => x_min + x_max,
false => overflow.left.max(overflow.right),
});
} else {
available_height = height
- 2.0
* (match y_min != 0.0 || y_max != 0.0 {
true => y_min + y_max,
false => overflow.top.max(overflow.bottom),
});
}
}
if let Some(apply) = options.apply {
apply(ApplyState {
state: MiddlewareState {
elements: elements.clone(),
..state
},
available_width,
available_height,
});
}
let next_dimensions = platform.get_dimensions(elements.floating);
if width != next_dimensions.width || height != next_dimensions.height {
MiddlewareReturn {
x: None,
y: None,
data: None,
reset: Some(crate::Reset::Value(ResetValue {
placement: None,
rects: Some(ResetRects::True),
})),
}
} else {
MiddlewareReturn {
x: None,
y: None,
data: None,
reset: None,
}
}
}
}
impl<'a, Element: Clone, Window: Clone>
MiddlewareWithOptions<Element, Window, SizeOptions<'a, Element, Window>>
for Size<'a, Element, Window>
{
fn options(&self) -> &Derivable<Element, Window, SizeOptions<'a, Element, Window>> {
&self.options
}
}