From 8a4c9b7571c3121408496c6b48c3abfeb25fd0cf Mon Sep 17 00:00:00 2001 From: chqoot-vgxu Date: Sat, 16 Dec 2023 17:47:39 +0100 Subject: [PATCH] Add support for FP render functions --- src/sdl2/rect.rs | 1291 +++++++++++++++++++++++++++++++++++++++----- src/sdl2/render.rs | 141 +++++ 2 files changed, 1282 insertions(+), 150 deletions(-) diff --git a/src/sdl2/rect.rs b/src/sdl2/rect.rs index 0043f2c997..4c5600b6c1 100644 --- a/src/sdl2/rect.rs +++ b/src/sdl2/rect.rs @@ -60,6 +60,14 @@ fn clamped_mul(a: i32, b: i32) -> i32 { } } +fn clamp_f32_size(val: f32) -> f32 { + if val <= 0.0 { + 1.0 + } else { + val + } +} + /// A (non-empty) rectangle. /// /// The width and height of a `Rect` must always be strictly positive (never @@ -962,206 +970,1039 @@ impl std::iter::Sum for Point { } } -#[cfg(test)] -mod test { - use super::{max_int_value, min_int_value, Point, Rect}; - - /// Used to compare "literal" (unclamped) rect values. - fn tuple(x: i32, y: i32, w: u32, h: u32) -> (i32, i32, u32, u32) { - (x, y, w, h) - } +/// A (non-empty) rectangle with float precision. +/// +/// The width and height of a `FRect` must always be strictly positive (never +/// zero). In cases where empty rects may need to be represented, it is +/// recommended to use `Option`, with `None` representing an empty +/// rectangle (see, for example, the output of the +/// [`intersection`](#method.intersection) method). +#[derive(Clone, Copy)] +pub struct FRect { + raw: sys::SDL_FRect, +} - #[test] - fn centered() { - // Tests both center_on and centered_on - assert_eq!( - Rect::new(0, 0, 10, 10).centered_on((0, 0)), - Rect::new(-5, -5, 10, 10) +impl ::std::fmt::Debug for FRect { + fn fmt(&self, fmt: &mut ::std::fmt::Formatter) -> Result<(), ::std::fmt::Error> { + return write!( + fmt, + "FRect {{ x: {}, y: {}, w: {}, h: {} }}", + self.raw.x, self.raw.y, self.raw.w, self.raw.h ); } +} - #[test] - fn enclose_points_valid() { - assert_eq!( - Some(tuple(2, 4, 4, 6)), - Rect::from_enclose_points(&[Point::new(2, 4), Point::new(5, 9)], None) - .map(|r| r.into()) - ); +impl PartialEq for FRect { + fn eq(&self, other: &FRect) -> bool { + self.raw.x == other.raw.x + && self.raw.y == other.raw.y + && self.raw.w == other.raw.w + && self.raw.h == other.raw.h } +} - #[test] - fn enclose_points_outside_clip_rect() { - assert_eq!( - Rect::from_enclose_points( - &[Point::new(0, 0), Point::new(10, 10)], - Some(Rect::new(3, 3, 1, 1)) - ), - None - ); +impl FRect { + /// Creates a new rectangle with float precision from the given values. + /// + /// `FRect`s must always be non-empty, so a `width` and/or `height` argument + /// of 0 or less will be replaced with 1. + pub fn new(x: f32, y: f32, width: f32, height: f32) -> FRect { + let raw = sys::SDL_FRect { + x, + y, + w: clamp_f32_size(width), + h: clamp_f32_size(height), + }; + FRect { raw } } - #[test] - fn enclose_points_max_values() { - // Try to enclose the top-left-most and bottom-right-most points. - assert_eq!( - Some(tuple( - min_int_value(), - min_int_value(), - max_int_value(), - max_int_value() - )), - Rect::from_enclose_points( - &[ - Point::new(i32::min_value(), i32::min_value()), - Point::new(i32::max_value(), i32::max_value()) - ], - None - ) - .map(|r| r.into()) - ); + /// Creates a new rectangle with float precision centered on the given position. + /// + /// `FRect`s must always be non-empty, so a `width` and/or `height` argument + /// of 0 or less will be replaced with 1. + pub fn from_center

