diff --git a/changelog.md b/changelog.md index 12808ac830..2b4890f912 100644 --- a/changelog.md +++ b/changelog.md @@ -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. diff --git a/src/sdl2/render.rs b/src/sdl2/render.rs index e18e9a06b6..fcb35c6f10 100644 --- a/src/sdl2/render.rs +++ b/src/sdl2/render.rs @@ -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 for Rect { + fn into(self) -> ClippingRect { + ClippingRect::Some(self) + } +} + +impl Into for Option { + 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(&self, position: R) -> ClippingRect + where + R: Into>, + { + let position: Option = 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 { /// Gets a reference to the associated window of the Canvas @@ -1099,31 +1168,51 @@ impl Canvas { } /// 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>>(&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 - 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(&mut self, arg: R) + where + R: Into, + { + 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 { + 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)) } } diff --git a/tests/render.rs b/tests/render.rs new file mode 100644 index 0000000000..d7dcbd8add --- /dev/null +++ b/tests/render.rs @@ -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 + ); +}