diff options
| author | 2023-11-03 14:00:12 +0100 | |
|---|---|---|
| committer | 2023-11-08 09:52:08 +0100 | |
| commit | 3df36b776107cf703ccda9c1b65ea888c2a2146e (patch) | |
| tree | 9ff31b1efdac935f78a27cb389015a7c83d4f35e | |
| parent | 23daba73c529fcd7e52c64af7c0806f2f0a4e809 (diff) | |
Add new PropertyTransformation DrawScale
Test: added DrawScaleTest
Bug: b/290184746
Flag: NONE
Change-Id: I5f7107eb5b976ee9541a6ff7088cc2fa6c6a5b76
7 files changed, 200 insertions, 4 deletions
diff --git a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/Element.kt b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/Element.kt index eb10afc1ee57..aae61bd0f554 100644 --- a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/Element.kt +++ b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/Element.kt @@ -33,7 +33,9 @@ import androidx.compose.ui.composed import androidx.compose.ui.draw.drawWithContent import androidx.compose.ui.geometry.Offset import androidx.compose.ui.geometry.isSpecified +import androidx.compose.ui.geometry.isUnspecified import androidx.compose.ui.geometry.lerp +import androidx.compose.ui.graphics.drawscope.scale import androidx.compose.ui.graphics.graphicsLayer import androidx.compose.ui.layout.IntermediateMeasureScope import androidx.compose.ui.layout.Measurable @@ -85,6 +87,9 @@ internal class Element(val key: ElementKey) { /** The size of this element. */ var size = SizeUnspecified + /** The draw scale of this element. */ + var drawScale = Scale.Default + /** The alpha of this element. */ var alpha = AlphaUnspecified } @@ -110,6 +115,13 @@ internal class Element(val key: ElementKey) { } } +data class Scale(val scaleX: Float, val scaleY: Float, val pivot: Offset = Offset.Unspecified) { + + companion object { + val Default = Scale(1f, 1f, Offset.Unspecified) + } +} + /** The implementation of [SceneScope.element]. */ @OptIn(ExperimentalComposeUiApi::class) internal fun Modifier.element( @@ -160,9 +172,24 @@ internal fun Modifier.element( } } + val drawScale by + remember(layoutImpl, element, scene, sceneValues) { + derivedStateOf { getDrawScale(layoutImpl, element, scene, sceneValues) } + } + drawWithContent { if (shouldDrawElement(layoutImpl, scene, element)) { - drawContent() + if (drawScale == Scale.Default) { + this@drawWithContent.drawContent() + } else { + scale( + drawScale.scaleX, + drawScale.scaleY, + if (drawScale.pivot.isUnspecified) center else drawScale.pivot + ) { + this@drawWithContent.drawContent() + } + } } } .modifierTransformations(layoutImpl, scene, element, sceneValues) @@ -377,6 +404,28 @@ private fun IntermediateMeasureScope.measure( return placeable } +private fun getDrawScale( + layoutImpl: SceneTransitionLayoutImpl, + element: Element, + scene: Scene, + sceneValues: Element.TargetValues +): Scale { + return computeValue( + layoutImpl, + scene, + element, + sceneValue = { Scale.Default }, + transformation = { it.drawScale }, + idleValue = Scale.Default, + currentValue = { Scale.Default }, + lastValue = { + sceneValues.lastValues.drawScale.takeIf { it != Scale.Default } + ?: element.lastSharedValues.drawScale + }, + ::lerp, + ) +} + @OptIn(ExperimentalComposeUiApi::class) private fun IntermediateMeasureScope.place( layoutImpl: SceneTransitionLayoutImpl, diff --git a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/SceneTransitions.kt b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/SceneTransitions.kt index b163a2a96039..72a2d612d8e3 100644 --- a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/SceneTransitions.kt +++ b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/SceneTransitions.kt @@ -25,6 +25,7 @@ import androidx.compose.ui.util.fastForEach import androidx.compose.ui.util.fastMap import com.android.compose.animation.scene.transformation.AnchoredSize import com.android.compose.animation.scene.transformation.AnchoredTranslate +import com.android.compose.animation.scene.transformation.DrawScale import com.android.compose.animation.scene.transformation.EdgeTranslate import com.android.compose.animation.scene.transformation.Fade import com.android.compose.animation.scene.transformation.ModifierTransformation @@ -126,6 +127,7 @@ data class TransitionSpec( val modifier = mutableListOf<ModifierTransformation>() var offset: PropertyTransformation<Offset>? = null var size: PropertyTransformation<IntSize>? = null + var drawScale: PropertyTransformation<Scale>? = null var alpha: PropertyTransformation<Float>? = null fun <T> onPropertyTransformation( @@ -144,6 +146,10 @@ data class TransitionSpec( throwIfNotNull(size, element, name = "size") size = root as PropertyTransformation<IntSize> } + is DrawScale -> { + throwIfNotNull(drawScale, element, name = "drawScale") + drawScale = root as PropertyTransformation<Scale> + } is Fade -> { throwIfNotNull(alpha, element, name = "alpha") alpha = root as PropertyTransformation<Float> @@ -167,7 +173,7 @@ data class TransitionSpec( } } - return ElementTransformations(shared, modifier, offset, size, alpha) + return ElementTransformations(shared, modifier, offset, size, drawScale, alpha) } private fun throwIfNotNull( @@ -187,5 +193,6 @@ internal class ElementTransformations( val modifier: List<ModifierTransformation>, val offset: PropertyTransformation<Offset>?, val size: PropertyTransformation<IntSize>?, + val drawScale: PropertyTransformation<Scale>?, val alpha: PropertyTransformation<Float>?, ) diff --git a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/TransitionDsl.kt b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/TransitionDsl.kt index 7b7ddfa5ec4e..ca66dff5e231 100644 --- a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/TransitionDsl.kt +++ b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/TransitionDsl.kt @@ -17,6 +17,7 @@ package com.android.compose.animation.scene import androidx.compose.animation.core.AnimationSpec +import androidx.compose.ui.geometry.Offset import androidx.compose.ui.graphics.RectangleShape import androidx.compose.ui.graphics.Shape import androidx.compose.ui.unit.Dp @@ -224,12 +225,22 @@ interface PropertyTransformationBuilder { /** * Scale the [width] and [height] of the element(s) matching [matcher]. Note that this scaling * is done during layout, so it will potentially impact the size and position of other elements. - * - * TODO(b/290184746): Also provide a scaleDrawing() to scale an element at drawing time. */ fun scaleSize(matcher: ElementMatcher, width: Float = 1f, height: Float = 1f) /** + * Scale the drawing with [scaleX] and [scaleY] of the element(s) matching [matcher]. Note this + * will only scale the draw inside of an element, therefore it won't impact layout of elements + * around it. + */ + fun scaleDraw( + matcher: ElementMatcher, + scaleX: Float = 1f, + scaleY: Float = 1f, + pivot: Offset = Offset.Unspecified + ) + + /** * Scale the element(s) matching [matcher] so that it grows/shrinks to the same size as [anchor] * . * diff --git a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/TransitionDslImpl.kt b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/TransitionDslImpl.kt index d2bfd91842ae..d4909892f492 100644 --- a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/TransitionDslImpl.kt +++ b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/TransitionDslImpl.kt @@ -21,10 +21,12 @@ import androidx.compose.animation.core.DurationBasedAnimationSpec import androidx.compose.animation.core.Spring import androidx.compose.animation.core.VectorConverter import androidx.compose.animation.core.spring +import androidx.compose.ui.geometry.Offset import androidx.compose.ui.graphics.Shape import androidx.compose.ui.unit.Dp import com.android.compose.animation.scene.transformation.AnchoredSize import com.android.compose.animation.scene.transformation.AnchoredTranslate +import com.android.compose.animation.scene.transformation.DrawScale import com.android.compose.animation.scene.transformation.EdgeTranslate import com.android.compose.animation.scene.transformation.Fade import com.android.compose.animation.scene.transformation.PropertyTransformation @@ -178,6 +180,10 @@ internal class TransitionBuilderImpl : TransitionBuilder { transformation(ScaleSize(matcher, width, height)) } + override fun scaleDraw(matcher: ElementMatcher, scaleX: Float, scaleY: Float, pivot: Offset) { + transformation(DrawScale(matcher, scaleX, scaleY, pivot)) + } + override fun anchoredSize(matcher: ElementMatcher, anchor: ElementKey) { transformation(AnchoredSize(matcher, anchor)) } diff --git a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/transformation/DrawScale.kt b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/transformation/DrawScale.kt new file mode 100644 index 000000000000..d1cf8ee6ad4a --- /dev/null +++ b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/transformation/DrawScale.kt @@ -0,0 +1,48 @@ +/* + * Copyright 2023 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.compose.animation.scene.transformation + +import androidx.compose.ui.geometry.Offset +import com.android.compose.animation.scene.Element +import com.android.compose.animation.scene.ElementMatcher +import com.android.compose.animation.scene.Scale +import com.android.compose.animation.scene.Scene +import com.android.compose.animation.scene.SceneTransitionLayoutImpl +import com.android.compose.animation.scene.TransitionState + +/** + * Scales the draw size of an element. Note this will only scale the draw inside of an element, + * therefore it won't impact layout of elements around it. + */ +internal class DrawScale( + override val matcher: ElementMatcher, + private val scaleX: Float, + private val scaleY: Float, + private val pivot: Offset = Offset.Unspecified, +) : PropertyTransformation<Scale> { + + override fun transform( + layoutImpl: SceneTransitionLayoutImpl, + scene: Scene, + element: Element, + sceneValues: Element.TargetValues, + transition: TransitionState.Transition, + value: Scale, + ): Scale { + return Scale(scaleX, scaleY, pivot) + } +} diff --git a/packages/SystemUI/compose/scene/src/com/android/compose/ui/util/MathHelpers.kt b/packages/SystemUI/compose/scene/src/com/android/compose/ui/util/MathHelpers.kt index eb1a634ff491..13747b72724e 100644 --- a/packages/SystemUI/compose/scene/src/com/android/compose/ui/util/MathHelpers.kt +++ b/packages/SystemUI/compose/scene/src/com/android/compose/ui/util/MathHelpers.kt @@ -17,7 +17,10 @@ package com.android.compose.ui.util +import androidx.compose.ui.geometry.isSpecified +import androidx.compose.ui.geometry.lerp import androidx.compose.ui.unit.IntSize +import com.android.compose.animation.scene.Scale import kotlin.math.roundToInt import kotlin.math.roundToLong @@ -43,3 +46,19 @@ fun lerp(start: IntSize, stop: IntSize, fraction: Float): IntSize { lerp(start.height, stop.height, fraction) ) } + +/** Linearly interpolate between [start] and [stop] with [fraction] fraction between them. */ +fun lerp(start: Scale, stop: Scale, fraction: Float): Scale { + val pivot = + when { + start.pivot.isSpecified && stop.pivot.isSpecified -> + lerp(start.pivot, stop.pivot, fraction) + start.pivot.isSpecified -> start.pivot + else -> stop.pivot + } + return Scale( + lerp(start.scaleX, stop.scaleX, fraction), + lerp(start.scaleY, stop.scaleY, fraction), + pivot + ) +} diff --git a/packages/SystemUI/compose/scene/tests/src/com/android/compose/ui/util/MathHelpersTest.kt b/packages/SystemUI/compose/scene/tests/src/com/android/compose/ui/util/MathHelpersTest.kt new file mode 100644 index 000000000000..3ec73c793049 --- /dev/null +++ b/packages/SystemUI/compose/scene/tests/src/com/android/compose/ui/util/MathHelpersTest.kt @@ -0,0 +1,56 @@ +package com.android.compose.ui.util + +import androidx.compose.ui.geometry.Offset +import androidx.test.ext.junit.runners.AndroidJUnit4 +import com.android.compose.animation.scene.Scale +import com.google.common.truth.Truth.assertThat +import org.junit.Test +import org.junit.runner.RunWith + +@RunWith(AndroidJUnit4::class) +class MathHelpersTest { + + @Test + fun lerpScaleWithPivotUnspecified() { + val scale1 = Scale(1f, 1f) + val scale2 = Scale(5f, 3f) + val expectedScale = Scale(3f, 2f) + + val actualScale = lerp(scale1, scale2, 0.5f) + + assertThat(actualScale).isEqualTo(expectedScale) + } + + @Test + fun lerpScaleWithFirstPivotSpecified() { + val scale1 = Scale(1f, 1f, Offset(1f, 1f)) + val scale2 = Scale(5f, 3f) + val expectedScale = Scale(3f, 2f, Offset(1f, 1f)) + + val actualScale = lerp(scale1, scale2, 0.5f) + + assertThat(actualScale).isEqualTo(expectedScale) + } + + @Test + fun lerpScaleWithSecondPivotSpecified() { + val scale1 = Scale(1f, 1f) + val scale2 = Scale(5f, 3f, Offset(1f, 1f)) + val expectedScale = Scale(3f, 2f, Offset(1f, 1f)) + + val actualScale = lerp(scale1, scale2, 0.5f) + + assertThat(actualScale).isEqualTo(expectedScale) + } + + @Test + fun lerpScaleWithBothPivotsSpecified() { + val scale1 = Scale(1f, 1f, Offset(1f, 1f)) + val scale2 = Scale(5f, 3f, Offset(3f, 5f)) + val expectedScale = Scale(3f, 2f, Offset(2f, 3f)) + + val actualScale = lerp(scale1, scale2, 0.5f) + + assertThat(actualScale).isEqualTo(expectedScale) + } +} |