1 /*
<lambda>null2  * 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.Easing
22 import androidx.compose.animation.core.Spring
23 import androidx.compose.animation.core.SpringSpec
24 import androidx.compose.animation.core.VectorConverter
25 import androidx.compose.animation.core.snap
26 import androidx.compose.animation.core.spring
27 import androidx.compose.foundation.gestures.Orientation
28 import androidx.compose.ui.geometry.Offset
29 import androidx.compose.ui.unit.Dp
30 import com.android.compose.animation.scene.content.state.TransitionState
31 import com.android.compose.animation.scene.transformation.AnchoredSize
32 import com.android.compose.animation.scene.transformation.AnchoredTranslate
33 import com.android.compose.animation.scene.transformation.DrawScale
34 import com.android.compose.animation.scene.transformation.EdgeTranslate
35 import com.android.compose.animation.scene.transformation.Fade
36 import com.android.compose.animation.scene.transformation.OverscrollTranslate
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.TransformationMatcher
41 import com.android.compose.animation.scene.transformation.TransformationRange
42 import com.android.compose.animation.scene.transformation.Translate
43 
44 internal fun transitionsImpl(builder: SceneTransitionsBuilder.() -> Unit): SceneTransitions {
45     val impl = SceneTransitionsBuilderImpl().apply(builder)
46     return SceneTransitions(
47         impl.defaultSwipeSpec,
48         impl.transitionSpecs,
49         impl.transitionOverscrollSpecs,
50         impl.interruptionHandler,
51         impl.defaultOverscrollProgressConverter,
52     )
53 }
54 
55 private class SceneTransitionsBuilderImpl : SceneTransitionsBuilder {
56     override var defaultSwipeSpec: SpringSpec<Float> = SceneTransitions.DefaultSwipeSpec
57     override var interruptionHandler: InterruptionHandler = DefaultInterruptionHandler
58     override var defaultOverscrollProgressConverter: ProgressConverter = ProgressConverter.Default
59 
60     val transitionSpecs = mutableListOf<TransitionSpecImpl>()
61     val transitionOverscrollSpecs = mutableListOf<OverscrollSpecImpl>()
62 
tonull63     override fun to(
64         to: ContentKey,
65         key: TransitionKey?,
66         preview: (TransitionBuilder.() -> Unit)?,
67         reversePreview: (TransitionBuilder.() -> Unit)?,
68         builder: TransitionBuilder.() -> Unit,
69     ) {
70         transition(from = null, to = to, key = key, preview, reversePreview, builder)
71     }
72 
fromnull73     override fun from(
74         from: ContentKey,
75         to: ContentKey?,
76         key: TransitionKey?,
77         preview: (TransitionBuilder.() -> Unit)?,
78         reversePreview: (TransitionBuilder.() -> Unit)?,
79         builder: TransitionBuilder.() -> Unit,
80     ) {
81         transition(from = from, to = to, key = key, preview, reversePreview, builder)
82     }
83 
overscrollnull84     override fun overscroll(
85         content: ContentKey,
86         orientation: Orientation,
87         builder: OverscrollBuilder.() -> Unit,
88     ) {
89         val impl = OverscrollBuilderImpl().apply(builder)
90         check(impl.transformationMatchers.isNotEmpty()) {
91             "This method does not allow empty transformations. " +
92                 "Use overscrollDisabled($content, $orientation) instead."
93         }
94         overscrollSpec(content, orientation, impl)
95     }
96 
overscrollDisablednull97     override fun overscrollDisabled(content: ContentKey, orientation: Orientation) {
98         overscrollSpec(content, orientation, OverscrollBuilderImpl())
99     }
100 
overscrollSpecnull101     private fun overscrollSpec(
102         content: ContentKey,
103         orientation: Orientation,
104         impl: OverscrollBuilderImpl,
105     ): OverscrollSpec {
106         val spec =
107             OverscrollSpecImpl(
108                 content = content,
109                 orientation = orientation,
110                 transformationSpec =
111                     TransformationSpecImpl(
112                         progressSpec = snap(),
113                         swipeSpec = null,
114                         distance = impl.distance,
115                         transformationMatchers = impl.transformationMatchers,
116                     ),
117                 progressConverter = impl.progressConverter,
118             )
119         transitionOverscrollSpecs.add(spec)
120         return spec
121     }
122 
transitionnull123     private fun transition(
124         from: ContentKey?,
125         to: ContentKey?,
126         key: TransitionKey?,
127         preview: (TransitionBuilder.() -> Unit)?,
128         reversePreview: (TransitionBuilder.() -> Unit)?,
129         builder: TransitionBuilder.() -> Unit,
130     ): TransitionSpec {
131         fun transformationSpec(
132             transition: TransitionState.Transition,
133             builder: TransitionBuilder.() -> Unit,
134         ): TransformationSpecImpl {
135             val impl = TransitionBuilderImpl(transition).apply(builder)
136             return TransformationSpecImpl(
137                 progressSpec = impl.spec,
138                 swipeSpec = impl.swipeSpec,
139                 distance = impl.distance,
140                 transformationMatchers = impl.transformationMatchers,
141             )
142         }
143 
144         val spec =
145             TransitionSpecImpl(
146                 key,
147                 from,
148                 to,
149                 previewTransformationSpec = preview?.let { { t -> transformationSpec(t, it) } },
150                 reversePreviewTransformationSpec =
151                     reversePreview?.let { { t -> transformationSpec(t, it) } },
152                 transformationSpec = { t -> transformationSpec(t, builder) },
153             )
154         transitionSpecs.add(spec)
155         return spec
156     }
157 }
158 
159 internal abstract class BaseTransitionBuilderImpl : BaseTransitionBuilder {
160     val transformationMatchers = mutableListOf<TransformationMatcher>()
161     private var range: TransformationRange? = null
162     protected var reversed = false
163     override var distance: UserActionDistance? = null
164 
fractionRangenull165     override fun fractionRange(
166         start: Float?,
167         end: Float?,
168         easing: Easing,
169         builder: PropertyTransformationBuilder.() -> Unit,
170     ) {
171         range = TransformationRange(start, end, easing)
172         builder()
173         range = null
174     }
175 
addTransformationnull176     protected fun addTransformation(
177         matcher: ElementMatcher,
178         transformation: Transformation.Factory,
179     ) {
180         transformationMatchers.add(
181             TransformationMatcher(
182                 matcher,
183                 transformation,
184                 range?.let { range ->
185                     if (reversed) {
186                         range.reversed()
187                     } else {
188                         range
189                     }
190                 },
191             )
192         )
193     }
194 
fadenull195     override fun fade(matcher: ElementMatcher) {
196         addTransformation(matcher, Fade.Factory)
197     }
198 
translatenull199     override fun translate(matcher: ElementMatcher, x: Dp, y: Dp) {
200         addTransformation(matcher, Translate.Factory(x, y))
201     }
202 
translatenull203     override fun translate(
204         matcher: ElementMatcher,
205         edge: Edge,
206         startsOutsideLayoutBounds: Boolean,
207     ) {
208         addTransformation(matcher, EdgeTranslate.Factory(edge, startsOutsideLayoutBounds))
209     }
210 
anchoredTranslatenull211     override fun anchoredTranslate(matcher: ElementMatcher, anchor: ElementKey) {
212         addTransformation(matcher, AnchoredTranslate.Factory(anchor))
213     }
214 
scaleSizenull215     override fun scaleSize(matcher: ElementMatcher, width: Float, height: Float) {
216         addTransformation(matcher, ScaleSize.Factory(width, height))
217     }
218 
scaleDrawnull219     override fun scaleDraw(matcher: ElementMatcher, scaleX: Float, scaleY: Float, pivot: Offset) {
220         addTransformation(matcher, DrawScale.Factory(scaleX, scaleY, pivot))
221     }
222 
anchoredSizenull223     override fun anchoredSize(
224         matcher: ElementMatcher,
225         anchor: ElementKey,
226         anchorWidth: Boolean,
227         anchorHeight: Boolean,
228     ) {
229         addTransformation(matcher, AnchoredSize.Factory(anchor, anchorWidth, anchorHeight))
230     }
231 
transformationnull232     override fun transformation(matcher: ElementMatcher, transformation: Transformation.Factory) {
233         check(range == null) { "Custom transformations can not be applied inside a range" }
234         addTransformation(matcher, transformation)
235     }
236 }
237 
238 internal class TransitionBuilderImpl(override val transition: TransitionState.Transition) :
239     BaseTransitionBuilderImpl(), TransitionBuilder {
240     override var spec: AnimationSpec<Float> = spring(stiffness = Spring.StiffnessLow)
241     override var swipeSpec: SpringSpec<Float>? = null
242     override var distance: UserActionDistance? = null
<lambda>null243     private val durationMillis: Int by lazy {
244         val spec = spec
245         if (spec !is DurationBasedAnimationSpec) {
246             error("timestampRange {} can only be used with a DurationBasedAnimationSpec")
247         }
248 
249         spec.vectorize(Float.VectorConverter).durationMillis
250     }
251 
reversednull252     override fun reversed(builder: TransitionBuilder.() -> Unit) {
253         reversed = true
254         builder()
255         reversed = false
256     }
257 
sharedElementnull258     override fun sharedElement(
259         matcher: ElementMatcher,
260         enabled: Boolean,
261         elevateInContent: ContentKey?,
262     ) {
263         check(
264             elevateInContent == null ||
265                 elevateInContent == transition.fromContent ||
266                 elevateInContent == transition.toContent
267         ) {
268             "elevateInContent (${elevateInContent?.debugName}) should be either fromContent " +
269                 "(${transition.fromContent.debugName}) or toContent " +
270                 "(${transition.toContent.debugName})"
271         }
272 
273         addTransformation(
274             matcher,
275             SharedElementTransformation.Factory(matcher, enabled, elevateInContent),
276         )
277     }
278 
timestampRangenull279     override fun timestampRange(
280         startMillis: Int?,
281         endMillis: Int?,
282         easing: Easing,
283         builder: PropertyTransformationBuilder.() -> Unit,
284     ) {
285         if (startMillis != null && (startMillis < 0 || startMillis > durationMillis)) {
286             error("invalid start value: startMillis=$startMillis durationMillis=$durationMillis")
287         }
288 
289         if (endMillis != null && (endMillis < 0 || endMillis > durationMillis)) {
290             error("invalid end value: endMillis=$startMillis durationMillis=$durationMillis")
291         }
292 
293         val start = startMillis?.let { it.toFloat() / durationMillis }
294         val end = endMillis?.let { it.toFloat() / durationMillis }
295         fractionRange(start, end, easing, builder)
296     }
297 }
298 
299 internal open class OverscrollBuilderImpl : BaseTransitionBuilderImpl(), OverscrollBuilder {
300     override var progressConverter: ProgressConverter? = null
301 
translatenull302     override fun translate(
303         matcher: ElementMatcher,
304         x: OverscrollScope.() -> Float,
305         y: OverscrollScope.() -> Float,
306     ) {
307         addTransformation(matcher, OverscrollTranslate.Factory(x, y))
308     }
309 }
310