1 /*
2  * Copyright 2019 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 androidx.compose.runtime.Immutable
22 import androidx.compose.runtime.Stable
23 import androidx.compose.ui.util.lerp
24 import kotlin.math.absoluteValue
25 import kotlin.math.max
26 import kotlin.math.min
27 
28 // TODO(mount): Normalize this class. There are many methods that can be extension functions.
29 /**
30  * An immutable, 2D, axis-aligned, floating-point rectangle whose coordinates are relative to a
31  * given origin.
32  */
33 @Immutable
34 @Suppress("DataClassDefinition")
35 data class Rect(
36     /** The offset of the left edge of this rectangle from the x axis. */
37     @Stable val left: Float,
38 
39     /** The offset of the top edge of this rectangle from the y axis. */
40     @Stable val top: Float,
41 
42     /** The offset of the right edge of this rectangle from the x axis. */
43     @Stable val right: Float,
44 
45     /** The offset of the bottom edge of this rectangle from the y axis. */
46     @Stable val bottom: Float
47 ) {
48 
49     companion object {
50         /** A rectangle with left, top, right, and bottom edges all at zero. */
51         @Stable val Zero: Rect = Rect(0.0f, 0.0f, 0.0f, 0.0f)
52     }
53 
54     /** The distance between the left and right edges of this rectangle. */
55     @Stable
56     inline val width: Float
57         get() = right - left
58 
59     /** The distance between the top and bottom edges of this rectangle. */
60     @Stable
61     inline val height: Float
62         get() = bottom - top
63 
64     /** The distance between the upper-left corner and the lower-right corner of this rectangle. */
65     @Stable
66     val size: Size
67         get() = Size(width, height)
68 
69     /** Whether any of the coordinates of this rectangle are equal to positive infinity. */
70     // included for consistency with Offset and Size
71     @Stable
72     val isInfinite: Boolean
73         get() =
74             (left == Float.POSITIVE_INFINITY) or
75                 (top == Float.POSITIVE_INFINITY) or
76                 (right == Float.POSITIVE_INFINITY) or
77                 (bottom == Float.POSITIVE_INFINITY)
78 
79     /** Whether all coordinates of this rectangle are finite. */
80     @Stable
81     val isFinite: Boolean
82         get() =
83             ((left.toRawBits() and 0x7fffffff) < FloatInfinityBase) and
84                 ((top.toRawBits() and 0x7fffffff) < FloatInfinityBase) and
85                 ((right.toRawBits() and 0x7fffffff) < FloatInfinityBase) and
86                 ((bottom.toRawBits() and 0x7fffffff) < FloatInfinityBase)
87 
88     /** Whether this rectangle encloses a non-zero area. Negative areas are considered empty. */
89     @Stable
90     val isEmpty: Boolean
91         get() = (left >= right) or (top >= bottom)
92 
93     /**
94      * Returns a new rectangle translated by the given offset.
95      *
96      * To translate a rectangle by separate x and y components rather than by an [Offset], consider
97      * [translate].
98      */
99     @Stable
translatenull100     fun translate(offset: Offset): Rect {
101         return Rect(left + offset.x, top + offset.y, right + offset.x, bottom + offset.y)
102     }
103 
104     /**
105      * Returns a new rectangle with translateX added to the x components and translateY added to the
106      * y components.
107      */
108     @Stable
translatenull109     fun translate(translateX: Float, translateY: Float): Rect {
110         return Rect(left + translateX, top + translateY, right + translateX, bottom + translateY)
111     }
112 
113     /** Returns a new rectangle with edges moved outwards by the given delta. */
114     @Stable
inflatenull115     fun inflate(delta: Float): Rect {
116         return Rect(left - delta, top - delta, right + delta, bottom + delta)
117     }
118 
119     /** Returns a new rectangle with edges moved inwards by the given delta. */
deflatenull120     @Stable fun deflate(delta: Float): Rect = inflate(-delta)
121 
122     /**
123      * Returns a new rectangle that is the intersection of the given rectangle and this rectangle.
124      * The two rectangles must overlap for this to be meaningful. If the two rectangles do not
125      * overlap, then the resulting Rect will have a negative width or height.
126      */
127     @Stable
128     fun intersect(other: Rect): Rect {
129         return Rect(
130             max(left, other.left),
131             max(top, other.top),
132             min(right, other.right),
133             min(bottom, other.bottom)
134         )
135     }
136 
137     /**
138      * Returns a new rectangle that is the intersection of the given rectangle and this rectangle.
139      * The two rectangles must overlap for this to be meaningful. If the two rectangles do not
140      * overlap, then the resulting Rect will have a negative width or height.
141      */
142     @Stable
intersectnull143     fun intersect(otherLeft: Float, otherTop: Float, otherRight: Float, otherBottom: Float): Rect {
144         return Rect(
145             max(left, otherLeft),
146             max(top, otherTop),
147             min(right, otherRight),
148             min(bottom, otherBottom)
149         )
150     }
151 
152     /** Whether `other` has a nonzero area of overlap with this rectangle. */
overlapsnull153     fun overlaps(other: Rect): Boolean {
154         return (left < other.right) and
155             (other.left < right) and
156             (top < other.bottom) and
157             (other.top < bottom)
158     }
159 
160     /** The lesser of the magnitudes of the [width] and the [height] of this rectangle. */
161     val minDimension: Float
162         get() = min(width.absoluteValue, height.absoluteValue)
163 
164     /** The greater of the magnitudes of the [width] and the [height] of this rectangle. */
165     val maxDimension: Float
166         get() = max(width.absoluteValue, height.absoluteValue)
167 
168     /** The offset to the intersection of the top and left edges of this rectangle. */
169     val topLeft: Offset
170         get() = Offset(left, top)
171 
172     /** The offset to the center of the top edge of this rectangle. */
173     val topCenter: Offset
174         get() = Offset(left + width / 2.0f, top)
175 
176     /** The offset to the intersection of the top and right edges of this rectangle. */
177     val topRight: Offset
178         get() = Offset(right, top)
179 
180     /** The offset to the center of the left edge of this rectangle. */
181     val centerLeft: Offset
182         get() = Offset(left, top + height / 2.0f)
183 
184     /**
185      * The offset to the point halfway between the left and right and the top and bottom edges of
186      * this rectangle.
187      *
188      * See also [Size.center].
189      */
190     val center: Offset
191         get() = Offset(left + width / 2.0f, top + height / 2.0f)
192 
193     /** The offset to the center of the right edge of this rectangle. */
194     val centerRight: Offset
195         get() = Offset(right, top + height / 2.0f)
196 
197     /** The offset to the intersection of the bottom and left edges of this rectangle. */
198     val bottomLeft: Offset
199         get() = Offset(left, bottom)
200 
201     /** The offset to the center of the bottom edge of this rectangle. */
202     val bottomCenter: Offset
203         get() {
204             return Offset(left + width / 2.0f, bottom)
205         }
206 
207     /** The offset to the intersection of the bottom and right edges of this rectangle. */
208     val bottomRight: Offset
209         get() {
210             return Offset(right, bottom)
211         }
212 
213     /**
214      * Whether the point specified by the given offset (which is assumed to be relative to the
215      * origin) lies between the left and right and the top and bottom edges of this rectangle.
216      *
217      * Rectangles include their top and left edges but exclude their bottom and right edges.
218      */
containsnull219     operator fun contains(offset: Offset): Boolean {
220         val x = offset.x
221         val y = offset.y
222         return (x >= left) and (x < right) and (y >= top) and (y < bottom)
223     }
224 
toStringnull225     override fun toString() =
226         "Rect.fromLTRB(" +
227             "${left.toStringAsFixed(1)}, " +
228             "${top.toStringAsFixed(1)}, " +
229             "${right.toStringAsFixed(1)}, " +
230             "${bottom.toStringAsFixed(1)})"
231 }
232 
233 /**
234  * Construct a rectangle from its left and top edges as well as its width and height.
235  *
236  * @param offset Offset to represent the top and left parameters of the Rect
237  * @param size Size to determine the width and height of this [Rect].
238  * @return Rect with [Rect.left] and [Rect.top] configured to [Offset.x] and [Offset.y] as
239  *   [Rect.right] and [Rect.bottom] to [Offset.x] + [Size.width] and [Offset.y] + [Size.height]
240  *   respectively
241  */
242 @Stable
243 fun Rect(offset: Offset, size: Size): Rect =
244     Rect(offset.x, offset.y, offset.x + size.width, offset.y + size.height)
245 
246 /**
247  * Construct the smallest rectangle that encloses the given offsets, treating them as vectors from
248  * the origin.
249  *
250  * @param topLeft Offset representing the left and top edges of the rectangle
251  * @param bottomRight Offset representing the bottom and right edges of the rectangle
252  */
253 @Stable
254 fun Rect(topLeft: Offset, bottomRight: Offset): Rect =
255     Rect(topLeft.x, topLeft.y, bottomRight.x, bottomRight.y)
256 
257 /**
258  * Construct a rectangle that bounds the given circle
259  *
260  * @param center Offset that represents the center of the circle
261  * @param radius Radius of the circle to enclose
262  */
263 @Stable
264 fun Rect(center: Offset, radius: Float): Rect =
265     Rect(center.x - radius, center.y - radius, center.x + radius, center.y + radius)
266 
267 /**
268  * Linearly interpolate between two rectangles.
269  *
270  * The [fraction] argument represents position on the timeline, with 0.0 meaning that the
271  * interpolation has not started, returning [start] (or something equivalent to [start]), 1.0
272  * meaning that the interpolation has finished, returning [stop] (or something equivalent to
273  * [stop]), and values in between meaning that the interpolation is at the relevant point on the
274  * timeline between [start] and [stop]. The interpolation can be extrapolated beyond 0.0 and 1.0, so
275  * negative values and values greater than 1.0 are valid (and can easily be generated by curves).
276  *
277  * Values for [fraction] are usually obtained from an [Animation<Float>], such as an
278  * `AnimationController`.
279  */
280 @Stable
281 fun lerp(start: Rect, stop: Rect, fraction: Float): Rect =
282     Rect(
283         lerp(start.left, stop.left, fraction),
284         lerp(start.top, stop.top, fraction),
285         lerp(start.right, stop.right, fraction),
286         lerp(start.bottom, stop.bottom, fraction)
287     )
288