switch tab icon/text colour by hard overlay clipped to the indicator

This commit is contained in:
austinried
2026-10-04 00:29:57 +00:00
parent 76eb4a061d
commit 38f3766dd1
@@ -64,6 +64,7 @@ import androidx.compose.runtime.Composable
import androidx.compose.runtime.LaunchedEffect import androidx.compose.runtime.LaunchedEffect
import androidx.compose.runtime.SideEffect import androidx.compose.runtime.SideEffect
import androidx.compose.runtime.getValue import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableFloatStateOf
import androidx.compose.runtime.mutableIntStateOf import androidx.compose.runtime.mutableIntStateOf
import androidx.compose.runtime.mutableStateMapOf import androidx.compose.runtime.mutableStateMapOf
import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.mutableStateOf
@@ -75,10 +76,12 @@ import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier import androidx.compose.ui.Modifier
import androidx.compose.ui.draw.clip import androidx.compose.ui.draw.clip
import androidx.compose.ui.draw.clipToBounds import androidx.compose.ui.draw.clipToBounds
import androidx.compose.ui.draw.drawWithContent
import androidx.compose.ui.focus.FocusRequester import androidx.compose.ui.focus.FocusRequester
import androidx.compose.ui.focus.focusRequester import androidx.compose.ui.focus.focusRequester
import androidx.compose.ui.geometry.Rect import androidx.compose.ui.geometry.Rect
import androidx.compose.ui.graphics.Color import androidx.compose.ui.graphics.Color
import androidx.compose.ui.graphics.drawscope.clipRect
import androidx.compose.ui.graphics.lerp import androidx.compose.ui.graphics.lerp
import androidx.compose.ui.graphics.vector.ImageVector import androidx.compose.ui.graphics.vector.ImageVector
import androidx.compose.ui.input.nestedscroll.nestedScroll import androidx.compose.ui.input.nestedscroll.nestedScroll
@@ -86,6 +89,7 @@ import androidx.compose.ui.layout.boundsInParent
import androidx.compose.ui.layout.layout import androidx.compose.ui.layout.layout
import androidx.compose.ui.layout.onGloballyPositioned import androidx.compose.ui.layout.onGloballyPositioned
import androidx.compose.ui.platform.LocalDensity import androidx.compose.ui.platform.LocalDensity
import androidx.compose.ui.semantics.clearAndSetSemantics
import androidx.compose.ui.unit.Constraints import androidx.compose.ui.unit.Constraints
import androidx.compose.ui.unit.Dp import androidx.compose.ui.unit.Dp
import androidx.compose.ui.unit.IntOffset import androidx.compose.ui.unit.IntOffset
@@ -637,26 +641,28 @@ private fun LibraryTabs(
} }
} }
Box( val current = bounds[page]
Modifier val neighbour = current?.let { bounds[if (fraction >= 0f) page + 1 else page - 1] }
.fillMaxWidth()
.padding(start = 8.dp, end = 12.dp),
) {
bounds[page]?.let { current ->
val neighbour = bounds[if (fraction >= 0f) page + 1 else page - 1]
val step = if (neighbour != null) transition else 0f val step = if (neighbour != null) transition else 0f
val left = current.left + ((neighbour?.left ?: current.left) - current.left) * step
val right = current.right + ((neighbour?.right ?: current.right) - current.right) * step
val stretch = with(density) { indicatorStretch.toPx() } * step val stretch = with(density) { indicatorStretch.toPx() } * step
val leading = stretch * 2f val leading = stretch * 2f
val trailing = stretch * 0.25f val trailing = stretch * 0.25f
val leftExtra = if (travel > 0) trailing else leading val leftExtra = if (travel > 0) trailing else leading
val rightExtra = if (travel > 0) leading else trailing val rightExtra = if (travel > 0) leading else trailing
val indicatorLeft = current?.let { it.left + ((neighbour?.left ?: it.left) - it.left) * step - leftExtra }
val indicatorRight = current?.let { it.right + ((neighbour?.right ?: it.right) - it.right) * step + rightExtra }
Box(
Modifier
.fillMaxWidth()
.padding(start = 8.dp, end = 12.dp),
) {
if (current != null && indicatorLeft != null && indicatorRight != null) {
Box( Box(
modifier = modifier =
Modifier Modifier
.offset { IntOffset((left - leftExtra).roundToInt(), current.top.roundToInt()) } .offset { IntOffset(indicatorLeft.roundToInt(), current.top.roundToInt()) }
.width(with(density) { (right - left + leftExtra + rightExtra).toDp() }) .width(with(density) { (indicatorRight - indicatorLeft).toDp() })
.height(with(density) { current.height.toDp() }) .height(with(density) { current.height.toDp() })
.clip(RoundedCornerShape(8.dp)) .clip(RoundedCornerShape(8.dp))
.background(artwork?.scheme?.primary ?: MaterialTheme.colorScheme.onBackground), .background(artwork?.scheme?.primary ?: MaterialTheme.colorScheme.onBackground),
@@ -678,6 +684,8 @@ private fun LibraryTabs(
page + (if (fraction >= 0f) 1 else -1) -> transition page + (if (fraction >= 0f) 1 else -1) -> transition
else -> 0f else -> 0f
}, },
indicatorLeft = indicatorLeft,
indicatorRight = indicatorRight,
onClick = { onTabSelected(tab) }, onClick = { onTabSelected(tab) },
modifier = modifier =
Modifier.onGloballyPositioned { coordinates -> Modifier.onGloballyPositioned { coordinates ->
@@ -709,23 +717,55 @@ private fun TabButton(
tab: LibraryTab, tab: LibraryTab,
artwork: ArtworkColors?, artwork: ArtworkColors?,
progress: Float, progress: Float,
indicatorLeft: Float?,
indicatorRight: Float?,
onClick: () -> Unit, onClick: () -> Unit,
modifier: Modifier = Modifier, modifier: Modifier = Modifier,
) { ) {
val selectedContent = artwork?.scheme?.onPrimary ?: MaterialTheme.colorScheme.background val selectedContent = artwork?.scheme?.onPrimary ?: MaterialTheme.colorScheme.background
val tintProgress = ((progress - 0.25f) / 0.6f).coerceIn(0f, 1f) val paddingStart = with(LocalDensity.current) { 8.dp.toPx() }
val content = lerp(MaterialTheme.colorScheme.onBackground, selectedContent, tintProgress) var tabLeft by remember { mutableFloatStateOf(0f) }
Row( Box(
modifier = modifier =
modifier modifier
.onGloballyPositioned { tabLeft = it.boundsInParent().left }
.clickable(onClick = onClick) .clickable(onClick = onClick)
.padding(start = 8.dp, end = 12.dp, top = 6.dp, bottom = 6.dp), .padding(start = 8.dp, end = 12.dp, top = 6.dp, bottom = 6.dp),
verticalAlignment = Alignment.CenterVertically,
) { ) {
TabContent(tab, progress, MaterialTheme.colorScheme.onBackground)
if (indicatorLeft != null && indicatorRight != null) {
Box(
modifier =
Modifier
.clearAndSetSemantics {}
.drawWithContent {
clipRect(
left = indicatorLeft - tabLeft - paddingStart,
top = 0f,
right = indicatorRight - tabLeft - paddingStart,
bottom = size.height,
) {
this@drawWithContent.drawContent()
}
},
) {
TabContent(tab, progress, selectedContent)
}
}
}
}
@Composable
private fun TabContent(
tab: LibraryTab,
progress: Float,
color: Color,
) {
Row(verticalAlignment = Alignment.CenterVertically) {
Icon( Icon(
imageVector = tab.icon, imageVector = tab.icon,
contentDescription = tab.label, contentDescription = tab.label,
tint = content, tint = color,
modifier = Modifier.size(24.dp), modifier = Modifier.size(24.dp),
) )
Box( Box(
@@ -745,7 +785,7 @@ private fun TabButton(
Text( Text(
text = tab.label, text = tab.label,
style = MaterialTheme.typography.titleSmall, style = MaterialTheme.typography.titleSmall,
color = content, color = color,
maxLines = 1, maxLines = 1,
softWrap = false, softWrap = false,
) )