| 1 | // Copyright 2020 Yevhenii Reizner |
| 2 | // |
| 3 | // Use of this source code is governed by a BSD-style license that can be |
| 4 | // found in the LICENSE file. |
| 5 | |
| 6 | use core::convert::TryFrom; |
| 7 | |
| 8 | use crate::{FiniteF32, IntSize, LengthU32, PathBuilder, Point, SaturateRound, Size, Transform}; |
| 9 | |
| 10 | #[cfg (all(not(feature = "std" ), feature = "no-std-float" ))] |
| 11 | use crate::NoStdFloat; |
| 12 | |
| 13 | /// An integer rectangle. |
| 14 | /// |
| 15 | /// # Guarantees |
| 16 | /// |
| 17 | /// - Width and height are in 1..=i32::MAX range. |
| 18 | /// - x+width and y+height does not overflow. |
| 19 | #[allow (missing_docs)] |
| 20 | #[derive (Copy, Clone, PartialEq, Debug)] |
| 21 | pub struct IntRect { |
| 22 | x: i32, |
| 23 | y: i32, |
| 24 | width: LengthU32, |
| 25 | height: LengthU32, |
| 26 | } |
| 27 | |
| 28 | impl IntRect { |
| 29 | /// Creates a new `IntRect`. |
| 30 | pub fn from_xywh(x: i32, y: i32, width: u32, height: u32) -> Option<Self> { |
| 31 | x.checked_add(i32::try_from(width).ok()?)?; |
| 32 | y.checked_add(i32::try_from(height).ok()?)?; |
| 33 | |
| 34 | Some(IntRect { |
| 35 | x, |
| 36 | y, |
| 37 | width: LengthU32::new(width)?, |
| 38 | height: LengthU32::new(height)?, |
| 39 | }) |
| 40 | } |
| 41 | |
| 42 | /// Creates a new `IntRect`. |
| 43 | pub fn from_ltrb(left: i32, top: i32, right: i32, bottom: i32) -> Option<Self> { |
| 44 | let width = u32::try_from(right.checked_sub(left)?).ok()?; |
| 45 | let height = u32::try_from(bottom.checked_sub(top)?).ok()?; |
| 46 | IntRect::from_xywh(left, top, width, height) |
| 47 | } |
| 48 | |
| 49 | /// Returns rect's X position. |
| 50 | pub fn x(&self) -> i32 { |
| 51 | self.x |
| 52 | } |
| 53 | |
| 54 | /// Returns rect's Y position. |
| 55 | pub fn y(&self) -> i32 { |
| 56 | self.y |
| 57 | } |
| 58 | |
| 59 | /// Returns rect's width. |
| 60 | pub fn width(&self) -> u32 { |
| 61 | self.width.get() |
| 62 | } |
| 63 | |
| 64 | /// Returns rect's height. |
| 65 | pub fn height(&self) -> u32 { |
| 66 | self.height.get() |
| 67 | } |
| 68 | |
| 69 | /// Returns rect's left edge. |
| 70 | pub fn left(&self) -> i32 { |
| 71 | self.x |
| 72 | } |
| 73 | |
| 74 | /// Returns rect's top edge. |
| 75 | pub fn top(&self) -> i32 { |
| 76 | self.y |
| 77 | } |
| 78 | |
| 79 | /// Returns rect's right edge. |
| 80 | pub fn right(&self) -> i32 { |
| 81 | // No overflow is guaranteed by constructors. |
| 82 | self.x + self.width.get() as i32 |
| 83 | } |
| 84 | |
| 85 | /// Returns rect's bottom edge. |
| 86 | pub fn bottom(&self) -> i32 { |
| 87 | // No overflow is guaranteed by constructors. |
| 88 | self.y + self.height.get() as i32 |
| 89 | } |
| 90 | |
| 91 | /// Returns rect's size. |
| 92 | pub fn size(&self) -> IntSize { |
| 93 | IntSize::from_wh_safe(self.width, self.height) |
| 94 | } |
| 95 | |
| 96 | /// Checks that the rect is completely includes `other` Rect. |
| 97 | pub fn contains(&self, other: &Self) -> bool { |
| 98 | self.x <= other.x |
| 99 | && self.y <= other.y |
| 100 | && self.right() >= other.right() |
| 101 | && self.bottom() >= other.bottom() |
| 102 | } |
| 103 | |
| 104 | /// Returns an intersection of two rectangles. |
| 105 | /// |
| 106 | /// Returns `None` otherwise. |
| 107 | pub fn intersect(&self, other: &Self) -> Option<Self> { |
| 108 | let left = self.x.max(other.x); |
| 109 | let top = self.y.max(other.y); |
| 110 | |
| 111 | let right = self.right().min(other.right()); |
| 112 | let bottom = self.bottom().min(other.bottom()); |
| 113 | |
| 114 | let w = u32::try_from(right.checked_sub(left)?).ok()?; |
| 115 | let h = u32::try_from(bottom.checked_sub(top)?).ok()?; |
| 116 | |
| 117 | IntRect::from_xywh(left, top, w, h) |
| 118 | } |
| 119 | |
| 120 | /// Insets the rectangle. |
| 121 | pub fn inset(&self, dx: i32, dy: i32) -> Option<Self> { |
| 122 | IntRect::from_ltrb( |
| 123 | self.left() + dx, |
| 124 | self.top() + dy, |
| 125 | self.right() - dx, |
| 126 | self.bottom() - dy, |
| 127 | ) |
| 128 | } |
| 129 | |
| 130 | /// Outsets the rectangle. |
| 131 | pub fn make_outset(&self, dx: i32, dy: i32) -> Option<Self> { |
| 132 | IntRect::from_ltrb( |
| 133 | self.left().saturating_sub(dx), |
| 134 | self.top().saturating_sub(dy), |
| 135 | self.right().saturating_add(dx), |
| 136 | self.bottom().saturating_add(dy), |
| 137 | ) |
| 138 | } |
| 139 | |
| 140 | /// Translates the rect by the specified offset. |
| 141 | pub fn translate(&self, tx: i32, ty: i32) -> Option<Self> { |
| 142 | IntRect::from_xywh(self.x() + tx, self.y() + ty, self.width(), self.height()) |
| 143 | } |
| 144 | |
| 145 | /// Translates the rect to the specified position. |
| 146 | pub fn translate_to(&self, x: i32, y: i32) -> Option<Self> { |
| 147 | IntRect::from_xywh(x, y, self.width(), self.height()) |
| 148 | } |
| 149 | |
| 150 | /// Converts into `Rect`. |
| 151 | pub fn to_rect(&self) -> Rect { |
| 152 | // Can't fail, because `IntRect` is always valid. |
| 153 | Rect::from_ltrb( |
| 154 | self.x as f32, |
| 155 | self.y as f32, |
| 156 | self.x as f32 + self.width.get() as f32, |
| 157 | self.y as f32 + self.height.get() as f32, |
| 158 | ) |
| 159 | .unwrap() |
| 160 | } |
| 161 | } |
| 162 | |
| 163 | #[cfg (test)] |
| 164 | mod int_rect_tests { |
| 165 | use super::*; |
| 166 | |
| 167 | #[test ] |
| 168 | fn tests() { |
| 169 | assert_eq!(IntRect::from_xywh(0, 0, 0, 0), None); |
| 170 | assert_eq!(IntRect::from_xywh(0, 0, 1, 0), None); |
| 171 | assert_eq!(IntRect::from_xywh(0, 0, 0, 1), None); |
| 172 | |
| 173 | assert_eq!(IntRect::from_xywh(0, 0, u32::MAX, u32::MAX), None); |
| 174 | assert_eq!(IntRect::from_xywh(0, 0, 1, u32::MAX), None); |
| 175 | assert_eq!(IntRect::from_xywh(0, 0, u32::MAX, 1), None); |
| 176 | |
| 177 | assert_eq!(IntRect::from_xywh(i32::MAX, 0, 1, 1), None); |
| 178 | assert_eq!(IntRect::from_xywh(0, i32::MAX, 1, 1), None); |
| 179 | |
| 180 | { |
| 181 | // No intersection. |
| 182 | let r1 = IntRect::from_xywh(1, 2, 3, 4).unwrap(); |
| 183 | let r2 = IntRect::from_xywh(11, 12, 13, 14).unwrap(); |
| 184 | assert_eq!(r1.intersect(&r2), None); |
| 185 | } |
| 186 | |
| 187 | { |
| 188 | // Second inside the first one. |
| 189 | let r1 = IntRect::from_xywh(1, 2, 30, 40).unwrap(); |
| 190 | let r2 = IntRect::from_xywh(11, 12, 13, 14).unwrap(); |
| 191 | assert_eq!(r1.intersect(&r2), IntRect::from_xywh(11, 12, 13, 14)); |
| 192 | } |
| 193 | |
| 194 | { |
| 195 | // Partial overlap. |
| 196 | let r1 = IntRect::from_xywh(1, 2, 30, 40).unwrap(); |
| 197 | let r2 = IntRect::from_xywh(11, 12, 50, 60).unwrap(); |
| 198 | assert_eq!(r1.intersect(&r2), IntRect::from_xywh(11, 12, 20, 30)); |
| 199 | } |
| 200 | } |
| 201 | } |
| 202 | |
| 203 | /// A rectangle defined by left, top, right and bottom edges. |
| 204 | /// |
| 205 | /// Can have zero width and/or height. But not a negative one. |
| 206 | /// |
| 207 | /// # Guarantees |
| 208 | /// |
| 209 | /// - All values are finite. |
| 210 | /// - Left edge is <= right. |
| 211 | /// - Top edge is <= bottom. |
| 212 | /// - Width and height are <= f32::MAX. |
| 213 | #[allow (missing_docs)] |
| 214 | #[derive (Copy, Clone, PartialEq)] |
| 215 | pub struct Rect { |
| 216 | left: FiniteF32, |
| 217 | top: FiniteF32, |
| 218 | right: FiniteF32, |
| 219 | bottom: FiniteF32, |
| 220 | } |
| 221 | |
| 222 | impl core::fmt::Debug for Rect { |
| 223 | fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { |
| 224 | f&mut DebugStruct<'_, '_>.debug_struct("Rect" ) |
| 225 | .field("left" , &self.left.get()) |
| 226 | .field("top" , &self.top.get()) |
| 227 | .field("right" , &self.right.get()) |
| 228 | .field(name:"bottom" , &self.bottom.get()) |
| 229 | .finish() |
| 230 | } |
| 231 | } |
| 232 | |
| 233 | impl Rect { |
| 234 | /// Creates new `Rect`. |
| 235 | pub fn from_ltrb(left: f32, top: f32, right: f32, bottom: f32) -> Option<Self> { |
| 236 | let left = FiniteF32::new(left)?; |
| 237 | let top = FiniteF32::new(top)?; |
| 238 | let right = FiniteF32::new(right)?; |
| 239 | let bottom = FiniteF32::new(bottom)?; |
| 240 | |
| 241 | if left.get() <= right.get() && top.get() <= bottom.get() { |
| 242 | // Width and height must not overflow. |
| 243 | checked_f32_sub(right.get(), left.get())?; |
| 244 | checked_f32_sub(bottom.get(), top.get())?; |
| 245 | |
| 246 | Some(Rect { |
| 247 | left, |
| 248 | top, |
| 249 | right, |
| 250 | bottom, |
| 251 | }) |
| 252 | } else { |
| 253 | None |
| 254 | } |
| 255 | } |
| 256 | |
| 257 | /// Creates new `Rect`. |
| 258 | pub fn from_xywh(x: f32, y: f32, w: f32, h: f32) -> Option<Self> { |
| 259 | Rect::from_ltrb(x, y, w + x, h + y) |
| 260 | } |
| 261 | |
| 262 | /// Returns the left edge. |
| 263 | pub fn left(&self) -> f32 { |
| 264 | self.left.get() |
| 265 | } |
| 266 | |
| 267 | /// Returns the top edge. |
| 268 | pub fn top(&self) -> f32 { |
| 269 | self.top.get() |
| 270 | } |
| 271 | |
| 272 | /// Returns the right edge. |
| 273 | pub fn right(&self) -> f32 { |
| 274 | self.right.get() |
| 275 | } |
| 276 | |
| 277 | /// Returns the bottom edge. |
| 278 | pub fn bottom(&self) -> f32 { |
| 279 | self.bottom.get() |
| 280 | } |
| 281 | |
| 282 | /// Returns rect's X position. |
| 283 | pub fn x(&self) -> f32 { |
| 284 | self.left.get() |
| 285 | } |
| 286 | |
| 287 | /// Returns rect's Y position. |
| 288 | pub fn y(&self) -> f32 { |
| 289 | self.top.get() |
| 290 | } |
| 291 | |
| 292 | /// Returns rect's width. |
| 293 | #[inline ] |
| 294 | pub fn width(&self) -> f32 { |
| 295 | self.right.get() - self.left.get() |
| 296 | } |
| 297 | |
| 298 | /// Returns rect's height. |
| 299 | #[inline ] |
| 300 | pub fn height(&self) -> f32 { |
| 301 | self.bottom.get() - self.top.get() |
| 302 | } |
| 303 | |
| 304 | /// Converts into an `IntRect` by adding 0.5 and discarding the fractional portion. |
| 305 | /// |
| 306 | /// Width and height are guarantee to be >= 1. |
| 307 | pub fn round(&self) -> Option<IntRect> { |
| 308 | IntRect::from_xywh( |
| 309 | i32::saturate_round(self.x()), |
| 310 | i32::saturate_round(self.y()), |
| 311 | core::cmp::max(1, i32::saturate_round(self.width()) as u32), |
| 312 | core::cmp::max(1, i32::saturate_round(self.height()) as u32), |
| 313 | ) |
| 314 | } |
| 315 | |
| 316 | /// Converts into an `IntRect` rounding outwards. |
| 317 | /// |
| 318 | /// Width and height are guarantee to be >= 1. |
| 319 | pub fn round_out(&self) -> Option<IntRect> { |
| 320 | IntRect::from_xywh( |
| 321 | i32::saturate_floor(self.x()), |
| 322 | i32::saturate_floor(self.y()), |
| 323 | core::cmp::max(1, i32::saturate_ceil(self.width()) as u32), |
| 324 | core::cmp::max(1, i32::saturate_ceil(self.height()) as u32), |
| 325 | ) |
| 326 | } |
| 327 | |
| 328 | /// Returns an intersection of two rectangles. |
| 329 | /// |
| 330 | /// Returns `None` otherwise. |
| 331 | pub fn intersect(&self, other: &Self) -> Option<Self> { |
| 332 | let left = self.x().max(other.x()); |
| 333 | let top = self.y().max(other.y()); |
| 334 | |
| 335 | let right = self.right().min(other.right()); |
| 336 | let bottom = self.bottom().min(other.bottom()); |
| 337 | |
| 338 | Rect::from_ltrb(left, top, right, bottom) |
| 339 | } |
| 340 | |
| 341 | /// Creates a Rect from Point array. |
| 342 | /// |
| 343 | /// Returns None if count is zero or if Point array contains an infinity or NaN. |
| 344 | pub fn from_points(points: &[Point]) -> Option<Self> { |
| 345 | use crate::f32x4_t::f32x4; |
| 346 | |
| 347 | if points.is_empty() { |
| 348 | return None; |
| 349 | } |
| 350 | |
| 351 | let mut offset = 0; |
| 352 | let mut min; |
| 353 | let mut max; |
| 354 | if points.len() & 1 != 0 { |
| 355 | let pt = points[0]; |
| 356 | min = f32x4([pt.x, pt.y, pt.x, pt.y]); |
| 357 | max = min; |
| 358 | offset += 1; |
| 359 | } else { |
| 360 | let pt0 = points[0]; |
| 361 | let pt1 = points[1]; |
| 362 | min = f32x4([pt0.x, pt0.y, pt1.x, pt1.y]); |
| 363 | max = min; |
| 364 | offset += 2; |
| 365 | } |
| 366 | |
| 367 | let mut accum = f32x4::default(); |
| 368 | while offset != points.len() { |
| 369 | let pt0 = points[offset + 0]; |
| 370 | let pt1 = points[offset + 1]; |
| 371 | let xy = f32x4([pt0.x, pt0.y, pt1.x, pt1.y]); |
| 372 | |
| 373 | accum *= xy; |
| 374 | min = min.min(xy); |
| 375 | max = max.max(xy); |
| 376 | offset += 2; |
| 377 | } |
| 378 | |
| 379 | let all_finite = accum * f32x4::default() == f32x4::default(); |
| 380 | let min: [f32; 4] = min.0; |
| 381 | let max: [f32; 4] = max.0; |
| 382 | if all_finite { |
| 383 | Rect::from_ltrb( |
| 384 | min[0].min(min[2]), |
| 385 | min[1].min(min[3]), |
| 386 | max[0].max(max[2]), |
| 387 | max[1].max(max[3]), |
| 388 | ) |
| 389 | } else { |
| 390 | None |
| 391 | } |
| 392 | } |
| 393 | |
| 394 | /// Insets the rectangle by the specified offset. |
| 395 | pub fn inset(&self, dx: f32, dy: f32) -> Option<Self> { |
| 396 | Rect::from_ltrb( |
| 397 | self.left() + dx, |
| 398 | self.top() + dy, |
| 399 | self.right() - dx, |
| 400 | self.bottom() - dy, |
| 401 | ) |
| 402 | } |
| 403 | |
| 404 | /// Outsets the rectangle by the specified offset. |
| 405 | pub fn outset(&self, dx: f32, dy: f32) -> Option<Self> { |
| 406 | self.inset(-dx, -dy) |
| 407 | } |
| 408 | |
| 409 | /// Transforms the rect using the provided `Transform`. |
| 410 | /// |
| 411 | /// This method is expensive. |
| 412 | pub fn transform(&self, ts: Transform) -> Option<Self> { |
| 413 | if !ts.is_identity() { |
| 414 | // TODO: remove allocation |
| 415 | let mut path = PathBuilder::from_rect(*self); |
| 416 | path = path.transform(ts)?; |
| 417 | Some(path.bounds()) |
| 418 | } else { |
| 419 | Some(*self) |
| 420 | } |
| 421 | } |
| 422 | |
| 423 | /// Applies a bounding box transform. |
| 424 | pub fn bbox_transform(&self, bbox: NonZeroRect) -> Self { |
| 425 | let x = self.x() * bbox.width() + bbox.x(); |
| 426 | let y = self.y() * bbox.height() + bbox.y(); |
| 427 | let w = self.width() * bbox.width(); |
| 428 | let h = self.height() * bbox.height(); |
| 429 | Self::from_xywh(x, y, w, h).unwrap() |
| 430 | } |
| 431 | |
| 432 | /// Converts into [`NonZeroRect`]. |
| 433 | pub fn to_non_zero_rect(&self) -> Option<NonZeroRect> { |
| 434 | NonZeroRect::from_xywh(self.x(), self.y(), self.width(), self.height()) |
| 435 | } |
| 436 | } |
| 437 | |
| 438 | fn checked_f32_sub(a: f32, b: f32) -> Option<f32> { |
| 439 | debug_assert!(a.is_finite()); |
| 440 | debug_assert!(b.is_finite()); |
| 441 | |
| 442 | let n: f64 = a as f64 - b as f64; |
| 443 | // Not sure if this is perfectly correct. |
| 444 | if n > f32::MIN as f64 && n < f32::MAX as f64 { |
| 445 | Some(n as f32) |
| 446 | } else { |
| 447 | None |
| 448 | } |
| 449 | } |
| 450 | |
| 451 | #[cfg (test)] |
| 452 | mod rect_tests { |
| 453 | use super::*; |
| 454 | |
| 455 | #[test ] |
| 456 | fn tests() { |
| 457 | assert_eq!(Rect::from_ltrb(10.0, 10.0, 5.0, 10.0), None); |
| 458 | assert_eq!(Rect::from_ltrb(10.0, 10.0, 10.0, 5.0), None); |
| 459 | assert_eq!(Rect::from_ltrb(f32::NAN, 10.0, 10.0, 10.0), None); |
| 460 | assert_eq!(Rect::from_ltrb(10.0, f32::NAN, 10.0, 10.0), None); |
| 461 | assert_eq!(Rect::from_ltrb(10.0, 10.0, f32::NAN, 10.0), None); |
| 462 | assert_eq!(Rect::from_ltrb(10.0, 10.0, 10.0, f32::NAN), None); |
| 463 | assert_eq!(Rect::from_ltrb(10.0, 10.0, 10.0, f32::INFINITY), None); |
| 464 | |
| 465 | let rect = Rect::from_ltrb(10.0, 20.0, 30.0, 40.0).unwrap(); |
| 466 | assert_eq!(rect.left(), 10.0); |
| 467 | assert_eq!(rect.top(), 20.0); |
| 468 | assert_eq!(rect.right(), 30.0); |
| 469 | assert_eq!(rect.bottom(), 40.0); |
| 470 | assert_eq!(rect.width(), 20.0); |
| 471 | assert_eq!(rect.height(), 20.0); |
| 472 | |
| 473 | let rect = Rect::from_ltrb(-30.0, 20.0, -10.0, 40.0).unwrap(); |
| 474 | assert_eq!(rect.width(), 20.0); |
| 475 | assert_eq!(rect.height(), 20.0); |
| 476 | } |
| 477 | |
| 478 | #[test ] |
| 479 | fn round_overflow() { |
| 480 | // minimum value that cause overflow |
| 481 | // because i32::MAX has no exact conversion to f32 |
| 482 | let x = 128.0; |
| 483 | // maximum width |
| 484 | let width = i32::MAX as f32; |
| 485 | |
| 486 | let rect = Rect::from_xywh(x, 0.0, width, 1.0).unwrap(); |
| 487 | assert_eq!(rect.round(), None); |
| 488 | assert_eq!(rect.round_out(), None); |
| 489 | } |
| 490 | } |
| 491 | |
| 492 | /// A rectangle defined by left, top, right and bottom edges. |
| 493 | /// |
| 494 | /// Similar to [`Rect`], but width and height guarantee to be non-zero and positive. |
| 495 | /// |
| 496 | /// # Guarantees |
| 497 | /// |
| 498 | /// - All values are finite. |
| 499 | /// - Left edge is < right. |
| 500 | /// - Top edge is < bottom. |
| 501 | /// - Width and height are <= f32::MAX. |
| 502 | /// - Width and height are > 0.0 |
| 503 | #[allow (missing_docs)] |
| 504 | #[derive (Copy, Clone, PartialEq)] |
| 505 | pub struct NonZeroRect { |
| 506 | left: FiniteF32, |
| 507 | top: FiniteF32, |
| 508 | right: FiniteF32, |
| 509 | bottom: FiniteF32, |
| 510 | } |
| 511 | |
| 512 | impl core::fmt::Debug for NonZeroRect { |
| 513 | fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { |
| 514 | f&mut DebugStruct<'_, '_>.debug_struct("NonZeroRect" ) |
| 515 | .field("left" , &self.left.get()) |
| 516 | .field("top" , &self.top.get()) |
| 517 | .field("right" , &self.right.get()) |
| 518 | .field(name:"bottom" , &self.bottom.get()) |
| 519 | .finish() |
| 520 | } |
| 521 | } |
| 522 | |
| 523 | impl NonZeroRect { |
| 524 | /// Creates new `NonZeroRect`. |
| 525 | pub fn from_ltrb(left: f32, top: f32, right: f32, bottom: f32) -> Option<Self> { |
| 526 | let left = FiniteF32::new(left)?; |
| 527 | let top = FiniteF32::new(top)?; |
| 528 | let right = FiniteF32::new(right)?; |
| 529 | let bottom = FiniteF32::new(bottom)?; |
| 530 | |
| 531 | if left.get() < right.get() && top.get() < bottom.get() { |
| 532 | // Width and height must not overflow. |
| 533 | checked_f32_sub(right.get(), left.get())?; |
| 534 | checked_f32_sub(bottom.get(), top.get())?; |
| 535 | |
| 536 | Some(Self { |
| 537 | left, |
| 538 | top, |
| 539 | right, |
| 540 | bottom, |
| 541 | }) |
| 542 | } else { |
| 543 | None |
| 544 | } |
| 545 | } |
| 546 | |
| 547 | /// Creates new `NonZeroRect`. |
| 548 | pub fn from_xywh(x: f32, y: f32, w: f32, h: f32) -> Option<Self> { |
| 549 | Self::from_ltrb(x, y, w + x, h + y) |
| 550 | } |
| 551 | |
| 552 | /// Returns the left edge. |
| 553 | pub fn left(&self) -> f32 { |
| 554 | self.left.get() |
| 555 | } |
| 556 | |
| 557 | /// Returns the top edge. |
| 558 | pub fn top(&self) -> f32 { |
| 559 | self.top.get() |
| 560 | } |
| 561 | |
| 562 | /// Returns the right edge. |
| 563 | pub fn right(&self) -> f32 { |
| 564 | self.right.get() |
| 565 | } |
| 566 | |
| 567 | /// Returns the bottom edge. |
| 568 | pub fn bottom(&self) -> f32 { |
| 569 | self.bottom.get() |
| 570 | } |
| 571 | |
| 572 | /// Returns rect's X position. |
| 573 | pub fn x(&self) -> f32 { |
| 574 | self.left.get() |
| 575 | } |
| 576 | |
| 577 | /// Returns rect's Y position. |
| 578 | pub fn y(&self) -> f32 { |
| 579 | self.top.get() |
| 580 | } |
| 581 | |
| 582 | /// Returns rect's width. |
| 583 | pub fn width(&self) -> f32 { |
| 584 | self.right.get() - self.left.get() |
| 585 | } |
| 586 | |
| 587 | /// Returns rect's height. |
| 588 | pub fn height(&self) -> f32 { |
| 589 | self.bottom.get() - self.top.get() |
| 590 | } |
| 591 | |
| 592 | /// Returns rect's size. |
| 593 | pub fn size(&self) -> Size { |
| 594 | Size::from_wh(self.width(), self.height()).unwrap() |
| 595 | } |
| 596 | |
| 597 | /// Translates the rect to the specified position. |
| 598 | pub fn translate_to(&self, x: f32, y: f32) -> Option<Self> { |
| 599 | Self::from_xywh(x, y, self.width(), self.height()) |
| 600 | } |
| 601 | |
| 602 | /// Transforms the rect using the provided `Transform`. |
| 603 | /// |
| 604 | /// This method is expensive. |
| 605 | pub fn transform(&self, ts: Transform) -> Option<Self> { |
| 606 | if !ts.is_identity() { |
| 607 | // TODO: remove allocation |
| 608 | let mut path = PathBuilder::from_rect(self.to_rect()); |
| 609 | path = path.transform(ts)?; |
| 610 | path.bounds().to_non_zero_rect() |
| 611 | } else { |
| 612 | Some(*self) |
| 613 | } |
| 614 | } |
| 615 | |
| 616 | /// Applies a bounding box transform. |
| 617 | pub fn bbox_transform(&self, bbox: NonZeroRect) -> Self { |
| 618 | let x = self.x() * bbox.width() + bbox.x(); |
| 619 | let y = self.y() * bbox.height() + bbox.y(); |
| 620 | let w = self.width() * bbox.width(); |
| 621 | let h = self.height() * bbox.height(); |
| 622 | Self::from_xywh(x, y, w, h).unwrap() |
| 623 | } |
| 624 | |
| 625 | /// Converts into [`Rect`]. |
| 626 | pub fn to_rect(&self) -> Rect { |
| 627 | Rect::from_xywh(self.x(), self.y(), self.width(), self.height()).unwrap() |
| 628 | } |
| 629 | |
| 630 | /// Converts into [`IntRect`]. |
| 631 | pub fn to_int_rect(&self) -> IntRect { |
| 632 | IntRect::from_xywh( |
| 633 | self.x().floor() as i32, |
| 634 | self.y().floor() as i32, |
| 635 | core::cmp::max(1, self.width().ceil() as u32), |
| 636 | core::cmp::max(1, self.height().ceil() as u32), |
| 637 | ) |
| 638 | .unwrap() |
| 639 | } |
| 640 | } |
| 641 | |