use godot_ffi as sys;
use sys::{ExtVariantType, GodotFfi, ffi_methods};
use crate::builtin::math::ApproxEq;
use crate::builtin::{Rect2i, Side, Vector2, real};
#[derive(Default, Copy, Clone, PartialEq, Debug)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
#[repr(C)]
pub struct Rect2 {
pub position: Vector2,
pub size: Vector2,
}
impl Rect2 {
#[inline]
pub const fn new(position: Vector2, size: Vector2) -> Self {
Self { position, size }
}
#[inline]
pub fn from_position_end(position: Vector2, end: Vector2) -> Self {
Self::new(position, end - position)
}
#[inline]
#[deprecated = "Renamed to `from_position_end`."]
pub fn from_corners(position: Vector2, end: Vector2) -> Self {
Self::from_position_end(position, end)
}
#[inline]
pub const fn from_components(x: real, y: real, width: real, height: real) -> Self {
Self {
position: Vector2::new(x, y),
size: Vector2::new(width, height),
}
}
#[inline]
pub const fn cast_int(self) -> Rect2i {
Rect2i {
position: self.position.cast_int(),
size: self.size.cast_int(),
}
}
#[inline]
pub fn abs(self) -> Self {
Self {
position: self.position + self.size.coord_min(Vector2::ZERO),
size: self.size.abs(),
}
}
#[inline]
pub fn encloses(self, b: Rect2) -> bool {
let end = self.end();
let b_end = b.end();
b.position.x >= self.position.x
&& b.position.y >= self.position.y
&& b_end.x <= end.x
&& b_end.y <= end.y
}
#[inline]
pub fn expand(self, to: Vector2) -> Self {
self.merge(Rect2::new(to, Vector2::ZERO))
}
#[inline]
pub fn merge(self, b: Self) -> Self {
let position = self.position.coord_min(b.position);
let end = self.end().coord_max(b.end());
Self::from_position_end(position, end)
}
#[inline]
pub fn area(self) -> real {
self.size.x * self.size.y
}
#[inline]
pub fn center(self) -> Vector2 {
self.position + (self.size / 2.0)
}
#[inline]
#[must_use]
pub fn grow(self, amount: real) -> Self {
let position = self.position - Vector2::new(amount, amount);
let size = self.size + Vector2::new(amount, amount) * 2.0;
Self { position, size }
}
#[inline]
pub fn grow_individual(self, left: real, top: real, right: real, bottom: real) -> Self {
Self::from_components(
self.position.x - left,
self.position.y - top,
self.size.x + left + right,
self.size.y + top + bottom,
)
}
#[inline]
pub fn grow_side(self, side: Side, amount: real) -> Self {
match side {
Side::LEFT => self.grow_individual(amount, 0.0, 0.0, 0.0),
Side::TOP => self.grow_individual(0.0, amount, 0.0, 0.0),
Side::RIGHT => self.grow_individual(0.0, 0.0, amount, 0.0),
Side::BOTTOM => self.grow_individual(0.0, 0.0, 0.0, amount),
}
}
#[inline]
pub fn has_area(self) -> bool {
self.size.x > 0.0 && self.size.y > 0.0
}
#[inline]
#[doc(alias = "has_point")]
pub fn contains_point(self, point: Vector2) -> bool {
let point = point - self.position;
point.abs() == point && point.x < self.size.x && point.y < self.size.y
}
#[inline]
pub fn intersect(self, b: Self) -> Option<Self> {
if !self.intersects(b) {
return None;
}
let mut rect = b;
rect.position = rect.position.coord_max(self.position);
let end = self.end();
let end_b = b.end();
rect.size = end.coord_min(end_b) - rect.position;
Some(rect)
}
#[inline]
pub fn intersects(self, b: Self) -> bool {
let end = self.end();
let end_b = b.end();
self.position.x <= end_b.x
&& end.x >= b.position.x
&& self.position.y <= end_b.y
&& end.y >= b.position.y
}
#[inline]
pub fn intersects_exclude_borders(self, b: Self) -> bool {
let end = self.end();
let end_b = b.end();
self.position.x < end_b.x
&& end.x > b.position.x
&& self.position.y < end_b.y
&& end.y > b.position.y
}
#[inline]
pub fn is_finite(self) -> bool {
self.position.is_finite() && self.size.is_finite()
}
#[inline]
pub fn end(self) -> Vector2 {
self.position + self.size
}
#[inline]
pub fn set_end(&mut self, end: Vector2) {
self.size = end - self.position
}
#[inline]
pub fn assert_nonnegative(self) {
assert!(
self.size.x >= 0.0 && self.size.y >= 0.0,
"size {:?} is negative",
self.size
);
}
}
unsafe impl GodotFfi for Rect2 {
const VARIANT_TYPE: ExtVariantType = ExtVariantType::Concrete(sys::VariantType::RECT2);
ffi_methods! { type sys::GDExtensionTypePtr = *mut Self; .. }
}
crate::meta::impl_godot_as_self!(Rect2: ByValue);
impl ApproxEq for Rect2 {
#[inline]
fn approx_eq(&self, other: &Self) -> bool {
Vector2::approx_eq(&self.position, &other.position)
&& Vector2::approx_eq(&self.size, &other.size)
}
}
impl std::fmt::Display for Rect2 {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f, "[P: {}, S: {}]", self.position, self.size)
}
}
#[cfg(test)] #[cfg_attr(published_docs, doc(cfg(test)))]
mod test {
#[cfg(feature = "serde")] #[cfg_attr(published_docs, doc(cfg(feature = "serde")))]
#[test]
fn serde_roundtrip() {
let rect = super::Rect2::default();
let expected_json = "{\"position\":{\"x\":0.0,\"y\":0.0},\"size\":{\"x\":0.0,\"y\":0.0}}";
crate::builtin::test_utils::roundtrip(&rect, expected_json);
}
}