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 38 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 48 override fun to(to: SceneKey, builder: TransitionBuilder.() -> Unit): TransitionSpec { 49 return transition(from = null, to = to, builder) 50 } 51 52 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 60 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 83 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 92 override fun punchHole(matcher: ElementMatcher, bounds: ElementKey, shape: Shape) { 93 transformations.add(PunchHole(matcher, bounds, shape)) 94 } 95 96 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 106 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 124 private fun transformation(transformation: PropertyTransformation<*>) { 125 if (range != null) { 126 transformations.add(RangedPropertyTransformation(transformation, range!!)) 127 } else { 128 transformations.add(transformation) 129 } 130 } 131 132 override fun fade(matcher: ElementMatcher) { 133 transformation(Fade(matcher)) 134 } 135 136 override fun translate(matcher: ElementMatcher, x: Dp, y: Dp) { 137 transformation(Translate(matcher, x, y)) 138 } 139 140 override fun translate( 141 matcher: ElementMatcher, 142 edge: Edge, 143 startsOutsideLayoutBounds: Boolean 144 ) { 145 transformation(EdgeTranslate(matcher, edge, startsOutsideLayoutBounds)) 146 } 147 148 override fun anchoredTranslate(matcher: ElementMatcher, anchor: ElementKey) { 149 transformation(AnchoredTranslate(matcher, anchor)) 150 } 151 152 override fun scaleSize(matcher: ElementMatcher, width: Float, height: Float) { 153 transformation(ScaleSize(matcher, width, height)) 154 } 155 156 override fun anchoredSize(matcher: ElementMatcher, anchor: ElementKey) { 157 transformation(AnchoredSize(matcher, anchor)) 158 } 159 } 160