1 /*
2  * Copyright 2020 The Android Open Source Project
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  *      http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16 
17 @file:Suppress("NOTHING_TO_INLINE")
18 
19 package androidx.compose.ui.geometry
20 
21 import kotlin.math.absoluteValue
22 import kotlin.math.max
23 import kotlin.math.min
24 
25 /**
26  * An mutable, 2D, axis-aligned, floating-point rectangle whose coordinates are relative to a given
27  * origin.
28  *
29  * @param left The offset of the left edge of this rectangle from the x axis.
30  * @param top The offset of the top edge of this rectangle from the y axis.
31  * @param right The offset of the right edge of this rectangle from the x axis.
32  * @param bottom The offset of the bottom edge of this rectangle from the y axis.
33  */
34 class MutableRect(var left: Float, var top: Float, var right: Float, var bottom: Float) {
35     /** The distance between the left and right edges of this rectangle. */
36     inline val width: Float
37         get() = right - left
38 
39     /** The distance between the top and bottom edges of this rectangle. */
40     inline val height: Float
41         get() = bottom - top
42 
43     /** The distance between the upper-left corner and the lower-right corner of this rectangle. */
44     val size: Size
45         get() = Size(width, height)
46 
47     /** Whether any of the coordinates of this rectangle are equal to positive infinity. */
48     // included for consistency with Offset and Size
49     val isInfinite: Boolean
50         get() =
51             (left == Float.POSITIVE_INFINITY) or
52                 (top == Float.POSITIVE_INFINITY) or
53                 (right == Float.POSITIVE_INFINITY) or
54                 (bottom == Float.POSITIVE_INFINITY)
55 
56     /** Whether all coordinates of this rectangle are finite. */
57     val isFinite: Boolean
58         get() =
59             ((left.toRawBits() and 0x7fffffff) < FloatInfinityBase) and
60                 ((top.toRawBits() and 0x7fffffff) < FloatInfinityBase) and
61                 ((right.toRawBits() and 0x7fffffff) < FloatInfinityBase) and
62                 ((bottom.toRawBits() and 0x7fffffff) < FloatInfinityBase)
63 
64     /** Whether this rectangle encloses a non-zero area. Negative areas are considered empty. */
65     val isEmpty: Boolean
66         get() = (left >= right) or (top >= bottom)
67 
68     /** Translates the rect by the provided [Offset]. */
translatenull69     fun translate(offset: Offset) = translate(offset.x, offset.y)
70 
71     /**
72      * Updates this rectangle with translateX added to the x components and translateY added to the
73      * y components.
74      */
75     fun translate(translateX: Float, translateY: Float) {
76         left += translateX
77         top += translateY
78         right += translateX
79         bottom += translateY
80     }
81 
82     /** Moves edges outwards by the given delta. */
inflatenull83     fun inflate(delta: Float) {
84         left -= delta
85         top -= delta
86         right += delta
87         bottom += delta
88     }
89 
90     /** Moves edges inwards by the given delta. */
deflatenull91     fun deflate(delta: Float) = inflate(-delta)
92 
93     /**
94      * Modifies `this` to be the intersection of this and the rect formed by [left], [top], [right],
95      * and [bottom].
96      */
97     fun intersect(left: Float, top: Float, right: Float, bottom: Float) {
98         this.left = max(left, this.left)
99         this.top = max(top, this.top)
100         this.right = min(right, this.right)
101         this.bottom = min(bottom, this.bottom)
102     }
103 
104     /** Whether `other` has a nonzero area of overlap with this rectangle. */
overlapsnull105     fun overlaps(other: Rect): Boolean {
106         return (left < other.right) and
107             (other.left < right) and
108             (top < other.bottom) and
109             (other.top < bottom)
110     }
111 
112     /** Whether `other` has a nonzero area of overlap with this rectangle. */
overlapsnull113     fun overlaps(other: MutableRect): Boolean {
114         if (right <= other.left || other.right <= left) return false
115         if (bottom <= other.top || other.bottom <= top) return false
116         return true
117     }
118 
119     /** The lesser of the magnitudes of the [width] and the [height] of this rectangle. */
120     val minDimension: Float
121         get() = min(width.absoluteValue, height.absoluteValue)
122 
123     /** The greater of the magnitudes of the [width] and the [height] of this rectangle. */
124     val maxDimension: Float
125         get() = max(width.absoluteValue, height.absoluteValue)
126 
127     /** The offset to the intersection of the top and left edges of this rectangle. */
128     val topLeft: Offset
129         get() = Offset(left, top)
130 
131     /** The offset to the center of the top edge of this rectangle. */
132     val topCenter: Offset
133         get() = Offset(left + width / 2.0f, top)
134 
135     /** The offset to the intersection of the top and right edges of this rectangle. */
136     val topRight: Offset
137         get() = Offset(right, top)
138 
139     /** The offset to the center of the left edge of this rectangle. */
140     val centerLeft: Offset
141         get() = Offset(left, top + height / 2.0f)
142 
143     /**
144      * The offset to the point halfway between the left and right and the top and bottom edges of
145      * this rectangle.
146      *
147      * See also [Size.center].
148      */
149     val center: Offset
150         get() = Offset(left + width / 2.0f, top + height / 2.0f)
151 
152     /** The offset to the center of the right edge of this rectangle. */
153     val centerRight: Offset
154         get() = Offset(right, top + height / 2.0f)
155 
156     /** The offset to the intersection of the bottom and left edges of this rectangle. */
157     val bottomLeft: Offset
158         get() = Offset(left, bottom)
159 
160     /** The offset to the center of the bottom edge of this rectangle. */
161     val bottomCenter: Offset
162         get() {
163             return Offset(left + width / 2.0f, bottom)
164         }
165 
166     /** The offset to the intersection of the bottom and right edges of this rectangle. */
167     val bottomRight: Offset
168         get() {
169             return Offset(right, bottom)
170         }
171 
172     /**
173      * Whether the point specified by the given offset (which is assumed to be relative to the
174      * origin) lies between the left and right and the top and bottom edges of this rectangle.
175      *
176      * Rectangles include their top and left edges but exclude their bottom and right edges.
177      */
containsnull178     operator fun contains(offset: Offset): Boolean {
179         val x = offset.x
180         val y = offset.y
181         return (x >= left) and (x < right) and (y >= top) and (y < bottom)
182     }
183 
184     /** Sets new bounds to ([left], [top], [right], [bottom]) */
setnull185     fun set(left: Float, top: Float, right: Float, bottom: Float) {
186         this.left = left
187         this.top = top
188         this.right = right
189         this.bottom = bottom
190     }
191 
toStringnull192     override fun toString() =
193         "MutableRect(" +
194             "${left.toStringAsFixed(1)}, " +
195             "${top.toStringAsFixed(1)}, " +
196             "${right.toStringAsFixed(1)}, " +
197             "${bottom.toStringAsFixed(1)})"
198 }
199 
200 fun MutableRect.toRect(): Rect = Rect(left, top, right, bottom)
201 
202 /**
203  * Construct a rectangle from its left and top edges as well as its width and height.
204  *
205  * @param offset Offset to represent the top and left parameters of the Rect
206  * @param size Size to determine the width and height of this [Rect].
207  * @return Rect with [Rect.left] and [Rect.top] configured to [Offset.x] and [Offset.y] as
208  *   [Rect.right] and [Rect.bottom] to [Offset.x] + [Size.width] and [Offset.y] + [Size.height]
209  *   respectively
210  */
211 fun MutableRect(offset: Offset, size: Size): MutableRect =
212     MutableRect(offset.x, offset.y, offset.x + size.width, offset.y + size.height)
213 
214 /**
215  * Construct the smallest rectangle that encloses the given offsets, treating them as vectors from
216  * the origin.
217  *
218  * @param topLeft Offset representing the left and top edges of the rectangle
219  * @param bottomRight Offset representing the bottom and right edges of the rectangle
220  */
221 fun MutableRect(topLeft: Offset, bottomRight: Offset): MutableRect =
222     MutableRect(topLeft.x, topLeft.y, bottomRight.x, bottomRight.y)
223 
224 /**
225  * Construct a rectangle that bounds the given circle
226  *
227  * @param center Offset that represents the center of the circle
228  * @param radius Radius of the circle to enclose
229  */
230 fun MutableRect(center: Offset, radius: Float): MutableRect =
231     MutableRect(center.x - radius, center.y - radius, center.x + radius, center.y + radius)
232