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