use super::Rect;
pub fn rects_intersect(a: &Rect, b: &Rect) -> bool {
a.x < b.x + b.width && b.x < a.x + a.width && a.y < b.y + b.height && b.y < a.y + a.height
}
const CORNER_STACK_GAP: f64 = 4.0;
pub fn stack_clear(mut rect: Rect, away_sign: f64, claims: &[Rect], bounds: &Rect) -> Rect {
let away_sign = if away_sign < 0.0 { -1.0 } else { 1.0 };
for _ in 0..=claims.len() {
let mut shifted = false;
for claim in claims {
if !rects_intersect(&rect, claim) {
continue;
}
let delta = if away_sign < 0.0 {
(rect.y + rect.height) - claim.y + CORNER_STACK_GAP
} else {
(claim.y + claim.height) - rect.y + CORNER_STACK_GAP
};
if delta > 0.0 {
rect.y += away_sign * delta;
shifted = true;
}
}
if !shifted {
break;
}
}
let max_y = (bounds.y + bounds.height - rect.height).max(bounds.y);
rect.y = rect.y.clamp(bounds.y, max_y);
rect
}
#[cfg(test)]
mod tests {
use super::*;
fn r(x: f64, y: f64, w: f64, h: f64) -> Rect {
Rect {
x,
y,
width: w,
height: h,
}
}
fn bounds() -> Rect {
r(-1000.0, -1000.0, 2000.0, 2000.0)
}
#[test]
fn disjoint_rects_do_not_intersect() {
assert!(!rects_intersect(
&r(0.0, 0.0, 10.0, 10.0),
&r(20.0, 0.0, 10.0, 10.0)
));
}
#[test]
fn touching_edges_do_not_count_as_intersecting() {
assert!(!rects_intersect(
&r(0.0, 0.0, 10.0, 10.0),
&r(10.0, 0.0, 10.0, 10.0)
));
assert!(!rects_intersect(
&r(0.0, 0.0, 10.0, 10.0),
&r(0.0, 10.0, 10.0, 10.0)
));
}
#[test]
fn overlapping_rects_intersect() {
assert!(rects_intersect(
&r(0.0, 0.0, 10.0, 10.0),
&r(5.0, 5.0, 10.0, 10.0)
));
}
#[test]
fn stack_clear_with_no_claims_is_a_no_op() {
let rect = r(0.0, 90.0, 20.0, 10.0);
let out = stack_clear(rect.clone(), -1.0, &[], &bounds());
assert_eq!(
(out.x, out.y, out.width, out.height),
(rect.x, rect.y, rect.width, rect.height)
);
}
#[test]
fn stack_clear_shifts_up_away_from_a_bottom_claim() {
let rect = r(0.0, 90.0, 20.0, 10.0); let claim = r(0.0, 95.0, 20.0, 10.0); let out = stack_clear(rect, -1.0, std::slice::from_ref(&claim), &bounds());
assert!(
out.y + out.height <= claim.y - CORNER_STACK_GAP + 1e-9,
"out={out:?}"
);
assert!(!rects_intersect(&out, &claim));
}
#[test]
fn stack_clear_shifts_down_away_from_a_top_claim() {
let rect = r(0.0, 10.0, 20.0, 10.0);
let claim = r(0.0, 5.0, 20.0, 10.0);
let out = stack_clear(rect, 1.0, std::slice::from_ref(&claim), &bounds());
assert!(
out.y >= claim.y + claim.height + CORNER_STACK_GAP - 1e-9,
"out={out:?}"
);
assert!(!rects_intersect(&out, &claim));
}
#[test]
fn stack_clear_ignores_a_horizontally_disjoint_claim() {
let rect = r(0.0, 90.0, 20.0, 10.0);
let claim = r(100.0, 90.0, 20.0, 10.0); let out = stack_clear(rect.clone(), -1.0, &[claim], &bounds());
assert_eq!((out.x, out.y), (rect.x, rect.y));
}
#[test]
fn stack_clear_clears_several_stacked_claims() {
let rect = r(0.0, 90.0, 20.0, 10.0);
let claims = [r(0.0, 95.0, 20.0, 10.0), r(0.0, 80.0, 20.0, 10.0)];
let out = stack_clear(rect, -1.0, &claims, &bounds());
for c in &claims {
assert!(!rects_intersect(&out, c), "out={out:?} still hits {c:?}");
}
}
#[test]
fn stack_clear_handles_overlapping_claims_in_one_pass_or_more() {
let rect = r(0.0, 90.0, 20.0, 10.0);
let claims = [r(0.0, 92.0, 20.0, 10.0), r(0.0, 85.0, 20.0, 12.0)];
let out = stack_clear(rect, -1.0, &claims, &bounds());
for c in &claims {
assert!(!rects_intersect(&out, c), "out={out:?} still hits {c:?}");
}
}
#[test]
fn stack_clear_never_pushes_the_box_above_the_bounds_top() {
let panel = r(0.0, 0.0, 100.0, 60.0);
let rect = r(0.0, 55.0, 20.0, 10.0); let claim = r(0.0, -500.0, 20.0, 600.0); let out = stack_clear(rect, -1.0, std::slice::from_ref(&claim), &panel);
assert!(out.y >= panel.y - 1e-9, "out={out:?} left the panel's top");
}
#[test]
fn stack_clear_pins_a_box_taller_than_bounds_to_the_top() {
let panel = r(0.0, 0.0, 100.0, 20.0);
let rect = r(0.0, 18.0, 20.0, 50.0); let out = stack_clear(rect, -1.0, &[], &panel);
assert_eq!(out.y, panel.y);
}
#[test]
fn stack_clear_without_claims_still_respects_bounds() {
let panel = r(0.0, 0.0, 100.0, 60.0);
let rect = r(0.0, -40.0, 20.0, 10.0);
let out = stack_clear(rect, -1.0, &[], &panel);
assert!(out.y >= panel.y - 1e-9);
assert!(out.y + out.height <= panel.y + panel.height + 1e-9);
}
}