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