use std::ops::Range;
use crate::pos::{Pos, Rect, Size, X, Y};
#[derive(Debug)]
pub struct FrameArea {
#[cfg(not(test))]
bounds: Rect,
#[cfg(test)]
pub bounds: Rect,
offset: Size,
vsize: Size,
}
impl FrameArea {
pub fn full(size: Size) -> Self {
Self { bounds: Rect::tlsz(Pos::rnew(0, 0), size), offset: Size::rnew(0, 0), vsize: size }
}
pub fn vgrow(&mut self, tl: Size, br: Size) {
self.offset += tl;
self.vsize += tl + br;
}
pub fn size(&self) -> Size {
self.vsize
}
pub fn rowinfo(&self, y: Y) -> Option<RowInfo> {
if y < self.offset.height {
return None;
}
let y = y - self.offset.height;
if y < self.bounds.size.height {
let y = y + self.bounds.tl.y;
let slice_start = self.bounds.tl.x.0;
let x_start = slice_start + self.offset.width.0;
let len = self.bounds.size.width.0;
Some(RowInfo {
real_y: y,
slice: slice_start..(slice_start + len),
xs: x_start..(x_start + len),
})
} else {
None
}
}
}
pub struct RowInfo {
pub real_y: Y,
pub slice: Range<usize>,
pub xs: Range<usize>,
}
macro_rules! split_impl {
($split_v:ident($y:ident: $Y:ident), $height:ident) => {
pub fn $split_v(self, $y: $Y) -> (Self, Self) {
let tvs = Size { $height: $y, ..self.vsize };
let bvs = self.vsize - $y;
if $y <= self.offset.$height {
return (
Self {
bounds: Rect::EMPTY,
offset: Size::ZERO, vsize: tvs,
},
Self { bounds: self.bounds, offset: self.offset - $y, vsize: bvs },
);
}
let $y = $y - self.offset.$height;
if $y < self.bounds.size.$height {
let (t, b) = self.bounds.$split_v($y);
return (
Self { bounds: t, offset: self.offset, vsize: tvs },
Self { bounds: b, offset: Size { $height: $Y(0), ..self.offset }, vsize: bvs },
);
}
(
Self { bounds: self.bounds, offset: self.offset, vsize: tvs },
Self { bounds: Rect::EMPTY, offset: Size::ZERO, vsize: bvs },
)
}
};
}
impl FrameArea {
split_impl!(split_v(y: Y), height);
split_impl!(split_h(x: X), width);
}
#[cfg(test)]
mod test {
use super::*;
macro_rules! area {
(
$sx:literal, $sy:literal
$(~ $gbx:literal, $gby:literal; $gax:literal, $gay:literal)?
) => {{
#[allow(unused_mut)]
let mut f = FrameArea::full(Size::rnew($sx, $sy));
$(
f.vgrow(Size::rnew($gbx, $gby), Size::rnew($gax, $gay));
)?
f
}}
}
macro_rules! assert_area {
(
$area:expr,
bounds: ($bx:expr, $by:expr; $bw:expr, $bh:expr),
offset: ($ox:expr, $oy:expr),
vsize: ($vx:expr, $vy:expr) $(,)?
) => {
let area = &$area;
assert_eq!(
area.bounds,
Rect::tlsz(Pos::rnew($bx, $by), Size::rnew($bw, $bh)),
concat!(stringify!($area), "bounds was wrong"),
);
assert_eq!(area.offset, Size::rnew($ox, $oy), concat!(stringify!($area), "offset was wrong"),);
assert_eq!(area.vsize, Size::rnew($vx, $vy), concat!(stringify!($area), "vsize was wrong"),);
};
}
macro_rules! area_test {
(
$name:ident:
(
$sx:literal, $sy:literal
$(~ $gbx:literal, $gby:literal; $gax:literal, $gay:literal)?
) / $split:literal =>
top: (
bounds: ($tbx:expr, $tby:expr; $tbw:expr, $tbh:expr),
offset: ($tox:expr, $toy:expr),
vsize: ($tvx:expr, $tvy:expr) $(,)?
),
bottom: (
bounds: ($bbx:expr, $bby:expr; $bbw:expr, $bbh:expr),
offset: ($box:expr, $boy:expr),
vsize: ($bvx:expr, $bvy:expr) $(,)?
) $(,)?
) => {
paste::paste! { area_test! {
@raw [<$name _v>]:
($sx, $sy $(~ $gbx, $gby; $gax, $gay)?) / split_v($split) =>
top: (
bounds: ($tbx, $tby; $tbw, $tbh),
offset: ($tox, $toy),
vsize: ($tvx, $tvy)
),
bottom: (
bounds: ($bbx, $bby; $bbw, $bbh),
offset: ($box, $boy),
vsize: ($bvx, $bvy)
),
} }
paste::paste! { area_test! {
@raw [<$name _h>]:
($sy, $sx $(~ $gby, $gbx; $gay, $gax)?) / split_h($split) =>
top: (
bounds: ($tby, $tbx; $tbh, $tbw),
offset: ($toy, $tox),
vsize: ($tvy, $tvx)
),
bottom: (
bounds: ($bby, $bbx; $bbh, $bbw),
offset: ($boy, $box),
vsize: ($bvy, $bvx)
),
} }
};
(@raw
$name:ident: ($($area:tt)*) / $mthd:ident($split:literal) =>
top: ($($top:tt)*), bottom: ($($bottom:tt)*) $(,)?
) => {
#[test]
fn $name() {
let area = area!($($area)*);
let (tl, br) = area.$mthd($split.into());
assert_area!(tl, $($top)*);
assert_area!(br, $($bottom)*);
}
}
}
area_test!(
matching_split_start: (4, 4) / 0 =>
top: (bounds: (0, 0; 0, 0), offset: (0, 0), vsize: (4, 0)),
bottom: (bounds: (0, 0; 4, 4), offset: (0, 0), vsize: (4, 4)),
);
area_test!(
matching_split_on_absolute: (4, 4) / 1 =>
top: (bounds: (0, 0; 4, 1), offset: (0, 0), vsize: (4, 1)),
bottom: (bounds: (0, 1; 4, 3), offset: (0, 0), vsize: (4, 3)),
);
area_test!(
matching_split_end: (4, 4) / 4 =>
top: (bounds: (0, 0; 4, 4), offset: (0, 0), vsize: (4, 4)),
bottom: (bounds: (0, 0; 0, 0), offset: (0, 0), vsize: (4, 0)),
);
area_test!(
wider_split_start: (4, 4 ~ 2, 0; 2, 0) / 0 =>
top: (bounds: (0, 0; 0, 0), offset: (0, 0), vsize: (8, 0)),
bottom: (bounds: (0, 0; 4, 4), offset: (2, 0), vsize: (8, 4)),
);
area_test!(
wider_split_on_absolute: (4, 4 ~ 2, 0; 2, 0) / 1 =>
top: (bounds: (0, 0; 4, 1), offset: (2, 0), vsize: (8, 1)),
bottom: (bounds: (0, 1; 4, 3), offset: (2, 0), vsize: (8, 3)),
);
area_test!(
wider_split_end: (4, 4 ~ 2, 0; 2, 0) / 4 =>
top: (bounds: (0, 0; 4, 4), offset: (2, 0), vsize: (8, 4)),
bottom: (bounds: (0, 0; 0, 0), offset: (0, 0), vsize: (8, 0)),
);
area_test!(
prelonger_split_start: (4, 4 ~ 0, 4; 0, 0) / 0 =>
top: (bounds: (0, 0; 0, 0), offset: (0, 0), vsize: (4, 0)),
bottom: (bounds: (0, 0; 4, 4), offset: (0, 4), vsize: (4, 8)),
);
area_test!(
prelonger_split_previrt: (4, 4 ~ 0, 4; 0, 0) / 2 =>
top: (bounds: (0, 0; 0, 0), offset: (0, 0), vsize: (4, 2)),
bottom: (bounds: (0, 0; 4, 4), offset: (0, 2), vsize: (4, 6)),
);
area_test!(
prelonger_split_barely_previrt: (4, 4 ~ 0, 4; 0, 0) / 4 =>
top: (bounds: (0, 0; 0, 0), offset: (0, 0), vsize: (4, 4)),
bottom: (bounds: (0, 0; 4, 4), offset: (0, 0), vsize: (4, 4)),
);
area_test!(
prelonger_split_on_absolute: (4, 4 ~ 0, 4; 0, 0) / 6 =>
top: (bounds: (0, 0; 4, 2), offset: (0, 4), vsize: (4, 6)),
bottom: (bounds: (0, 2; 4, 2), offset: (0, 0), vsize: (4, 2)),
);
area_test!(
prelonger_split_end: (4, 4 ~ 0, 4; 0, 0) / 8 =>
top: (bounds: (0, 0; 4, 4), offset: (0, 4), vsize: (4, 8)),
bottom: (bounds: (0, 0; 0, 0), offset: (0, 0), vsize: (4, 0)),
);
area_test!(
postlonger_split_start: (4, 4 ~ 0, 0; 0, 4) / 0 =>
top: (bounds: (0, 0; 0, 0), offset: (0, 0), vsize: (4, 0)),
bottom: (bounds: (0, 0; 4, 4), offset: (0, 0), vsize: (4, 8)),
);
area_test!(
postlonger_split_on_absolute: (4, 4 ~ 0, 0; 0, 4) / 2 =>
top: (bounds: (0, 0; 4, 2), offset: (0, 0), vsize: (4, 2)),
bottom: (bounds: (0, 2; 4, 2), offset: (0, 0), vsize: (4, 6)),
);
area_test!(
postlonger_split_barely_postvirt: (4, 4 ~ 0, 0; 0, 4) / 4 =>
top: (bounds: (0, 0; 4, 4), offset: (0, 0), vsize: (4, 4)),
bottom: (bounds: (0, 0; 0, 0), offset: (0, 0), vsize: (4, 4)),
);
area_test!(
postlonger_split_postvirt: (4, 4 ~ 0, 0; 0, 4) / 6 =>
top: (bounds: (0, 0; 4, 4), offset: (0, 0), vsize: (4, 6)),
bottom: (bounds: (0, 0; 0, 0), offset: (0, 0), vsize: (4, 2)),
);
area_test!(
postlonger_split_end: (4, 4 ~ 0, 0; 0, 4) / 8 =>
top: (bounds: (0, 0; 4, 4), offset: (0, 0), vsize: (4, 8)),
bottom: (bounds: (0, 0; 0, 0), offset: (0, 0), vsize: (4, 0)),
);
area_test!(
longer_split_start: (4, 4 ~ 0, 3; 0, 2) / 0 =>
top: (bounds: (0, 0; 0, 0), offset: (0, 0), vsize: (4, 0)),
bottom: (bounds: (0, 0; 4, 4), offset: (0, 3), vsize: (4, 9)),
);
area_test!(
longer_split_previrt: (4, 4 ~ 0, 3; 0, 2) / 1 =>
top: (bounds: (0, 0; 0, 0), offset: (0, 0), vsize: (4, 1)),
bottom: (bounds: (0, 0; 4, 4), offset: (0, 2), vsize: (4, 8)),
);
area_test!(
longer_split_barely_previrt: (4, 4 ~ 0, 3; 0, 2) / 3 =>
top: (bounds: (0, 0; 0, 0), offset: (0, 0), vsize: (4, 3)),
bottom: (bounds: (0, 0; 4, 4), offset: (0, 0), vsize: (4, 6)),
);
area_test!(
longer_split_absolute: (4, 4 ~ 0, 3; 0, 2) / 4 =>
top: (bounds: (0, 0; 4, 1), offset: (0, 3), vsize: (4, 4)),
bottom: (bounds: (0, 1; 4, 3), offset: (0, 0), vsize: (4, 5)),
);
area_test!(
longer_split_barely_postvirt: (4, 4 ~ 0, 3; 0, 2) / 7 =>
top: (bounds: (0, 0; 4, 4), offset: (0, 3), vsize: (4, 7)),
bottom: (bounds: (0, 0; 0, 0), offset: (0, 0), vsize: (4, 2)),
);
area_test!(
longer_split_postvirt: (4, 4 ~ 0, 3; 0, 2) / 8 =>
top: (bounds: (0, 0; 4, 4), offset: (0, 3), vsize: (4, 8)),
bottom: (bounds: (0, 0; 0, 0), offset: (0, 0), vsize: (4, 1)),
);
}