(center: P, width: f32, height: f32) -> FRect + where + P: Into, + { + let raw = sys::SDL_FRect { + x: 0.0, + y: 0.0, + w: clamp_f32_size(width), + h: clamp_f32_size(height), + }; + let mut rect = FRect { raw }; + rect.center_on(center.into()); + rect } - #[test] - fn has_intersection() { - let rect = Rect::new(0, 0, 10, 10); - assert!(rect.has_intersection(Rect::new(9, 9, 10, 10))); - // edge - assert!(!rect.has_intersection(Rect::new(10, 10, 10, 10))); - // out - assert!(!rect.has_intersection(Rect::new(11, 11, 10, 10))); + /// The horizontal position of this rectangle. + #[inline] + pub fn x(&self) -> f32 { + self.raw.x } - #[test] - fn intersection() { - let rect = Rect::new(0, 0, 10, 10); - assert_eq!(rect & Rect::new(9, 9, 10, 10), Some(Rect::new(9, 9, 1, 1))); - assert_eq!(rect & Rect::new(11, 11, 10, 10), None); + /// The vertical position of this rectangle. + #[inline] + pub fn y(&self) -> f32 { + self.raw.y } - #[test] - fn union() { - assert_eq!( - Rect::new(0, 0, 1, 1) | Rect::new(9, 9, 1, 1), - Rect::new(0, 0, 10, 10) - ); + /// The width of this rectangle. + pub fn width(&self) -> f32 { + self.raw.w as f32 } - #[test] - fn intersect_line() { - assert_eq!( - Rect::new(1, 1, 5, 5).intersect_line(Point::new(0, 0), Point::new(10, 10)), - Some((Point::new(1, 1), Point::new(5, 5))) - ); + /// The height of this rectangle. + pub fn height(&self) -> f32 { + self.raw.h as f32 } - #[test] - fn clamp_size_zero() { - assert_eq!(tuple(0, 0, 1, 1), Rect::new(0, 0, 0, 0).into()); + /// Returns the width and height of this rectangle. + pub fn size(&self) -> (f32, f32) { + (self.width(), self.height()) } - #[test] - fn clamp_position_min() { - assert_eq!( - tuple(min_int_value(), min_int_value(), 1, 1), - Rect::new(i32::min_value(), i32::min_value(), 1, 1).into() - ); + /// Sets the horizontal position of this rectangle to the given value, + /// clamped to be less than or equal to i32::max_value() / 2. + pub fn set_x(&mut self, x: f32) { + self.raw.x = x; } - #[test] - fn clamp_size_max() { - assert_eq!( - tuple(0, 0, max_int_value(), max_int_value()), - Rect::new(0, 0, max_int_value() + 1, max_int_value() + 1).into() - ); + /// Sets the vertical position of this rectangle to the given value, + /// clamped to be less than or equal to i32::max_value() / 2. + pub fn set_y(&mut self, y: f32) { + self.raw.y = y; } - #[test] - fn clamp_i32_max() { - assert_eq!( - tuple(0, 0, max_int_value(), max_int_value()), - Rect::new(0, 0, i32::max_value() as u32, i32::max_value() as u32).into() - ) + /// Sets the width of this rectangle to the given value, + /// clamped to be less than or equal to i32::max_value() / 2. + /// + /// `FRect`s must always be non-empty, so a `width` argument of 0 will be + /// replaced with 1. + pub fn set_width(&mut self, width: f32) { + self.raw.w = clamp_f32_size(width); } - #[test] - fn clamp_position_max() { - assert_eq!( - tuple(max_int_value() as i32, max_int_value() as i32, 1, 1), - Rect::new(max_int_value() as i32 + 1, max_int_value() as i32 + 1, 1, 1).into() - ); + /// Sets the height of this rectangle to the given value, + /// clamped to be less than or equal to i32::max_value() / 2. + /// + /// `FRect`s must always be non-empty, so a `height` argument of 0 will be + /// replaced with 1. + pub fn set_height(&mut self, height: f32) { + self.raw.h = clamp_f32_size(height); } - #[test] - fn shifted() { - // Groups all functions into a single assertion - let rect = Rect::new(5, 5, 10, 10) - .left_shifted(5) - .right_shifted(5) - .top_shifted(5) - .bottom_shifted(5); - assert_eq!(rect, Rect::new(5, 5, 10, 10)); + /// Returns the x-position of the left side of this rectangle. + pub fn left(&self) -> f32 { + self.raw.x } - #[test] - fn rect_into() { - let test: (i32, i32, u32, u32) = (-11, 5, 50, 20); - assert_eq!(test, Rect::new(-11, 5, 50, 20).into()); + /// Returns the x-position of the right side of this rectangle. + pub fn right(&self) -> f32 { + self.raw.x + self.raw.w } - #[test] - fn rect_from() { - assert_eq!(Rect::from((-11, 5, 50, 20)), Rect::new(-11, 5, 50, 20)); + /// Returns the y-position of the top side of this rectangle. + pub fn top(&self) -> f32 { + self.raw.y } - #[test] - fn point_into() { - let test: (i32, i32) = (-11, 5); - assert_eq!(test, Point::new(-11, 5).into()); + /// Returns the y-position of the bottom side of this rectangle. + pub fn bottom(&self) -> f32 { + self.raw.y + self.raw.h } - #[test] - fn point_from() { - let test: (i32, i32) = (-11, 5); - assert_eq!(test, Point::new(-11, 5).into()); + /// Shifts this rectangle to the left by `offset`. + /// + /// # Example + /// + /// ``` + /// use sdl2::rect::FRect; + /// assert_eq!(FRect::new(0.0, 0.0, 10.0, 10.0).left_shifted(5.0), FRect::new(-5.0, 0.0, 10.0, 10.0)); + /// ``` + pub fn left_shifted(mut self, offset: f32) -> FRect { + self.offset(-offset, self.y()); + self } - #[test] - fn point_add() { - assert_eq!(Point::new(-5, 7), Point::new(-11, 5) + Point::new(6, 2)); + /// Shifts this rectangle to the right by `offset`. + /// + /// # Example + /// + /// ``` + /// use sdl2::rect::FRect; + /// assert_eq!(FRect::new(0.0, 0.0, 10.0, 10.0).right_shifted(5.0), FRect::new(5.0, 0.0, 10.0, 10.0)); + /// ``` + pub fn right_shifted(mut self, offset: f32) -> FRect { + self.offset(offset, self.y()); + self } - #[test] - fn point_add_assign() { - let mut point = Point::new(-11, 5); - point += Point::new(6, 2); - assert_eq!(point, Point::new(-11, 5) + Point::new(6, 2)); + /// Shifts this rectangle to the top by `offset`. + /// + /// # Example + /// + /// ``` + /// use sdl2::rect::FRect; + /// assert_eq!(FRect::new(0.0, 0.0, 10.0, 10.0).top_shifted(5.00), FRect::new(0.0, -5.0, 10.0, 10.0)); + /// ``` + pub fn top_shifted(mut self, offset: f32) -> FRect { + self.offset(self.x(), -offset); + self } - #[test] - fn point_sub() { - assert_eq!(Point::new(-17, 3), Point::new(-11, 5) - Point::new(6, 2)); + /// Shifts this rectangle to the bottom by `offset`. + /// + /// # Example + /// + /// ``` + /// use sdl2::rect::FRect; + /// assert_eq!(FRect::new(0.0, 0.0, 10.0, 10.0).bottom_shifted(5.0), FRect::new(0.0, 5.0, 10.0, 10.0)); + /// ``` + pub fn bottom_shifted(mut self, offset: f32) -> FRect { + self.offset(self.x(), offset); + self } - #[test] - fn point_sub_assign() { - let mut point = Point::new(-11, 5); - point -= Point::new(6, 2); - assert_eq!(point, Point::new(-11, 5) - Point::new(6, 2)); + /// Returns the center position of this rectangle. + /// + /// Note that if the width or height is not a multiple of two, + /// the center will be rounded down. + /// + /// # Example + /// + /// ``` + /// use sdl2::rect::{FRect, FPoint}; + /// let rect = FRect::new(1.0, 0.0, 2.0, 3.0); + /// assert_eq!(FPoint::new(2.0, 1.5), rect.center()); + /// ``` + pub fn center(&self) -> FPoint { + let x = self.raw.x + (self.raw.w / 2.0); + let y = self.raw.y + (self.raw.h / 2.0); + FPoint::new(x, y) } - #[test] - fn point_mul() { - assert_eq!(Point::new(-33, 15), Point::new(-11, 5) * 3); + /// Returns the top-left corner of this rectangle. + /// + /// # Example + /// + /// ``` + /// use sdl2::rect::{FRect, FPoint}; + /// let rect = FRect::new(1.0, 0.0, 2.0, 3.0); + /// assert_eq!(FPoint::new(1.0, 0.0), rect.top_left()); + /// ``` + pub fn top_left(&self) -> FPoint { + FPoint::new(self.left(), self.top()) } - #[test] - fn point_mul_assign() { - let mut point = Point::new(-11, 5); - point *= 3; - assert_eq!(point, Point::new(-11, 5) * 3); + /// Returns the top-right corner of this rectangle. + /// + /// # Example + /// + /// ``` + /// use sdl2::rect::{FRect, FPoint}; + /// let rect = FRect::new(1.0, 0.0, 2.0, 3.0); + /// assert_eq!(FPoint::new(3.0, 0.0), rect.top_right()); + /// ``` + pub fn top_right(&self) -> FPoint { + FPoint::new(self.right(), self.top()) } - #[test] + /// Returns the bottom-left corner of this rectangle. + /// + /// # Example + /// + /// ``` + /// use sdl2::rect::{FRect, FPoint}; + /// let rect = FRect::new(1.0, 0.0, 2.0, 3.0); + /// assert_eq!(FPoint::new(1.0, 3.0), rect.bottom_left()); + /// ``` + pub fn bottom_left(&self) -> FPoint { + FPoint::new(self.left(), self.bottom()) + } + + /// Returns the bottom-right corner of this rectangle. + /// + /// # Example + /// + /// ``` + /// use sdl2::rect::{FRect, FPoint}; + /// let rect = FRect::new(1.0, 0.0, 2.0, 3.0); + /// assert_eq!(FPoint::new(3.0, 3.0), rect.bottom_right()); + /// ``` + pub fn bottom_right(&self) -> FPoint { + FPoint::new(self.right(), self.bottom()) + } + + /// Sets the position of the right side of this rectangle to the given + /// value, clamped to be greater than 0. + pub fn set_right(&mut self, right: f32) { + self.raw.x = clamp_f32_size(clamp_f32_size(right) - self.raw.w); + } + + /// Sets the position of the bottom side of this rectangle to the given + /// value, clamped to be greater than 0. + pub fn set_bottom(&mut self, bottom: f32) { + self.raw.y = clamp_f32_size(clamp_f32_size(bottom) - self.raw.h); + } + + /// Centers the rectangle on the given point (in place). + #[inline] + pub fn center_on

