1 /* 2 * Copyright (C) 2006 The Android Open Source Project 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 17 package android.graphics; 18 19 import android.annotation.CheckResult; 20 import android.os.Parcel; 21 import android.os.Parcelable; 22 23 import java.io.PrintWriter; 24 import java.util.regex.Matcher; 25 import java.util.regex.Pattern; 26 27 /** 28 * Rect holds four integer coordinates for a rectangle. The rectangle is 29 * represented by the coordinates of its 4 edges (left, top, right bottom). 30 * These fields can be accessed directly. Use width() and height() to retrieve 31 * the rectangle's width and height. Note: most methods do not check to see that 32 * the coordinates are sorted correctly (i.e. left <= right and top <= bottom). 33 * <p> 34 * Note that the right and bottom coordinates are exclusive. This means a Rect 35 * being drawn untransformed onto a {@link android.graphics.Canvas} will draw 36 * into the column and row described by its left and top coordinates, but not 37 * those of its bottom and right. 38 */ 39 public final class Rect implements Parcelable { 40 public int left; 41 public int top; 42 public int right; 43 public int bottom; 44 45 /** 46 * A helper class for flattened rectange pattern recognition. A separate 47 * class to avoid an initialization dependency on a regular expression 48 * causing Rect to not be initializable with an ahead-of-time compilation 49 * scheme. 50 */ 51 private static final class UnflattenHelper { 52 private static final Pattern FLATTENED_PATTERN = Pattern.compile( 53 "(-?\\d+) (-?\\d+) (-?\\d+) (-?\\d+)"); 54 getMatcher(String str)55 static Matcher getMatcher(String str) { 56 return FLATTENED_PATTERN.matcher(str); 57 } 58 } 59 60 /** 61 * Create a new empty Rect. All coordinates are initialized to 0. 62 */ Rect()63 public Rect() {} 64 65 /** 66 * Create a new rectangle with the specified coordinates. Note: no range 67 * checking is performed, so the caller must ensure that left <= right and 68 * top <= bottom. 69 * 70 * @param left The X coordinate of the left side of the rectangle 71 * @param top The Y coordinate of the top of the rectangle 72 * @param right The X coordinate of the right side of the rectangle 73 * @param bottom The Y coordinate of the bottom of the rectangle 74 */ Rect(int left, int top, int right, int bottom)75 public Rect(int left, int top, int right, int bottom) { 76 this.left = left; 77 this.top = top; 78 this.right = right; 79 this.bottom = bottom; 80 } 81 82 /** 83 * Create a new rectangle, initialized with the values in the specified 84 * rectangle (which is left unmodified). 85 * 86 * @param r The rectangle whose coordinates are copied into the new 87 * rectangle. 88 */ Rect(Rect r)89 public Rect(Rect r) { 90 if (r == null) { 91 left = top = right = bottom = 0; 92 } else { 93 left = r.left; 94 top = r.top; 95 right = r.right; 96 bottom = r.bottom; 97 } 98 } 99 100 @Override equals(Object o)101 public boolean equals(Object o) { 102 if (this == o) return true; 103 if (o == null || getClass() != o.getClass()) return false; 104 105 Rect r = (Rect) o; 106 return left == r.left && top == r.top && right == r.right && bottom == r.bottom; 107 } 108 109 @Override hashCode()110 public int hashCode() { 111 int result = left; 112 result = 31 * result + top; 113 result = 31 * result + right; 114 result = 31 * result + bottom; 115 return result; 116 } 117 118 @Override toString()119 public String toString() { 120 StringBuilder sb = new StringBuilder(32); 121 sb.append("Rect("); sb.append(left); sb.append(", "); 122 sb.append(top); sb.append(" - "); sb.append(right); 123 sb.append(", "); sb.append(bottom); sb.append(")"); 124 return sb.toString(); 125 } 126 127 /** 128 * Return a string representation of the rectangle in a compact form. 129 */ toShortString()130 public String toShortString() { 131 return toShortString(new StringBuilder(32)); 132 } 133 134 /** 135 * Return a string representation of the rectangle in a compact form. 136 * @hide 137 */ toShortString(StringBuilder sb)138 public String toShortString(StringBuilder sb) { 139 sb.setLength(0); 140 sb.append('['); sb.append(left); sb.append(','); 141 sb.append(top); sb.append("]["); sb.append(right); 142 sb.append(','); sb.append(bottom); sb.append(']'); 143 return sb.toString(); 144 } 145 146 /** 147 * Return a string representation of the rectangle in a well-defined format. 148 * 149 * <p>You can later recover the Rect from this string through 150 * {@link #unflattenFromString(String)}. 151 * 152 * @return Returns a new String of the form "left top right bottom" 153 */ flattenToString()154 public String flattenToString() { 155 StringBuilder sb = new StringBuilder(32); 156 // WARNING: Do not change the format of this string, it must be 157 // preserved because Rects are saved in this flattened format. 158 sb.append(left); 159 sb.append(' '); 160 sb.append(top); 161 sb.append(' '); 162 sb.append(right); 163 sb.append(' '); 164 sb.append(bottom); 165 return sb.toString(); 166 } 167 168 /** 169 * Returns a Rect from a string of the form returned by {@link #flattenToString}, 170 * or null if the string is not of that form. 171 */ unflattenFromString(String str)172 public static Rect unflattenFromString(String str) { 173 Matcher matcher = UnflattenHelper.getMatcher(str); 174 if (!matcher.matches()) { 175 return null; 176 } 177 return new Rect(Integer.parseInt(matcher.group(1)), 178 Integer.parseInt(matcher.group(2)), 179 Integer.parseInt(matcher.group(3)), 180 Integer.parseInt(matcher.group(4))); 181 } 182 183 /** 184 * Print short representation to given writer. 185 * @hide 186 */ printShortString(PrintWriter pw)187 public void printShortString(PrintWriter pw) { 188 pw.print('['); pw.print(left); pw.print(','); 189 pw.print(top); pw.print("]["); pw.print(right); 190 pw.print(','); pw.print(bottom); pw.print(']'); 191 } 192 193 /** 194 * Returns true if the rectangle is empty (left >= right or top >= bottom) 195 */ isEmpty()196 public final boolean isEmpty() { 197 return left >= right || top >= bottom; 198 } 199 200 /** 201 * @return the rectangle's width. This does not check for a valid rectangle 202 * (i.e. left <= right) so the result may be negative. 203 */ width()204 public final int width() { 205 return right - left; 206 } 207 208 /** 209 * @return the rectangle's height. This does not check for a valid rectangle 210 * (i.e. top <= bottom) so the result may be negative. 211 */ height()212 public final int height() { 213 return bottom - top; 214 } 215 216 /** 217 * @return the horizontal center of the rectangle. If the computed value 218 * is fractional, this method returns the largest integer that is 219 * less than the computed value. 220 */ centerX()221 public final int centerX() { 222 return (left + right) >> 1; 223 } 224 225 /** 226 * @return the vertical center of the rectangle. If the computed value 227 * is fractional, this method returns the largest integer that is 228 * less than the computed value. 229 */ centerY()230 public final int centerY() { 231 return (top + bottom) >> 1; 232 } 233 234 /** 235 * @return the exact horizontal center of the rectangle as a float. 236 */ exactCenterX()237 public final float exactCenterX() { 238 return (left + right) * 0.5f; 239 } 240 241 /** 242 * @return the exact vertical center of the rectangle as a float. 243 */ exactCenterY()244 public final float exactCenterY() { 245 return (top + bottom) * 0.5f; 246 } 247 248 /** 249 * Set the rectangle to (0,0,0,0) 250 */ setEmpty()251 public void setEmpty() { 252 left = right = top = bottom = 0; 253 } 254 255 /** 256 * Set the rectangle's coordinates to the specified values. Note: no range 257 * checking is performed, so it is up to the caller to ensure that 258 * left <= right and top <= bottom. 259 * 260 * @param left The X coordinate of the left side of the rectangle 261 * @param top The Y coordinate of the top of the rectangle 262 * @param right The X coordinate of the right side of the rectangle 263 * @param bottom The Y coordinate of the bottom of the rectangle 264 */ set(int left, int top, int right, int bottom)265 public void set(int left, int top, int right, int bottom) { 266 this.left = left; 267 this.top = top; 268 this.right = right; 269 this.bottom = bottom; 270 } 271 272 /** 273 * Copy the coordinates from src into this rectangle. 274 * 275 * @param src The rectangle whose coordinates are copied into this 276 * rectangle. 277 */ set(Rect src)278 public void set(Rect src) { 279 this.left = src.left; 280 this.top = src.top; 281 this.right = src.right; 282 this.bottom = src.bottom; 283 } 284 285 /** 286 * Offset the rectangle by adding dx to its left and right coordinates, and 287 * adding dy to its top and bottom coordinates. 288 * 289 * @param dx The amount to add to the rectangle's left and right coordinates 290 * @param dy The amount to add to the rectangle's top and bottom coordinates 291 */ offset(int dx, int dy)292 public void offset(int dx, int dy) { 293 left += dx; 294 top += dy; 295 right += dx; 296 bottom += dy; 297 } 298 299 /** 300 * Offset the rectangle to a specific (left, top) position, 301 * keeping its width and height the same. 302 * 303 * @param newLeft The new "left" coordinate for the rectangle 304 * @param newTop The new "top" coordinate for the rectangle 305 */ offsetTo(int newLeft, int newTop)306 public void offsetTo(int newLeft, int newTop) { 307 right += newLeft - left; 308 bottom += newTop - top; 309 left = newLeft; 310 top = newTop; 311 } 312 313 /** 314 * Inset the rectangle by (dx,dy). If dx is positive, then the sides are 315 * moved inwards, making the rectangle narrower. If dx is negative, then the 316 * sides are moved outwards, making the rectangle wider. The same holds true 317 * for dy and the top and bottom. 318 * 319 * @param dx The amount to add(subtract) from the rectangle's left(right) 320 * @param dy The amount to add(subtract) from the rectangle's top(bottom) 321 */ inset(int dx, int dy)322 public void inset(int dx, int dy) { 323 left += dx; 324 top += dy; 325 right -= dx; 326 bottom -= dy; 327 } 328 329 /** 330 * Insets the rectangle on all sides specified by the dimensions of the {@code insets} 331 * rectangle. 332 * @hide 333 * @param insets The rectangle specifying the insets on all side. 334 */ inset(Rect insets)335 public void inset(Rect insets) { 336 left += insets.left; 337 top += insets.top; 338 right -= insets.right; 339 bottom -= insets.bottom; 340 } 341 342 /** 343 * Insets the rectangle on all sides specified by the insets. 344 * @hide 345 * @param left The amount to add from the rectangle's left 346 * @param top The amount to add from the rectangle's top 347 * @param right The amount to subtract from the rectangle's right 348 * @param bottom The amount to subtract from the rectangle's bottom 349 */ inset(int left, int top, int right, int bottom)350 public void inset(int left, int top, int right, int bottom) { 351 this.left += left; 352 this.top += top; 353 this.right -= right; 354 this.bottom -= bottom; 355 } 356 357 /** 358 * Returns true if (x,y) is inside the rectangle. The left and top are 359 * considered to be inside, while the right and bottom are not. This means 360 * that for a x,y to be contained: left <= x < right and top <= y < bottom. 361 * An empty rectangle never contains any point. 362 * 363 * @param x The X coordinate of the point being tested for containment 364 * @param y The Y coordinate of the point being tested for containment 365 * @return true iff (x,y) are contained by the rectangle, where containment 366 * means left <= x < right and top <= y < bottom 367 */ contains(int x, int y)368 public boolean contains(int x, int y) { 369 return left < right && top < bottom // check for empty first 370 && x >= left && x < right && y >= top && y < bottom; 371 } 372 373 /** 374 * Returns true iff the 4 specified sides of a rectangle are inside or equal 375 * to this rectangle. i.e. is this rectangle a superset of the specified 376 * rectangle. An empty rectangle never contains another rectangle. 377 * 378 * @param left The left side of the rectangle being tested for containment 379 * @param top The top of the rectangle being tested for containment 380 * @param right The right side of the rectangle being tested for containment 381 * @param bottom The bottom of the rectangle being tested for containment 382 * @return true iff the the 4 specified sides of a rectangle are inside or 383 * equal to this rectangle 384 */ contains(int left, int top, int right, int bottom)385 public boolean contains(int left, int top, int right, int bottom) { 386 // check for empty first 387 return this.left < this.right && this.top < this.bottom 388 // now check for containment 389 && this.left <= left && this.top <= top 390 && this.right >= right && this.bottom >= bottom; 391 } 392 393 /** 394 * Returns true iff the specified rectangle r is inside or equal to this 395 * rectangle. An empty rectangle never contains another rectangle. 396 * 397 * @param r The rectangle being tested for containment. 398 * @return true iff the specified rectangle r is inside or equal to this 399 * rectangle 400 */ contains(Rect r)401 public boolean contains(Rect r) { 402 // check for empty first 403 return this.left < this.right && this.top < this.bottom 404 // now check for containment 405 && left <= r.left && top <= r.top && right >= r.right && bottom >= r.bottom; 406 } 407 408 /** 409 * If the rectangle specified by left,top,right,bottom intersects this 410 * rectangle, return true and set this rectangle to that intersection, 411 * otherwise return false and do not change this rectangle. No check is 412 * performed to see if either rectangle is empty. Note: To just test for 413 * intersection, use {@link #intersects(Rect, Rect)}. 414 * 415 * @param left The left side of the rectangle being intersected with this 416 * rectangle 417 * @param top The top of the rectangle being intersected with this rectangle 418 * @param right The right side of the rectangle being intersected with this 419 * rectangle. 420 * @param bottom The bottom of the rectangle being intersected with this 421 * rectangle. 422 * @return true if the specified rectangle and this rectangle intersect 423 * (and this rectangle is then set to that intersection) else 424 * return false and do not change this rectangle. 425 */ 426 @CheckResult intersect(int left, int top, int right, int bottom)427 public boolean intersect(int left, int top, int right, int bottom) { 428 if (this.left < right && left < this.right && this.top < bottom && top < this.bottom) { 429 if (this.left < left) this.left = left; 430 if (this.top < top) this.top = top; 431 if (this.right > right) this.right = right; 432 if (this.bottom > bottom) this.bottom = bottom; 433 return true; 434 } 435 return false; 436 } 437 438 /** 439 * If the specified rectangle intersects this rectangle, return true and set 440 * this rectangle to that intersection, otherwise return false and do not 441 * change this rectangle. No check is performed to see if either rectangle 442 * is empty. To just test for intersection, use intersects() 443 * 444 * @param r The rectangle being intersected with this rectangle. 445 * @return true if the specified rectangle and this rectangle intersect 446 * (and this rectangle is then set to that intersection) else 447 * return false and do not change this rectangle. 448 */ 449 @CheckResult intersect(Rect r)450 public boolean intersect(Rect r) { 451 return intersect(r.left, r.top, r.right, r.bottom); 452 } 453 454 /** 455 * If rectangles a and b intersect, return true and set this rectangle to 456 * that intersection, otherwise return false and do not change this 457 * rectangle. No check is performed to see if either rectangle is empty. 458 * To just test for intersection, use intersects() 459 * 460 * @param a The first rectangle being intersected with 461 * @param b The second rectangle being intersected with 462 * @return true iff the two specified rectangles intersect. If they do, set 463 * this rectangle to that intersection. If they do not, return 464 * false and do not change this rectangle. 465 */ 466 @CheckResult setIntersect(Rect a, Rect b)467 public boolean setIntersect(Rect a, Rect b) { 468 if (a.left < b.right && b.left < a.right && a.top < b.bottom && b.top < a.bottom) { 469 left = Math.max(a.left, b.left); 470 top = Math.max(a.top, b.top); 471 right = Math.min(a.right, b.right); 472 bottom = Math.min(a.bottom, b.bottom); 473 return true; 474 } 475 return false; 476 } 477 478 /** 479 * Returns true if this rectangle intersects the specified rectangle. 480 * In no event is this rectangle modified. No check is performed to see 481 * if either rectangle is empty. To record the intersection, use intersect() 482 * or setIntersect(). 483 * 484 * @param left The left side of the rectangle being tested for intersection 485 * @param top The top of the rectangle being tested for intersection 486 * @param right The right side of the rectangle being tested for 487 * intersection 488 * @param bottom The bottom of the rectangle being tested for intersection 489 * @return true iff the specified rectangle intersects this rectangle. In 490 * no event is this rectangle modified. 491 */ intersects(int left, int top, int right, int bottom)492 public boolean intersects(int left, int top, int right, int bottom) { 493 return this.left < right && left < this.right && this.top < bottom && top < this.bottom; 494 } 495 496 /** 497 * Returns true iff the two specified rectangles intersect. In no event are 498 * either of the rectangles modified. To record the intersection, 499 * use {@link #intersect(Rect)} or {@link #setIntersect(Rect, Rect)}. 500 * 501 * @param a The first rectangle being tested for intersection 502 * @param b The second rectangle being tested for intersection 503 * @return true iff the two specified rectangles intersect. In no event are 504 * either of the rectangles modified. 505 */ intersects(Rect a, Rect b)506 public static boolean intersects(Rect a, Rect b) { 507 return a.left < b.right && b.left < a.right && a.top < b.bottom && b.top < a.bottom; 508 } 509 510 /** 511 * Update this Rect to enclose itself and the specified rectangle. If the 512 * specified rectangle is empty, nothing is done. If this rectangle is empty 513 * it is set to the specified rectangle. 514 * 515 * @param left The left edge being unioned with this rectangle 516 * @param top The top edge being unioned with this rectangle 517 * @param right The right edge being unioned with this rectangle 518 * @param bottom The bottom edge being unioned with this rectangle 519 */ union(int left, int top, int right, int bottom)520 public void union(int left, int top, int right, int bottom) { 521 if ((left < right) && (top < bottom)) { 522 if ((this.left < this.right) && (this.top < this.bottom)) { 523 if (this.left > left) this.left = left; 524 if (this.top > top) this.top = top; 525 if (this.right < right) this.right = right; 526 if (this.bottom < bottom) this.bottom = bottom; 527 } else { 528 this.left = left; 529 this.top = top; 530 this.right = right; 531 this.bottom = bottom; 532 } 533 } 534 } 535 536 /** 537 * Update this Rect to enclose itself and the specified rectangle. If the 538 * specified rectangle is empty, nothing is done. If this rectangle is empty 539 * it is set to the specified rectangle. 540 * 541 * @param r The rectangle being unioned with this rectangle 542 */ union(Rect r)543 public void union(Rect r) { 544 union(r.left, r.top, r.right, r.bottom); 545 } 546 547 /** 548 * Update this Rect to enclose itself and the [x,y] coordinate. There is no 549 * check to see that this rectangle is non-empty. 550 * 551 * @param x The x coordinate of the point to add to the rectangle 552 * @param y The y coordinate of the point to add to the rectangle 553 */ union(int x, int y)554 public void union(int x, int y) { 555 if (x < left) { 556 left = x; 557 } else if (x > right) { 558 right = x; 559 } 560 if (y < top) { 561 top = y; 562 } else if (y > bottom) { 563 bottom = y; 564 } 565 } 566 567 /** 568 * Swap top/bottom or left/right if there are flipped (i.e. left > right 569 * and/or top > bottom). This can be called if 570 * the edges are computed separately, and may have crossed over each other. 571 * If the edges are already correct (i.e. left <= right and top <= bottom) 572 * then nothing is done. 573 */ sort()574 public void sort() { 575 if (left > right) { 576 int temp = left; 577 left = right; 578 right = temp; 579 } 580 if (top > bottom) { 581 int temp = top; 582 top = bottom; 583 bottom = temp; 584 } 585 } 586 587 /** 588 * Parcelable interface methods 589 */ describeContents()590 public int describeContents() { 591 return 0; 592 } 593 594 /** 595 * Write this rectangle to the specified parcel. To restore a rectangle from 596 * a parcel, use readFromParcel() 597 * @param out The parcel to write the rectangle's coordinates into 598 */ writeToParcel(Parcel out, int flags)599 public void writeToParcel(Parcel out, int flags) { 600 out.writeInt(left); 601 out.writeInt(top); 602 out.writeInt(right); 603 out.writeInt(bottom); 604 } 605 606 public static final Parcelable.Creator<Rect> CREATOR = new Parcelable.Creator<Rect>() { 607 /** 608 * Return a new rectangle from the data in the specified parcel. 609 */ 610 public Rect createFromParcel(Parcel in) { 611 Rect r = new Rect(); 612 r.readFromParcel(in); 613 return r; 614 } 615 616 /** 617 * Return an array of rectangles of the specified size. 618 */ 619 public Rect[] newArray(int size) { 620 return new Rect[size]; 621 } 622 }; 623 624 /** 625 * Set the rectangle's coordinates from the data stored in the specified 626 * parcel. To write a rectangle to a parcel, call writeToParcel(). 627 * 628 * @param in The parcel to read the rectangle's coordinates from 629 */ readFromParcel(Parcel in)630 public void readFromParcel(Parcel in) { 631 left = in.readInt(); 632 top = in.readInt(); 633 right = in.readInt(); 634 bottom = in.readInt(); 635 } 636 637 /** 638 * Scales up the rect by the given scale. 639 * @hide 640 */ scale(float scale)641 public void scale(float scale) { 642 if (scale != 1.0f) { 643 left = (int) (left * scale + 0.5f); 644 top = (int) (top * scale + 0.5f); 645 right = (int) (right * scale + 0.5f); 646 bottom = (int) (bottom * scale + 0.5f); 647 } 648 } 649 650 } 651