Skip to content

Commit

Permalink
Merge pull request #1450 from jagprog5/clipping_rect_ambiguous
Browse files Browse the repository at this point in the history
add ClippingRect
  • Loading branch information
Cobrand authored Feb 19, 2025
2 parents 07ae517 + e7f0184 commit 3486770
Show file tree
Hide file tree
Showing 3 changed files with 185 additions and 12 deletions.
2 changes: 2 additions & 0 deletions changelog.md
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,8 @@ when upgrading from a version of rust-sdl2 to another.

### Next

[PR #1450](https://github.com/Rust-SDL2/rust-sdl2/pull/1450) **BREAKING CHANGE** Create ClippingRect type, to disambiguate between no and zero area clipping rect.

[PR #1416](https://github.com/Rust-SDL2/rust-sdl2/pull/1416) Apply clippy fixes, fix deprecations and other code quality improvements.

[PR #1408](https://github.com/Rust-SDL2/rust-sdl2/pull/1408) Allow comparing `Version`s, add constant with the version the bindings were compiled with.
Expand Down
113 changes: 101 additions & 12 deletions src/sdl2/render.rs
Original file line number Diff line number Diff line change
Expand Up @@ -406,6 +406,75 @@ impl RenderTarget for Window {
type Context = WindowContext;
}

#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub enum ClippingRect {
/// a non-zero area clipping rect
Some(Rect),
/// a clipping rect with zero area
Zero,
/// the absence of a clipping rect
None,
}

impl Into<ClippingRect> for Rect {
fn into(self) -> ClippingRect {
ClippingRect::Some(self)
}
}

impl Into<ClippingRect> for Option<Rect> {
fn into(self) -> ClippingRect {
match self {
Some(v) => v.into(),
None => ClippingRect::None,
}
}
}

impl ClippingRect {
pub fn intersection(&self, other: ClippingRect) -> ClippingRect {
match self {
ClippingRect::Zero => ClippingRect::Zero,
ClippingRect::None => other,
ClippingRect::Some(self_rect) => match other {
ClippingRect::Zero => ClippingRect::Zero,
ClippingRect::None => *self,
ClippingRect::Some(rect) => match self_rect.intersection(rect) {
Some(v) => ClippingRect::Some(v),
None => ClippingRect::Zero,
},
},
}
}

/// shrink the clipping rect to the part which contains the position
pub fn intersect_rect<R>(&self, position: R) -> ClippingRect
where
R: Into<Option<Rect>>,
{
let position: Option<Rect> = position.into();
match position {
Some(position) => {
match self {
ClippingRect::Some(rect) => match rect.intersection(position) {
Some(v) => ClippingRect::Some(v),
None => ClippingRect::Zero,
},
ClippingRect::Zero => ClippingRect::Zero,
ClippingRect::None => {
// clipping rect has infinite area, so it's just whatever position is
ClippingRect::Some(position)
}
}
}
None => {
// position is zero area so intersection result is zero
ClippingRect::Zero
}
}
}
}

/// Methods for the `WindowCanvas`.
impl Canvas<Window> {
/// Gets a reference to the associated window of the Canvas
Expand Down Expand Up @@ -1099,31 +1168,51 @@ impl<T: RenderTarget> Canvas<T> {
}

/// Sets the clip rectangle for rendering on the specified target.
///
/// If the rectangle is `None`, clipping will be disabled.
#[doc(alias = "SDL_RenderSetClipRect")]
pub fn set_clip_rect<R: Into<Option<Rect>>>(&mut self, rect: R) {
let rect = rect.into();
// as_ref is important because we need rect to live until the end of the FFI call, but map_or consumes an Option<T>
let ptr = rect.as_ref().map_or(ptr::null(), |rect| rect.raw());
let ret = unsafe { sys::SDL_RenderSetClipRect(self.context.raw, ptr) };
pub fn set_clip_rect<R>(&mut self, arg: R)
where
R: Into<ClippingRect>,
{
let arg: ClippingRect = arg.into();
let ret = match arg {
ClippingRect::Some(r) => unsafe {
sys::SDL_RenderSetClipRect(self.context.raw, r.raw())
},
ClippingRect::Zero => {
let r = sys::SDL_Rect {
x: 0,
y: 0,
w: 0,
h: 0,
};
let r: *const sys::SDL_Rect = &r;
unsafe { sys::SDL_RenderSetClipRect(self.context.raw, r) }
}
ClippingRect::None => unsafe {
sys::SDL_RenderSetClipRect(self.context.raw, ptr::null())
},
};
if ret != 0 {
panic!("Could not set clip rect: {}", get_error())
}
}

/// Gets the clip rectangle for the current target.
///
/// Returns `None` if clipping is disabled.
#[doc(alias = "SDL_RenderGetClipRect")]
pub fn clip_rect(&self) -> Option<Rect> {
pub fn clip_rect(&self) -> ClippingRect {
let clip_enabled = unsafe { sys::SDL_RenderIsClipEnabled(self.context.raw) };

if sys::SDL_bool::SDL_FALSE == clip_enabled {
return ClippingRect::None;
}

let mut raw = mem::MaybeUninit::uninit();
unsafe { sys::SDL_RenderGetClipRect(self.context.raw, raw.as_mut_ptr()) };
let raw = unsafe { raw.assume_init() };
if raw.w == 0 || raw.h == 0 {
None
ClippingRect::Zero
} else {
Some(Rect::from_ll(raw))
ClippingRect::Some(Rect::from_ll(raw))
}
}

Expand Down
82 changes: 82 additions & 0 deletions tests/render.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,82 @@
use sdl2::{rect::Rect, render::ClippingRect};

extern crate sdl2;

#[test]
fn clipping_rect_intersection() {
// a zero area clipping rect intersecting with anything else gives zero
assert_eq!(
ClippingRect::Zero.intersection(ClippingRect::Zero),
ClippingRect::Zero
);
assert_eq!(
ClippingRect::Zero.intersection(ClippingRect::None),
ClippingRect::Zero
);
assert_eq!(
ClippingRect::Zero.intersection(ClippingRect::Some(Rect::new(0, 0, 1, 1))),
ClippingRect::Zero
);

// none gives whatever the arg was
assert_eq!(
ClippingRect::None.intersection(ClippingRect::Zero),
ClippingRect::Zero
);
assert_eq!(
ClippingRect::None.intersection(ClippingRect::None),
ClippingRect::None
);
assert_eq!(
ClippingRect::None.intersection(ClippingRect::Some(Rect::new(0, 0, 1, 1))),
ClippingRect::Some(Rect::new(0, 0, 1, 1))
);

assert_eq!(
ClippingRect::Some(Rect::new(0, 0, 1, 1)).intersection(ClippingRect::Zero),
ClippingRect::Zero
);
assert_eq!(
ClippingRect::Some(Rect::new(0, 0, 1, 1)).intersection(ClippingRect::None),
ClippingRect::Some(Rect::new(0, 0, 1, 1))
);
assert_eq!(
ClippingRect::Some(Rect::new(0, 0, 10, 10))
.intersection(ClippingRect::Some(Rect::new(20, 20, 1, 1))),
ClippingRect::Zero
);

assert_eq!(
ClippingRect::Some(Rect::new(0, 0, 10, 10))
.intersection(ClippingRect::Some(Rect::new(5, 5, 10, 10))),
ClippingRect::Some(Rect::new(5, 5, 5, 5))
);
}

#[test]
fn clipping_rect_intersect_rect() {
assert_eq!(ClippingRect::Zero.intersect_rect(None), ClippingRect::Zero);
assert_eq!(
ClippingRect::Zero.intersect_rect(Rect::new(0, 0, 1, 1)),
ClippingRect::Zero
);

assert_eq!(ClippingRect::None.intersect_rect(None), ClippingRect::Zero);
assert_eq!(
ClippingRect::None.intersect_rect(Rect::new(0, 0, 1, 1)),
ClippingRect::Some(Rect::new(0, 0, 1, 1))
);

assert_eq!(
ClippingRect::Some(Rect::new(0, 0, 1, 1)).intersect_rect(None),
ClippingRect::Zero
);
assert_eq!(
ClippingRect::Some(Rect::new(0, 0, 10, 10)).intersect_rect(Rect::new(5, 5, 10, 10)),
ClippingRect::Some(Rect::new(5, 5, 5, 5))
);
assert_eq!(
ClippingRect::Some(Rect::new(0, 0, 10, 10)).intersect_rect(Rect::new(20, 20, 1, 1)),
ClippingRect::Zero
);
}

0 comments on commit 3486770

Please sign in to comment.