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