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