|
|
@ -18,10 +18,11 @@ package com.google.samples.apps.nowinandroid.core.designsystem.component.scrollb
|
|
|
|
|
|
|
|
|
|
|
|
import androidx.compose.animation.core.animateDpAsState
|
|
|
|
import androidx.compose.animation.core.animateDpAsState
|
|
|
|
import androidx.compose.foundation.gestures.Orientation
|
|
|
|
import androidx.compose.foundation.gestures.Orientation
|
|
|
|
|
|
|
|
import androidx.compose.foundation.gestures.detectHorizontalDragGestures
|
|
|
|
import androidx.compose.foundation.gestures.detectTapGestures
|
|
|
|
import androidx.compose.foundation.gestures.detectTapGestures
|
|
|
|
import androidx.compose.foundation.gestures.draggable
|
|
|
|
import androidx.compose.foundation.gestures.detectVerticalDragGestures
|
|
|
|
import androidx.compose.foundation.gestures.rememberDraggableState
|
|
|
|
|
|
|
|
import androidx.compose.foundation.hoverable
|
|
|
|
import androidx.compose.foundation.hoverable
|
|
|
|
|
|
|
|
import androidx.compose.foundation.interaction.DragInteraction
|
|
|
|
import androidx.compose.foundation.interaction.MutableInteractionSource
|
|
|
|
import androidx.compose.foundation.interaction.MutableInteractionSource
|
|
|
|
import androidx.compose.foundation.interaction.PressInteraction
|
|
|
|
import androidx.compose.foundation.interaction.PressInteraction
|
|
|
|
import androidx.compose.foundation.layout.Box
|
|
|
|
import androidx.compose.foundation.layout.Box
|
|
|
@ -41,6 +42,7 @@ import androidx.compose.runtime.setValue
|
|
|
|
import androidx.compose.ui.Alignment
|
|
|
|
import androidx.compose.ui.Alignment
|
|
|
|
import androidx.compose.ui.Modifier
|
|
|
|
import androidx.compose.ui.Modifier
|
|
|
|
import androidx.compose.ui.geometry.Offset
|
|
|
|
import androidx.compose.ui.geometry.Offset
|
|
|
|
|
|
|
|
import androidx.compose.ui.input.pointer.PointerInputChange
|
|
|
|
import androidx.compose.ui.input.pointer.pointerInput
|
|
|
|
import androidx.compose.ui.input.pointer.pointerInput
|
|
|
|
import androidx.compose.ui.layout.onGloballyPositioned
|
|
|
|
import androidx.compose.ui.layout.onGloballyPositioned
|
|
|
|
import androidx.compose.ui.layout.positionInRoot
|
|
|
|
import androidx.compose.ui.layout.positionInRoot
|
|
|
@ -53,7 +55,9 @@ import androidx.compose.ui.unit.max
|
|
|
|
import androidx.compose.ui.util.packFloats
|
|
|
|
import androidx.compose.ui.util.packFloats
|
|
|
|
import androidx.compose.ui.util.unpackFloat1
|
|
|
|
import androidx.compose.ui.util.unpackFloat1
|
|
|
|
import androidx.compose.ui.util.unpackFloat2
|
|
|
|
import androidx.compose.ui.util.unpackFloat2
|
|
|
|
|
|
|
|
import kotlinx.coroutines.TimeoutCancellationException
|
|
|
|
import kotlinx.coroutines.delay
|
|
|
|
import kotlinx.coroutines.delay
|
|
|
|
|
|
|
|
import kotlinx.coroutines.withTimeout
|
|
|
|
import kotlin.math.max
|
|
|
|
import kotlin.math.max
|
|
|
|
import kotlin.math.min
|
|
|
|
import kotlin.math.min
|
|
|
|
|
|
|
|
|
|
|
@ -221,14 +225,6 @@ fun Scrollbar(
|
|
|
|
a = track.size * thumbTravelPercent,
|
|
|
|
a = track.size * thumbTravelPercent,
|
|
|
|
b = track.size - thumbSizePx,
|
|
|
|
b = track.size - thumbSizePx,
|
|
|
|
)
|
|
|
|
)
|
|
|
|
val draggableState = rememberDraggableState { delta ->
|
|
|
|
|
|
|
|
if (draggedOffset == Offset.Unspecified) return@rememberDraggableState
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
draggedOffset = when (orientation) {
|
|
|
|
|
|
|
|
Orientation.Vertical -> draggedOffset.copy(y = draggedOffset.y + delta)
|
|
|
|
|
|
|
|
Orientation.Horizontal -> draggedOffset.copy(x = draggedOffset.x + delta)
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
// scrollbar track container
|
|
|
|
// scrollbar track container
|
|
|
|
Box(
|
|
|
|
Box(
|
|
|
@ -251,12 +247,17 @@ fun Scrollbar(
|
|
|
|
.pointerInput(Unit) {
|
|
|
|
.pointerInput(Unit) {
|
|
|
|
detectTapGestures(
|
|
|
|
detectTapGestures(
|
|
|
|
onPress = { offset ->
|
|
|
|
onPress = { offset ->
|
|
|
|
|
|
|
|
try {
|
|
|
|
|
|
|
|
// Wait for a long press before scrolling
|
|
|
|
|
|
|
|
withTimeout(viewConfiguration.longPressTimeoutMillis) {
|
|
|
|
|
|
|
|
tryAwaitRelease()
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
} catch (e: TimeoutCancellationException) {
|
|
|
|
|
|
|
|
// Start the press triggered scroll
|
|
|
|
val initialPress = PressInteraction.Press(offset)
|
|
|
|
val initialPress = PressInteraction.Press(offset)
|
|
|
|
interactionSource?.tryEmit(initialPress)
|
|
|
|
interactionSource?.tryEmit(initialPress)
|
|
|
|
|
|
|
|
|
|
|
|
// Start the press
|
|
|
|
|
|
|
|
pressedOffset = offset
|
|
|
|
pressedOffset = offset
|
|
|
|
|
|
|
|
|
|
|
|
interactionSource?.tryEmit(
|
|
|
|
interactionSource?.tryEmit(
|
|
|
|
when {
|
|
|
|
when {
|
|
|
|
tryAwaitRelease() -> PressInteraction.Release(initialPress)
|
|
|
|
tryAwaitRelease() -> PressInteraction.Release(initialPress)
|
|
|
@ -266,21 +267,57 @@ fun Scrollbar(
|
|
|
|
|
|
|
|
|
|
|
|
// End the press
|
|
|
|
// End the press
|
|
|
|
pressedOffset = Offset.Unspecified
|
|
|
|
pressedOffset = Offset.Unspecified
|
|
|
|
|
|
|
|
}
|
|
|
|
},
|
|
|
|
},
|
|
|
|
)
|
|
|
|
)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
// Process scrollbar drags
|
|
|
|
// Process scrollbar drags
|
|
|
|
.draggable(
|
|
|
|
.pointerInput(Unit) {
|
|
|
|
state = draggableState,
|
|
|
|
var dragInteraction: DragInteraction.Start? = null
|
|
|
|
orientation = orientation,
|
|
|
|
val onDragStart: (Offset) -> Unit = { offset ->
|
|
|
|
interactionSource = interactionSource,
|
|
|
|
val start = DragInteraction.Start()
|
|
|
|
onDragStarted = { startedPosition: Offset ->
|
|
|
|
dragInteraction = start
|
|
|
|
draggedOffset = startedPosition
|
|
|
|
interactionSource?.tryEmit(start)
|
|
|
|
},
|
|
|
|
draggedOffset = offset
|
|
|
|
onDragStopped = {
|
|
|
|
}
|
|
|
|
|
|
|
|
val onDragEnd: () -> Unit = {
|
|
|
|
|
|
|
|
dragInteraction?.let { interactionSource?.tryEmit(DragInteraction.Stop(it)) }
|
|
|
|
draggedOffset = Offset.Unspecified
|
|
|
|
draggedOffset = Offset.Unspecified
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
val onDragCancel: () -> Unit = {
|
|
|
|
|
|
|
|
dragInteraction?.let { interactionSource?.tryEmit(DragInteraction.Cancel(it)) }
|
|
|
|
|
|
|
|
draggedOffset = Offset.Unspecified
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
val onDrag: (change: PointerInputChange, dragAmount: Float) -> Unit =
|
|
|
|
|
|
|
|
onDrag@{ _, delta ->
|
|
|
|
|
|
|
|
if (draggedOffset == Offset.Unspecified) return@onDrag
|
|
|
|
|
|
|
|
draggedOffset = when (orientation) {
|
|
|
|
|
|
|
|
Orientation.Vertical -> draggedOffset.copy(
|
|
|
|
|
|
|
|
y = draggedOffset.y + delta,
|
|
|
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
Orientation.Horizontal -> draggedOffset.copy(
|
|
|
|
|
|
|
|
x = draggedOffset.x + delta,
|
|
|
|
|
|
|
|
)
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
when (orientation) {
|
|
|
|
|
|
|
|
Orientation.Horizontal -> detectHorizontalDragGestures(
|
|
|
|
|
|
|
|
onDragStart = onDragStart,
|
|
|
|
|
|
|
|
onDragEnd = onDragEnd,
|
|
|
|
|
|
|
|
onDragCancel = onDragCancel,
|
|
|
|
|
|
|
|
onHorizontalDrag = onDrag,
|
|
|
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
Orientation.Vertical -> detectVerticalDragGestures(
|
|
|
|
|
|
|
|
onDragStart = onDragStart,
|
|
|
|
|
|
|
|
onDragEnd = onDragEnd,
|
|
|
|
|
|
|
|
onDragCancel = onDragCancel,
|
|
|
|
|
|
|
|
onVerticalDrag = onDrag,
|
|
|
|
|
|
|
|
)
|
|
|
|
|
|
|
|
}
|
|
|
|
},
|
|
|
|
},
|
|
|
|
),
|
|
|
|
|
|
|
|
) {
|
|
|
|
) {
|
|
|
|
val scrollbarThumbMovedDp = max(
|
|
|
|
val scrollbarThumbMovedDp = max(
|
|
|
|
a = with(localDensity) { thumbMovedPx.toDp() },
|
|
|
|
a = with(localDensity) { thumbMovedPx.toDp() },
|
|
|
@ -338,6 +375,7 @@ fun Scrollbar(
|
|
|
|
a = currentThumbMovedPercent + delta,
|
|
|
|
a = currentThumbMovedPercent + delta,
|
|
|
|
b = destinationThumbMovedPercent,
|
|
|
|
b = destinationThumbMovedPercent,
|
|
|
|
)
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
|
|
else -> max(
|
|
|
|
else -> max(
|
|
|
|
a = currentThumbMovedPercent + delta,
|
|
|
|
a = currentThumbMovedPercent + delta,
|
|
|
|
b = destinationThumbMovedPercent,
|
|
|
|
b = destinationThumbMovedPercent,
|
|
|
|