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 com.android.compose.animation.scene.transformation
18 
19 import androidx.compose.ui.geometry.Offset
20 import androidx.compose.ui.unit.Density
21 import androidx.compose.ui.unit.Dp
22 import com.android.compose.animation.scene.ContentKey
23 import com.android.compose.animation.scene.ElementKey
24 import com.android.compose.animation.scene.OverscrollScope
25 import com.android.compose.animation.scene.content.state.TransitionState
26 
27 internal class Translate private constructor(private val x: Dp, private val y: Dp) :
28     InterpolatedPropertyTransformation<Offset> {
29     override val property = PropertyTransformation.Property.Offset
30 
transformnull31     override fun PropertyTransformationScope.transform(
32         content: ContentKey,
33         element: ElementKey,
34         transition: TransitionState.Transition,
35         idleValue: Offset,
36     ): Offset {
37         return Offset(idleValue.x + x.toPx(), idleValue.y + y.toPx())
38     }
39 
40     class Factory(private val x: Dp, private val y: Dp) : Transformation.Factory {
createnull41         override fun create(): Transformation = Translate(x, y)
42     }
43 }
44 
45 internal class OverscrollTranslate
46 private constructor(
47     private val x: OverscrollScope.() -> Float,
48     private val y: OverscrollScope.() -> Float,
49 ) : InterpolatedPropertyTransformation<Offset> {
50     override val property = PropertyTransformation.Property.Offset
51 
52     private val cachedOverscrollScope = CachedOverscrollScope()
53 
54     override fun PropertyTransformationScope.transform(
55         content: ContentKey,
56         element: ElementKey,
57         transition: TransitionState.Transition,
58         value: Offset,
59     ): Offset {
60         // As this object is created by OverscrollBuilderImpl and we retrieve the current
61         // OverscrollSpec only when the transition implements HasOverscrollProperties, we can assume
62         // that this method was invoked after performing this check.
63         val overscrollProperties = transition as TransitionState.HasOverscrollProperties
64         val overscrollScope =
65             cachedOverscrollScope.getFromCacheOrCompute(density = this, overscrollProperties)
66 
67         return Offset(x = value.x + overscrollScope.x(), y = value.y + overscrollScope.y())
68     }
69 
70     class Factory(
71         private val x: OverscrollScope.() -> Float,
72         private val y: OverscrollScope.() -> Float,
73     ) : Transformation.Factory {
74         override fun create(): Transformation = OverscrollTranslate(x, y)
75     }
76 }
77 
78 /**
79  * A helper class to cache a [OverscrollScope] given a [Density] and
80  * [TransitionState.HasOverscrollProperties]. This helps avoid recreating a scope every frame
81  * whenever an overscroll transition is computed.
82  */
83 private class CachedOverscrollScope {
84     private var previousScope: OverscrollScope? = null
85     private var previousDensity: Density? = null
86     private var previousOverscrollProperties: TransitionState.HasOverscrollProperties? = null
87 
getFromCacheOrComputenull88     fun getFromCacheOrCompute(
89         density: Density,
90         overscrollProperties: TransitionState.HasOverscrollProperties,
91     ): OverscrollScope {
92         if (
93             previousScope == null ||
94                 density != previousDensity ||
95                 previousOverscrollProperties != overscrollProperties
96         ) {
97             val scope =
98                 object : OverscrollScope, Density by density {
99                     override val absoluteDistance: Float
100                         get() = overscrollProperties.absoluteDistance
101                 }
102 
103             previousScope = scope
104             previousDensity = density
105             previousOverscrollProperties = overscrollProperties
106             return scope
107         }
108 
109         return checkNotNull(previousScope)
110     }
111 }
112