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 package androidx.compose.material3.common
17 
18 import androidx.compose.foundation.layout.Box
19 import androidx.compose.foundation.layout.size
20 import androidx.compose.runtime.Composable
21 import androidx.compose.runtime.remember
22 import androidx.compose.ui.Modifier
23 import androidx.compose.ui.draw.drawWithCache
24 import androidx.compose.ui.draw.paint
25 import androidx.compose.ui.geometry.Size
26 import androidx.compose.ui.graphics.Color
27 import androidx.compose.ui.graphics.ColorFilter
28 import androidx.compose.ui.graphics.ColorProducer
29 import androidx.compose.ui.graphics.ImageBitmap
30 import androidx.compose.ui.graphics.layer.drawLayer
31 import androidx.compose.ui.graphics.painter.BitmapPainter
32 import androidx.compose.ui.graphics.painter.Painter
33 import androidx.compose.ui.graphics.toolingGraphicsLayer
34 import androidx.compose.ui.graphics.vector.ImageVector
35 import androidx.compose.ui.graphics.vector.rememberVectorPainter
36 import androidx.compose.ui.layout.ContentScale
37 import androidx.compose.ui.semantics.Role
38 import androidx.compose.ui.semantics.contentDescription
39 import androidx.compose.ui.semantics.role
40 import androidx.compose.ui.semantics.semantics
41 import androidx.compose.ui.unit.dp
42 
43 /**
44  * A Material Design icon component that draws [imageVector] using [tint], with an optional tint
45  * color. If [imageVector] has no intrinsic size, this component will use the recommended default
46  * size. Icon is an opinionated component designed to be used with single-color icons so that they
47  * can be tinted correctly for the component they are placed in. For multicolored icons and icons
48  * that should not be tinted, use null for [tint]. For generic images that should not be tinted, and
49  * do not follow the recommended icon size, use the generic [androidx.compose.foundation.Image]
50  * instead. For a clickable icon, see [IconButton].
51  *
52  * To learn more about icons, see
53  * [Material Design icons](https://m3.material.io/styles/icons/overview)
54  *
55  * @param imageVector [ImageVector] to draw inside this icon
56  * @param contentDescription text used by accessibility services to describe what this icon
57  *   represents. This should always be provided unless this icon is used for decorative purposes,
58  *   and does not represent a meaningful action that a user can take. This text should be localized,
59  *   such as by using [androidx.compose.ui.res.stringResource] or similar
60  * @param modifier the [Modifier] to be applied to this icon
61  * @param tint tint to be applied to [imageVector]. If [Color.Unspecified] is provided, then no tint
62  *   is applied.
63  * @sample androidx.compose.material3.common.samples.IconSample
64  */
65 @Composable
Iconnull66 fun Icon(
67     imageVector: ImageVector,
68     contentDescription: String?,
69     modifier: Modifier = Modifier,
70     tint: Color = Color.Unspecified,
71 ) {
72     Icon(
73         painter = rememberVectorPainter(imageVector),
74         contentDescription = contentDescription,
75         modifier = modifier,
76         tint = tint
77     )
78 }
79 
80 /**
81  * A Material Design icon component that draws [bitmap] using [tint], with an optional tint color.
82  * If [bitmap] has no intrinsic size, this component will use the recommended default size. Icon is
83  * an opinionated component designed to be used with single-color icons so that they can be tinted
84  * correctly for the component they are placed in. For multicolored icons and icons that should not
85  * be tinted, use [Color.Unspecified] for [tint]. For generic images that should not be tinted, and
86  * do not follow the recommended icon size, use the generic [androidx.compose.foundation.Image]
87  * instead. For a clickable icon, see [IconButton].
88  *
89  * To learn more about icons, see
90  * [Material Design icons](https://m3.material.io/styles/icons/overview)
91  *
92  * @param bitmap [ImageBitmap] to draw inside this icon
93  * @param contentDescription text used by accessibility services to describe what this icon
94  *   represents. This should always be provided unless this icon is used for decorative purposes,
95  *   and does not represent a meaningful action that a user can take. This text should be localized,
96  *   such as by using [androidx.compose.ui.res.stringResource] or similar
97  * @param modifier the [Modifier] to be applied to this icon
98  * @param tint tint to be applied to [bitmap]. If [Color.Unspecified] is provided, then no tint is
99  *   applied.
100  */
101 @Composable
Iconnull102 fun Icon(
103     bitmap: ImageBitmap,
104     contentDescription: String?,
105     modifier: Modifier = Modifier,
106     tint: Color = Color.Unspecified,
107 ) {
108     val painter = remember(bitmap) { BitmapPainter(bitmap) }
109     Icon(
110         painter = painter,
111         contentDescription = contentDescription,
112         modifier = modifier,
113         tint = tint
114     )
115 }
116 
117 /**
118  * A Material Design icon component that draws [painter] using [tint], with an optional tint color.
119  * If [painter] has no intrinsic size, this component will use the recommended default size. Icon is
120  * an opinionated component designed to be used with single-color icons so that they can be tinted
121  * correctly for the component they are placed in. For multicolored icons and icons that should not
122  * be tinted, use [Color.Unspecified] for [tint]. For generic images that should not be tinted, and
123  * do not follow the recommended icon size, use the generic [androidx.compose.foundation.Image]
124  * instead. For a clickable icon, see [IconButton].
125  *
126  * To learn more about icons, see
127  * [Material Design icons](https://m3.material.io/styles/icons/overview)
128  *
129  * @param painter [Painter] to draw inside this icon
130  * @param contentDescription text used by accessibility services to describe what this icon
131  *   represents. This should always be provided unless this icon is used for decorative purposes,
132  *   and does not represent a meaningful action that a user can take. This text should be localized,
133  *   such as by using [androidx.compose.ui.res.stringResource] or similar
134  * @param modifier the [Modifier] to be applied to this icon
135  * @param tint tint to be applied to [painter]. If [Color.Unspecified] is provided, then no tint is
136  *   applied.
137  */
138 @Composable
Iconnull139 fun Icon(
140     painter: Painter,
141     contentDescription: String?,
142     modifier: Modifier = Modifier,
143     tint: Color = Color.Unspecified,
144 ) {
145     val colorFilter =
146         remember(tint) { if (tint == Color.Unspecified) null else ColorFilter.tint(tint) }
147     val semantics =
148         if (contentDescription != null) {
149             Modifier.semantics {
150                 this.contentDescription = contentDescription
151                 this.role = Role.Image
152             }
153         } else {
154             Modifier
155         }
156     Box(
157         modifier
158             .toolingGraphicsLayer()
159             .defaultSizeFor(painter)
160             .paint(painter, colorFilter = colorFilter, contentScale = ContentScale.Fit)
161             .then(semantics)
162     )
163 }
164 
165 /**
166  * A Material Design icon component that draws [painter] using [tint], with an optional tint color.
167  * If [painter] has no intrinsic size, this component will use the recommended default size. Icon is
168  * an opinionated component designed to be used with single-color icons so that they can be tinted
169  * correctly for the component they are placed in. For multicolored icons and icons that should not
170  * be tinted, use [Color.Unspecified] for [tint]. For generic images that should not be tinted, and
171  * do not follow the recommended icon size, use the generic [androidx.compose.foundation.Image]
172  * instead. For a clickable icon, see [IconButton].
173  *
174  * To learn more about icons, see
175  * [Material Design icons](https://m3.material.io/styles/icons/overview)
176  *
177  * @param painter [Painter] to draw inside this icon
178  * @param contentDescription text used by accessibility services to describe what this icon
179  *   represents. This should always be provided unless this icon is used for decorative purposes,
180  *   and does not represent a meaningful action that a user can take. This text should be localized,
181  *   such as by using [androidx.compose.ui.res.stringResource] or similar
182  * @param modifier the [Modifier] to be applied to this icon
183  * @param tint tint to be applied to [painter]. If null, then no tint is applied.
184  */
185 @Composable
Iconnull186 fun Icon(
187     painter: Painter,
188     tint: ColorProducer?,
189     contentDescription: String?,
190     modifier: Modifier = Modifier,
191 ) {
192     Icon(
193         painter = painter,
194         tint = Color.Unspecified,
195         contentDescription = contentDescription,
196         modifier =
197             modifier.drawWithCache {
198                 val layer = obtainGraphicsLayer()
199                 layer.apply {
200                     record { drawContent() }
201                     tint?.let { this@apply.colorFilter = ColorFilter.tint(it()) }
202                 }
203                 onDrawWithContent { drawLayer(graphicsLayer = layer) }
204             }
205     )
206 }
207 
defaultSizeFornull208 private fun Modifier.defaultSizeFor(painter: Painter) =
209     this.then(
210         if (painter.intrinsicSize == Size.Unspecified || painter.intrinsicSize.isInfinite()) {
211             DefaultIconSizeModifier
212         } else {
213             Modifier
214         }
215     )
216 
isInfinitenull217 private fun Size.isInfinite() = width.isInfinite() && height.isInfinite()
218 
219 // Default icon size, for icons with no intrinsic size information
220 // Replace hardcoded size with IconButtonTokens.IconSize
221 private val DefaultIconSizeModifier = Modifier.size(24.dp)
222