[go: nahoru, domu]

blob: 6cea7dd5c41894b004728dd8dc11f0fb2f2d81dd [file] [log] [blame]
/*
* Copyright 2023 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.compose.material3
import androidx.compose.animation.AnimatedVisibility
import androidx.compose.animation.Crossfade
import androidx.compose.animation.ExitTransition
import androidx.compose.animation.core.Animatable
import androidx.compose.animation.core.AnimationVector1D
import androidx.compose.animation.core.VectorConverter
import androidx.compose.animation.core.tween
import androidx.compose.animation.fadeIn
import androidx.compose.animation.scaleIn
import androidx.compose.foundation.BorderStroke
import androidx.compose.foundation.interaction.FocusInteraction
import androidx.compose.foundation.interaction.Interaction
import androidx.compose.foundation.interaction.InteractionSource
import androidx.compose.foundation.interaction.MutableInteractionSource
import androidx.compose.foundation.interaction.PressInteraction
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.IntrinsicSize
import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.RowScope
import androidx.compose.foundation.layout.defaultMinSize
import androidx.compose.foundation.layout.height
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.size
import androidx.compose.foundation.layout.width
import androidx.compose.foundation.selection.selectable
import androidx.compose.foundation.selection.selectableGroup
import androidx.compose.foundation.selection.toggleable
import androidx.compose.foundation.shape.CornerBasedShape
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.filled.Check
import androidx.compose.material3.tokens.MotionTokens
import androidx.compose.material3.tokens.OutlinedSegmentedButtonTokens
import androidx.compose.material3.tokens.OutlinedSegmentedButtonTokens.DisabledLabelTextColor
import androidx.compose.material3.tokens.OutlinedSegmentedButtonTokens.DisabledLabelTextOpacity
import androidx.compose.material3.tokens.OutlinedSegmentedButtonTokens.DisabledOutlineOpacity
import androidx.compose.material3.tokens.OutlinedSegmentedButtonTokens.OutlineColor
import androidx.compose.material3.tokens.OutlinedSegmentedButtonTokens.SelectedContainerColor
import androidx.compose.material3.tokens.OutlinedSegmentedButtonTokens.SelectedLabelTextColor
import androidx.compose.material3.tokens.OutlinedSegmentedButtonTokens.UnselectedLabelTextColor
import androidx.compose.runtime.Composable
import androidx.compose.runtime.Immutable
import androidx.compose.runtime.LaunchedEffect
import androidx.compose.runtime.ReadOnlyComposable
import androidx.compose.runtime.Stable
import androidx.compose.runtime.State
import androidx.compose.runtime.mutableIntStateOf
import androidx.compose.runtime.remember
import androidx.compose.runtime.rememberCoroutineScope
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.graphics.RectangleShape
import androidx.compose.ui.graphics.Shape
import androidx.compose.ui.graphics.TransformOrigin
import androidx.compose.ui.layout.Layout
import androidx.compose.ui.layout.Measurable
import androidx.compose.ui.layout.MeasureResult
import androidx.compose.ui.layout.MeasureScope
import androidx.compose.ui.layout.MultiContentMeasurePolicy
import androidx.compose.ui.layout.layout
import androidx.compose.ui.semantics.Role
import androidx.compose.ui.semantics.role
import androidx.compose.ui.semantics.semantics
import androidx.compose.ui.unit.Constraints
import androidx.compose.ui.unit.Dp
import androidx.compose.ui.unit.dp
import androidx.compose.ui.util.fastForEach
import androidx.compose.ui.util.fastMap
import androidx.compose.ui.util.fastMaxBy
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.launch
/**
* <a href="https://m3.material.io/components/segmented-buttons/overview" class="external" target="_blank">Material Segmented Button</a>.
* Segmented buttons help people select options, switch views, or sort elements.
*
* A default Toggleable Segmented Button. Also known as Outlined Segmented Button.
* See [Modifier.toggleable].
*
* Toggleable segmented buttons should be used for cases where the selection is not mutually
* exclusive.
*
* This should typically be used inside of a [MultiChoiceSegmentedButtonRow]
*
* For a sample showing Segmented button with only checked icons see:
* @sample androidx.compose.material3.samples.SegmentedButtonMultiSelectSample
*
* @param checked whether this button is checked or not
* @param onCheckedChange callback to be invoked when the button is clicked.
* therefore the change of checked state in requested.
* @param shape the shape for this button
* @param modifier the [Modifier] to be applied to this button
* @param enabled controls the enabled state of this button. When `false`, this component will not
* respond to user input, and it will appear visually disabled and disabled to accessibility
* services.
* @param colors [SegmentedButtonColors] that will be used to resolve the colors used for this
* @param border the border for this button, see [SegmentedButtonColors]
* Button in different states
* @param interactionSource the [MutableInteractionSource] representing the stream of [Interaction]s
* for this button. You can create and pass in your own `remember`ed instance to observe
* [Interaction]s and customize the appearance / behavior of this button in different states.
* @param icon the icon slot for this button, you can pass null in unchecked, in which case
* the content will displace to show the checked icon, or pass different icon lambdas for
* unchecked and checked in which case the icons will crossfade.
* @param label content to be rendered inside this button
*/
@Composable
@ExperimentalMaterial3Api
fun MultiChoiceSegmentedButtonRowScope.SegmentedButton(
checked: Boolean,
onCheckedChange: (Boolean) -> Unit,
shape: Shape,
modifier: Modifier = Modifier,
enabled: Boolean = true,
colors: SegmentedButtonColors = SegmentedButtonDefaults.colors(),
border: BorderStroke = SegmentedButtonDefaults.borderStroke(
colors.borderColor(enabled, checked)
),
interactionSource: MutableInteractionSource = remember { MutableInteractionSource() },
icon: @Composable () -> Unit = { SegmentedButtonDefaults.Icon(checked) },
label: @Composable () -> Unit,
) {
val containerColor = colors.containerColor(enabled, checked)
val contentColor = colors.contentColor(enabled, checked)
val interactionCount = interactionSource.interactionCountAsState()
Surface(
modifier = modifier
.weight(1f)
.interactionZIndex(checked, interactionCount)
.defaultMinSize(
minWidth = ButtonDefaults.MinWidth,
minHeight = ButtonDefaults.MinHeight
),
checked = checked,
onCheckedChange = onCheckedChange,
enabled = enabled,
shape = shape,
color = containerColor,
contentColor = contentColor,
border = border,
interactionSource = interactionSource
) {
SegmentedButtonContent(icon, label)
}
}
/**
* <a href="https://m3.material.io/components/segmented-buttons/overview" class="external" target="_blank">Material Segmented Button</a>.
* Segmented buttons help people select options, switch views, or sort elements.
*
* A default Toggleable Segmented Button. Also known as Outlined Segmented Button.
* See [Modifier.selectable].
*
* Selectable segmented buttons should be used for cases where the selection is mutually
* exclusive, when only one button can be selected at a time.
*
* This should typically be used inside of a [SingleChoiceSegmentedButtonRow]
*
* For a sample showing Segmented button with only checked icons see:
* @sample androidx.compose.material3.samples.SegmentedButtonSingleSelectSample
*
* @param selected whether this button is selected or not
* @param onClick callback to be invoked when the button is clicked.
* therefore the change of checked state in requested.
* @param shape the shape for this button
* @param modifier the [Modifier] to be applied to this button
* @param enabled controls the enabled state of this button. When `false`, this component will not
* respond to user input, and it will appear visually disabled and disabled to accessibility
* services.
* @param colors [SegmentedButtonColors] that will be used to resolve the colors used for this
* @param border the border for this button, see [SegmentedButtonColors]
* Button in different states
* @param interactionSource the [MutableInteractionSource] representing the stream of [Interaction]s
* for this button. You can create and pass in your own `remember`ed instance to observe
* [Interaction]s and customize the appearance / behavior of this button in different states.
* @param icon the icon slot for this button, you can pass null in unchecked, in which case
* the content will displace to show the checked icon, or pass different icon lambdas for
* unchecked and checked in which case the icons will crossfade.
* @param label content to be rendered inside this button
*/
@Composable
@ExperimentalMaterial3Api
fun SingleChoiceSegmentedButtonRowScope.SegmentedButton(
selected: Boolean,
onClick: () -> Unit,
shape: Shape,
modifier: Modifier = Modifier,
enabled: Boolean = true,
colors: SegmentedButtonColors = SegmentedButtonDefaults.colors(),
border: BorderStroke = SegmentedButtonDefaults.borderStroke(
colors.borderColor(enabled, selected)
),
interactionSource: MutableInteractionSource = remember { MutableInteractionSource() },
icon: @Composable () -> Unit = { SegmentedButtonDefaults.Icon(selected) },
label: @Composable () -> Unit,
) {
val containerColor = colors.containerColor(enabled, selected)
val contentColor = colors.contentColor(enabled, selected)
val interactionCount = interactionSource.interactionCountAsState()
Surface(
modifier = modifier
.weight(1f)
.interactionZIndex(selected, interactionCount)
.defaultMinSize(
minWidth = ButtonDefaults.MinWidth,
minHeight = ButtonDefaults.MinHeight
)
.semantics { role = Role.RadioButton },
selected = selected,
onClick = onClick,
enabled = enabled,
shape = shape,
color = containerColor,
contentColor = contentColor,
border = border,
interactionSource = interactionSource
) {
SegmentedButtonContent(icon, label)
}
}
/**
* <a href="https://m3.material.io/components/segmented-buttons/overview" class="external" target="_blank">Material Segmented Button</a>.
*
* A Layout to correctly position and size [SegmentedButton]s in a Row.
* It handles overlapping items so that strokes of the item are correctly on top of each other.
* [SingleChoiceSegmentedButtonRow] is used when the selection only allows one value, for correct
* semantics.
*
* @sample androidx.compose.material3.samples.SegmentedButtonSingleSelectSample
*
* @param modifier the [Modifier] to be applied to this row
* @param space the dimension of the overlap between buttons. Should be equal to the stroke width
* used on the items.
* @param content the content of this Segmented Button Row, typically a sequence of
* [SegmentedButton]s
*/
@Composable
@ExperimentalMaterial3Api
fun SingleChoiceSegmentedButtonRow(
modifier: Modifier = Modifier,
space: Dp = SegmentedButtonDefaults.BorderWidth,
content: @Composable SingleChoiceSegmentedButtonRowScope.() -> Unit
) {
Row(
modifier = modifier
.selectableGroup()
.defaultMinSize(minHeight = OutlinedSegmentedButtonTokens.ContainerHeight)
.width(IntrinsicSize.Min),
horizontalArrangement = Arrangement.spacedBy(-space),
verticalAlignment = Alignment.CenterVertically
) {
val scope = remember { SingleChoiceSegmentedButtonScopeWrapper(this) }
scope.content()
}
}
/**
* <a href="https://m3.material.io/components/segmented-buttons/overview" class="external" target="_blank">Material Segmented Button</a>.
*
* A Layout to correctly position, size, and add semantics to [SegmentedButton]s in a Row.
* It handles overlapping items so that strokes of the item are correctly on top of each other.
*
* [MultiChoiceSegmentedButtonRow] is used when the selection allows multiple value, for correct
* semantics.
*
* @sample androidx.compose.material3.samples.SegmentedButtonMultiSelectSample
*
* @param modifier the [Modifier] to be applied to this row
* @param space the dimension of the overlap between buttons. Should be equal to the stroke width
* used on the items.
* @param content the content of this Segmented Button Row, typically a sequence of
* [SegmentedButton]s
*
*/
@Composable
@ExperimentalMaterial3Api
fun MultiChoiceSegmentedButtonRow(
modifier: Modifier = Modifier,
space: Dp = SegmentedButtonDefaults.BorderWidth,
content: @Composable MultiChoiceSegmentedButtonRowScope.() -> Unit
) {
Row(
modifier = modifier
.defaultMinSize(minHeight = OutlinedSegmentedButtonTokens.ContainerHeight)
.width(IntrinsicSize.Min),
horizontalArrangement = Arrangement.spacedBy(-space),
verticalAlignment = Alignment.CenterVertically
) {
val scope = remember { MultiChoiceSegmentedButtonScopeWrapper(this) }
scope.content()
}
}
@ExperimentalMaterial3Api
@Composable
private fun SegmentedButtonContent(
icon: @Composable () -> Unit,
content: @Composable () -> Unit,
) {
Box(
contentAlignment = Alignment.Center,
modifier = Modifier.padding(ButtonDefaults.TextButtonContentPadding)
) {
val typography =
MaterialTheme.typography.fromToken(OutlinedSegmentedButtonTokens.LabelTextFont)
ProvideTextStyle(typography) {
val scope = rememberCoroutineScope()
val measurePolicy = remember { SegmentedButtonContentMeasurePolicy(scope) }
Layout(
modifier = Modifier.height(IntrinsicSize.Min),
contents = listOf(icon, content),
measurePolicy = measurePolicy
)
}
}
}
internal class SegmentedButtonContentMeasurePolicy(
val scope: CoroutineScope
) : MultiContentMeasurePolicy {
var animatable: Animatable<Int, AnimationVector1D>? = null
private var initialOffset: Int? = null
@OptIn(ExperimentalMaterial3Api::class)
override fun MeasureScope.measure(
measurables: List<List<Measurable>>,
constraints: Constraints
): MeasureResult {
val (iconMeasurables, contentMeasurables) = measurables
val iconPlaceables = iconMeasurables.fastMap { it.measure(constraints) }
val iconWidth = iconPlaceables.fastMaxBy { it.width }?.width ?: 0
val contentPlaceables = contentMeasurables.fastMap { it.measure(constraints) }
val contentWidth = contentPlaceables.fastMaxBy { it.width }?.width
val height = contentPlaceables.fastMaxBy { it.height }?.height ?: 0
val width = maxOf(SegmentedButtonDefaults.IconSize.roundToPx(), iconWidth) +
IconSpacing.roundToPx() +
(contentWidth ?: 0)
val offsetX = if (iconWidth == 0) {
-(SegmentedButtonDefaults.IconSize.roundToPx() + IconSpacing.roundToPx()) / 2
} else {
0
}
if (initialOffset == null) {
initialOffset = offsetX
} else {
val anim = animatable ?: Animatable(initialOffset!!, Int.VectorConverter)
.also { animatable = it }
if (anim.targetValue != offsetX) {
scope.launch {
anim.animateTo(offsetX, tween(MotionTokens.DurationMedium3.toInt()))
}
}
}
return layout(width, height) {
iconPlaceables.fastForEach {
it.place(0, (height - it.height) / 2)
}
val contentOffsetX = SegmentedButtonDefaults.IconSize.roundToPx() +
IconSpacing.roundToPx() + (animatable?.value ?: offsetX)
contentPlaceables.fastForEach {
it.place(
contentOffsetX,
(height - it.height) / 2
)
}
}
}
}
@Composable
private fun InteractionSource.interactionCountAsState(): State<Int> {
val interactionCount = remember { mutableIntStateOf(0) }
LaunchedEffect(this) {
this@interactionCountAsState.interactions.collect { interaction ->
when (interaction) {
is PressInteraction.Press,
is FocusInteraction.Focus -> {
interactionCount.intValue++
}
is PressInteraction.Release,
is FocusInteraction.Unfocus,
is PressInteraction.Cancel -> {
interactionCount.intValue--
}
}
}
}
return interactionCount
}
/** Scope for the children of a [SingleChoiceSegmentedButtonRow] */
@ExperimentalMaterial3Api
interface SingleChoiceSegmentedButtonRowScope : RowScope
/** Scope for the children of a [MultiChoiceSegmentedButtonRow] */
@ExperimentalMaterial3Api
interface MultiChoiceSegmentedButtonRowScope : RowScope
/* Contains defaults to be used with [SegmentedButtonRow] and [SegmentedButton] */
@ExperimentalMaterial3Api
@Stable
object SegmentedButtonDefaults {
/**
* Creates a [SegmentedButtonColors] that represents the different colors
* used in a [SegmentedButton] in different states.
*
* @param activeContainerColor the color used for the container when enabled and active
* @param activeContentColor the color used for the content when enabled and active
* @param activeBorderColor the color used for the border when enabled and active
* @param inactiveContainerColor the color used for the container when enabled and inactive
* @param inactiveContentColor the color used for the content when enabled and inactive
* @param inactiveBorderColor the color used for the border when enabled and active
* @param disabledActiveContainerColor the color used for the container
* when disabled and active
* @param disabledActiveContentColor the color used for the content when disabled and active
* @param disabledActiveBorderColor the color used for the border when disabled and active
* @param disabledInactiveContainerColor the color used for the container
* when disabled and inactive
* @param disabledInactiveContentColor the color used for the content when disabled and
* unchecked
* @param disabledInactiveBorderColor the color used for the border when disabled and inactive
*/
@Composable
fun colors(
activeContainerColor: Color = SelectedContainerColor.value,
activeContentColor: Color = SelectedLabelTextColor.value,
activeBorderColor: Color = OutlineColor.value,
inactiveContainerColor: Color = MaterialTheme.colorScheme.surface,
inactiveContentColor: Color = UnselectedLabelTextColor.value,
inactiveBorderColor: Color = activeBorderColor,
disabledActiveContainerColor: Color = activeContainerColor,
disabledActiveContentColor: Color = DisabledLabelTextColor.value
.copy(alpha = DisabledLabelTextOpacity),
disabledActiveBorderColor: Color = OutlineColor.value
.copy(alpha = DisabledOutlineOpacity),
disabledInactiveContainerColor: Color = inactiveContainerColor,
disabledInactiveContentColor: Color = disabledActiveContentColor,
disabledInactiveBorderColor: Color = activeBorderColor,
): SegmentedButtonColors = SegmentedButtonColors(
activeContainerColor = activeContainerColor,
activeContentColor = activeContentColor,
activeBorderColor = activeBorderColor,
inactiveContainerColor = inactiveContainerColor,
inactiveContentColor = inactiveContentColor,
inactiveBorderColor = inactiveBorderColor,
disabledActiveContainerColor = disabledActiveContainerColor,
disabledActiveContentColor = disabledActiveContentColor,
disabledActiveBorderColor = disabledActiveBorderColor,
disabledInactiveContainerColor = disabledInactiveContainerColor,
disabledInactiveContentColor = disabledInactiveContentColor,
disabledInactiveBorderColor = disabledInactiveBorderColor
)
/**
* The shape of the segmented button container, for correct behavior this should or the desired
* [CornerBasedShape] should be used with [itemShape] and passed to each segmented button.
*/
val baseShape: CornerBasedShape
@Composable
@ReadOnlyComposable
get() = OutlinedSegmentedButtonTokens.Shape.value as CornerBasedShape
/** Default border width used in segmented button */
val BorderWidth: Dp = OutlinedSegmentedButtonTokens.OutlineWidth
/**
* A shape constructor that the button in [index] should have when there are [count] buttons in
* the container.
*
* @param index the index for this button in the row
* @param count the count of buttons in this row
* @param baseShape the [CornerBasedShape] the base shape that should be used in buttons that
* are not in the start or the end.
*/
@Composable
@ReadOnlyComposable
fun itemShape(index: Int, count: Int, baseShape: CornerBasedShape = this.baseShape): Shape {
if (count == 1) {
return baseShape
}
return when (index) {
0 -> baseShape.start()
count - 1 -> baseShape.end()
else -> RectangleShape
}
}
/**
* Icon size to use for icons used in [SegmentedButton]
*/
val IconSize = OutlinedSegmentedButtonTokens.IconSize
/** And icon to indicate the segmented button is checked or selected */
@Composable
fun ActiveIcon() {
Icon(
imageVector = Icons.Filled.Check,
contentDescription = null,
modifier = Modifier.size(IconSize)
)
}
/**
* The default implementation of icons for Segmented Buttons.
*
* @param active whether the button is activated or not.
* @param activeContent usually a checkmark icon of [IconSize] dimensions.
* @param inactiveContent typically an icon of [IconSize]. It shows only when the button is not
* checked.
*/
@Composable
fun Icon(
active: Boolean,
activeContent: @Composable () -> Unit = { ActiveIcon() },
inactiveContent: (@Composable () -> Unit)? = null
) {
if (inactiveContent == null) {
AnimatedVisibility(
visible = active,
exit = ExitTransition.None,
enter = fadeIn(tween(MotionTokens.DurationMedium3.toInt())) + scaleIn(
initialScale = 0f,
transformOrigin = TransformOrigin(0f, 1f),
animationSpec = tween(MotionTokens.DurationMedium3.toInt()),
),
) {
activeContent()
}
} else {
Crossfade(targetState = active) {
if (it) activeContent() else inactiveContent()
}
}
}
/**
* Default factory for Segmented Button [BorderStroke] can be customized through [width],
* and [color]. When using a width different than default make sure to also update
* [MultiChoiceSegmentedButtonRow] or [SingleChoiceSegmentedButtonRow] space param.
*/
fun borderStroke(
color: Color,
width: Dp = BorderWidth,
): BorderStroke = BorderStroke(width = width, color = color)
}
/**
* The different colors used in parts of the [SegmentedButton] in different states
*
* @constructor create an instance with arbitrary colors, see [SegmentedButtonDefaults] for a
* factory method using the default material3 spec
*
* @param activeContainerColor the color used for the container when enabled and active
* @param activeContentColor the color used for the content when enabled and active
* @param activeBorderColor the color used for the border when enabled and active
* @param inactiveContainerColor the color used for the container when enabled and inactive
* @param inactiveContentColor the color used for the content when enabled and inactive
* @param inactiveBorderColor the color used for the border when enabled and active
* @param disabledActiveContainerColor the color used for the container when disabled and active
* @param disabledActiveContentColor the color used for the content when disabled and active
* @param disabledActiveBorderColor the color used for the border when disabled and active
* @param disabledInactiveContainerColor the color used for the container
* when disabled and inactive
* @param disabledInactiveContentColor the color used for the content when disabled and inactive
* @param disabledInactiveBorderColor the color used for the border when disabled and inactive
*/
@Immutable
@ExperimentalMaterial3Api
class SegmentedButtonColors(
// enabled & active
val activeContainerColor: Color,
val activeContentColor: Color,
val activeBorderColor: Color,
// enabled & inactive
val inactiveContainerColor: Color,
val inactiveContentColor: Color,
val inactiveBorderColor: Color,
// disable & active
val disabledActiveContainerColor: Color,
val disabledActiveContentColor: Color,
val disabledActiveBorderColor: Color,
// disable & inactive
val disabledInactiveContainerColor: Color,
val disabledInactiveContentColor: Color,
val disabledInactiveBorderColor: Color
) {
/**
* Represents the color used for the SegmentedButton's border,
* depending on [enabled] and [active].
*
* @param enabled whether the [SegmentedButton] is enabled or not
* @param active whether the [SegmentedButton] item is checked or not
*/
internal fun borderColor(enabled: Boolean, active: Boolean): Color {
return when {
enabled && active -> activeBorderColor
enabled && !active -> inactiveBorderColor
!enabled && active -> disabledActiveContentColor
else -> disabledInactiveContentColor
}
}
/**
* Represents the content color passed to the items
*
* @param enabled whether the [SegmentedButton] is enabled or not
* @param checked whether the [SegmentedButton] item is checked or not
*/
internal fun contentColor(enabled: Boolean, checked: Boolean): Color {
return when {
enabled && checked -> activeContentColor
enabled && !checked -> inactiveContentColor
!enabled && checked -> disabledActiveContentColor
else -> disabledInactiveContentColor
}
}
/**
* Represents the container color passed to the items
*
* @param enabled whether the [SegmentedButton] is enabled or not
* @param active whether the [SegmentedButton] item is active or not
*/
internal fun containerColor(enabled: Boolean, active: Boolean): Color {
return when {
enabled && active -> activeContainerColor
enabled && !active -> inactiveContainerColor
!enabled && active -> disabledActiveContainerColor
else -> disabledInactiveContainerColor
}
}
override fun equals(other: Any?): Boolean {
if (this === other) return true
if (other === null) return false
if (this::class != other::class) return false
other as SegmentedButtonColors
if (activeBorderColor != other.activeBorderColor) return false
if (activeContentColor != other.activeContentColor) return false
if (activeContainerColor != other.activeContainerColor) return false
if (inactiveBorderColor != other.inactiveBorderColor) return false
if (inactiveContentColor != other.inactiveContentColor) return false
if (inactiveContainerColor != other.inactiveContainerColor) return false
if (disabledActiveBorderColor != other.disabledActiveBorderColor) return false
if (disabledActiveContentColor != other.disabledActiveContentColor) return false
if (disabledActiveContainerColor != other.disabledActiveContainerColor) return false
if (disabledInactiveBorderColor != other.disabledInactiveBorderColor) return false
if (disabledInactiveContentColor != other.disabledInactiveContentColor) return false
if (disabledInactiveContainerColor != other.disabledInactiveContainerColor) return false
return true
}
override fun hashCode(): Int {
var result = activeBorderColor.hashCode()
result = 31 * result + activeContentColor.hashCode()
result = 31 * result + activeContainerColor.hashCode()
result = 31 * result + inactiveBorderColor.hashCode()
result = 31 * result + inactiveContentColor.hashCode()
result = 31 * result + inactiveContainerColor.hashCode()
result = 31 * result + disabledActiveBorderColor.hashCode()
result = 31 * result + disabledActiveContentColor.hashCode()
result = 31 * result + disabledActiveContainerColor.hashCode()
result = 31 * result + disabledInactiveBorderColor.hashCode()
result = 31 * result + disabledInactiveContentColor.hashCode()
result = 31 * result + disabledInactiveContainerColor.hashCode()
return result
}
}
private fun Modifier.interactionZIndex(checked: Boolean, interactionCount: State<Int>) =
this.layout { measurable, constraints ->
val placeable = measurable.measure(constraints)
layout(placeable.width, placeable.height) {
val zIndex = interactionCount.value + if (checked) CheckedZIndexFactor else 0f
placeable.place(0, 0, zIndex)
}
}
private const val CheckedZIndexFactor = 5f
private val IconSpacing = 8.dp
@OptIn(ExperimentalMaterial3Api::class)
private class SingleChoiceSegmentedButtonScopeWrapper(scope: RowScope) :
SingleChoiceSegmentedButtonRowScope, RowScope by scope
@OptIn(ExperimentalMaterial3Api::class)
private class MultiChoiceSegmentedButtonScopeWrapper(scope: RowScope) :
MultiChoiceSegmentedButtonRowScope, RowScope by scope