1 /*
2  * Copyright 2024 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 package androidx.compose.material3.carousel
18 
19 import androidx.compose.foundation.gestures.snapping.SnapPosition
20 import kotlin.math.max
21 import kotlin.math.min
22 import kotlin.math.roundToInt
23 
24 /**
25  * Calculates the offset from the beginning of the carousel container needed to snap to the item at
26  * [itemIndex].
27  *
28  * This method takes into account the correct keyline list needed to allow the item to be fully
29  * visible and located at a focal position.
30  */
getSnapPositionOffsetnull31 internal fun getSnapPositionOffset(strategy: Strategy, itemIndex: Int, itemCount: Int): Int {
32     if (!strategy.isValid) return 0
33 
34     val numOfFocalKeylines =
35         strategy.defaultKeylines.lastFocalIndex - strategy.defaultKeylines.firstFocalIndex
36     val startStepsSize = strategy.startKeylineSteps.size + numOfFocalKeylines
37     val endStepsSize = strategy.endKeylineSteps.size + numOfFocalKeylines
38 
39     var offset =
40         (strategy.defaultKeylines.firstFocal.unadjustedOffset - strategy.itemMainAxisSize / 2F)
41             .roundToInt()
42 
43     if (itemIndex < startStepsSize) {
44         var startIndex = max(0, startStepsSize - 1 - itemIndex)
45         startIndex = min(strategy.startKeylineSteps.size - 1, startIndex)
46         val startKeylines = strategy.startKeylineSteps[startIndex]
47         offset =
48             (startKeylines.firstFocal.unadjustedOffset - strategy.itemMainAxisSize / 2f)
49                 .roundToInt()
50     }
51     if (itemCount > numOfFocalKeylines + 1 && itemIndex >= itemCount - endStepsSize) {
52         var endIndex = max(0, itemIndex - itemCount + endStepsSize)
53         endIndex = min(strategy.endKeylineSteps.size - 1, endIndex)
54         val endKeylines = strategy.endKeylineSteps[endIndex]
55         offset =
56             (endKeylines.firstFocal.unadjustedOffset - strategy.itemMainAxisSize / 2f).roundToInt()
57     }
58 
59     return offset
60 }
61 
KeylineSnapPositionnull62 internal fun KeylineSnapPosition(pageSize: CarouselPageSize): SnapPosition =
63     object : SnapPosition {
64         override fun position(
65             layoutSize: Int,
66             itemSize: Int,
67             beforeContentPadding: Int,
68             afterContentPadding: Int,
69             itemIndex: Int,
70             itemCount: Int
71         ): Int {
72             return getSnapPositionOffset(pageSize.strategy, itemIndex, itemCount)
73         }
74     }
75