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 @file:Suppress("NOTHING_TO_INLINE")
17
18 package androidx.compose.ui.unit
19
20 import androidx.compose.runtime.Immutable
21 import androidx.compose.runtime.Stable
22 import androidx.compose.ui.geometry.isSpecified
23 import androidx.compose.ui.util.floatFromBits
24 import androidx.compose.ui.util.lerp
25
26 /**
27 * We encode the unit information and float value into the single 64-bit long integer. The higher
28 * 32bit represents the metadata of this value and lower 32bit represents the bit representation of
29 * the float value. Currently lower 8bits in the metadata bits are used for unit information.
30 *
31 * Bits
32 *
33 * ```
34 * |-------|-------|-------|-------|-------|-------|-------|-------|
35 * FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF: Float Value
36 * UUUUUUUU : Unit Information
37 * XXXXXXXXXXXXXXXXXXXXXXXX : Unused bits
38 * ```
39 */
40 private const val UNIT_MASK = 0xFFL shl 32 // 0xFF_0000_0000
41 private const val UNIT_TYPE_UNSPECIFIED = 0x00L shl 32 // 0x00_0000_0000
42 private const val UNIT_TYPE_SP = 0x01L shl 32 // 0x01_0000_0000
43 private const val UNIT_TYPE_EM = 0x02L shl 32 // 0x02_0000_0000
44
45 /** An enum class defining for type of [TextUnit]. */
46 @kotlin.jvm.JvmInline
47 value class TextUnitType(internal val type: Long) {
toStringnull48 override fun toString(): String {
49 return when (this) {
50 Unspecified -> "Unspecified"
51 Sp -> "Sp"
52 Em -> "Em"
53 else -> "Invalid"
54 }
55 }
56
57 companion object {
58 val Unspecified = TextUnitType(UNIT_TYPE_UNSPECIFIED)
59 val Sp = TextUnitType(UNIT_TYPE_SP)
60 val Em = TextUnitType(UNIT_TYPE_EM)
61 }
62 }
63
64 /**
65 * Construct a new TextUnit.
66 *
67 * @param value of the dimension
68 * @param type dimension
69 */
TextUnitnull70 fun TextUnit(value: Float, type: TextUnitType): TextUnit = pack(type.type, value)
71
72 /**
73 * The unit used for text related dimension value.
74 *
75 * This unit can hold either scaled pixels (SP), relative font size (EM) and special unit
76 * Unspecified for indicating inheriting from other style or using the default value. It can be
77 * created with [sp] or [em]. (e.g. 15.sp or 18.em) which can be applied to [Int], [Double], and
78 * [Float].
79 *
80 * Note that do not store this value in your persistent storage or send to another process since the
81 * internal representation may be changed in future.
82 */
83 @Immutable
84 @kotlin.jvm.JvmInline
85 value class TextUnit internal constructor(internal val packedValue: Long) {
86 /**
87 * This is the same as multiplying the [TextUnit] by -1.0.
88 *
89 * This operation works only if the operand is not equal to [TextUnit.Unspecified]. The result
90 * of this operation is the same unit type of the given one.
91 *
92 * @throws IllegalArgumentException if this [TextUnit]'s type is [TextUnitType.Unspecified].
93 */
94 inline operator fun unaryMinus(): TextUnit {
95 checkArithmetic(this)
96 return pack(rawType, -value)
97 }
98
99 /**
100 * Divide a [TextUnit] by a scalar.
101 *
102 * This operation works only if the left operand is not equal to [TextUnit.Unspecified]. The
103 * result of this operation is the same unit type of the given one.
104 *
105 * @throws IllegalArgumentException if this [TextUnit]'s type is [TextUnitType.Unspecified].
106 */
107 inline operator fun div(other: Float): TextUnit {
108 checkArithmetic(this)
109 return pack(rawType, value / other)
110 }
111
112 /**
113 * Divide a [TextUnit] by a scalar.
114 *
115 * This operation works only if the left operand is not equal to [TextUnit.Unspecified]. The
116 * result of this operation is the same unit type of the given one.
117 *
118 * @throws IllegalArgumentException if this [TextUnit]'s type is [TextUnitType.Unspecified].
119 */
120 inline operator fun div(other: Double): TextUnit {
121 checkArithmetic(this)
122 return pack(rawType, (value / other).toFloat())
123 }
124
125 /**
126 * Divide a [TextUnit] by a scalar.
127 *
128 * This operation works only if the left operand is not equal to [TextUnit.Unspecified]. The
129 * result of this operation is the same unit type of the given one.
130 *
131 * @throws IllegalArgumentException if this [TextUnit]'s type is [TextUnitType.Unspecified].
132 */
133 inline operator fun div(other: Int): TextUnit {
134 checkArithmetic(this)
135 return pack(rawType, value / other)
136 }
137
138 /**
139 * Multiply a [TextUnit] by a scalar.
140 *
141 * This operation works only if the left operand is not equal to [TextUnit.Unspecified]. The
142 * result of this operation is the same unit type of the given one.
143 *
144 * @throws IllegalArgumentException if this [TextUnit]'s type is [TextUnitType.Unspecified].
145 */
146 inline operator fun times(other: Float): TextUnit {
147 checkArithmetic(this)
148 return pack(rawType, value * other)
149 }
150
151 /**
152 * Multiply a [TextUnit] by a scalar.
153 *
154 * This operation works only if the left operand is not equal to [TextUnit.Unspecified]. The
155 * result of this operation is the same unit type of the given one.
156 *
157 * @throws IllegalArgumentException if this [TextUnit]'s type is [TextUnitType.Unspecified].
158 */
159 inline operator fun times(other: Double): TextUnit {
160 checkArithmetic(this)
161 return pack(rawType, (value * other).toFloat())
162 }
163
164 /**
165 * Multiply a [TextUnit] by a scalar.
166 *
167 * This operation works only if the left operand is not equal to [TextUnit.Unspecified]. The
168 * result of this operation is the same unit type of the given one.
169 *
170 * @throws IllegalArgumentException if this [TextUnit]'s type is [TextUnitType.Unspecified].
171 */
172 inline operator fun times(other: Int): TextUnit {
173 checkArithmetic(this)
174 return pack(rawType, value * other)
175 }
176
177 /**
178 * Support comparing Dimensions with comparison operators.
179 *
180 * @return 0 if this [TextUnit] equals to the [other], a negative number if it's less than the
181 * [other], or a positive number if it's greater than the [other].
182 * @throws IllegalArgumentException if this [TextUnit] and the [other] has different
183 * [TextUnitType]s or either of the two has the [TextUnitType] equals to
184 * [TextUnitType.Unspecified].
185 */
186 inline operator fun compareTo(other: TextUnit): Int {
187 checkArithmetic(this, other)
188 return value.compareTo(other.value)
189 }
190
191 override fun toString(): String {
192 return when (type) {
193 TextUnitType.Unspecified -> "Unspecified"
194 TextUnitType.Sp -> "$value.sp"
195 TextUnitType.Em -> "$value.em"
196 else -> "Invalid"
197 }
198 }
199
200 companion object {
201 internal val TextUnitTypes =
202 arrayOf(TextUnitType.Unspecified, TextUnitType.Sp, TextUnitType.Em)
203
204 /**
205 * A special [TextUnit] instance for representing inheriting from parent value.
206 *
207 * Notice that performing arithmetic operations on [Unspecified] may result in an
208 * [IllegalArgumentException].
209 */
210 @Stable val Unspecified = pack(UNIT_TYPE_UNSPECIFIED, Float.NaN)
211 }
212
213 /**
214 * A helper function for getting underlying type information in raw bits.
215 *
216 * Use [TextUnit.type] in public places.
217 */
218 @PublishedApi
219 internal val rawType: Long
220 get() = packedValue and UNIT_MASK
221
222 /** A type information of this TextUnit. */
223 val type: TextUnitType
224 get() = TextUnitTypes[(rawType ushr 32).toInt()]
225
226 /** True if this is a SP unit type. */
227 val isSp
228 get() = rawType == UNIT_TYPE_SP
229
230 /** True if this is a EM unit type. */
231 val isEm
232 get() = rawType == UNIT_TYPE_EM
233
234 /**
235 * Returns the value of this [TextUnit].
236 *
237 * For example, the value of 3.sp equals to 3, and value of 5.em equals to 5. The value of
238 * [TextUnit]s whose [TextUnitType] is [TextUnitType.Unspecified] is undefined.
239 */
240 val value
241 get() = floatFromBits((packedValue and 0xFFFF_FFFFL).toInt())
242 }
243
244 /** `false` when this is [TextUnit.Unspecified]. */
245 @Stable
246 inline val TextUnit.isSpecified: Boolean
247 get() = !isUnspecified
248
249 /** `true` when this is [TextUnit.Unspecified]. */
250 @Stable
251 inline val TextUnit.isUnspecified: Boolean
252 get() = rawType == 0x0L // UNIT_TYPE_UNSPECIFIED
253
254 /**
255 * If this [TextUnit] [isSpecified] then this is returned, otherwise [block] is executed and its
256 * result is returned.
257 */
takeOrElsenull258 inline fun TextUnit.takeOrElse(block: () -> TextUnit): TextUnit = if (isSpecified) this else block()
259
260 /** Creates a SP unit [TextUnit] */
261 @Stable
262 val Float.sp: TextUnit
263 get() = pack(UNIT_TYPE_SP, this)
264
265 /** Creates an EM unit [TextUnit] */
266 @Stable
267 val Float.em: TextUnit
268 get() = pack(UNIT_TYPE_EM, this)
269
270 /** Creates a SP unit [TextUnit] */
271 @Stable
272 val Double.sp: TextUnit
273 get() = pack(UNIT_TYPE_SP, this.toFloat())
274
275 /** Creates an EM unit [TextUnit] */
276 @Stable
277 val Double.em: TextUnit
278 get() = pack(UNIT_TYPE_EM, this.toFloat())
279
280 /** Creates a SP unit [TextUnit] */
281 @Stable
282 val Int.sp: TextUnit
283 get() = pack(UNIT_TYPE_SP, this.toFloat())
284
285 /** Creates an EM unit [TextUnit] */
286 @Stable
287 val Int.em: TextUnit
288 get() = pack(UNIT_TYPE_EM, this.toFloat())
289
290 /**
291 * Multiply a [TextUnit] by a scalar.
292 *
293 * This operation works only if the right operand is not equal to [TextUnit.Unspecified]. The result
294 * of this operation is the same unit type of the given one.
295 */
296 @Stable
297 inline operator fun Float.times(other: TextUnit): TextUnit {
298 checkArithmetic(other)
299 return pack(other.rawType, this * other.value)
300 }
301
302 /**
303 * Multiply a [TextUnit] by a scalar.
304 *
305 * This operation works only if the right operand is not equal to [TextUnit.Unspecified]. The result
306 * of this operation is the same unit type of the given one.
307 */
308 @Stable
timesnull309 inline operator fun Double.times(other: TextUnit): TextUnit {
310 checkArithmetic(other)
311 return pack(other.rawType, this.toFloat() * other.value)
312 }
313
314 /**
315 * Multiply a [TextUnit] by a scalar.
316 *
317 * This operation works only if the right operand is not equal to [TextUnit.Unspecified]. The result
318 * of this operation is the same unit type of the given one.
319 */
320 @Stable
timesnull321 inline operator fun Int.times(other: TextUnit): TextUnit {
322 checkArithmetic(other)
323 return pack(other.rawType, this * other.value)
324 }
325
326 @PublishedApi
packnull327 internal fun pack(unitType: Long, v: Float): TextUnit =
328 TextUnit(unitType or (v.toRawBits().toLong() and 0xFFFF_FFFFL))
329
330 @PublishedApi
331 internal fun checkArithmetic(a: TextUnit) {
332 requirePrecondition(!a.isUnspecified) { "Cannot perform operation for Unspecified type." }
333 }
334
335 @PublishedApi
checkArithmeticnull336 internal fun checkArithmetic(a: TextUnit, b: TextUnit) {
337 requirePrecondition(!a.isUnspecified && !b.isUnspecified) {
338 "Cannot perform operation for Unspecified type."
339 }
340 requirePrecondition(a.type == b.type) { "Cannot perform operation for ${a.type} and ${b.type}" }
341 }
342
343 @PublishedApi
checkArithmeticnull344 internal fun checkArithmetic(a: TextUnit, b: TextUnit, c: TextUnit) {
345 requirePrecondition(!a.isUnspecified && !b.isUnspecified && !c.isUnspecified) {
346 "Cannot perform operation for Unspecified type."
347 }
348 requirePrecondition(a.type == b.type && b.type == c.type) {
349 "Cannot perform operation for ${a.type} and ${b.type}"
350 }
351 }
352
353 /**
354 * Linearly interpolate between two [TextUnit]s.
355 *
356 * The [fraction] argument represents position on the timeline, with 0.0 meaning that the
357 * interpolation has not started, returning [start] (or something equivalent to [start]), 1.0
358 * meaning that the interpolation has finished, returning [stop] (or something equivalent to
359 * [stop]), and values in between meaning that the interpolation is at the relevant point on the
360 * timeline between [start] and [stop]. The interpolation can be extrapolated beyond 0.0 and 1.0, so
361 * negative values and values greater than 1.0 are valid.
362 *
363 * @throws IllegalArgumentException if [start] and [stop] have different [TextUnitType]s, or either
364 * of the two has its [TextUnitType] equal to [TextUnitType.Unspecified].
365 */
366 @Stable
lerpnull367 fun lerp(start: TextUnit, stop: TextUnit, fraction: Float): TextUnit {
368 checkArithmetic(start, stop)
369 return pack(start.rawType, lerp(start.value, stop.value, fraction))
370 }
371