____ _ _______ __
/ __ \ | /| / / __/ |/ /
/ /_/ / |/ |/ / _// /
\____/|__/|__/___/_||_/
owenclave / app/src/main/java/io/nekohasekai/sagernet/ui/compose/components/ExpressiveSegmentedButton.kt
package io.nekohasekai.sagernet.ui.compose.components
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.RowScope
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.padding
import androidx.compose.material3.ButtonGroupDefaults
import androidx.compose.material3.ExperimentalMaterial3ExpressiveApi
import androidx.compose.material3.Icon
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.Text
import androidx.compose.material3.ToggleButton
import androidx.compose.material3.ToggleButtonDefaults
import androidx.compose.runtime.Composable
import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.vector.ImageVector
import androidx.compose.ui.text.style.TextOverflow
import androidx.compose.ui.unit.dp
/**
* A genuine M3E segmented selector built on [ToggleButton] with connected
* button-group shapes (leading/middle/trailing). Selecting an item morphs its
* corners — the signature expressive connected-group interaction.
*/
@OptIn(ExperimentalMaterial3ExpressiveApi::class)
@Composable
fun ExpressiveSegmentedButton(
items: List,
selected: T,
onSelect: (T) -> Unit,
label: (T) -> String,
modifier: Modifier = Modifier,
icon: ((T) -> ImageVector?)? = null,
) {
Row(
modifier = modifier.fillMaxWidth(),
horizontalArrangement = Arrangement.spacedBy(ButtonGroupDefaults.ConnectedSpaceBetween),
) {
items.forEachIndexed { index, item ->
ToggleButton(
checked = selected == item,
onCheckedChange = { onSelect(item) },
modifier = Modifier.weight(1f),
shapes = when (index) {
0 -> ButtonGroupDefaults.connectedLeadingButtonShapes()
items.lastIndex -> ButtonGroupDefaults.connectedTrailingButtonShapes()
else -> ButtonGroupDefaults.connectedMiddleButtonShapes()
},
) {
val ic = icon?.invoke(item)
if (ic != null) {
Icon(
ic,
contentDescription = null,
modifier = Modifier.padding(end = ToggleButtonDefaults.IconSpacing),
)
}
Text(
label(item),
maxLines = 1,
overflow = TextOverflow.Ellipsis,
style = MaterialTheme.typography.labelLarge,
)
}
}
}
}