1 /*
2  * Copyright 2023 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.foundation.pager
18 
19 import androidx.compose.animation.core.DecayAnimationSpec
20 import androidx.compose.foundation.internal.requirePrecondition
21 import androidx.compose.runtime.Stable
22 
23 /**
24  * [PagerSnapDistance] defines the way the [Pager] will treat the distance between the current page
25  * and the page where it will settle.
26  */
27 @Stable
28 interface PagerSnapDistance {
29 
30     /**
31      * Provides a chance to change where the [Pager] fling will settle.
32      *
33      * @param startPage The current page right before the fling starts.
34      * @param suggestedTargetPage The proposed target page where this fling will stop. This target
35      *   will be the page that will be correctly positioned (snapped) after naturally decaying with
36      *   [velocity] using a [DecayAnimationSpec].
37      * @param velocity The initial fling velocity.
38      * @param pageSize The page size for this [Pager] in pixels.
39      * @param pageSpacing The spacing used between pages in pixels.
40      * @return An updated target page where to settle. Note that this value needs to be between 0
41      *   and the total count of pages in this pager. If an invalid value is passed, the pager will
42      *   coerce within the valid values.
43      */
calculateTargetPagenull44     fun calculateTargetPage(
45         startPage: Int,
46         suggestedTargetPage: Int,
47         velocity: Float,
48         pageSize: Int,
49         pageSpacing: Int
50     ): Int
51 
52     companion object {
53         /**
54          * Limits the maximum number of pages that can be flung per fling gesture.
55          *
56          * @param pages The maximum number of extra pages that can be flung at once.
57          */
58         fun atMost(pages: Int): PagerSnapDistance {
59             requirePrecondition(pages >= 0) {
60                 "pages should be greater than or equal to 0. You have used $pages."
61             }
62             return PagerSnapDistanceMaxPages(pages)
63         }
64     }
65 }
66 
67 /**
68  * Limits the maximum number of pages that can be flung per fling gesture.
69  *
70  * @param pagesLimit The maximum number of extra pages that can be flung at once.
71  */
72 internal class PagerSnapDistanceMaxPages(private val pagesLimit: Int) : PagerSnapDistance {
calculateTargetPagenull73     override fun calculateTargetPage(
74         startPage: Int,
75         suggestedTargetPage: Int,
76         velocity: Float,
77         pageSize: Int,
78         pageSpacing: Int,
79     ): Int {
80         debugLog {
81             "PagerSnapDistanceMaxPages: startPage=$startPage " +
82                 "suggestedTargetPage=$suggestedTargetPage " +
83                 "velocity=$velocity " +
84                 "pageSize=$pageSize " +
85                 "pageSpacing$pageSpacing"
86         }
87         val startPageLong = startPage.toLong()
88         val minRange = (startPageLong - pagesLimit).coerceAtLeast(0).toInt()
89         val maxRange = (startPageLong + pagesLimit).coerceAtMost(Int.MAX_VALUE.toLong()).toInt()
90         return suggestedTargetPage.coerceIn(minRange, maxRange)
91     }
92 
equalsnull93     override fun equals(other: Any?): Boolean {
94         return if (other is PagerSnapDistanceMaxPages) {
95             this.pagesLimit == other.pagesLimit
96         } else {
97             false
98         }
99     }
100 
hashCodenull101     override fun hashCode(): Int {
102         return pagesLimit.hashCode()
103     }
104 }
105 
debugLognull106 private inline fun debugLog(generateMsg: () -> String) {
107     if (PagerDebugConfig.PagerSnapDistance) {
108         println("PagerSnapDistance: ${generateMsg()}")
109     }
110 }
111