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 android.text.TextUtils;
24import java.io.PrintWriter;
25import java.util.regex.Matcher;
26import 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 */
40public 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
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     */
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     */
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     */
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
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
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
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     */
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     */
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     */
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     */
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     */
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     */
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     */
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     */
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     */
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     */
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     */
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     */
249    public final float exactCenterY() {
250        return (top + bottom) * 0.5f;
251    }
252
253    /**
254     * Set the rectangle to (0,0,0,0)
255     */
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     */
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     */
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     */
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     */
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     */
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     */
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     */
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     */
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     */
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     */
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
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
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
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     */
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     */
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     */
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     */
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     */
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     */
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     */
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     */
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     */
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     */
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