|
|
|
@ -16,6 +16,7 @@
|
|
|
|
|
|
|
|
|
|
package com.google.samples.apps.nowinandroid.core.designsystem.component.scrollbar
|
|
|
|
|
|
|
|
|
|
import android.annotation.SuppressLint
|
|
|
|
|
import androidx.compose.animation.animateColorAsState
|
|
|
|
|
import androidx.compose.animation.core.Spring
|
|
|
|
|
import androidx.compose.animation.core.SpringSpec
|
|
|
|
@ -38,17 +39,31 @@ import androidx.compose.foundation.shape.RoundedCornerShape
|
|
|
|
|
import androidx.compose.material3.MaterialTheme
|
|
|
|
|
import androidx.compose.runtime.Composable
|
|
|
|
|
import androidx.compose.runtime.LaunchedEffect
|
|
|
|
|
import androidx.compose.runtime.State
|
|
|
|
|
import androidx.compose.runtime.getValue
|
|
|
|
|
import androidx.compose.runtime.mutableStateOf
|
|
|
|
|
import androidx.compose.runtime.remember
|
|
|
|
|
import androidx.compose.runtime.setValue
|
|
|
|
|
import androidx.compose.ui.Modifier
|
|
|
|
|
import androidx.compose.ui.geometry.Size
|
|
|
|
|
import androidx.compose.ui.graphics.Color
|
|
|
|
|
import androidx.compose.ui.graphics.ColorProducer
|
|
|
|
|
import androidx.compose.ui.graphics.Outline
|
|
|
|
|
import androidx.compose.ui.graphics.Shape
|
|
|
|
|
import androidx.compose.ui.graphics.drawOutline
|
|
|
|
|
import androidx.compose.ui.graphics.drawscope.ContentDrawScope
|
|
|
|
|
import androidx.compose.ui.node.DrawModifierNode
|
|
|
|
|
import androidx.compose.ui.node.ModifierNodeElement
|
|
|
|
|
import androidx.compose.ui.node.invalidateDraw
|
|
|
|
|
import androidx.compose.ui.unit.LayoutDirection
|
|
|
|
|
import androidx.compose.ui.unit.dp
|
|
|
|
|
import com.google.samples.apps.nowinandroid.core.designsystem.component.scrollbar.ThumbState.Active
|
|
|
|
|
import com.google.samples.apps.nowinandroid.core.designsystem.component.scrollbar.ThumbState.Dormant
|
|
|
|
|
import com.google.samples.apps.nowinandroid.core.designsystem.component.scrollbar.ThumbState.Inactive
|
|
|
|
|
import kotlinx.coroutines.Job
|
|
|
|
|
import kotlinx.coroutines.delay
|
|
|
|
|
import kotlinx.coroutines.flow.mapLatest
|
|
|
|
|
import kotlinx.coroutines.launch
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* The time period for showing the scrollbar thumb after interacting with it, before it fades away
|
|
|
|
@ -130,12 +145,7 @@ private fun ScrollableState.DraggableScrollbarThumb(
|
|
|
|
|
Horizontal -> height(12.dp).fillMaxWidth()
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
.background(
|
|
|
|
|
color = scrollbarThumbColor(
|
|
|
|
|
interactionSource = interactionSource,
|
|
|
|
|
),
|
|
|
|
|
shape = RoundedCornerShape(16.dp),
|
|
|
|
|
),
|
|
|
|
|
.scrollThumb(this, interactionSource),
|
|
|
|
|
)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
@ -155,31 +165,72 @@ private fun ScrollableState.DecorativeScrollbarThumb(
|
|
|
|
|
Horizontal -> height(2.dp).fillMaxWidth()
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
.background(
|
|
|
|
|
color = scrollbarThumbColor(
|
|
|
|
|
interactionSource = interactionSource,
|
|
|
|
|
),
|
|
|
|
|
shape = RoundedCornerShape(16.dp),
|
|
|
|
|
),
|
|
|
|
|
.scrollThumb(this, interactionSource),
|
|
|
|
|
)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// TODO: This lint is removed in 1.6 as the recommendation has changed
|
|
|
|
|
// remove when project is upgraded
|
|
|
|
|
@SuppressLint("ComposableModifierFactory")
|
|
|
|
|
@Composable
|
|
|
|
|
private fun Modifier.scrollThumb(
|
|
|
|
|
scrollableState: ScrollableState,
|
|
|
|
|
interactionSource: InteractionSource
|
|
|
|
|
): Modifier {
|
|
|
|
|
val colorState = scrollbarThumbColor(scrollableState, interactionSource)
|
|
|
|
|
return this then ScrollThumbElement { colorState.value }
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private data class ScrollThumbElement(val colorProducer: ColorProducer)
|
|
|
|
|
: ModifierNodeElement<ScrollThumbNode>() {
|
|
|
|
|
override fun create(): ScrollThumbNode = ScrollThumbNode(colorProducer)
|
|
|
|
|
override fun update(node: ScrollThumbNode) {
|
|
|
|
|
node.colorProducer = colorProducer
|
|
|
|
|
node.invalidateDraw()
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private class ScrollThumbNode(var colorProducer: ColorProducer): DrawModifierNode, Modifier.Node() {
|
|
|
|
|
private val shape = RoundedCornerShape(16.dp)
|
|
|
|
|
|
|
|
|
|
// naive cache outline calculation if size is the same
|
|
|
|
|
private var lastSize: Size? = null
|
|
|
|
|
private var lastLayoutDirection: LayoutDirection? = null
|
|
|
|
|
private var lastOutline: Outline? = null
|
|
|
|
|
|
|
|
|
|
override fun ContentDrawScope.draw() {
|
|
|
|
|
val color = colorProducer()
|
|
|
|
|
val outline =
|
|
|
|
|
if (size == lastSize && layoutDirection == lastLayoutDirection) {
|
|
|
|
|
lastOutline!!
|
|
|
|
|
} else {
|
|
|
|
|
shape.createOutline(size, layoutDirection, this)
|
|
|
|
|
}
|
|
|
|
|
if (color != Color.Unspecified) drawOutline(outline, color = color)
|
|
|
|
|
|
|
|
|
|
lastOutline = outline
|
|
|
|
|
lastSize = size
|
|
|
|
|
lastLayoutDirection = layoutDirection
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* The color of the scrollbar thumb as a function of its interaction state.
|
|
|
|
|
* @param interactionSource source of interactions in the scrolling container
|
|
|
|
|
*/
|
|
|
|
|
@Composable
|
|
|
|
|
private fun ScrollableState.scrollbarThumbColor(
|
|
|
|
|
interactionSource: InteractionSource,
|
|
|
|
|
): Color {
|
|
|
|
|
private fun scrollbarThumbColor(
|
|
|
|
|
scrollableState: ScrollableState,
|
|
|
|
|
interactionSource: InteractionSource
|
|
|
|
|
): State<Color> {
|
|
|
|
|
var state by remember { mutableStateOf(Dormant) }
|
|
|
|
|
val pressed by interactionSource.collectIsPressedAsState()
|
|
|
|
|
val hovered by interactionSource.collectIsHoveredAsState()
|
|
|
|
|
val dragged by interactionSource.collectIsDraggedAsState()
|
|
|
|
|
val active = (canScrollForward || canScrollForward) &&
|
|
|
|
|
(pressed || hovered || dragged || isScrollInProgress)
|
|
|
|
|
val active = (scrollableState.canScrollForward || scrollableState.canScrollForward) &&
|
|
|
|
|
(pressed || hovered || dragged || scrollableState.isScrollInProgress)
|
|
|
|
|
|
|
|
|
|
val color by animateColorAsState(
|
|
|
|
|
val color = animateColorAsState(
|
|
|
|
|
targetValue = when (state) {
|
|
|
|
|
Active -> MaterialTheme.colorScheme.onSurface.copy(0.5f)
|
|
|
|
|
Inactive -> MaterialTheme.colorScheme.onSurface.copy(alpha = 0.2f)
|
|
|
|
|