From 1e49fd5067e5fcc44f3bc6bb4accb3dc087a137a Mon Sep 17 00:00:00 2001 From: Jean-Luc Makiola Date: Wed, 23 Sep 2026 17:13:07 +0200 Subject: [PATCH] components: ReorderableColumn drag shadow follows the card --- .../floret/components/ReorderableColumn.kt | 28 +++++++++++++++++-- 1 file changed, 25 insertions(+), 3 deletions(-) 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 e99eb0b..cb162b1 100644 --- a/components/src/main/kotlin/de/jeanlucmakiola/floret/components/ReorderableColumn.kt +++ b/components/src/main/kotlin/de/jeanlucmakiola/floret/components/ReorderableColumn.kt @@ -11,7 +11,6 @@ import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.height -import androidx.compose.foundation.shape.RoundedCornerShape import androidx.compose.runtime.Composable import androidx.compose.runtime.derivedStateOf import androidx.compose.runtime.getValue @@ -21,10 +20,18 @@ import androidx.compose.runtime.remember import androidx.compose.runtime.rememberCoroutineScope import androidx.compose.runtime.setValue import androidx.compose.ui.Modifier +import androidx.compose.ui.geometry.Offset +import androidx.compose.ui.geometry.Size +import androidx.compose.ui.geometry.translate +import androidx.compose.ui.graphics.Outline +import androidx.compose.ui.graphics.Path +import androidx.compose.ui.graphics.Shape import androidx.compose.ui.graphics.graphicsLayer import androidx.compose.ui.input.pointer.pointerInput import androidx.compose.ui.platform.LocalDensity +import androidx.compose.ui.unit.Density import androidx.compose.ui.unit.Dp +import androidx.compose.ui.unit.LayoutDirection import androidx.compose.ui.unit.dp import androidx.compose.ui.zIndex import kotlinx.coroutines.Job @@ -53,7 +60,8 @@ private val RowGap: Dp = 2.dp * [GroupedRow]'s card shaping — pass `gapBelow = false` there, this owns spacing) * and a `dragHandle` [Modifier] to attach to the element that starts a drag. * Pass [rowHeight] matching the rows' non-reorder `minHeight`, so they keep their - * size when a screen switches into reordering. + * size when a screen switches into reordering. [rowInset] is the card's horizontal + * inset inside its slot, so the held row's shadow hugs the card, not the slot. */ @Composable fun ReorderableColumn( @@ -62,6 +70,7 @@ fun ReorderableColumn( onReorder: (List) -> Unit, modifier: Modifier = Modifier, rowHeight: Dp = ReorderableRowHeight, + rowInset: Dp = GroupedListInset, rowContent: @Composable (item: T, position: Position, dragHandle: Modifier, isDragging: Boolean) -> Unit, ) { val pitchPx = with(LocalDensity.current) { (rowHeight + RowGap).toPx() } @@ -174,7 +183,7 @@ fun ReorderableColumn( scaleX = 1.02f scaleY = 1.02f shadowElevation = 8.dp.toPx() - shape = RoundedCornerShape(20.dp) + shape = InsetShape(groupedShape(positionOf(index, order.size), 22.dp, 6.dp), rowInset) clip = false } }, @@ -184,3 +193,16 @@ fun ReorderableColumn( } } } + +/** [base] laid out [inset] in from both sides of the slot. */ +private class InsetShape(private val base: Shape, private val inset: Dp) : Shape { + override fun createOutline(size: Size, layoutDirection: LayoutDirection, density: Density): Outline { + val dx = with(density) { inset.toPx() } + val inner = Size((size.width - 2 * dx).coerceAtLeast(0f), size.height) + return when (val outline = base.createOutline(inner, layoutDirection, density)) { + is Outline.Rectangle -> Outline.Rectangle(outline.rect.translate(Offset(dx, 0f))) + is Outline.Rounded -> Outline.Rounded(outline.roundRect.translate(Offset(dx, 0f))) + is Outline.Generic -> Outline.Generic(Path().apply { addPath(outline.path, Offset(dx, 0f)) }) + } + } +}