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
18 
19 import androidx.compose.animation.core.AnimationSpec
20 import androidx.compose.animation.core.DurationBasedAnimationSpec
21 import androidx.compose.animation.core.Spring
22 import androidx.compose.animation.core.SpringSpec
23 import androidx.compose.animation.core.VectorConverter
24 import androidx.compose.animation.core.snap
25 import androidx.compose.animation.core.spring
26 import androidx.compose.foundation.gestures.Orientation
27 import androidx.compose.ui.geometry.Offset
28 import androidx.compose.ui.unit.Dp
29 import com.android.compose.animation.scene.transformation.AnchoredSize
30 import com.android.compose.animation.scene.transformation.AnchoredTranslate
31 import com.android.compose.animation.scene.transformation.DrawScale
32 import com.android.compose.animation.scene.transformation.EdgeTranslate
33 import com.android.compose.animation.scene.transformation.Fade
34 import com.android.compose.animation.scene.transformation.OverscrollTranslate
35 import com.android.compose.animation.scene.transformation.PropertyTransformation
36 import com.android.compose.animation.scene.transformation.RangedPropertyTransformation
37 import com.android.compose.animation.scene.transformation.ScaleSize
38 import com.android.compose.animation.scene.transformation.SharedElementTransformation
39 import com.android.compose.animation.scene.transformation.Transformation
40 import com.android.compose.animation.scene.transformation.TransformationRange
41 import com.android.compose.animation.scene.transformation.Translate
42 
transitionsImplnull43 internal fun transitionsImpl(
44     builder: SceneTransitionsBuilder.() -> Unit,
45 ): SceneTransitions {
46     val impl = SceneTransitionsBuilderImpl().apply(builder)
47     return SceneTransitions(
48         impl.defaultSwipeSpec,
49         impl.transitionSpecs,
50         impl.transitionOverscrollSpecs,
51         impl.interruptionHandler,
52     )
53 }
54 
55 private class SceneTransitionsBuilderImpl : SceneTransitionsBuilder {
56     override var defaultSwipeSpec: SpringSpec<Float> = SceneTransitions.DefaultSwipeSpec
57     override var interruptionHandler: InterruptionHandler = DefaultInterruptionHandler
58 
59     val transitionSpecs = mutableListOf<TransitionSpecImpl>()
60     val transitionOverscrollSpecs = mutableListOf<OverscrollSpecImpl>()
61 
tonull62     override fun to(
63         to: SceneKey,
64         key: TransitionKey?,
65         builder: TransitionBuilder.() -> Unit
66     ): TransitionSpec {
67         return transition(from = null, to = to, key = key, builder)
68     }
69 
fromnull70     override fun from(
71         from: SceneKey,
72         to: SceneKey?,
73         key: TransitionKey?,
74         builder: TransitionBuilder.() -> Unit
75     ): TransitionSpec {
76         return transition(from = from, to = to, key = key, builder)
77     }
78 
overscrollnull79     override fun overscroll(
80         scene: SceneKey,
81         orientation: Orientation,
82         builder: OverscrollBuilder.() -> Unit
83     ): OverscrollSpec {
84         fun transformationSpec(): TransformationSpecImpl {
85             val impl = OverscrollBuilderImpl().apply(builder)
86             return TransformationSpecImpl(
87                 progressSpec = snap(),
88                 swipeSpec = null,
89                 distance = impl.distance,
90                 transformations = impl.transformations,
91             )
92         }
93         val spec = OverscrollSpecImpl(scene, orientation, transformationSpec())
94         transitionOverscrollSpecs.add(spec)
95         return spec
96     }
97 
transitionnull98     private fun transition(
99         from: SceneKey?,
100         to: SceneKey?,
101         key: TransitionKey?,
102         builder: TransitionBuilder.() -> Unit,
103     ): TransitionSpec {
104         fun transformationSpec(): TransformationSpecImpl {
105             val impl = TransitionBuilderImpl().apply(builder)
106             return TransformationSpecImpl(
107                 progressSpec = impl.spec,
108                 swipeSpec = impl.swipeSpec,
109                 distance = impl.distance,
110                 transformations = impl.transformations,
111             )
112         }
113 
114         val spec = TransitionSpecImpl(key, from, to, ::transformationSpec)
115         transitionSpecs.add(spec)
116         return spec
117     }
118 }
119 
120 internal abstract class BaseTransitionBuilderImpl : BaseTransitionBuilder {
121     val transformations = mutableListOf<Transformation>()
122     private var range: TransformationRange? = null
123     protected var reversed = false
124     override var distance: UserActionDistance? = null
125 
fractionRangenull126     override fun fractionRange(
127         start: Float?,
128         end: Float?,
129         builder: PropertyTransformationBuilder.() -> Unit
130     ) {
131         range = TransformationRange(start, end)
132         builder()
133         range = null
134     }
135 
transformationnull136     protected fun transformation(transformation: PropertyTransformation<*>) {
137         val transformation =
138             if (range != null) {
139                 RangedPropertyTransformation(transformation, range!!)
140             } else {
141                 transformation
142             }
143 
144         transformations.add(
145             if (reversed) {
146                 transformation.reversed()
147             } else {
148                 transformation
149             }
150         )
151     }
152 
fadenull153     override fun fade(matcher: ElementMatcher) {
154         transformation(Fade(matcher))
155     }
156 
translatenull157     override fun translate(matcher: ElementMatcher, x: Dp, y: Dp) {
158         transformation(Translate(matcher, x, y))
159     }
160 
translatenull161     override fun translate(
162         matcher: ElementMatcher,
163         edge: Edge,
164         startsOutsideLayoutBounds: Boolean
165     ) {
166         transformation(EdgeTranslate(matcher, edge, startsOutsideLayoutBounds))
167     }
168 
anchoredTranslatenull169     override fun anchoredTranslate(matcher: ElementMatcher, anchor: ElementKey) {
170         transformation(AnchoredTranslate(matcher, anchor))
171     }
172 
scaleSizenull173     override fun scaleSize(matcher: ElementMatcher, width: Float, height: Float) {
174         transformation(ScaleSize(matcher, width, height))
175     }
176 
scaleDrawnull177     override fun scaleDraw(matcher: ElementMatcher, scaleX: Float, scaleY: Float, pivot: Offset) {
178         transformation(DrawScale(matcher, scaleX, scaleY, pivot))
179     }
180 
anchoredSizenull181     override fun anchoredSize(
182         matcher: ElementMatcher,
183         anchor: ElementKey,
184         anchorWidth: Boolean,
185         anchorHeight: Boolean,
186     ) {
187         transformation(AnchoredSize(matcher, anchor, anchorWidth, anchorHeight))
188     }
189 }
190 
191 internal class TransitionBuilderImpl : BaseTransitionBuilderImpl(), TransitionBuilder {
192     override var spec: AnimationSpec<Float> = spring(stiffness = Spring.StiffnessLow)
193     override var swipeSpec: SpringSpec<Float>? = null
194     override var distance: UserActionDistance? = null
<lambda>null195     private val durationMillis: Int by lazy {
196         val spec = spec
197         if (spec !is DurationBasedAnimationSpec) {
198             error("timestampRange {} can only be used with a DurationBasedAnimationSpec")
199         }
200 
201         spec.vectorize(Float.VectorConverter).durationMillis
202     }
203 
reversednull204     override fun reversed(builder: TransitionBuilder.() -> Unit) {
205         reversed = true
206         builder()
207         reversed = false
208     }
209 
sharedElementnull210     override fun sharedElement(matcher: ElementMatcher, enabled: Boolean) {
211         transformations.add(SharedElementTransformation(matcher, enabled))
212     }
213 
timestampRangenull214     override fun timestampRange(
215         startMillis: Int?,
216         endMillis: Int?,
217         builder: PropertyTransformationBuilder.() -> Unit
218     ) {
219         if (startMillis != null && (startMillis < 0 || startMillis > durationMillis)) {
220             error("invalid start value: startMillis=$startMillis durationMillis=$durationMillis")
221         }
222 
223         if (endMillis != null && (endMillis < 0 || endMillis > durationMillis)) {
224             error("invalid end value: endMillis=$startMillis durationMillis=$durationMillis")
225         }
226 
227         val start = startMillis?.let { it.toFloat() / durationMillis }
228         val end = endMillis?.let { it.toFloat() / durationMillis }
229         fractionRange(start, end, builder)
230     }
231 }
232 
233 internal open class OverscrollBuilderImpl : BaseTransitionBuilderImpl(), OverscrollBuilder {
translatenull234     override fun translate(
235         matcher: ElementMatcher,
236         x: OverscrollScope.() -> Float,
237         y: OverscrollScope.() -> Float
238     ) {
239         transformation(OverscrollTranslate(matcher, x, y))
240     }
241 }
242