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
17package android.graphics;
18
19import android.annotation.CheckResult;
20import android.os.Parcel;
21import android.os.Parcelable;
22
23import java.io.PrintWriter;
24import java.util.regex.Matcher;
25import 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 */
39public 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
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     */
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     */
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     */
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
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
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
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     */
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     */
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     */
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     */
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     */
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     */
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     */
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     */
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     */
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     */
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     */
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     */
244    public final float exactCenterY() {
245        return (top + bottom) * 0.5f;
246    }
247
248    /**
249     * Set the rectangle to (0,0,0,0)
250     */
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     */
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     */
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     */
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     */
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     */
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     */
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     */
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     */
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     */
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     */
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
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
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
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     */
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     */
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     */
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     */
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     */
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     */
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     */
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     */
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     */
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     */
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