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", "KotlinRedundantDiagnosticSuppress")
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 androidx.compose.ui.util.packFloats
25 import androidx.compose.ui.util.unpackFloat1
26 import androidx.compose.ui.util.unpackFloat2
27 
28 /**
29  * Constructs a Radius with the given [x] and [y] parameters for the size of the radius along the x
30  * and y axis respectively. By default the radius along the Y axis matches that of the given x-axis
31  * unless otherwise specified. Negative radii values are clamped to 0.
32  */
CornerRadiusnull33 @Stable inline fun CornerRadius(x: Float, y: Float = x) = CornerRadius(packFloats(x, y))
34 
35 /**
36  * A radius for either circular or elliptical (oval) shapes.
37  *
38  * Note consumers should create an instance of this class through the corresponding function
39  * constructor as it is represented as an inline class with 2 float parameters packed into a single
40  * long to reduce allocation overhead
41  */
42 @Immutable
43 @kotlin.jvm.JvmInline
44 value class CornerRadius(val packedValue: Long) {
45     /** The radius value on the horizontal axis. */
46     @Stable
47     inline val x: Float
48         get() = unpackFloat1(packedValue)
49 
50     /** The radius value on the vertical axis. */
51     @Stable
52     inline val y: Float
53         get() = unpackFloat2(packedValue)
54 
55     @Stable inline operator fun component1(): Float = x
56 
57     @Stable inline operator fun component2(): Float = y
58 
59     /**
60      * Returns a copy of this Radius instance optionally overriding the radius parameter for the x
61      * or y axis
62      */
63     fun copy(x: Float = unpackFloat1(packedValue), y: Float = unpackFloat2(packedValue)) =
64         CornerRadius(packFloats(x, y))
65 
66     companion object {
67         /**
68          * A radius with [x] and [y] values set to zero.
69          *
70          * You can use [CornerRadius.Zero] with [RoundRect] to have right-angle corners.
71          */
72         @Stable val Zero: CornerRadius = CornerRadius(0x0L)
73     }
74 
75     /** Whether this corner radius is 0 in x, y, or both. */
76     @Stable
77     inline fun isZero(): Boolean {
78         // account for +/- 0.0f
79         val v = packedValue and DualUnsignedFloatMask
80         return ((v - 0x00000001_00000001L) and v.inv() and 0x80000000_80000000UL.toLong()) != 0L
81     }
82 
83     /** Whether this corner radius describes a quarter circle (x == y). */
84     @Stable
85     inline fun isCircular(): Boolean {
86         return (packedValue ushr 32) == (packedValue and 0xffff_ffffL)
87     }
88 
89     /**
90      * Unary negation operator.
91      *
92      * Returns a Radius with the distances negated.
93      *
94      * Radiuses with negative values aren't geometrically meaningful, but could occur as part of
95      * expressions. For example, negating a radius of one pixel and then adding the result to
96      * another radius is equivalent to subtracting a radius of one pixel from the other.
97      */
98     @Stable inline operator fun unaryMinus() = CornerRadius(packedValue xor DualFloatSignBit)
99 
100     /**
101      * Binary subtraction operator.
102      *
103      * Returns a radius whose [x] value is the left-hand-side operand's [x] minus the
104      * right-hand-side operand's [x] and whose [y] value is the left-hand-side operand's [y] minus
105      * the right-hand-side operand's [y].
106      */
107     @Stable
108     operator fun minus(other: CornerRadius): CornerRadius {
109         return CornerRadius(
110             packFloats(
111                 unpackFloat1(packedValue) - unpackFloat1(other.packedValue),
112                 unpackFloat2(packedValue) - unpackFloat2(other.packedValue)
113             )
114         )
115     }
116 
117     /**
118      * Binary addition operator.
119      *
120      * Returns a radius whose [x] value is the sum of the [x] values of the two operands, and whose
121      * [y] value is the sum of the [y] values of the two operands.
122      */
123     @Stable
124     operator fun plus(other: CornerRadius): CornerRadius {
125         return CornerRadius(
126             packFloats(
127                 unpackFloat1(packedValue) + unpackFloat1(other.packedValue),
128                 unpackFloat2(packedValue) + unpackFloat2(other.packedValue)
129             )
130         )
131     }
132 
133     /**
134      * Multiplication operator.
135      *
136      * Returns a radius whose coordinates are the coordinates of the left-hand-side operand (a
137      * radius) multiplied by the scalar right-hand-side operand (a Float).
138      */
139     @Stable
140     operator fun times(operand: Float) =
141         CornerRadius(
142             packFloats(unpackFloat1(packedValue) * operand, unpackFloat2(packedValue) * operand)
143         )
144 
145     /**
146      * Division operator.
147      *
148      * Returns a radius whose coordinates are the coordinates of the left-hand-side operand (a
149      * radius) divided by the scalar right-hand-side operand (a Float).
150      */
151     @Stable
152     operator fun div(operand: Float) =
153         CornerRadius(
154             packFloats(unpackFloat1(packedValue) / operand, unpackFloat2(packedValue) / operand)
155         )
156 
157     override fun toString(): String {
158         return if (x == y) {
159             "CornerRadius.circular(${x.toStringAsFixed(1)})"
160         } else {
161             "CornerRadius.elliptical(${x.toStringAsFixed(1)}, ${y.toStringAsFixed(1)})"
162         }
163     }
164 }
165 
166 /**
167  * Linearly interpolate between two radii.
168  *
169  * The [fraction] argument represents position on the timeline, with 0.0 meaning that the
170  * interpolation has not started, returning [start] (or something equivalent to [start]), 1.0
171  * meaning that the interpolation has finished, returning [stop] (or something equivalent to
172  * [stop]), and values in between meaning that the interpolation is at the relevant point on the
173  * timeline between [start] and [stop]. The interpolation can be extrapolated beyond 0.0 and 1.0, so
174  * negative values and values greater than 1.0 are valid (and can easily be generated by curves).
175  *
176  * Values for [fraction] are usually obtained from an [Animation<Float>], such as an
177  * `AnimationController`.
178  */
179 @Stable
lerpnull180 fun lerp(start: CornerRadius, stop: CornerRadius, fraction: Float): CornerRadius {
181     return CornerRadius(
182         packFloats(
183             lerp(unpackFloat1(start.packedValue), unpackFloat1(stop.packedValue), fraction),
184             lerp(unpackFloat2(start.packedValue), unpackFloat2(stop.packedValue), fraction)
185         )
186     )
187 }
188