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.VectorConverter
23 import androidx.compose.animation.core.spring
24 import androidx.compose.ui.graphics.Shape
25 import androidx.compose.ui.unit.Dp
26 import com.android.compose.animation.scene.transformation.AnchoredSize
27 import com.android.compose.animation.scene.transformation.AnchoredTranslate
28 import com.android.compose.animation.scene.transformation.EdgeTranslate
29 import com.android.compose.animation.scene.transformation.Fade
30 import com.android.compose.animation.scene.transformation.PropertyTransformation
31 import com.android.compose.animation.scene.transformation.PunchHole
32 import com.android.compose.animation.scene.transformation.RangedPropertyTransformation
33 import com.android.compose.animation.scene.transformation.ScaleSize
34 import com.android.compose.animation.scene.transformation.Transformation
35 import com.android.compose.animation.scene.transformation.TransformationRange
36 import com.android.compose.animation.scene.transformation.Translate
37
transitionsImplnull38 internal fun transitionsImpl(
39 builder: SceneTransitionsBuilder.() -> Unit,
40 ): SceneTransitions {
41 val impl = SceneTransitionsBuilderImpl().apply(builder)
42 return SceneTransitions(impl.transitionSpecs)
43 }
44
45 private class SceneTransitionsBuilderImpl : SceneTransitionsBuilder {
46 val transitionSpecs = mutableListOf<TransitionSpec>()
47
tonull48 override fun to(to: SceneKey, builder: TransitionBuilder.() -> Unit): TransitionSpec {
49 return transition(from = null, to = to, builder)
50 }
51
fromnull52 override fun from(
53 from: SceneKey,
54 to: SceneKey?,
55 builder: TransitionBuilder.() -> Unit
56 ): TransitionSpec {
57 return transition(from = from, to = to, builder)
58 }
59
transitionnull60 private fun transition(
61 from: SceneKey?,
62 to: SceneKey?,
63 builder: TransitionBuilder.() -> Unit,
64 ): TransitionSpec {
65 val impl = TransitionBuilderImpl().apply(builder)
66 val spec =
67 TransitionSpec(
68 from,
69 to,
70 impl.transformations,
71 impl.spec,
72 )
73 transitionSpecs.add(spec)
74 return spec
75 }
76 }
77
78 internal class TransitionBuilderImpl : TransitionBuilder {
79 val transformations = mutableListOf<Transformation>()
80 override var spec: AnimationSpec<Float> = spring(stiffness = Spring.StiffnessLow)
81
82 private var range: TransformationRange? = null
<lambda>null83 private val durationMillis: Int by lazy {
84 val spec = spec
85 if (spec !is DurationBasedAnimationSpec) {
86 error("timestampRange {} can only be used with a DurationBasedAnimationSpec")
87 }
88
89 spec.vectorize(Float.VectorConverter).durationMillis
90 }
91
punchHolenull92 override fun punchHole(matcher: ElementMatcher, bounds: ElementKey, shape: Shape) {
93 transformations.add(PunchHole(matcher, bounds, shape))
94 }
95
fractionRangenull96 override fun fractionRange(
97 start: Float?,
98 end: Float?,
99 builder: PropertyTransformationBuilder.() -> Unit
100 ) {
101 range = TransformationRange(start, end)
102 builder()
103 range = null
104 }
105
timestampRangenull106 override fun timestampRange(
107 startMillis: Int?,
108 endMillis: Int?,
109 builder: PropertyTransformationBuilder.() -> Unit
110 ) {
111 if (startMillis != null && (startMillis < 0 || startMillis > durationMillis)) {
112 error("invalid start value: startMillis=$startMillis durationMillis=$durationMillis")
113 }
114
115 if (endMillis != null && (endMillis < 0 || endMillis > durationMillis)) {
116 error("invalid end value: endMillis=$startMillis durationMillis=$durationMillis")
117 }
118
119 val start = startMillis?.let { it.toFloat() / durationMillis }
120 val end = endMillis?.let { it.toFloat() / durationMillis }
121 fractionRange(start, end, builder)
122 }
123
transformationnull124 private fun transformation(transformation: PropertyTransformation<*>) {
125 if (range != null) {
126 transformations.add(RangedPropertyTransformation(transformation, range!!))
127 } else {
128 transformations.add(transformation)
129 }
130 }
131
fadenull132 override fun fade(matcher: ElementMatcher) {
133 transformation(Fade(matcher))
134 }
135
translatenull136 override fun translate(matcher: ElementMatcher, x: Dp, y: Dp) {
137 transformation(Translate(matcher, x, y))
138 }
139
translatenull140 override fun translate(
141 matcher: ElementMatcher,
142 edge: Edge,
143 startsOutsideLayoutBounds: Boolean
144 ) {
145 transformation(EdgeTranslate(matcher, edge, startsOutsideLayoutBounds))
146 }
147
anchoredTranslatenull148 override fun anchoredTranslate(matcher: ElementMatcher, anchor: ElementKey) {
149 transformation(AnchoredTranslate(matcher, anchor))
150 }
151
scaleSizenull152 override fun scaleSize(matcher: ElementMatcher, width: Float, height: Float) {
153 transformation(ScaleSize(matcher, width, height))
154 }
155
anchoredSizenull156 override fun anchoredSize(matcher: ElementMatcher, anchor: ElementKey) {
157 transformation(AnchoredSize(matcher, anchor))
158 }
159 }
160