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.transformation 18 19 import androidx.compose.ui.Modifier 20 import androidx.compose.ui.draw.drawWithContent 21 import androidx.compose.ui.geometry.Offset 22 import androidx.compose.ui.geometry.toRect 23 import androidx.compose.ui.graphics.BlendMode 24 import androidx.compose.ui.graphics.Color 25 import androidx.compose.ui.graphics.Paint 26 import androidx.compose.ui.graphics.RectangleShape 27 import androidx.compose.ui.graphics.Shape 28 import androidx.compose.ui.graphics.drawOutline 29 import androidx.compose.ui.graphics.drawscope.DrawScope 30 import androidx.compose.ui.graphics.drawscope.drawIntoCanvas 31 import androidx.compose.ui.graphics.drawscope.translate 32 import androidx.compose.ui.graphics.withSaveLayer 33 import androidx.compose.ui.unit.toSize 34 import com.android.compose.animation.scene.Element 35 import com.android.compose.animation.scene.ElementKey 36 import com.android.compose.animation.scene.ElementMatcher 37 import com.android.compose.animation.scene.Scene 38 import com.android.compose.animation.scene.SceneTransitionLayoutImpl 39 40 /** Punch a hole in an element using the bounds of another element and a given [shape]. */ 41 internal class PunchHole( 42 override val matcher: ElementMatcher, 43 private val bounds: ElementKey, 44 private val shape: Shape, 45 ) : ModifierTransformation { 46 override fun Modifier.transform( 47 layoutImpl: SceneTransitionLayoutImpl, 48 scene: Scene, 49 element: Element, 50 sceneValues: Element.SceneValues, 51 ): Modifier { 52 return drawWithContent { 53 val bounds = layoutImpl.elements[bounds] 54 if ( 55 bounds == null || 56 bounds.lastSize == Element.SizeUnspecified || 57 bounds.lastOffset == Offset.Unspecified 58 ) { 59 drawContent() 60 return@drawWithContent 61 } 62 63 drawIntoCanvas { canvas -> 64 canvas.withSaveLayer(size.toRect(), Paint()) { 65 drawContent() 66 67 val offset = bounds.lastOffset - element.lastOffset 68 translate(offset.x, offset.y) { drawHole(bounds) } 69 } 70 } 71 } 72 } 73 74 private fun DrawScope.drawHole(bounds: Element) { 75 if (shape == RectangleShape) { 76 drawRect(Color.Black, blendMode = BlendMode.DstOut) 77 return 78 } 79 80 // TODO(b/290184746): Cache outline if the size of bounds does not change. 81 drawOutline( 82 shape.createOutline( 83 bounds.lastSize.toSize(), 84 layoutDirection, 85 this, 86 ), 87 Color.Black, 88 blendMode = BlendMode.DstOut, 89 ) 90 } 91 } 92