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