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.unit
20
21 import androidx.compose.runtime.Immutable
22 import androidx.compose.runtime.Stable
23 import androidx.compose.ui.util.fastCoerceAtLeast
24 import androidx.compose.ui.util.fastCoerceIn
25 import kotlin.jvm.JvmInline
26 import kotlin.math.min
27
28 /**
29 * Immutable constraints for measuring layouts, used by [layouts][androidx.compose.ui.layout.Layout]
30 * or [layout modifiers][androidx.compose.ui.layout.LayoutModifier] to measure their layout
31 * children. The parent chooses the [Constraints] defining a range, in pixels, within which the
32 * measured layout should choose a size:
33 * - `minWidth` <= `chosenWidth` <= `maxWidth`
34 * - `minHeight` <= `chosenHeight` <= `maxHeight`
35 *
36 * For more details about how layout measurement works, see
37 * [androidx.compose.ui.layout.MeasurePolicy] or
38 * [androidx.compose.ui.layout.LayoutModifier.measure].
39 *
40 * A set of [Constraints] can have infinite maxWidth and/or maxHeight. This is a trick often used by
41 * parents to ask their children for their preferred size: unbounded constraints force children
42 * whose default behavior is to fill the available space (always size to maxWidth/maxHeight) to have
43 * an opinion about their preferred size. Most commonly, when measured with unbounded [Constraints],
44 * these children will fallback to size themselves to wrap their content, instead of expanding to
45 * fill the available space (this is not always true as it depends on the child layout model, but is
46 * a common behavior for core layout components).
47 *
48 * [Constraints] uses a [Long] to represent four values, [minWidth], [minHeight], [maxWidth], and
49 * [maxHeight]. The range of the values varies to allow for at most 256K in one dimension. There are
50 * four possible maximum ranges, 13 bits/18 bits, and 15 bits/16 bits for either width or height,
51 * depending on the needs. For example, a width could range up to 18 bits and the height up to 13
52 * bits. Alternatively, the width could range up to 16 bits and the height up to 15 bits. The height
53 * and width requirements can be reversed, with a height of up to 18 bits and width of 13 bits or
54 * height of 16 bits and width of 15 bits. Any constraints exceeding this range will fail.
55 */
56 @Immutable
57 @JvmInline
58 value class Constraints(@PublishedApi internal val value: Long) {
59 /**
60 * Indicates how the bits are assigned. One of:
61 * - MinFocusWidth
62 * - MaxFocusWidth
63 * - MinFocusHeight
64 * - MaxFocusHeight
65 */
66 private inline val focusIndex
67 get() = (value and FocusMask).toInt()
68
69 /** The minimum width that the measurement can take, in pixels. */
70 val minWidth: Int
71 get() {
72 val mask = widthMask(indexToBitOffset(focusIndex))
73 return ((value shr 2).toInt() and mask)
74 }
75
76 /**
77 * The maximum width that the measurement can take, in pixels. This will either be a positive
78 * value greater than or equal to [minWidth] or [Constraints.Infinity].
79 */
80 val maxWidth: Int
81 get() {
82 val mask = widthMask(indexToBitOffset(focusIndex))
83 val width = ((value shr 33).toInt() and mask)
84 return if (width == 0) Infinity else width - 1
85 }
86
87 /** The minimum height that the measurement can take, in pixels. */
88 val minHeight: Int
89 get() {
90 val bitOffset = indexToBitOffset(focusIndex)
91 val mask = heightMask(bitOffset)
92 val offset = minHeightOffsets(bitOffset)
93 return (value shr offset).toInt() and mask
94 }
95
96 /**
97 * The maximum height that the measurement can take, in pixels. This will either be a positive
98 * value greater than or equal to [minHeight] or [Constraints.Infinity].
99 */
100 val maxHeight: Int
101 get() {
102 val bitOffset = indexToBitOffset(focusIndex)
103 val mask = heightMask(bitOffset)
104 val offset = minHeightOffsets(bitOffset) + 31
105 val height = (value shr offset).toInt() and mask
106 return if (height == 0) Infinity else height - 1
107 }
108
109 /**
110 * `false` when [maxWidth] is [Infinity] and `true` if [maxWidth] is a non-[Infinity] value.
111 *
112 * @see hasBoundedHeight
113 */
114 val hasBoundedWidth: Boolean
115 get() {
116 val mask = widthMask(indexToBitOffset(focusIndex))
117 return ((value shr 33).toInt() and mask) != 0
118 }
119
120 /**
121 * `false` when [maxHeight] is [Infinity] and `true` if [maxHeight] is a non-[Infinity] value.
122 *
123 * @see hasBoundedWidth
124 */
125 val hasBoundedHeight: Boolean
126 get() {
127 val bitOffset = indexToBitOffset(focusIndex)
128 val mask = heightMask(bitOffset)
129 val offset = minHeightOffsets(bitOffset) + 31
130 return ((value shr offset).toInt() and mask) != 0
131 }
132
133 /** Whether there is exactly one width value that satisfies the constraints. */
134 @Stable
135 val hasFixedWidth: Boolean
136 get() {
137 val mask = widthMask(indexToBitOffset(focusIndex))
138 val minWidth = ((value shr 2).toInt() and mask)
139 val maxWidth =
<lambda>null140 ((value shr 33).toInt() and mask).let { if (it == 0) Infinity else it - 1 }
141 return minWidth == maxWidth
142 }
143
144 /** Whether there is exactly one height value that satisfies the constraints. */
145 @Stable
146 val hasFixedHeight: Boolean
147 get() {
148 val bitOffset = indexToBitOffset(focusIndex)
149 val mask = heightMask(bitOffset)
150 val offset = minHeightOffsets(bitOffset)
151 val minHeight = (value shr offset).toInt() and mask
152 val maxHeight =
<lambda>null153 ((value shr (offset + 31)).toInt() and mask).let {
154 if (it == 0) Infinity else it - 1
155 }
156 return minHeight == maxHeight
157 }
158
159 /**
160 * Whether the area of a component respecting these constraints will definitely be 0. This is
161 * true when at least one of maxWidth and maxHeight are 0.
162 */
163 @Stable
164 val isZero: Boolean
165 get() {
166 val bitOffset = indexToBitOffset(focusIndex)
167 val maxWidth = ((value shr 33).toInt() and widthMask(bitOffset)) - 1
168 val offset = minHeightOffsets(bitOffset) + 31
169 val maxHeight = ((value shr offset).toInt() and heightMask(bitOffset)) - 1
170 return (maxWidth == 0) or (maxHeight == 0)
171 }
172
173 /**
174 * Copies the existing [Constraints], replacing some of [minWidth], [minHeight], [maxWidth], or
175 * [maxHeight] as desired. [minWidth] and [minHeight] must be positive and [maxWidth] and
176 * [maxHeight] must be greater than or equal to [minWidth] and [minHeight], respectively, or
177 * [Infinity].
178 */
copynull179 fun copy(
180 minWidth: Int = this.minWidth,
181 maxWidth: Int = this.maxWidth,
182 minHeight: Int = this.minHeight,
183 maxHeight: Int = this.maxHeight
184 ): Constraints {
185 requirePrecondition(
186 maxWidth >= minWidth && maxHeight >= minHeight && minWidth >= 0 && minHeight >= 0
187 ) {
188 """
189 maxWidth must be >= than minWidth,
190 maxHeight must be >= than minHeight,
191 minWidth and minHeight must be >= 0
192 """
193 .trimIndent()
194 }
195 return createConstraints(minWidth, maxWidth, minHeight, maxHeight)
196 }
197
198 /**
199 * Copies the existing [Constraints], setting [minWidth] and [minHeight] to 0, and preserving
200 * [maxWidth] and [maxHeight] as-is.
201 */
copyMaxDimensionsnull202 inline fun copyMaxDimensions() = Constraints(value and MaxDimensionsAndFocusMask)
203
204 override fun toString(): String {
205 val maxWidth = maxWidth
206 val maxWidthStr = if (maxWidth == Infinity) "Infinity" else maxWidth.toString()
207 val maxHeight = maxHeight
208 val maxHeightStr = if (maxHeight == Infinity) "Infinity" else maxHeight.toString()
209 return "Constraints(minWidth = $minWidth, maxWidth = $maxWidthStr, " +
210 "minHeight = $minHeight, maxHeight = $maxHeightStr)"
211 }
212
213 companion object {
214 /**
215 * A value that [maxWidth] or [maxHeight] will be set to when the constraint should be
216 * considered infinite. [hasBoundedWidth] or [hasBoundedHeight] will be `false` when
217 * [maxWidth] or [maxHeight] is [Infinity], respectively.
218 */
219 const val Infinity = Int.MAX_VALUE
220
221 /** Creates constraints for fixed size in both dimensions. */
222 @Stable
fixednull223 fun fixed(width: Int, height: Int): Constraints {
224 requirePrecondition((width >= 0) and (height >= 0)) { "width and height must be >= 0" }
225 return createConstraints(width, width, height, height)
226 }
227
228 /** Creates constraints for fixed width and unspecified height. */
229 @Stable
fixedWidthnull230 fun fixedWidth(width: Int): Constraints {
231 requirePrecondition(width >= 0) { "width must be >= 0" }
232 return createConstraints(
233 minWidth = width,
234 maxWidth = width,
235 minHeight = 0,
236 maxHeight = Infinity
237 )
238 }
239
240 /** Creates constraints for fixed height and unspecified width. */
241 @Stable
fixedHeightnull242 fun fixedHeight(height: Int): Constraints {
243 requirePrecondition(height >= 0) { "height must be >= 0" }
244 return createConstraints(
245 minWidth = 0,
246 maxWidth = Infinity,
247 minHeight = height,
248 maxHeight = height
249 )
250 }
251
252 // This should be removed before the next release
253 @Deprecated(
254 "Replace with fitPrioritizingWidth",
255 replaceWith =
256 ReplaceWith(
257 "Constraints.fitPrioritizingWidth(minWidth, maxWidth, minHeight, maxHeight)"
258 )
259 )
260 @Stable
restrictConstraintsnull261 fun restrictConstraints(
262 minWidth: Int,
263 maxWidth: Int,
264 minHeight: Int,
265 maxHeight: Int,
266 prioritizeWidth: Boolean = true
267 ): Constraints {
268 return if (prioritizeWidth) {
269 fitPrioritizingWidth(minWidth, maxWidth, minHeight, maxHeight)
270 } else {
271 fitPrioritizingHeight(minWidth, maxWidth, minHeight, maxHeight)
272 }
273 }
274
275 /**
276 * Returns [Constraints] that match as close as possible to the values passed. If the
277 * dimensions are outside of those that can be represented, the constraints are limited to
278 * those that can be represented.
279 *
280 * [Constraints] is a `value class` based on a [Long] and 4 integers must be limited to fit
281 * within its size. The larger dimension has up to 18 bits (262,143) and the smaller as few
282 * as 13 bits (8191). The width is granted as much space as it needs or caps the size to 18
283 * bits. The height is given the remaining space.
284 *
285 * This can be useful when layout constraints are possible to be extremely large, but not
286 * everything is possible to display on the device. For example a text layout where an
287 * entire chapter of a book is measured in one Layout and it isn't possible to break up the
288 * content to show in a `LazyColumn`.
289 */
290 @Stable
fitPrioritizingWidthnull291 fun fitPrioritizingWidth(
292 minWidth: Int,
293 maxWidth: Int,
294 minHeight: Int,
295 maxHeight: Int,
296 ): Constraints {
297 val minW = min(minWidth, MaxFocusMask - 1)
298 val maxW =
299 if (maxWidth == Infinity) {
300 Infinity
301 } else {
302 min(maxWidth, MaxFocusMask - 1)
303 }
304 val consumed = if (maxW == Infinity) minW else maxW
305 val maxAllowed = maxAllowedForSize(consumed)
306 val maxH = if (maxHeight == Infinity) Infinity else min(maxAllowed, maxHeight)
307 val minH = min(maxAllowed, minHeight)
308 return Constraints(minW, maxW, minH, maxH)
309 }
310
311 /**
312 * Returns [Constraints] that match as close as possible to the values passed. If the
313 * dimensions are outside of those that can be represented, the constraints are limited to
314 * those that can be represented.
315 *
316 * [Constraints] is a `value class` based on a [Long] and 4 integers must be limited to fit
317 * within its size. The larger dimension has up to 18 bits (262,143) and the smaller as few
318 * as 13 bits (8191). The height is granted as much space as it needs or caps the size to 18
319 * bits. The width is given the remaining space.
320 *
321 * This can be useful when layout constraints are possible to be extremely large, but not
322 * everything is possible to display on the device. For example a text layout where an
323 * entire chapter of a book is measured in one Layout and it isn't possible to break up the
324 * content to show in a `LazyColumn`.
325 */
326 @Stable
fitPrioritizingHeightnull327 fun fitPrioritizingHeight(
328 minWidth: Int,
329 maxWidth: Int,
330 minHeight: Int,
331 maxHeight: Int,
332 ): Constraints {
333 val minH = min(minHeight, MaxFocusMask - 1)
334 val maxH =
335 if (maxHeight == Infinity) {
336 Infinity
337 } else {
338 min(maxHeight, MaxFocusMask - 1)
339 }
340 val consumed = if (maxH == Infinity) minH else maxH
341 val maxAllowed = maxAllowedForSize(consumed)
342 val maxW = if (maxWidth == Infinity) Infinity else min(maxAllowed, maxWidth)
343 val minW = min(maxAllowed, minWidth)
344 return Constraints(minW, maxW, minH, maxH)
345 }
346 }
347 }
348
349 // Redefinition of Constraints.Infinity to bypass the companion object
350 private const val Infinity = Int.MAX_VALUE
351
352 /**
353 * The mask to retrieve the focus:
354 * - MaxFocusHeight = 0x0. The bit distribution when the focus of the bits should be on the height,
355 * and a a maximal number of bits assigned to the height. 13 bits assigned to width, 18 bits
356 * assigned to height.
357 * - MinFocusHeight = 0x1. The bit distribution when the focus of the bits should be on the height,
358 * but only a minimal difference in focus. 15 bits assigned to width, 16 bits assigned to height.
359 * - MinFocusWidth = 0x2. The bit distribution when the focus of the bits should be on the width,
360 * but only a minimal difference in focus. 16 bits assigned to width, 15 bits assigned to height.
361 * - MaxFocusWidth = 0x3 .The bit distribution when the focus of the bits should be on the width,
362 * and a maximal number of bits assigned to the width. 18 bits assigned to width, 13 bits assigned
363 * to height.
364 */
365 private const val FocusMask = 0x3L
366
367 /** The number of bits used for the focused dimension when there is minimal focus. */
368 private const val MinFocusBits = 16
369 private const val MaxAllowedForMinFocusBits = (1 shl (31 - MinFocusBits)) - 2
370
371 /** The mask to use for the focused dimension when there is minimal focus. */
372 private const val MinFocusMask = 0xFFFF // 64K (16 bits)
373
374 /** The number of bits used for the non-focused dimension when there is minimal focus. */
375 private const val MinNonFocusBits = 15
376 private const val MaxAllowedForMinNonFocusBits = (1 shl (31 - MinNonFocusBits)) - 2
377
378 /** The mask to use for the non-focused dimension when there is minimal focus. */
379 private const val MinNonFocusMask = 0x7FFF // 32K (15 bits)
380
381 /** The number of bits to use for the focused dimension when there is maximal focus. */
382 private const val MaxFocusBits = 18
383 private const val MaxAllowedForMaxFocusBits = (1 shl (31 - MaxFocusBits)) - 2
384
385 /** The mask to use for the focused dimension when there is maximal focus. */
386 private const val MaxFocusMask = 0x3FFFF // 256K-1 (18 bits)
387
388 /** The number of bits to use for the non-focused dimension when there is maximal focus. */
389 private const val MaxNonFocusBits = 13
390 private const val MaxAllowedForMaxNonFocusBits = (1 shl (31 - MaxNonFocusBits)) - 2
391
392 /** The mask to use for the non-focused dimension when there is maximal focus. */
393 private const val MaxNonFocusMask = 0x1FFF // 8K (13 bits)
394
395 // 0xFFFFFFFE_00000003UL.toLong(), written as a signed value to declare it const
396 @PublishedApi internal const val MaxDimensionsAndFocusMask = -0x00000001_FFFFFFFDL
397
398 // Wrap those throws in functions to avoid inlining the string building at the call sites
399 // Keep internal for codegen
throwInvalidConstraintExceptionnull400 internal fun throwInvalidConstraintException(widthVal: Int, heightVal: Int) {
401 throw IllegalArgumentException(
402 "Can't represent a width of $widthVal and height of $heightVal in Constraints"
403 )
404 }
405
406 // Keep internal for codegen
throwInvalidConstraintsSizeExceptionnull407 internal fun throwInvalidConstraintsSizeException(size: Int): Nothing {
408 throw IllegalArgumentException("Can't represent a size of $size in Constraints")
409 }
410
411 /** Creates a [Constraints], only checking that the values fit in the packed Long. */
createConstraintsnull412 internal fun createConstraints(
413 minWidth: Int,
414 maxWidth: Int,
415 minHeight: Int,
416 maxHeight: Int
417 ): Constraints {
418 val heightVal = if (maxHeight == Infinity) minHeight else maxHeight
419 val heightBits = bitsNeedForSizeUnchecked(heightVal)
420
421 val widthVal = if (maxWidth == Infinity) minWidth else maxWidth
422 val widthBits = bitsNeedForSizeUnchecked(widthVal)
423
424 if (widthBits + heightBits > 31) {
425 throwInvalidConstraintException(widthVal, heightVal)
426 }
427
428 // Same as if (maxWidth == Infinity) 0 else maxWidth + 1 but branchless
429 // in DEX and saves 2 instructions on aarch64. Relies on integer overflow
430 // since Infinity == Int.MAX_VALUE
431 var maxWidthValue = maxWidth + 1
432 maxWidthValue = maxWidthValue and (maxWidthValue shr 31).inv()
433
434 var maxHeightValue = maxHeight + 1
435 maxHeightValue = maxHeightValue and (maxHeightValue shr 31).inv()
436
437 // widthBits can be one of 13, 15, 16, or 18
438 // by subtracting 13 we obtain a bit offset as expected by bitOffsetToIndex()
439 val bitOffset = widthBits - 13
440 val focus = bitOffsetToIndex(bitOffset)
441
442 val minHeightOffset = minHeightOffsets(bitOffset)
443 val maxHeightOffset = minHeightOffset + 31
444
445 val value =
446 focus.toLong() or
447 (minWidth.toLong() shl 2) or
448 (maxWidthValue.toLong() shl 33) or
449 (minHeight.toLong() shl minHeightOffset) or
450 (maxHeightValue.toLong() shl maxHeightOffset)
451 return Constraints(value)
452 }
453
bitsNeedForSizeUncheckednull454 internal fun bitsNeedForSizeUnchecked(size: Int): Int {
455 return when {
456 size < MaxNonFocusMask -> MaxNonFocusBits
457 size < MinNonFocusMask -> MinNonFocusBits
458 size < MinFocusMask -> MinFocusBits
459 size < MaxFocusMask -> MaxFocusBits
460 else -> 255
461 }
462 }
463
maxAllowedForSizenull464 private inline fun maxAllowedForSize(size: Int): Int {
465 return when {
466 size < MaxNonFocusMask -> MaxAllowedForMaxNonFocusBits
467 size < MinNonFocusMask -> MaxAllowedForMinNonFocusBits
468 size < MinFocusMask -> MaxAllowedForMinFocusBits
469 size < MaxFocusMask -> MaxAllowedForMaxFocusBits
470 else -> throwInvalidConstraintsSizeException(size)
471 }
472 }
473
474 /**
475 * Create a [Constraints]. [minWidth] and [minHeight] must be positive and [maxWidth] and
476 * [maxHeight] must be greater than or equal to [minWidth] and [minHeight], respectively, or
477 * [Infinity][Constraints.Infinity].
478 */
479 @Stable
Constraintsnull480 fun Constraints(
481 minWidth: Int = 0,
482 maxWidth: Int = Infinity,
483 minHeight: Int = 0,
484 maxHeight: Int = Infinity
485 ): Constraints {
486 requirePrecondition(
487 (maxWidth >= minWidth) and (maxHeight >= minHeight) and (minWidth >= 0) and (minHeight >= 0)
488 ) {
489 """
490 maxWidth must be >= than minWidth,
491 maxHeight must be >= than minHeight,
492 minWidth and minHeight must be >= 0
493 """
494 .trimIndent()
495 }
496 return createConstraints(minWidth, maxWidth, minHeight, maxHeight)
497 }
498
499 /**
500 * Takes [otherConstraints] and returns the result of coercing them in the current constraints. Note
501 * this means that any size satisfying the resulting constraints will satisfy the current
502 * constraints, but they might not satisfy the [otherConstraints] when the two set of constraints
503 * are disjoint. Examples (showing only width, height works the same): (minWidth=2,
504 * maxWidth=10).constrain(minWidth=7, maxWidth=12) -> (minWidth = 7, maxWidth = 10) (minWidth=2,
505 * maxWidth=10).constrain(minWidth=11, maxWidth=12) -> (minWidth=10, maxWidth=10) (minWidth=2,
506 * maxWidth=10).constrain(minWidth=5, maxWidth=7) -> (minWidth=5, maxWidth=7)
507 */
Constraintsnull508 fun Constraints.constrain(otherConstraints: Constraints): Constraints {
509 val minWidth = minWidth
510 val maxWidth = maxWidth
511 val minHeight = minHeight
512 val maxHeight = maxHeight
513 return Constraints(
514 minWidth = otherConstraints.minWidth.fastCoerceIn(minWidth, maxWidth),
515 maxWidth = otherConstraints.maxWidth.fastCoerceIn(minWidth, maxWidth),
516 minHeight = otherConstraints.minHeight.fastCoerceIn(minHeight, maxHeight),
517 maxHeight = otherConstraints.maxHeight.fastCoerceIn(minHeight, maxHeight)
518 )
519 }
520
521 /** Takes a size and returns the closest size to it that satisfies the constraints. */
522 @Stable
Constraintsnull523 fun Constraints.constrain(size: IntSize) =
524 IntSize(
525 width = size.width.fastCoerceIn(minWidth, maxWidth),
526 height = size.height.fastCoerceIn(minHeight, maxHeight)
527 )
528
529 /** Takes a width and returns the closest size to it that satisfies the constraints. */
530 @Stable fun Constraints.constrainWidth(width: Int) = width.fastCoerceIn(minWidth, maxWidth)
531
532 /** Takes a height and returns the closest size to it that satisfies the constraints. */
533 @Stable fun Constraints.constrainHeight(height: Int) = height.fastCoerceIn(minHeight, maxHeight)
534
535 /** Takes a size and returns whether it satisfies the current constraints. */
536 @Stable
537 fun Constraints.isSatisfiedBy(size: IntSize): Boolean {
538 return size.width in minWidth..maxWidth && size.height in minHeight..maxHeight
539 }
540
541 /** Returns the Constraints obtained by offsetting the current instance with the given values. */
542 @Stable
Constraintsnull543 fun Constraints.offset(horizontal: Int = 0, vertical: Int = 0) =
544 Constraints(
545 (minWidth + horizontal).fastCoerceAtLeast(0),
546 addMaxWithMinimum(maxWidth, horizontal),
547 (minHeight + vertical).fastCoerceAtLeast(0),
548 addMaxWithMinimum(maxHeight, vertical)
549 )
550
551 private inline fun addMaxWithMinimum(max: Int, value: Int): Int {
552 return if (max == Infinity) {
553 max
554 } else {
555 (max + value).fastCoerceAtLeast(0)
556 }
557 }
558
559 // NOTE: The functions below are not on the companion object to avoid unnecessary
560 // static resolutions. Even inlined, calling these functions requires a
561 // static inline access in case initialization is required. Making them top
562 // level functions addresses this issues.
563
564 // The following describes the computations performed by the 4 functions below.
565 // Each function is a mapping from an index to a value.
566 //
567 // index = 0 -> MaxFocusHeight = 15
568 // index = 1 -> MinFocusHeight = 17
569 // index = 2 -> MinFocusWidth = 18
570 // index = 3 -> MaxFocusWidth = 20
571 //
572 // MinHeightOffsets = 2 + 13 + (index and 0x1 shl 1) + ((index and 0x2 shr 1) * 3)
573 //
574 // The sub-expression `(index and 0x1 shl 1) + ((index and 0x2 shr 1) * 3)` applies
575 // the following mapping:
576 //
577 // 0 -> 0
578 // 1 -> 2
579 // 2 -> 3
580 // 3 -> 5
581 //
582 // From this mapping we can build all the other mappings:
583 //
584 // index = 0 -> MaxNonFocusMask = 0x1fff (13 bits)
585 // index = 1 -> MinNonFocusMask = 0x7fff (15 bits)
586 // index = 2 -> MinFocusMask = 0xffff (16 bits)
587 // index = 3 -> MaxFocusMask = 0x3ffff (18 bits)
588 //
589 // WidthMask = (1 shl (13 + (index and 0x1 shl 1) + ((index and 0x2 shr 1) * 3))) - 1
590 //
591 // Here we used the pattern `(1 << n) - 1` to set all first n bits to 1.
592 //
593 // index = 0 -> MaxFocusMask = 0x3ffff
594 // index = 1 -> MinFocusMask = 0xffff
595 // index = 2 -> MinNonFocusMask = 0x7fff
596 // index = 3 -> MaxNonFocusMask = 0x1fff
597 //
598 // HeightMask = (1 shl (18 - (index and 0x1 shl 1) - ((index and 0x2 shr 1) * 3))) - 1
599 //
600 // To optimize computations, the common part that follows is factored into the
601 // `indexToBitOffset` function:
602 //
603 // (index and 0x1 shl 1) + ((index and 0x2 shr 1) * 3)
604 //
605 // We are therefore left with:
606 //
607 // MinHeightOffsets = 15 + indexToBitOffset(index)
608 // WidthMask = (1 shl (13 + indexToBitOffset(index))) - 1
609 // HeightMask = (1 shl (18 - indexToBitOffset(index))) - 1
610
611 /**
612 * Maps an index (MaxFocusHeight, MinFocusHeight, MinFocusWidth, MaxFocusWidth) to a "bit offset":
613 * 0, 2, 3 or 5. That bit offset is used by [minHeightOffsets], [widthMask], and [heightMask] to
614 * compute other values without the need of lookup tables. For instance, [minHeightOffsets] returns
615 * `2 + 13 + bitOffset`.
616 */
indexToBitOffsetnull617 private inline fun indexToBitOffset(index: Int) =
618 (index and 0x1 shl 1) + ((index and 0x2 shr 1) * 3)
619
620 /** Maps a bit offset (0, 2, 3, or 5) to an index. It's the inverse of indexToBitOffset() */
621 private inline fun bitOffsetToIndex(bits: Int) = (bits shr 1) + (bits and 0x1)
622
623 /**
624 * Minimum Height shift offsets into Long value, indexed by FocusMask Max offsets are these + 31
625 * Width offsets are always either 2 (min) or 33 (max)
626 */
627 private inline fun minHeightOffsets(bitOffset: Int) = 15 + bitOffset
628
629 /** The mask to use for both minimum and maximum width. */
630 private inline fun widthMask(bitOffset: Int) = (1 shl (13 + bitOffset)) - 1
631
632 /** The mask to use for both minimum and maximum height. */
633 private inline fun heightMask(bitOffset: Int) = (1 shl (18 - bitOffset)) - 1
634