use leptos::prelude::window;
use web_sys::DomRect;
use crate::overlay::placement::Placement;
pub struct AnchorOffset {
pub top: f64,
pub left: f64,
pub transform: String,
pub placement: Placement,
pub max_height: Option<f64>,
}
pub fn resolve_anchor_offset(
placement: Placement,
target_rect: &DomRect,
content_rect: &DomRect,
arrow_height: Option<f64>,
) -> Option<AnchorOffset> {
use Placement::*;
let placement_list = match placement {
TopStart => vec![TopStart, BottomStart, Right, Left, TopStart],
Top => vec![Top, Bottom, Right, Left, Top],
TopEnd => vec![TopEnd, BottomEnd, Right, Left, TopEnd],
BottomStart => vec![BottomStart, TopStart, Right, Left, BottomStart],
Bottom => vec![Bottom, Top, Right, Left, Bottom],
BottomEnd => vec![BottomEnd, TopEnd, Right, Left, BottomEnd],
RightStart => vec![RightStart, LeftStart, Top, Bottom, RightStart],
Right => vec![Right, Left, Top, Bottom, Right],
RightEnd => vec![RightEnd, LeftEnd, Top, Bottom, RightEnd],
LeftStart => vec![LeftStart, RightStart, Top, Bottom, LeftStart],
Left => vec![Left, Right, Top, Bottom, Left],
LeftEnd => vec![LeftEnd, RightEnd, Top, Bottom, LeftEnd],
};
let placement_len = placement_list.len();
let mut placement_list = placement_list.into_iter().enumerate();
loop {
let Some((index, placement)) = placement_list.next() else {
return None;
};
let must = placement_len == index + 1;
let rt = match placement {
TopStart => placement_top_start(target_rect, content_rect, arrow_height, must),
Top => placement_top(target_rect, content_rect, arrow_height, must),
TopEnd => placement_top_end(target_rect, content_rect, arrow_height, must),
BottomStart => placement_bottom_start(target_rect, content_rect, arrow_height, must),
Bottom => placement_bottom(target_rect, content_rect, arrow_height, must),
BottomEnd => placement_bottom_end(target_rect, content_rect, arrow_height, must),
RightStart => placement_right_start(target_rect, content_rect, arrow_height, must),
Right => placement_right(target_rect, content_rect, arrow_height, must),
RightEnd => placement_right_end(target_rect, content_rect, arrow_height, must),
LeftStart => placement_left_start(target_rect, content_rect, arrow_height, must),
Left => placement_left(target_rect, content_rect, arrow_height, must),
LeftEnd => placement_left_end(target_rect, content_rect, arrow_height, must),
};
if rt.is_some() {
return rt;
}
}
}
fn placement_top_start(
target_rect: &DomRect,
content_rect: &DomRect,
arrow_height: Option<f64>,
must: bool,
) -> Option<AnchorOffset> {
let content_height = content_rect.height() + arrow_height.unwrap_or_default();
let target_top = target_rect.top();
let top = target_top - content_height;
if !must && top < 0.0 {
return None;
}
let target_left = target_rect.left();
if !must {
let Some(window_inner_width) = window_inner_width() else {
return None;
};
let content_width = content_rect.width();
if target_left + content_width > window_inner_width {
return None;
}
}
Some(AnchorOffset {
top,
left: target_left,
transform: String::new(),
placement: Placement::TopStart,
max_height: Some(target_top.max(0.0)),
})
}
fn placement_top(
target_rect: &DomRect,
content_rect: &DomRect,
arrow_height: Option<f64>,
must: bool,
) -> Option<AnchorOffset> {
let content_height = content_rect.height() + arrow_height.unwrap_or_default();
let target_top = target_rect.top();
let top = target_top - content_height;
if !must && top < 0.0 {
return None;
}
let target_width_center = target_rect.left() + target_rect.width() / 2.0;
if !must {
let Some(window_inner_width) = window_inner_width() else {
return None;
};
let content_width_half = content_rect.width() / 2.0;
if content_width_half > target_width_center
|| target_width_center + content_width_half > window_inner_width
{
return None;
}
}
Some(AnchorOffset {
top,
left: target_width_center,
transform: String::from("translateX(-50%)"),
placement: Placement::Top,
max_height: Some(target_top.max(0.0)),
})
}
fn placement_top_end(
target_rect: &DomRect,
content_rect: &DomRect,
arrow_height: Option<f64>,
must: bool,
) -> Option<AnchorOffset> {
let content_height = content_rect.height() + arrow_height.unwrap_or_default();
let target_top = target_rect.top();
let top = target_top - content_height;
if !must && top < 0.0 {
return None;
}
let target_right = target_rect.right();
if !must {
let content_width = content_rect.width();
if target_right < content_width {
return None;
}
}
Some(AnchorOffset {
top,
left: target_right,
transform: String::from("translateX(-100%)"),
placement: Placement::TopEnd,
max_height: Some(target_top.max(0.0)),
})
}
fn placement_bottom_start(
target_rect: &DomRect,
content_rect: &DomRect,
arrow_height: Option<f64>,
must: bool,
) -> Option<AnchorOffset> {
let Some(window_inner_height) = window_inner_height() else {
return None;
};
let content_height = content_rect.height();
let target_bottom = target_rect.bottom() + arrow_height.unwrap_or_default();
let top = target_bottom;
if !must && target_bottom + content_height > window_inner_height {
return None;
}
let target_left = target_rect.left();
if !must {
let Some(window_inner_width) = window_inner_width() else {
return None;
};
let content_width = content_rect.width();
if target_left + content_width > window_inner_width {
return None;
}
}
let max_heigth = if target_bottom > 0.0 {
window_inner_height - target_bottom
} else {
0.0
};
Some(AnchorOffset {
top,
left: target_left,
transform: String::new(),
placement: Placement::BottomStart,
max_height: Some(max_heigth),
})
}
fn placement_bottom(
target_rect: &DomRect,
content_rect: &DomRect,
arrow_height: Option<f64>,
must: bool,
) -> Option<AnchorOffset> {
let Some(window_inner_height) = window_inner_height() else {
return None;
};
let content_height = content_rect.height();
let target_bottom = target_rect.bottom() + arrow_height.unwrap_or_default();
let top = target_bottom;
if !must && target_bottom + content_height > window_inner_height {
return None;
}
let target_width_center = target_rect.left() + target_rect.width() / 2.0;
if !must {
let Some(window_inner_width) = window_inner_width() else {
return None;
};
let content_width_half = content_rect.width() / 2.0;
if content_width_half > target_width_center
|| target_width_center + content_width_half > window_inner_width
{
return None;
}
}
let max_heigth = if target_bottom > 0.0 {
window_inner_height - target_bottom
} else {
0.0
};
Some(AnchorOffset {
top,
left: target_width_center,
transform: String::from("translateX(-50%)"),
placement: Placement::Bottom,
max_height: Some(max_heigth),
})
}
fn placement_bottom_end(
target_rect: &DomRect,
content_rect: &DomRect,
arrow_height: Option<f64>,
must: bool,
) -> Option<AnchorOffset> {
let Some(window_inner_height) = window_inner_height() else {
return None;
};
let content_height = content_rect.height();
let target_bottom = target_rect.bottom() + arrow_height.unwrap_or_default();
let top = target_bottom;
if !must && target_bottom + content_height > window_inner_height {
return None;
}
let target_right = target_rect.right();
if !must {
let content_width = content_rect.width();
if target_right < content_width {
return None;
}
}
let max_heigth = if target_bottom > 0.0 {
window_inner_height - target_bottom
} else {
0.0
};
Some(AnchorOffset {
top,
left: target_right,
transform: String::from("translateX(-100%)"),
placement: Placement::BottomEnd,
max_height: Some(max_heigth),
})
}
fn placement_right_start(
target_rect: &DomRect,
content_rect: &DomRect,
arrow_height: Option<f64>,
must: bool,
) -> Option<AnchorOffset> {
let Some(window_inner_width) = window_inner_width() else {
return None;
};
let content_width = content_rect.width();
let target_right = target_rect.right();
let left = target_right + arrow_height.unwrap_or_default();
if !must && left + content_width > window_inner_width {
return None;
}
let top = target_rect.top();
if !must {
let Some(window_inner_height) = window_inner_height() else {
return None;
};
let content_height = content_rect.height();
if content_height + top > window_inner_height {
return None;
}
}
Some(AnchorOffset {
top,
left,
transform: String::new(),
placement: Placement::RightStart,
max_height: None,
})
}
fn placement_right(
target_rect: &DomRect,
content_rect: &DomRect,
arrow_height: Option<f64>,
must: bool,
) -> Option<AnchorOffset> {
let Some(window_inner_width) = window_inner_width() else {
return None;
};
let content_width = content_rect.width();
let target_right = target_rect.right();
let left = target_right + arrow_height.unwrap_or_default();
if !must && left + content_width > window_inner_width {
return None;
}
let target_height_center = target_rect.top() + target_rect.height() / 2.0;
if !must {
let Some(window_inner_height) = window_inner_height() else {
return None;
};
let content_height_half = content_rect.height() / 2.0;
if content_height_half > target_height_center
|| target_height_center + content_height_half > window_inner_height
{
return None;
}
}
Some(AnchorOffset {
top: target_height_center,
left,
transform: String::from("translateY(-50%)"),
placement: Placement::Right,
max_height: None,
})
}
fn placement_right_end(
target_rect: &DomRect,
content_rect: &DomRect,
arrow_height: Option<f64>,
must: bool,
) -> Option<AnchorOffset> {
let Some(window_inner_width) = window_inner_width() else {
return None;
};
let content_width = content_rect.width();
let target_right = target_rect.right();
let left = target_right + arrow_height.unwrap_or_default();
if !must && left + content_width > window_inner_width {
return None;
}
let target_bottom = target_rect.bottom();
if !must {
if target_bottom < content_rect.height() {
return None;
}
}
Some(AnchorOffset {
top: target_bottom,
left,
transform: String::from("translateY(-100%)"),
placement: Placement::RightEnd,
max_height: None,
})
}
fn placement_left_start(
target_rect: &DomRect,
content_rect: &DomRect,
arrow_height: Option<f64>,
must: bool,
) -> Option<AnchorOffset> {
let content_width = content_rect.width() + arrow_height.unwrap_or_default();
let target_left = target_rect.left();
let left = target_left - content_width;
if !must && left < 0.0 {
return None;
}
let top = target_rect.top();
if !must {
let Some(window_inner_height) = window_inner_height() else {
return None;
};
let content_height = content_rect.height();
if content_height + top > window_inner_height {
return None;
}
}
Some(AnchorOffset {
top,
left,
transform: String::new(),
placement: Placement::LeftStart,
max_height: None,
})
}
fn placement_left(
target_rect: &DomRect,
content_rect: &DomRect,
arrow_height: Option<f64>,
must: bool,
) -> Option<AnchorOffset> {
let content_width = content_rect.width() + arrow_height.unwrap_or_default();
let target_left = target_rect.left();
let left = target_left - content_width;
if !must && left < 0.0 {
return None;
}
let target_height_center = target_rect.top() + target_rect.height() / 2.0;
if !must {
let Some(window_inner_height) = window_inner_height() else {
return None;
};
let content_height_half = content_rect.height() / 2.0;
if content_height_half > target_height_center
|| target_height_center + content_height_half > window_inner_height
{
return None;
}
}
Some(AnchorOffset {
top: target_height_center,
left,
transform: String::from("translateY(-50%)"),
placement: Placement::Left,
max_height: None,
})
}
fn placement_left_end(
target_rect: &DomRect,
content_rect: &DomRect,
arrow_height: Option<f64>,
must: bool,
) -> Option<AnchorOffset> {
let content_width = content_rect.width() + arrow_height.unwrap_or_default();
let target_left = target_rect.left();
let left = target_left - content_width;
if !must && left < 0.0 {
return None;
}
let target_bottom = target_rect.bottom();
if !must {
if target_bottom < content_rect.height() {
return None;
}
}
Some(AnchorOffset {
top: target_bottom,
left,
transform: String::from("translateY(-100%)"),
placement: Placement::LeftEnd,
max_height: None,
})
}
fn window_inner_width() -> Option<f64> {
let Ok(inner_width) = window().inner_width() else {
return None;
};
let Some(inner_width) = inner_width.as_f64() else {
return None;
};
Some(inner_width)
}
fn window_inner_height() -> Option<f64> {
let Ok(inner_height) = window().inner_height() else {
return None;
};
let Some(inner_height) = inner_height.as_f64() else {
return None;
};
Some(inner_height)
}