(&mut self, point: P) + where + P: Into<(f32, f32)>, + { + let (x, y) = point.into(); + self.raw.x = x - self.raw.w / 2.0; + self.raw.y = y - self.raw.h / 2.0; + } + + /// Centers the rectangle on the given point. + #[inline] + pub fn centered_on

(mut self, point: P) -> FRect + where + P: Into<(f32, f32)>, + { + self.center_on(point); + self + } + + /// Move this rect. + #[inline] + pub fn offset(&mut self, x: f32, y: f32) { + self.raw.x += x; + self.raw.y += y; + } + + /// Moves this rect to the given position. + pub fn reposition

(&mut self, point: P) + where + P: Into<(f32, f32)>, + { + let (x, y) = point.into(); + self.raw.x = x; + self.raw.y = y; + } + + /// Resizes this rect to the given size after clamping the values. + pub fn resize(&mut self, width: f32, height: f32) { + self.raw.w = clamp_f32_size(width); + self.raw.h = clamp_f32_size(height); + } + + /// Checks whether this rectangle contains a given point. + /// + /// Points along the right and bottom edges are not considered to be inside + /// the rectangle. Another way to look at it is that this method returns true if + /// and only if the given point would be painted by a call to + /// [`Renderer::fill_frect`]( + /// ../render/struct.Renderer.html#method.fill_frect). + /// + /// # Examples + /// + /// ``` + /// use sdl2::rect::{FRect, FPoint}; + /// let rect = FRect::new(1.0, 2.0, 3.0, 4.0); + /// assert!(rect.contains_point(FPoint::new(1.0, 2.0))); + /// assert!(!rect.contains_point(FPoint::new(0.0, 1.0))); + /// assert!(rect.contains_point(FPoint::new(3.0, 5.0))); + /// assert!(!rect.contains_point(FPoint::new(4.0, 6.0))); + /// ``` + pub fn contains_point

