389 lines
17 KiB
Markdown
389 lines
17 KiB
Markdown
When creating a Glimmer Icon component, refer to the following source code in
|
|
`Icon.kt`:
|
|
|
|
|
|
```kotlin
|
|
/*
|
|
* Copyright 2025 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 androidx.xr.glimmer
|
|
|
|
import androidx.compose.foundation.layout.Box
|
|
import androidx.compose.foundation.layout.size
|
|
import androidx.compose.runtime.Composable
|
|
import androidx.compose.runtime.ProvidableCompositionLocal
|
|
import androidx.compose.runtime.compositionLocalOf
|
|
import androidx.compose.runtime.remember
|
|
import androidx.compose.runtime.structuralEqualityPolicy
|
|
import androidx.compose.ui.Modifier
|
|
import androidx.compose.ui.draw.CacheDrawModifierNode
|
|
import androidx.compose.ui.draw.paint
|
|
import androidx.compose.ui.graphics.Color
|
|
import androidx.compose.ui.graphics.ColorFilter
|
|
import androidx.compose.ui.graphics.ColorProducer
|
|
import androidx.compose.ui.graphics.ImageBitmap
|
|
import androidx.compose.ui.graphics.isSpecified
|
|
import androidx.compose.ui.graphics.layer.drawLayer
|
|
import androidx.compose.ui.graphics.painter.BitmapPainter
|
|
import androidx.compose.ui.graphics.painter.Painter
|
|
import androidx.compose.ui.graphics.toolingGraphicsLayer
|
|
import androidx.compose.ui.graphics.vector.ImageVector
|
|
import androidx.compose.ui.graphics.vector.rememberVectorPainter
|
|
import androidx.compose.ui.layout.ContentScale
|
|
import androidx.compose.ui.node.DelegatingNode
|
|
import androidx.compose.ui.node.ModifierNodeElement
|
|
import androidx.compose.ui.node.invalidateDraw
|
|
import androidx.compose.ui.semantics.Role
|
|
import androidx.compose.ui.semantics.contentDescription
|
|
import androidx.compose.ui.semantics.role
|
|
import androidx.compose.ui.semantics.semantics
|
|
import androidx.compose.ui.unit.Dp
|
|
|
|
/**
|
|
* An icon component that draws [imageVector], with a default size of [LocalIconSize], and applies a
|
|
* content color tint. Icon is an opinionated component designed to be used with single-color icons
|
|
* so that they can be tinted correctly for the component they are placed in. The recommended icon
|
|
* sizes can be retrieved using [GlimmerTheme.Companion.iconSizes]. A size can be set explicitly
|
|
* using [size], and components can use [LocalIconSize] to set the preferred size for any Icons
|
|
* inside the component. The content color used to tint this icon is provided by [surface]. To set a
|
|
* custom tint color, use the Icon overload with a tint parameter. For multicolored icons and icons
|
|
* that should not be tinted, use the overload and set [Color.Unspecified] as the tint color. For
|
|
* generic images that should not be tinted, and should not use the provided icon size, use the
|
|
* generic [androidx.compose.foundation.Image] instead.
|
|
*
|
|
* @param imageVector [ImageVector] to draw inside this icon
|
|
* @param contentDescription text used by accessibility services to describe what this icon
|
|
* represents. This should always be provided unless this icon is used for decorative purposes,
|
|
* and does not represent a meaningful action that a user can take. This text should be localized,
|
|
* such as by using [androidx.compose.ui.res.stringResource] or similar
|
|
* @param modifier the [Modifier] to be applied to this icon
|
|
* @see LocalIconSize
|
|
* @see GlimmerTheme.Companion.iconSizes
|
|
*/
|
|
@Composable
|
|
public fun Icon(
|
|
imageVector: ImageVector,
|
|
contentDescription: String?,
|
|
modifier: Modifier = Modifier,
|
|
) {
|
|
Icon(
|
|
painter = rememberVectorPainter(imageVector),
|
|
contentDescription = contentDescription,
|
|
modifier = modifier,
|
|
)
|
|
}
|
|
|
|
/**
|
|
* An icon component that draws [imageVector], with a default size of [LocalIconSize], and applies a
|
|
* tint of [tint]. Icon is an opinionated component designed to be used with single-color icons so
|
|
* that they can be tinted correctly for the component they are placed in. The recommended icon
|
|
* sizes can be retrieved using [GlimmerTheme.Companion.iconSizes]. A size can be set explicitly
|
|
* using [size], and components can use [LocalIconSize] to set the preferred size for any Icons
|
|
* inside the component. Use the other overload of Icon without a [tint] parameter to apply the
|
|
* recommended content color provided by a [surface]. For multicolored icons and icons that should
|
|
* not be tinted, set [tint] to [Color.Unspecified]. For generic images that should not be tinted,
|
|
* and should not use the provided icon size, use the generic [androidx.compose.foundation.Image]
|
|
* instead.
|
|
*
|
|
* @param imageVector [ImageVector] to draw inside this icon
|
|
* @param tint tint to be applied to [imageVector]. If [Color.Unspecified] is provided, then no tint
|
|
* is applied.
|
|
* @param contentDescription text used by accessibility services to describe what this icon
|
|
* represents. This should always be provided unless this icon is used for decorative purposes,
|
|
* and does not represent a meaningful action that a user can take. This text should be localized,
|
|
* such as by using [androidx.compose.ui.res.stringResource] or similar
|
|
* @param modifier the [Modifier] to be applied to this icon
|
|
* @see LocalIconSize
|
|
* @see GlimmerTheme.Companion.iconSizes
|
|
*/
|
|
@Composable
|
|
public fun Icon(
|
|
imageVector: ImageVector,
|
|
tint: Color,
|
|
contentDescription: String?,
|
|
modifier: Modifier = Modifier,
|
|
) {
|
|
Icon(
|
|
painter = rememberVectorPainter(imageVector),
|
|
tint =
|
|
if (tint.isSpecified) {
|
|
{ tint }
|
|
} else null,
|
|
contentDescription = contentDescription,
|
|
modifier = modifier,
|
|
)
|
|
}
|
|
|
|
/**
|
|
* An icon component that draws [bitmap], with a default size of [LocalIconSize], and applies a
|
|
* content color tint. Icon is an opinionated component designed to be used with single-color icons
|
|
* so that they can be tinted correctly for the component they are placed in. The recommended icon
|
|
* sizes can be retrieved using [GlimmerTheme.Companion.iconSizes]. A size can be set explicitly
|
|
* using [size], and components can use [LocalIconSize] to set the preferred size for any Icons
|
|
* inside the component. The content color used to tint this icon is provided by [surface]. To set a
|
|
* custom tint color, use the Icon overload with a tint parameter. For multicolored icons and icons
|
|
* that should not be tinted, use the overload and set [Color.Unspecified] as the tint color. For
|
|
* generic images that should not be tinted, and should not use the provided icon size, use the
|
|
* generic [androidx.compose.foundation.Image] instead.
|
|
*
|
|
* @param bitmap [ImageBitmap] to draw inside this icon
|
|
* @param contentDescription text used by accessibility services to describe what this icon
|
|
* represents. This should always be provided unless this icon is used for decorative purposes,
|
|
* and does not represent a meaningful action that a user can take. This text should be localized,
|
|
* such as by using [androidx.compose.ui.res.stringResource] or similar
|
|
* @param modifier the [Modifier] to be applied to this icon
|
|
* @see LocalIconSize
|
|
* @see GlimmerTheme.Companion.iconSizes
|
|
*/
|
|
@Composable
|
|
public fun Icon(bitmap: ImageBitmap, contentDescription: String?, modifier: Modifier = Modifier) {
|
|
val painter = remember(bitmap) { BitmapPainter(bitmap) }
|
|
Icon(painter = painter, contentDescription = contentDescription, modifier = modifier)
|
|
}
|
|
|
|
/**
|
|
* An icon component that draws [bitmap], with a default size of [LocalIconSize], and applies a tint
|
|
* of [tint]. Icon is an opinionated component designed to be used with single-color icons so that
|
|
* they can be tinted correctly for the component they are placed in. The recommended icon sizes can
|
|
* be retrieved using [GlimmerTheme.Companion.iconSizes]. A size can be set explicitly using [size],
|
|
* and components can use [LocalIconSize] to set the preferred size for any Icons inside the
|
|
* component. Use the other overload of Icon without a [tint] parameter to apply the recommended
|
|
* content color provided by a [surface]. For multicolored icons and icons that should not be
|
|
* tinted, set [tint] to [Color.Unspecified]. For generic images that should not be tinted, and
|
|
* should not use the provided icon size, use the generic [androidx.compose.foundation.Image]
|
|
* instead.
|
|
*
|
|
* @param bitmap [ImageBitmap] to draw inside this icon
|
|
* @param tint tint to be applied to [bitmap]. If [Color.Unspecified] is provided, then no tint is
|
|
* applied.
|
|
* @param contentDescription text used by accessibility services to describe what this icon
|
|
* represents. This should always be provided unless this icon is used for decorative purposes,
|
|
* and does not represent a meaningful action that a user can take. This text should be localized,
|
|
* such as by using [androidx.compose.ui.res.stringResource] or similar
|
|
* @param modifier the [Modifier] to be applied to this icon
|
|
* @see LocalIconSize
|
|
* @see GlimmerTheme.Companion.iconSizes
|
|
*/
|
|
@Composable
|
|
public fun Icon(
|
|
bitmap: ImageBitmap,
|
|
tint: Color,
|
|
contentDescription: String?,
|
|
modifier: Modifier = Modifier,
|
|
) {
|
|
val painter = remember(bitmap) { BitmapPainter(bitmap) }
|
|
Icon(
|
|
painter = painter,
|
|
tint =
|
|
if (tint.isSpecified) {
|
|
{ tint }
|
|
} else null,
|
|
contentDescription = contentDescription,
|
|
modifier = modifier,
|
|
)
|
|
}
|
|
|
|
/**
|
|
* An icon component that draws [painter], with a default size of [LocalIconSize], and applies a
|
|
* content color tint. Icon is an opinionated component designed to be used with single-color icons
|
|
* so that they can be tinted correctly for the component they are placed in. The recommended icon
|
|
* sizes can be retrieved using [GlimmerTheme.Companion.iconSizes]. A size can be set explicitly
|
|
* using [size], and components can use [LocalIconSize] to set the preferred size for any Icons
|
|
* inside the component. The content color used to tint this icon is provided by [surface]. To set a
|
|
* custom tint color, use the Icon overload with a tint parameter. For multicolored icons and icons
|
|
* that should not be tinted, use the overload and set `null` for the tint parameter. For generic
|
|
* images that should not be tinted, and should not use the provided icon size, use the generic
|
|
* [androidx.compose.foundation.Image] instead.
|
|
*
|
|
* @param painter [Painter] to draw inside this icon
|
|
* @param contentDescription text used by accessibility services to describe what this icon
|
|
* represents. This should always be provided unless this icon is used for decorative purposes,
|
|
* and does not represent a meaningful action that a user can take. This text should be localized,
|
|
* such as by using [androidx.compose.ui.res.stringResource] or similar
|
|
* @param modifier the [Modifier] to be applied to this icon
|
|
* @see LocalIconSize
|
|
* @see GlimmerTheme.Companion.iconSizes
|
|
*/
|
|
@Composable
|
|
public fun Icon(painter: Painter, contentDescription: String?, modifier: Modifier = Modifier) {
|
|
Icon(
|
|
painter = painter,
|
|
useContentColor = true,
|
|
tint = null,
|
|
contentDescription = contentDescription,
|
|
modifier = modifier,
|
|
)
|
|
}
|
|
|
|
/**
|
|
* An icon component that draws [painter], with a default size of [LocalIconSize], and applies a
|
|
* tint of [tint]. Icon is an opinionated component designed to be used with single-color icons so
|
|
* that they can be tinted correctly for the component they are placed in. The recommended icon
|
|
* sizes can be retrieved using [GlimmerTheme.Companion.iconSizes]. A size can be set explicitly
|
|
* using [size], and components can use [LocalIconSize] to set the preferred size for any Icons
|
|
* inside the component. Use the other overload of Icon without a [tint] parameter to apply the
|
|
* recommended content color provided by a [surface]. For multicolored icons and icons that should
|
|
* not be tinted, set [tint] to `null`. For generic images that should not be tinted, and should not
|
|
* use the provided icon size, use the generic [androidx.compose.foundation.Image] instead.
|
|
*
|
|
* @param painter [Painter] to draw inside this icon
|
|
* @param tint tint to be applied to [painter]. If null, then no tint is applied.
|
|
* @param contentDescription text used by accessibility services to describe what this icon
|
|
* represents. This should always be provided unless this icon is used for decorative purposes,
|
|
* and does not represent a meaningful action that a user can take. This text should be localized,
|
|
* such as by using [androidx.compose.ui.res.stringResource] or similar
|
|
* @param modifier the [Modifier] to be applied to this icon
|
|
* @see LocalIconSize
|
|
* @see GlimmerTheme.Companion.iconSizes
|
|
*/
|
|
@Composable
|
|
public fun Icon(
|
|
painter: Painter,
|
|
tint: ColorProducer?,
|
|
contentDescription: String?,
|
|
modifier: Modifier = Modifier,
|
|
) {
|
|
Icon(
|
|
painter = painter,
|
|
useContentColor = false,
|
|
tint = tint,
|
|
contentDescription = contentDescription,
|
|
modifier = modifier,
|
|
)
|
|
}
|
|
|
|
/**
|
|
* CompositionLocal containing the preferred size of an icon. This value will be used by [Icon] by
|
|
* default - it can be overridden with a [size] modifier.
|
|
*/
|
|
public val LocalIconSize: ProvidableCompositionLocal<Dp> =
|
|
compositionLocalOf(structuralEqualityPolicy()) { GlimmerTheme._iconSizes.medium }
|
|
|
|
@Composable
|
|
private fun Icon(
|
|
painter: Painter,
|
|
useContentColor: Boolean,
|
|
tint: ColorProducer?,
|
|
contentDescription: String?,
|
|
modifier: Modifier,
|
|
) {
|
|
val colorFilter =
|
|
if (useContentColor || tint != null) {
|
|
IconColorFilterElement(
|
|
useContentColor = useContentColor,
|
|
tint = tint,
|
|
painter = painter,
|
|
)
|
|
} else {
|
|
Modifier
|
|
}
|
|
val semantics =
|
|
if (contentDescription != null) {
|
|
Modifier.semantics {
|
|
this.contentDescription = contentDescription
|
|
this.role = Role.Image
|
|
}
|
|
} else {
|
|
Modifier
|
|
}
|
|
Box(
|
|
modifier
|
|
.toolingGraphicsLayer()
|
|
.size(LocalIconSize.current)
|
|
.then(colorFilter)
|
|
.paint(painter, contentScale = ContentScale.Fit)
|
|
.then(semantics)
|
|
)
|
|
}
|
|
|
|
@Suppress("ModifierNodeInspectableProperties")
|
|
private class IconColorFilterElement(
|
|
private val useContentColor: Boolean,
|
|
private val tint: ColorProducer?,
|
|
private val painter: Painter,
|
|
) : ModifierNodeElement<IconColorFilterNode>() {
|
|
override fun create() = IconColorFilterNode(useContentColor, tint, painter)
|
|
|
|
override fun update(node: IconColorFilterNode) {
|
|
node.update(useContentColor, tint, painter)
|
|
}
|
|
|
|
override fun equals(other: Any?): Boolean {
|
|
if (this === other) return true
|
|
if (other !is IconColorFilterElement) return false
|
|
|
|
if (useContentColor != other.useContentColor) return false
|
|
if (tint != other.tint) return false
|
|
if (painter != other.painter) return false
|
|
|
|
return true
|
|
}
|
|
|
|
override fun hashCode(): Int {
|
|
var result = useContentColor.hashCode()
|
|
result = 31 * result + (tint?.hashCode() ?: 0)
|
|
result = 31 * result + painter.hashCode()
|
|
return result
|
|
}
|
|
}
|
|
|
|
private class IconColorFilterNode(
|
|
private var useContentColor: Boolean,
|
|
private var tint: ColorProducer?,
|
|
private var painter: Painter,
|
|
) : DelegatingNode() {
|
|
val cacheDrawNode =
|
|
delegate(
|
|
CacheDrawModifierNode {
|
|
val layer = obtainGraphicsLayer()
|
|
layer.apply { record { drawContent() } }
|
|
var cachedTintColor = Color.Unspecified
|
|
var cachedColorFilter: ColorFilter? = null
|
|
onDrawWithContent {
|
|
val tintColor =
|
|
if (useContentColor) {
|
|
currentContentColor()
|
|
} else {
|
|
tint?.invoke() ?: Color.Unspecified
|
|
}
|
|
if (cachedTintColor != tintColor) {
|
|
cachedTintColor = tintColor
|
|
cachedColorFilter =
|
|
if (tintColor.isSpecified) ColorFilter.tint(tintColor) else null
|
|
layer.colorFilter = cachedColorFilter
|
|
}
|
|
drawLayer(graphicsLayer = layer)
|
|
}
|
|
}
|
|
)
|
|
|
|
fun update(useContentColor: Boolean, tint: ColorProducer?, painter: Painter) {
|
|
val painterChanged = this.painter != painter
|
|
if (this.useContentColor != useContentColor || this.tint != tint || painterChanged) {
|
|
this.useContentColor = useContentColor
|
|
this.tint = tint
|
|
this.painter = painter
|
|
if (painterChanged) {
|
|
cacheDrawNode.invalidateDrawCache()
|
|
} else {
|
|
cacheDrawNode.invalidateDraw()
|
|
}
|
|
}
|
|
}
|
|
}
|
|
```
|
|
|
|
<br /> |