• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright (C) 2022 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.modifiers
18 
19 import androidx.compose.ui.Modifier
20 import androidx.compose.ui.geometry.Size
21 import androidx.compose.ui.graphics.Color
22 import androidx.compose.ui.graphics.Outline
23 import androidx.compose.ui.graphics.RectangleShape
24 import androidx.compose.ui.graphics.Shape
25 import androidx.compose.ui.graphics.drawOutline
26 import androidx.compose.ui.graphics.drawscope.ContentDrawScope
27 import androidx.compose.ui.node.DrawModifierNode
28 import androidx.compose.ui.node.ModifierNodeElement
29 import androidx.compose.ui.node.ObserverModifierNode
30 import androidx.compose.ui.node.invalidateDraw
31 import androidx.compose.ui.node.observeReads
32 import androidx.compose.ui.platform.InspectorInfo
33 import androidx.compose.ui.platform.debugInspectorInfo
34 import androidx.compose.ui.unit.LayoutDirection
35 
36 /**
37  * Draws a background in a given [shape] and with a [color] or [alpha] that can be animated.
38  *
39  * @param color color to paint background with
40  * @param alpha alpha of the background
41  * @param shape desired shape of the background
42  */
animatedBackgroundnull43 fun Modifier.animatedBackground(
44     color: () -> Color,
45     alpha: () -> Float = DefaultAlpha,
46     shape: Shape = RectangleShape,
47 ) =
48     this.then(
49         BackgroundElement(
50             color = color,
51             alpha = alpha,
52             shape = shape,
53             inspectorInfo =
54                 debugInspectorInfo {
55                     name = "background"
56                     value = color
57                     properties["color"] = color
58                     properties["alpha"] = alpha
59                     properties["shape"] = shape
60                 },
61         )
62     )
63 
<lambda>null64 private val DefaultAlpha = { 1f }
65 
66 private class BackgroundElement(
67     private val color: () -> Color,
68     private val alpha: () -> Float,
69     private val shape: Shape,
70     private val inspectorInfo: InspectorInfo.() -> Unit,
71 ) : ModifierNodeElement<BackgroundNode>() {
createnull72     override fun create(): BackgroundNode {
73         return BackgroundNode(color, alpha, shape)
74     }
75 
updatenull76     override fun update(node: BackgroundNode) {
77         node.color = color
78         node.alpha = alpha
79         node.shape = shape
80     }
81 
inspectablePropertiesnull82     override fun InspectorInfo.inspectableProperties() {
83         inspectorInfo()
84     }
85 
hashCodenull86     override fun hashCode(): Int {
87         var result = color.hashCode()
88         result = 31 * result + alpha.hashCode()
89         result = 31 * result + shape.hashCode()
90         return result
91     }
92 
equalsnull93     override fun equals(other: Any?): Boolean {
94         val otherModifier = other as? BackgroundElement ?: return false
95         return color == otherModifier.color &&
96             alpha == otherModifier.alpha &&
97             shape == otherModifier.shape
98     }
99 }
100 
101 private class BackgroundNode(var color: () -> Color, var alpha: () -> Float, var shape: Shape) :
102     DrawModifierNode, Modifier.Node(), ObserverModifierNode {
103 
104     // Naively cache outline calculation if input parameters are the same, we manually observe
105     // reads inside shape#createOutline separately
106     private var lastSize: Size = Size.Unspecified
107     private var lastLayoutDirection: LayoutDirection? = null
108     private var lastOutline: Outline? = null
109     private var lastShape: Shape? = null
110     private var tmpOutline: Outline? = null
111 
drawnull112     override fun ContentDrawScope.draw() {
113         if (shape === RectangleShape) {
114             // shortcut to avoid Outline calculation and allocation
115             drawRect()
116         } else {
117             drawOutline()
118         }
119         drawContent()
120     }
121 
onObservedReadsChangednull122     override fun onObservedReadsChanged() {
123         // Reset cached properties
124         lastSize = Size.Unspecified
125         lastLayoutDirection = null
126         lastOutline = null
127         lastShape = null
128         // Invalidate draw so we build the cache again - this is needed because observeReads within
129         // the draw scope obscures the state reads from the draw scope's observer
130         invalidateDraw()
131     }
132 
ContentDrawScopenull133     private fun ContentDrawScope.drawRect() {
134         drawRect(color = color(), alpha = alpha())
135     }
136 
drawOutlinenull137     private fun ContentDrawScope.drawOutline() {
138         val outline = getOutline()
139         drawOutline(outline, color = color(), alpha = alpha())
140     }
141 
getOutlinenull142     private fun ContentDrawScope.getOutline(): Outline {
143         val outline: Outline?
144         if (size == lastSize && layoutDirection == lastLayoutDirection && lastShape == shape) {
145             outline = lastOutline!!
146         } else {
147             // Manually observe reads so we can directly invalidate the outline when it changes
148             // Use tmpOutline to avoid creating an object reference to local var outline
149             observeReads { tmpOutline = shape.createOutline(size, layoutDirection, this) }
150             outline = tmpOutline
151             tmpOutline = null
152         }
153         lastOutline = outline
154         lastSize = size
155         lastLayoutDirection = layoutDirection
156         lastShape = shape
157         return outline!!
158     }
159 }
160