From 66af2080004206345d41a59e1e2f2e3fd68a8324 Mon Sep 17 00:00:00 2001 From: John Giorshev Date: Sat, 8 Feb 2025 12:17:09 -0500 Subject: [PATCH] clipping rect disambiguate --- src/sdl3/render.rs | 115 ++++++++++++++++++++++++++++++++++++++++----- tests/render.rs | 82 ++++++++++++++++++++++++++++++++ 2 files changed, 184 insertions(+), 13 deletions(-) create mode 100644 tests/render.rs diff --git a/src/sdl3/render.rs b/src/sdl3/render.rs index 8b1f4e41..fd82836d 100644 --- a/src/sdl3/render.rs +++ b/src/sdl3/render.rs @@ -272,6 +272,75 @@ impl TryFrom for BlendMode { } } +#[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 + } + } + } +} + /// Manages what keeps a `SDL_Renderer` alive /// /// When the `RendererContext` is dropped, it destroys the `SDL_Renderer` @@ -1129,31 +1198,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_SetRenderClipRect")] - 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::render::SDL_SetRenderClipRect(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 { + sdl3_sys::everything::SDL_SetRenderClipRect(self.context.raw, r.raw()) + }, + ClippingRect::Zero => { + let r = sdl3_sys::everything::SDL_Rect { + x: 0, + y: 0, + w: 0, + h: 0, + }; + let r: *const sdl3_sys::everything::SDL_Rect = &r; + unsafe { sdl3_sys::everything::SDL_SetRenderClipRect(self.context.raw, r) } + } + ClippingRect::None => unsafe { + sdl3_sys::everything::SDL_SetRenderClipRect(self.context.raw, ptr::null()) + }, + }; if !ret { 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_GetRenderClipRect")] - pub fn clip_rect(&self) -> Option { + pub fn clip_rect(&self) -> ClippingRect { + let clip_enabled = unsafe { sdl3_sys::everything::SDL_RenderClipEnabled(self.context.raw) }; + + if !clip_enabled { + return ClippingRect::None; + } + let mut raw = mem::MaybeUninit::uninit(); - unsafe { sys::render::SDL_GetRenderClipRect(self.context.raw, raw.as_mut_ptr()) }; + unsafe { sdl3_sys::everything::SDL_GetRenderClipRect(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 00000000..8ca5c435 --- /dev/null +++ b/tests/render.rs @@ -0,0 +1,82 @@ +use sdl3::{rect::Rect, render::ClippingRect}; + +extern crate sdl3; + +#[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 + ); +}