(&self, point: P) -> bool + where + P: Into<(f32, f32)>, + { + let (x, y) = point.into(); + let inside_x = x >= self.left() && x < self.right(); + inside_x && (y >= self.top() && y < self.bottom()) + } + + /// Checks whether this rectangle completely contains another rectangle. + /// + /// This method returns true if and only if every point contained by + /// `other` is also contained by `self`; in other words, if the + /// intersection of `self` and `other` is equal to `other`. + /// + /// # Examples + /// + /// ``` + /// use sdl2::rect::FRect; + /// let rect = FRect::new(1.0, 2.0, 3.0, 4.0); + /// assert!(rect.contains_rect(rect)); + /// assert!(rect.contains_rect(FRect::new(3.0, 3.0, 1.0, 1.0))); + /// assert!(!rect.contains_rect(FRect::new(2.0, 1.0, 1.0, 1.0))); + /// assert!(!rect.contains_rect(FRect::new(3.0, 3.0, 2.0, 1.0))); + /// ``` + pub fn contains_rect(&self, other: FRect) -> bool { + other.left() >= self.left() + && other.right() <= self.right() + && other.top() >= self.top() + && other.bottom() <= self.bottom() + } + + /// Returns the underlying C FRect. + // this can prevent introducing UB until + // https://github.com/rust-lang/rust-clippy/issues/5953 is fixed + #[allow(clippy::trivially_copy_pass_by_ref)] + pub fn raw(&self) -> *const sys::SDL_FRect { + &self.raw + } + + pub fn raw_mut(&mut self) -> *mut sys::SDL_FRect { + self.raw() as *mut _ + } + + #[doc(alias = "SDL_FRect")] + pub fn raw_slice(slice: &[FRect]) -> *const sys::SDL_FRect { + slice.as_ptr() as *const sys::SDL_FRect + } + + pub fn from_ll(raw: sys::SDL_FRect) -> FRect { + FRect::new(raw.x, raw.y, raw.w, raw.h) + } + + /// Calculate a minimal rectangle enclosing a set of points. + /// If a clipping rectangle is given, only points that are within it will be + /// considered. + #[doc(alias = "SDL_EncloseFPoints")] + pub fn from_enclose_points>>( + points: &[FPoint], + clipping_rect: R, + ) -> Option + where + R: Into>, + { + let clipping_rect = clipping_rect.into(); + + if points.is_empty() { + return None; + } + + let mut out = mem::MaybeUninit::uninit(); + + let clip_ptr = match clipping_rect.as_ref() { + Some(r) => r.raw(), + None => ptr::null(), + }; + + let result = unsafe { + sys::SDL_EncloseFPoints( + FPoint::raw_slice(points), + points.len() as i32, + clip_ptr, + out.as_mut_ptr(), + ) != sys::SDL_bool::SDL_FALSE + }; + + if result { + let out = unsafe { out.assume_init() }; + + // Return an error if the dimensions are too large. + Some(FRect::from_ll(out)) + } else { + None + } + } + + /// Determines whether two rectangles intersect. + /// + /// Rectangles that share an edge but don't actually overlap are not + /// considered to intersect. + /// + /// # Examples + /// + /// ``` + /// use sdl2::rect::FRect; + /// let rect = FRect::new(0.0, 0.0, 5.0, 5.0); + /// assert!(rect.has_intersection(rect)); + /// assert!(rect.has_intersection(FRect::new(2.0, 2.0, 5.0, 5.0))); + /// assert!(!rect.has_intersection(FRect::new(5.0, 0.0, 5.0, 5.0))); + /// ``` + #[doc(alias = "SDL_HasIntersectionF")] + pub fn has_intersection(&self, other: FRect) -> bool { + unsafe { sys::SDL_HasIntersectionF(self.raw(), other.raw()) != sys::SDL_bool::SDL_FALSE } + } + + /// Calculates the intersection of two rectangles. + /// + /// Returns `None` if the two rectangles don't intersect. Rectangles that + /// share an edge but don't actually overlap are not considered to + /// intersect. + /// + /// The bitwise AND operator `&` can also be used. + /// + /// # Examples + /// + /// ``` + /// use sdl2::rect::FRect; + /// let rect = FRect::new(0.0, 0.0, 5.0, 5.0); + /// assert_eq!(rect.intersection(rect), Some(rect)); + /// assert_eq!(rect.intersection(FRect::new(2.0, 2.0, 5.0, 5.0)), + /// Some(FRect::new(2.0, 2.0, 3.0, 3.0))); + /// assert_eq!(rect.intersection(FRect::new(5.0, 0.0, 5.0, 5.0)), None); + /// ``` + #[doc(alias = "SDL_IntersectFRect")] + pub fn intersection(&self, other: FRect) -> Option { + let mut out = mem::MaybeUninit::uninit(); + + let success = unsafe { + sys::SDL_IntersectFRect(self.raw(), other.raw(), out.as_mut_ptr()) + != sys::SDL_bool::SDL_FALSE + }; + + if success { + let out = unsafe { out.assume_init() }; + Some(FRect::from_ll(out)) + } else { + None + } + } + + /// Calculates the union of two rectangles (i.e. the smallest rectangle + /// that contains both). + /// + /// The bitwise OR operator `|` can also be used. + /// + /// # Examples + /// + /// ``` + /// use sdl2::rect::FRect; + /// let rect = FRect::new(0.0, 0.0, 5.0, 5.0); + /// assert_eq!(rect.union(rect), rect); + /// assert_eq!(rect.union(FRect::new(2.0, 2.0, 5.0, 5.0)), FRect::new(0.0, 0.0, 7.0, 7.0)); + /// assert_eq!(rect.union(FRect::new(5.0, 0.0, 5.0, 5.0)), FRect::new(0.0, 0.0, 10.0, 5.0)); + /// ``` + #[doc(alias = "SDL_UnionFRect")] + pub fn union(&self, other: FRect) -> FRect { + let mut out = mem::MaybeUninit::uninit(); + + unsafe { + // If `self` and `other` are both empty, `out` remains uninitialized. + // Because empty rectangles aren't allowed in Rect, we don't need to worry about this. + sys::SDL_UnionFRect(self.raw(), other.raw(), out.as_mut_ptr()) + }; + + let out = unsafe { out.assume_init() }; + + FRect::from_ll(out) + } + + /// Calculates the intersection of a rectangle and a line segment and + /// returns the points of their intersection. + #[doc(alias = "SDL_IntersectFRectAndLine")] + pub fn intersect_line(&self, start: FPoint, end: FPoint) -> Option<(FPoint, FPoint)> { + let (mut start_x, mut start_y) = (start.x(), start.y()); + let (mut end_x, mut end_y) = (end.x(), end.y()); + + let intersected = unsafe { + sys::SDL_IntersectFRectAndLine( + self.raw(), + &mut start_x, + &mut start_y, + &mut end_x, + &mut end_y, + ) != sys::SDL_bool::SDL_FALSE + }; + + if intersected { + Some((FPoint::new(start_x, start_y), FPoint::new(end_x, end_y))) + } else { + None + } + } +} + +impl Deref for FRect { + type Target = sys::SDL_FRect; + + /// # Example + /// + /// ```rust + /// use sdl2::rect::FRect; + /// let rect = FRect::new(2.0, 3.0, 4.0, 5.0); + /// assert_eq!(2.0, rect.x); + /// ``` + fn deref(&self) -> &sys::SDL_FRect { + &self.raw + } +} + +impl DerefMut for FRect { + /// # Example + /// + /// ```rust + /// use sdl2::rect::FRect; + /// let mut rect = FRect::new(2.0, 3.0, 4.0, 5.0); + /// rect.x = 60.0; + /// assert_eq!(60.0, rect.x); + /// ``` + fn deref_mut(&mut self) -> &mut sys::SDL_FRect { + &mut self.raw + } +} + +impl Into for FRect { + fn into(self) -> sys::SDL_FRect { + self.raw + } +} + +impl Into<(f32, f32, f32, f32)> for FRect { + fn into(self) -> (f32, f32, f32, f32) { + (self.raw.x, self.raw.y, self.raw.w, self.raw.h) + } +} + +impl From for FRect { + fn from(raw: sys::SDL_FRect) -> FRect { + FRect { raw } + } +} + +impl From<(f32, f32, f32, f32)> for FRect { + fn from((x, y, width, height): (f32, f32, f32, f32)) -> FRect { + FRect::new(x, y, width, height) + } +} + +impl AsRef for FRect { + fn as_ref(&self) -> &sys::SDL_FRect { + &self.raw + } +} + +impl AsMut for FRect { + fn as_mut(&mut self) -> &mut sys::SDL_FRect { + &mut self.raw + } +} + +// Intersection +impl BitAnd for FRect { + type Output = Option; + #[doc(alias = "SDL_FPoint")] + fn bitand(self, rhs: FRect) -> Option { + self.intersection(rhs) + } +} + +// Union +impl BitOr for FRect { + type Output = FRect; + fn bitor(self, rhs: FRect) -> FRect { + self.union(rhs) + } +} + +/// Immutable point type with float precision, consisting of x and y. +#[derive(Copy, Clone)] +pub struct FPoint { + raw: sys::SDL_FPoint, +} + +impl ::std::fmt::Debug for FPoint { + fn fmt(&self, fmt: &mut ::std::fmt::Formatter) -> Result<(), ::std::fmt::Error> { + return write!(fmt, "FPoint {{ x: {}, y: {} }}", self.raw.x, self.raw.y); + } +} + +impl PartialEq for FPoint { + fn eq(&self, other: &FPoint) -> bool { + self.raw.x == other.raw.x && self.raw.y == other.raw.y + } +} + +impl Deref for FPoint { + type Target = sys::SDL_FPoint; + + /// # Example + /// + /// ```rust + /// use sdl2::rect::FPoint; + /// let point = FPoint::new(2.0, 3.0); + /// assert_eq!(2.0, point.x); + /// ``` + fn deref(&self) -> &sys::SDL_FPoint { + &self.raw + } +} + +impl DerefMut for FPoint { + /// # Example + /// + /// ```rust + /// use sdl2::rect::FPoint; + /// let mut point = FPoint::new(2.0, 3.0); + /// point.x = 4.0; + /// assert_eq!(4.0, point.x); + /// ``` + fn deref_mut(&mut self) -> &mut sys::SDL_FPoint { + &mut self.raw + } +} + +impl AsRef for FPoint { + fn as_ref(&self) -> &sys::SDL_FPoint { + &self.raw + } +} + +impl AsMut for FPoint { + fn as_mut(&mut self) -> &mut sys::SDL_FPoint { + &mut self.raw + } +} + +impl From for FPoint { + fn from(prim: sys::SDL_FPoint) -> FPoint { + FPoint { raw: prim } + } +} + +impl From<(f32, f32)> for FPoint { + fn from((x, y): (f32, f32)) -> FPoint { + FPoint::new(x, y) + } +} + +impl Into for FPoint { + fn into(self) -> sys::SDL_FPoint { + self.raw + } +} + +impl Into<(f32, f32)> for FPoint { + fn into(self) -> (f32, f32) { + (self.x(), self.y()) + } +} + +impl FPoint { + /// Creates a new point from the given coordinates. + pub fn new(x: f32, y: f32) -> FPoint { + FPoint { + raw: sys::SDL_FPoint { + x, + y, + }, + } + } + + pub fn from_ll(raw: sys::SDL_FPoint) -> FPoint { + FPoint::new(raw.x, raw.y) + } + + #[doc(alias = "SDL_FPoint")] + pub fn raw_slice(slice: &[FPoint]) -> *const sys::SDL_FPoint { + slice.as_ptr() as *const sys::SDL_FPoint + } + + // this can prevent introducing UB until + // https://github.com/rust-lang/rust-clippy/issues/5953 is fixed + #[allow(clippy::trivially_copy_pass_by_ref)] + pub fn raw(&self) -> *const sys::SDL_FPoint { + &self.raw + } + + /// Returns a new point by shifting this point's coordinates by the given + /// x and y values. + pub fn offset(self, x: f32, y: f32) -> FPoint { + let x = self.raw.x + x; + let y = self.raw.y + y; + FPoint::new(x, y) + } + + /// Returns a new point by multiplying this point's coordinates by the + /// given scale factor. + pub fn scale(self, f: f32) -> FPoint { + FPoint::new(self.raw.x * f, self.raw.y * f) + } + + /// Returns the x-coordinate of this point. + pub fn x(self) -> f32 { + self.raw.x + } + + /// Returns the y-coordinate of this point. + pub fn y(self) -> f32 { + self.raw.y + } +} + +impl Add for FPoint { + type Output = FPoint; + + fn add(self, rhs: FPoint) -> FPoint { + self.offset(rhs.x(), rhs.y()) + } +} + +impl AddAssign for FPoint { + fn add_assign(&mut self, rhs: FPoint) { + self.raw.x = self.x() + rhs.x(); + self.raw.y = self.y() + rhs.y(); + } +} + +impl Neg for FPoint { + type Output = FPoint; + + fn neg(self) -> FPoint { + FPoint::new(-self.x(), -self.y()) + } +} + +impl Sub for FPoint { + type Output = FPoint; + + fn sub(self, rhs: FPoint) -> FPoint { + self.offset(-rhs.x(), -rhs.y()) + } +} + +impl SubAssign for FPoint { + fn sub_assign(&mut self, rhs: FPoint) { + self.raw.x = self.x() - rhs.x(); + self.raw.y = self.y() - rhs.y(); + } +} + +impl Mul for FPoint { + type Output = FPoint; + + fn mul(self, rhs: f32) -> FPoint { + self.scale(rhs) + } +} + +impl MulAssign for FPoint { + fn mul_assign(&mut self, rhs: f32) { + self.raw.x = self.x() * rhs; + self.raw.y = self.y() * rhs; + } +} + +impl Div for FPoint { + type Output = FPoint; + + fn div(self, rhs: f32) -> FPoint { + FPoint::new(self.x() / rhs, self.y() / rhs) + } +} + +impl DivAssign for FPoint { + fn div_assign(&mut self, rhs: f32) { + self.raw.x /= rhs; + self.raw.y /= rhs; + } +} + +impl std::iter::Sum for FPoint { + fn sum>(iter: I) -> Self { + iter.fold(FPoint::new(0.0, 0.0), FPoint::add) + } +} + +#[cfg(test)] +mod test { + use super::{max_int_value, min_int_value, Point, Rect, FPoint, FRect}; + + /// Used to compare "literal" (unclamped) rect values. + fn tuple(x: i32, y: i32, w: u32, h: u32) -> (i32, i32, u32, u32) { + (x, y, w, h) + } + + #[test] + fn centered() { + // Tests both center_on and centered_on + assert_eq!( + Rect::new(0, 0, 10, 10).centered_on((0, 0)), + Rect::new(-5, -5, 10, 10) + ); + } + + #[test] + fn enclose_points_valid() { + assert_eq!( + Some(tuple(2, 4, 4, 6)), + Rect::from_enclose_points(&[Point::new(2, 4), Point::new(5, 9)], None) + .map(|r| r.into()) + ); + } + + #[test] + fn enclose_points_outside_clip_rect() { + assert_eq!( + Rect::from_enclose_points( + &[Point::new(0, 0), Point::new(10, 10)], + Some(Rect::new(3, 3, 1, 1)) + ), + None + ); + } + + #[test] + fn enclose_points_max_values() { + // Try to enclose the top-left-most and bottom-right-most points. + assert_eq!( + Some(tuple( + min_int_value(), + min_int_value(), + max_int_value(), + max_int_value() + )), + Rect::from_enclose_points( + &[ + Point::new(i32::min_value(), i32::min_value()), + Point::new(i32::max_value(), i32::max_value()) + ], + None + ) + .map(|r| r.into()) + ); + } + + #[test] + fn has_intersection() { + let rect = Rect::new(0, 0, 10, 10); + assert!(rect.has_intersection(Rect::new(9, 9, 10, 10))); + // edge + assert!(!rect.has_intersection(Rect::new(10, 10, 10, 10))); + // out + assert!(!rect.has_intersection(Rect::new(11, 11, 10, 10))); + } + + #[test] + fn intersection() { + let rect = Rect::new(0, 0, 10, 10); + assert_eq!(rect & Rect::new(9, 9, 10, 10), Some(Rect::new(9, 9, 1, 1))); + assert_eq!(rect & Rect::new(11, 11, 10, 10), None); + } + + #[test] + fn union() { + assert_eq!( + Rect::new(0, 0, 1, 1) | Rect::new(9, 9, 1, 1), + Rect::new(0, 0, 10, 10) + ); + } + + #[test] + fn intersect_line() { + assert_eq!( + Rect::new(1, 1, 5, 5).intersect_line(Point::new(0, 0), Point::new(10, 10)), + Some((Point::new(1, 1), Point::new(5, 5))) + ); + } + + #[test] + fn clamp_size_zero() { + assert_eq!(tuple(0, 0, 1, 1), Rect::new(0, 0, 0, 0).into()); + } + + #[test] + fn clamp_position_min() { + assert_eq!( + tuple(min_int_value(), min_int_value(), 1, 1), + Rect::new(i32::min_value(), i32::min_value(), 1, 1).into() + ); + } + + #[test] + fn clamp_size_max() { + assert_eq!( + tuple(0, 0, max_int_value(), max_int_value()), + Rect::new(0, 0, max_int_value() + 1, max_int_value() + 1).into() + ); + } + + #[test] + fn clamp_i32_max() { + assert_eq!( + tuple(0, 0, max_int_value(), max_int_value()), + Rect::new(0, 0, i32::max_value() as u32, i32::max_value() as u32).into() + ) + } + + #[test] + fn clamp_position_max() { + assert_eq!( + tuple(max_int_value() as i32, max_int_value() as i32, 1, 1), + Rect::new(max_int_value() as i32 + 1, max_int_value() as i32 + 1, 1, 1).into() + ); + } + + #[test] + fn shifted() { + // Groups all functions into a single assertion + let rect = Rect::new(5, 5, 10, 10) + .left_shifted(5) + .right_shifted(5) + .top_shifted(5) + .bottom_shifted(5); + assert_eq!(rect, Rect::new(5, 5, 10, 10)); + } + + #[test] + fn rect_into() { + let test: (i32, i32, u32, u32) = (-11, 5, 50, 20); + assert_eq!(test, Rect::new(-11, 5, 50, 20).into()); + } + + #[test] + fn rect_from() { + assert_eq!(Rect::from((-11, 5, 50, 20)), Rect::new(-11, 5, 50, 20)); + } + + #[test] + fn point_into() { + let test: (i32, i32) = (-11, 5); + assert_eq!(test, Point::new(-11, 5).into()); + } + + #[test] + fn point_from() { + let test: (i32, i32) = (-11, 5); + assert_eq!(test, Point::new(-11, 5).into()); + } + + #[test] + fn point_add() { + assert_eq!(Point::new(-5, 7), Point::new(-11, 5) + Point::new(6, 2)); + } + + #[test] + fn point_add_assign() { + let mut point = Point::new(-11, 5); + point += Point::new(6, 2); + assert_eq!(point, Point::new(-11, 5) + Point::new(6, 2)); + } + + #[test] + fn point_sub() { + assert_eq!(Point::new(-17, 3), Point::new(-11, 5) - Point::new(6, 2)); + } + + #[test] + fn point_sub_assign() { + let mut point = Point::new(-11, 5); + point -= Point::new(6, 2); + assert_eq!(point, Point::new(-11, 5) - Point::new(6, 2)); + } + + #[test] + fn point_mul() { + assert_eq!(Point::new(-33, 15), Point::new(-11, 5) * 3); + } + + #[test] + fn point_mul_assign() { + let mut point = Point::new(-11, 5); + point *= 3; + assert_eq!(point, Point::new(-11, 5) * 3); + } + + #[test] fn point_mul_clamp() { assert_eq!( Point::new(0x7fffffff, -0x7fffffff), @@ -1193,4 +2034,154 @@ mod test { let points_sum: Point = vec![Point::new(-11, 5), Point::new(6, 2)].into_iter().sum(); assert_eq!(Point::new(-5, 7), points_sum); } + + #[test] + fn frect_centered() { + // Tests both center_on and centered_on + assert_eq!( + FRect::new(0.0, 0.0, 10.0, 10.0).centered_on((0.0, 0.0)), + FRect::new(-5.0, -5.0, 10.0, 10.0) + ); + } + + #[test] + fn frect_enclose_points_valid() { + assert_eq!( + Some((2.0, 4.0, 4.0, 6.0)), + FRect::from_enclose_points(&[FPoint::new(2.0, 4.0), FPoint::new(5.0, 9.0)], None) + .map(|r| r.into()) + ); + } + + #[test] + fn frect_enclose_points_outside_clip_rect() { + assert_eq!( + FRect::from_enclose_points( + &[FPoint::new(0.0, 0.0), FPoint::new(10.0, 10.0)], + Some(FRect::new(3.0, 3.0, 1.0, 1.0)) + ), + None + ); + } + + #[test] + fn frect_has_intersection() { + let rect = FRect::new(0.0, 0.0, 10.0, 10.0); + assert!(rect.has_intersection(FRect::new(9.0, 9.0, 10.0, 10.0))); + // edge + assert!(!rect.has_intersection(FRect::new(10.0, 10.0, 10.0, 10.0))); + // out + assert!(!rect.has_intersection(FRect::new(11.0, 11.0, 10.0, 10.0))); + } + + #[test] + fn frect_intersection() { + let rect = FRect::new(0.0, 0.0, 10.0, 10.0); + assert_eq!(rect & FRect::new(9.0, 9.0, 10.0, 10.0), Some(FRect::new(9.0, 9.0, 1.0, 1.0))); + assert_eq!(rect & FRect::new(11.0, 11.0, 10.0, 10.0), None); + } + + #[test] + fn frect_union() { + assert_eq!( + FRect::new(0.0, 0.0, 1.0, 1.0) | FRect::new(9.0, 9.0, 1.0, 1.0), + FRect::new(0.0, 0.0, 10.0, 10.0) + ); + } + + #[test] + fn frect_intersect_line() { + assert_eq!( + FRect::new(1.0, 1.0, 5.0, 5.0).intersect_line(FPoint::new(0.0, 0.0), FPoint::new(10.0, 10.0)), + Some((FPoint::new(1.0, 1.0), FPoint::new(5.0, 5.0))) + ); + } + + #[test] + fn frect_shifted() { + // Groups all functions into a single assertion + let rect = FRect::new(0.0, 0.0, 10.0, 10.0) + .left_shifted(5.0) + .right_shifted(5.0) + .top_shifted(5.0) + .bottom_shifted(5.0); + assert_eq!(rect, FRect::new(0.0, 0.0, 10.0, 10.0)); + } + + #[test] + fn frect_into() { + let test: (f32, f32, f32, f32) = (-11.0, 5.0, 50.0, 20.0); + assert_eq!(test, FRect::new(-11.0, 5.0, 50.0, 20.0).into()); + } + + #[test] + fn frect_from() { + assert_eq!(FRect::from((-11.0, 5.0, 50.0, 20.0)), FRect::new(-11.0, 5.0, 50.0, 20.0)); + } + + #[test] + fn fpoint_into() { + let test: (f32, f32) = (-11.0, 5.0); + assert_eq!(test, FPoint::new(-11.0, 5.0).into()); + } + + #[test] + fn fpoint_from() { + let test: (f32, f32) = (-11.0, 5.0); + assert_eq!(test, FPoint::new(-11.0, 5.0).into()); + } + + #[test] + fn fpoint_add() { + assert_eq!(FPoint::new(-5.0, 7.0), FPoint::new(-11.0, 5.0) + FPoint::new(6.0, 2.0)); + } + + #[test] + fn fpoint_add_assign() { + let mut point = FPoint::new(-11.0, 5.0); + point += FPoint::new(6.0, 2.0); + assert_eq!(point, FPoint::new(-11.0, 5.0) + FPoint::new(6.0, 2.0)); + } + + #[test] + fn fpoint_sub() { + assert_eq!(FPoint::new(-17.0, 3.0), FPoint::new(-11.0, 5.0) - FPoint::new(6.0, 2.0)); + } + + #[test] + fn fpoint_sub_assign() { + let mut point = FPoint::new(-11.0, 5.0); + point -= FPoint::new(6.0, 2.0); + assert_eq!(point, FPoint::new(-11.0, 5.0) - FPoint::new(6.0, 2.0)); + } + + #[test] + fn fpoint_mul() { + assert_eq!(FPoint::new(-33.0, 15.0), FPoint::new(-11.0, 5.0) * 3.0); + } + + #[test] + fn fpoint_mul_assign() { + let mut point = FPoint::new(-11.0, 5.0); + point *= 3.0; + assert_eq!(point, FPoint::new(-11.0, 5.0) * 3.0); + } + + #[test] + fn fpoint_div() { + assert_eq!(FPoint::new(-3.0, 1.0), FPoint::new(-9.0, 3.0) / 3.0); + } + + #[test] + fn fpoint_div_assign() { + let mut point = FPoint::new(-11.0, 5.0); + point /= 3.0; + assert_eq!(point, FPoint::new(-11.0, 5.0) / 3.0); + } + + #[test] + fn fpoint_sum() { + let points_sum: FPoint = vec![FPoint::new(-11.0, 5.0), FPoint::new(6.0, 2.0)].into_iter().sum(); + assert_eq!(FPoint::new(-5.0, 7.0), points_sum); + } } diff --git a/src/sdl2/render.rs b/src/sdl2/render.rs index cb645752b9..056e404bfc 100644 --- a/src/sdl2/render.rs +++ b/src/sdl2/render.rs @@ -34,6 +34,8 @@ use crate::pixels; use crate::pixels::PixelFormatEnum; use crate::rect::Point; use crate::rect::Rect; +use crate::rect::FPoint; +use crate::rect::FRect; use crate::surface; use crate::surface::{Surface, SurfaceContext, SurfaceRef}; use crate::video::{Window, WindowContext}; @@ -1331,6 +1333,145 @@ impl Canvas { } } + /// Draws a point on the current rendering target. + /// Errors if drawing fails for any reason (e.g. driver failure) + #[doc(alias = "SDL_RenderDrawPointF")] + pub fn draw_fpoint>(&mut self, point: P) -> Result<(), String> { + let point = point.into(); + let result = unsafe { sys::SDL_RenderDrawPointF(self.context.raw, point.x(), point.y()) }; + if result != 0 { + Err(get_error()) + } else { + Ok(()) + } + } + + /// Draws multiple points on the current rendering target. + /// Errors if drawing fails for any reason (e.g. driver failure) + #[doc(alias = "SDL_RenderDrawPointsF")] + pub fn draw_fpoints<'a, P: Into<&'a [FPoint]>>(&mut self, points: P) -> Result<(), String> { + let points = points.into(); + let result = unsafe { + sys::SDL_RenderDrawPointsF( + self.context.raw, + FPoint::raw_slice(points), + points.len() as c_int, + ) + }; + if result != 0 { + Err(get_error()) + } else { + Ok(()) + } + } + + /// Draws a line on the current rendering target. + /// Errors if drawing fails for any reason (e.g. driver failure) + #[doc(alias = "SDL_RenderDrawLineF")] + pub fn draw_fline, P2: Into>( + &mut self, + start: P1, + end: P2, + ) -> Result<(), String> { + let start = start.into(); + let end = end.into(); + let result = unsafe { + sys::SDL_RenderDrawLineF(self.context.raw, start.x(), start.y(), end.x(), end.y()) + }; + if result != 0 { + Err(get_error()) + } else { + Ok(()) + } + } + + /// Draws a series of connected lines on the current rendering target. + /// Errors if drawing fails for any reason (e.g. driver failure) + #[doc(alias = "SDL_RenderDrawLinesF")] + pub fn draw_flines<'a, P: Into<&'a [FPoint]>>(&mut self, points: P) -> Result<(), String> { + let points = points.into(); + let result = unsafe { + sys::SDL_RenderDrawLinesF( + self.context.raw, + FPoint::raw_slice(points), + points.len() as c_int, + ) + }; + if result != 0 { + Err(get_error()) + } else { + Ok(()) + } + } + + /// Draws a rectangle on the current rendering target. + /// Errors if drawing fails for any reason (e.g. driver failure) + #[doc(alias = "SDL_RenderDrawRectF")] + pub fn draw_frect(&mut self, rect: FRect) -> Result<(), String> { + let result = unsafe { sys::SDL_RenderDrawRectF(self.context.raw, rect.raw()) }; + if result != 0 { + Err(get_error()) + } else { + Ok(()) + } + } + + /// Draws some number of rectangles on the current rendering target. + /// Errors if drawing fails for any reason (e.g. driver failure) + #[doc(alias = "SDL_RenderDrawRectsF")] + pub fn draw_frects(&mut self, rects: &[FRect]) -> Result<(), String> { + let result = unsafe { + sys::SDL_RenderDrawRectsF( + self.context.raw, + FRect::raw_slice(rects), + rects.len() as c_int, + ) + }; + if result != 0 { + Err(get_error()) + } else { + Ok(()) + } + } + + /// Fills a rectangle on the current rendering target with the drawing + /// color. + /// Passing None will fill the entire rendering target. + /// Errors if drawing fails for any reason (e.g. driver failure) + #[doc(alias = "SDL_RenderFillRectF")] + pub fn fill_frect>>(&mut self, rect: R) -> Result<(), String> { + let result = unsafe { + sys::SDL_RenderFillRectF( + self.context.raw, + rect.into().as_ref().map(|r| r.raw()).unwrap_or(ptr::null()), + ) + }; + if result != 0 { + Err(get_error()) + } else { + Ok(()) + } + } + + /// Fills some number of rectangles on the current rendering target with + /// the drawing color. + /// Errors if drawing fails for any reason (e.g. driver failure) + #[doc(alias = "SDL_RenderFillRectsF")] + pub fn fill_frects(&mut self, rects: &[FRect]) -> Result<(), String> { + let result = unsafe { + sys::SDL_RenderFillRectsF( + self.context.raw, + FRect::raw_slice(rects), + rects.len() as c_int, + ) + }; + if result != 0 { + Err(get_error()) + } else { + Ok(()) + } + } + /// Copies a portion of the texture to the current rendering target. /// /// * If `src` is `None`, the entire texture is copied.