#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub struct ClipRect {
pub x0: i64,
pub y0: i64,
pub x1: i64,
pub y1: i64,
}
impl ClipRect {
pub fn full(width: u32, height: u32) -> Self {
Self {
x0: 0,
y0: 0,
x1: width as i64,
y1: height as i64,
}
}
pub fn from_rect(x: i64, y: i64, w: i64, h: i64) -> Self {
Self {
x0: x,
y0: y,
x1: x + w.max(0),
y1: y + h.max(0),
}
}
pub fn is_empty(&self) -> bool {
self.x1 <= self.x0 || self.y1 <= self.y0
}
pub fn intersect(&self, other: &ClipRect) -> ClipRect {
ClipRect {
x0: self.x0.max(other.x0),
y0: self.y0.max(other.y0),
x1: self.x1.min(other.x1),
y1: self.y1.min(other.y1),
}
}
pub fn contains(&self, x: i64, y: i64) -> bool {
x >= self.x0 && x < self.x1 && y >= self.y0 && y < self.y1
}
}
#[derive(Clone, Debug)]
pub struct ClipStack {
stack: Vec<ClipRect>,
}
impl ClipStack {
pub fn new(width: u32, height: u32) -> Self {
Self {
stack: vec![ClipRect::full(width, height)],
}
}
pub fn current(&self) -> ClipRect {
*self.stack.last().unwrap_or(&ClipRect {
x0: 0,
y0: 0,
x1: 0,
y1: 0,
})
}
pub fn push(&mut self, clip: ClipRect) {
let next = self.current().intersect(&clip);
self.stack.push(next);
}
pub fn pop(&mut self) {
if self.stack.len() > 1 {
self.stack.pop();
}
}
pub fn depth(&self) -> usize {
self.stack.len()
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn nested_clips_intersect() {
let mut s = ClipStack::new(100, 100);
assert_eq!(s.current(), ClipRect::full(100, 100));
s.push(ClipRect::from_rect(10, 10, 50, 50)); s.push(ClipRect::from_rect(40, 40, 50, 50)); let c = s.current();
assert_eq!(
c,
ClipRect {
x0: 40,
y0: 40,
x1: 60,
y1: 60
}
);
s.pop();
assert_eq!(
s.current(),
ClipRect {
x0: 10,
y0: 10,
x1: 60,
y1: 60
}
);
}
#[test]
fn base_clip_never_popped() {
let mut s = ClipStack::new(10, 10);
s.pop();
s.pop();
assert_eq!(s.depth(), 1);
assert_eq!(s.current(), ClipRect::full(10, 10));
}
#[test]
fn contains_and_empty() {
let c = ClipRect::from_rect(0, 0, 5, 5);
assert!(c.contains(0, 0));
assert!(c.contains(4, 4));
assert!(!c.contains(5, 5));
assert!(ClipRect::from_rect(0, 0, 0, 5).is_empty());
let disjoint =
ClipRect::from_rect(0, 0, 5, 5).intersect(&ClipRect::from_rect(10, 10, 5, 5));
assert!(disjoint.is_empty());
}
}