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