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