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