diff --git a/components/src/main/kotlin/de/jeanlucmakiola/floret/components/ReorderableColumn.kt b/components/src/main/kotlin/de/jeanlucmakiola/floret/components/ReorderableColumn.kt index cb162b1..edb362b 100644 --- a/components/src/main/kotlin/de/jeanlucmakiola/floret/components/ReorderableColumn.kt +++ b/components/src/main/kotlin/de/jeanlucmakiola/floret/components/ReorderableColumn.kt @@ -84,6 +84,8 @@ fun ReorderableColumn( // release settle — re-based onto the row's new slot once the order commits // (see onDragEnd), then eased down to zero. var dragOffset by remember { mutableFloatStateOf(0f) } + // The finger's own travel; [dragOffset] is this held to the list's ends. + var rawOffset by remember { mutableFloatStateOf(0f) } // The running release/cancel settle, cancelled if a new drag pre-empts it. // Purely visual — the order commit (see onDragEnd) never depends on it. var settleJob by remember { mutableStateOf(null) } @@ -131,10 +133,13 @@ fun ReorderableColumn( settleJob?.cancel() draggedKey = key dragOffset = 0f + rawOffset = 0f }, onDrag = { change, amount -> change.consume() - dragOffset += amount.y + rawOffset += amount.y + val from = order.indexOfFirst { keyOf(it) == key } + dragOffset = rubberBand(rawOffset, -from * pitchPx, (order.lastIndex - from) * pitchPx, pitchPx * 0.4f) }, onDragEnd = { val from = order.indexOfFirst { keyOf(it) == key } @@ -206,3 +211,16 @@ private class InsetShape(private val base: Shape, private val inset: Dp) : Shape } } } + +/** + * [value] as is between [min] and [max]; past either end it keeps following with + * growing resistance and never gets further than [stretch] beyond it. + */ +private fun rubberBand(value: Float, min: Float, max: Float, stretch: Float): Float { + fun resist(over: Float) = stretch * (1f - 1f / (over / stretch + 1f)) + return when { + value > max -> max + resist(value - max) + value < min -> min - resist(min - value) + else -> value + } +}