use crate::Rect;
#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
pub enum ObjectFit {
Fill,
#[default]
Contain,
Cover,
ContainInteger,
}
pub fn fit_rect(intrinsic: (f32, f32), container: Rect, fit: ObjectFit) -> (Rect, bool) {
let (iw, ih) = intrinsic;
if iw <= 0.0 || ih <= 0.0 || container.width <= 0.0 || container.height <= 0.0 {
return (container, false);
}
match fit {
ObjectFit::Fill => (container, false),
ObjectFit::Contain | ObjectFit::Cover | ObjectFit::ContainInteger => {
let sx = container.width / iw;
let sy = container.height / ih;
let (s, clip) = match fit {
ObjectFit::Cover => (sx.max(sy), true),
ObjectFit::ContainInteger => (sx.min(sy).floor().max(1.0).min(sx.min(sy)), false),
_ => (sx.min(sy), false),
};
let w = iw * s;
let h = ih * s;
let x = container.x + (container.width - w) * 0.5;
let y = container.y + (container.height - h) * 0.5;
(Rect::new(x, y, w, h), clip)
}
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn fill_returns_container_and_no_clip() {
let c = Rect::new(0.0, 0.0, 120.0, 60.0);
let (rect, clip) = fit_rect((10.0, 10.0), c, ObjectFit::Fill);
assert_eq!(rect, c);
assert!(!clip);
}
#[test]
fn contain_letterboxes_wide_box() {
let c = Rect::new(0.0, 0.0, 120.0, 60.0);
let (rect, clip) = fit_rect((10.0, 10.0), c, ObjectFit::Contain);
assert_eq!(rect, Rect::new(30.0, 0.0, 60.0, 60.0));
assert!(!clip);
}
#[test]
fn cover_overflows_and_clips() {
let c = Rect::new(0.0, 0.0, 120.0, 60.0);
let (rect, clip) = fit_rect((10.0, 10.0), c, ObjectFit::Cover);
assert_eq!(rect, Rect::new(0.0, -30.0, 120.0, 120.0));
assert!(clip);
}
#[test]
fn contain_respects_container_origin() {
let c = Rect::new(5.0, 7.0, 120.0, 60.0);
let (rect, _) = fit_rect((10.0, 10.0), c, ObjectFit::Contain);
assert_eq!(rect, Rect::new(35.0, 7.0, 60.0, 60.0));
}
#[test]
fn degenerate_intrinsic_fills() {
let c = Rect::new(0.0, 0.0, 120.0, 60.0);
let (rect, clip) = fit_rect((0.0, 10.0), c, ObjectFit::Contain);
assert_eq!(rect, c);
assert!(!clip);
}
#[test]
fn default_is_contain() {
assert_eq!(ObjectFit::default(), ObjectFit::Contain);
}
#[test]
fn contain_integer_floors_the_scale_and_widens_the_letterbox() {
let c = Rect::new(0.0, 0.0, 1300.0, 740.0);
let (rect, clip) = fit_rect((320.0, 180.0), c, ObjectFit::ContainInteger);
assert_eq!(rect, Rect::new(10.0, 10.0, 1280.0, 720.0));
assert!(!clip);
}
#[test]
fn contain_integer_fills_an_exact_multiple() {
let c = Rect::new(0.0, 0.0, 1280.0, 720.0);
let (rect, _) = fit_rect((320.0, 180.0), c, ObjectFit::ContainInteger);
assert_eq!(rect, Rect::new(0.0, 0.0, 1280.0, 720.0));
}
#[test]
fn contain_integer_below_one_falls_back_to_fitting() {
let c = Rect::new(0.0, 0.0, 160.0, 90.0);
let (rect, _) = fit_rect((320.0, 180.0), c, ObjectFit::ContainInteger);
assert_eq!(rect, Rect::new(0.0, 0.0, 160.0, 90.0));
}
}