• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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