Skip to content

Commit

Permalink
Fancy drag and drop WIP
Browse files Browse the repository at this point in the history
  • Loading branch information
alexvanyo committed Dec 26, 2024
1 parent 4cd773d commit 2c30606
Show file tree
Hide file tree
Showing 5 changed files with 54 additions and 64 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -23,8 +23,8 @@ import androidx.compose.ui.unit.IntSize
/**
* A finite rectangular region of a cell universe.
*
* This is represented by an [IntRect], with the [IntRect.topLeft] being the top-left most point (inclusive), and
* [IntRect.bottomRight] being the bottom-right most point (exclusive).
* This is represented by an [IntRect] [intRect], with the [IntRect.topLeft] being the top-left most point (inclusive),
* and [IntRect.bottomRight] being the bottom-right most point (exclusive).
*/
@JvmInline
value class CellWindow(val intRect: IntRect) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.size
import androidx.compose.runtime.Composable
import androidx.compose.ui.Modifier
import androidx.compose.ui.geometry.Offset
import androidx.compose.ui.unit.IntOffset
import androidx.compose.ui.unit.IntRect
import androidx.compose.ui.unit.IntSize
Expand Down Expand Up @@ -61,6 +62,7 @@ internal fun InteractableCellsPreview(modifier: Modifier = Modifier) {
IntSize(10, 10),
),
),
pixelOffsetFromCenter = Offset.Zero,
)
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,6 @@ import androidx.compose.runtime.rememberUpdatedState
import androidx.compose.runtime.setValue
import androidx.compose.ui.Modifier
import androidx.compose.ui.geometry.Offset
import androidx.compose.ui.graphics.graphicsLayer
import androidx.compose.ui.input.pointer.PointerEventType
import androidx.compose.ui.input.pointer.PointerType
import androidx.compose.ui.input.pointer.pointerInput
Expand Down Expand Up @@ -393,14 +392,6 @@ private fun CellWindowImpl(
}

Box {
// Create the offset modifier for adjusting the components that expect to be precisely sized to a multiple
// of scaledCellDpSize to represent exactly the current CellWindow
val cellWindowOffsetModifier = Modifier
.graphicsLayer {
this.translationX = -fracPixelOffsetFromCenter.x
this.translationY = -fracPixelOffsetFromCenter.y
}

// Apply the navigable modifier around the cells, but not the selection overlay.
// This ensures gestures for the selection overlay are given precedence over the cells.
Box(
Expand Down Expand Up @@ -441,7 +432,9 @@ private fun CellWindowImpl(
},
scaledCellDpSize = scaledCellDpSize,
cellWindow = cellWindow,
modifier = cellWindowOffsetModifier,
pixelOffsetFromCenter = fracPixelOffsetFromCenter,
modifier = Modifier

)
}
}
Expand All @@ -457,7 +450,7 @@ private fun CellWindowImpl(
getSelectionCellState = cellWindowUiState::getSelectionCellState,
scaledCellDpSize = scaledCellDpSize,
cellWindow = cellWindow,
modifier = cellWindowOffsetModifier,
pixelOffsetFromCenter = fracPixelOffsetFromCenter,
)
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,7 @@ import androidx.compose.ui.Modifier
import androidx.compose.ui.geometry.Offset
import androidx.compose.ui.geometry.Rect
import androidx.compose.ui.geometry.isSpecified
import androidx.compose.ui.graphics.graphicsLayer
import androidx.compose.ui.input.pointer.HistoricalChange
import androidx.compose.ui.input.pointer.PointerType
import androidx.compose.ui.input.pointer.pointerInput
Expand Down Expand Up @@ -81,10 +82,15 @@ fun InteractableCells(
setSelectionSessionState: (SessionValue<SelectionState>) -> Unit,
scaledCellDpSize: Dp,
cellWindow: CellWindow,
pixelOffsetFromCenter: Offset,
modifier: Modifier = Modifier,
) {
Surface(
modifier = modifier
.graphicsLayer {
this.translationX = -pixelOffsetFromCenter.x
this.translationY = -pixelOffsetFromCenter.y
}
.requiredSize(
scaledCellDpSize * cellWindow.width,
scaledCellDpSize * cellWindow.height,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -61,7 +61,6 @@ import androidx.compose.ui.geometry.toRect
import androidx.compose.ui.graphics.BlendMode
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.graphics.ColorFilter
import androidx.compose.ui.graphics.PathEffect
import androidx.compose.ui.graphics.StrokeCap
import androidx.compose.ui.graphics.drawscope.ContentDrawScope
import androidx.compose.ui.graphics.drawscope.DrawScope
Expand All @@ -77,15 +76,14 @@ import androidx.compose.ui.unit.Dp
import androidx.compose.ui.unit.IntOffset
import androidx.compose.ui.unit.dp
import androidx.compose.ui.unit.round
import androidx.compose.ui.unit.toIntRect
import androidx.compose.ui.unit.toOffset
import androidx.compose.ui.unit.toRect
import androidx.compose.ui.unit.toSize
import androidx.compose.ui.util.packInts
import androidx.compose.ui.util.unpackInt1
import androidx.compose.ui.util.unpackInt2
import com.alexvanyo.composelife.geometry.times
import com.alexvanyo.composelife.logging.Logger
import com.alexvanyo.composelife.logging.d
import com.alexvanyo.composelife.model.CellState
import com.alexvanyo.composelife.model.CellWindow
import com.alexvanyo.composelife.model.di.CellStateParserProvider
Expand All @@ -107,9 +105,10 @@ import kotlinx.coroutines.launch
import kotlin.math.max
import kotlin.math.min
import kotlin.math.roundToInt
import kotlin.uuid.Uuid

/**
* The overlay based on the [selectionState].
* The overlay based on the [selectionSessionState].
*/
context(CellStateParserProvider)
@Suppress("LongMethod", "LongParameterList")
Expand All @@ -120,6 +119,7 @@ fun SelectionOverlay(
getSelectionCellState: (SelectionState) -> CellState,
scaledCellDpSize: Dp,
cellWindow: CellWindow,
pixelOffsetFromCenter: Offset,
modifier: Modifier = Modifier,
) {
val selectionSessionStateValueHolder = rememberSessionValueHolder(
Expand All @@ -135,8 +135,7 @@ fun SelectionOverlay(

val scaledCellPixelSize = with(LocalDensity.current) { scaledCellDpSize.toPx() }

val dropAvailableBorderColor = MaterialTheme.colorScheme.secondary
val dropPreviewBorderColor = MaterialTheme.colorScheme.tertiary
val dropAvailableBorderColor = MaterialTheme.colorScheme.tertiary
val dropPreviewCellStateBorderColor = MaterialTheme.colorScheme.secondary

AnimatedContent(
Expand All @@ -155,31 +154,43 @@ fun SelectionOverlay(
.drawWithContent {
drawContent()

when (val cellStateDropState = cellStateDropStateHolder.cellStateDropState) {
CellStateDropState.ApplicableDropAvailable -> {
Logger.d { "drawApplicableDropAvailable" }
drawRect(Color.Red.copy(alpha = 0.3f))
drawApplicableDropAvailable(
color = dropAvailableBorderColor,
when (cellStateDropStateHolder.cellStateDropState) {
CellStateDropState.ApplicableDropAvailable,
is CellStateDropState.DropPreview -> {
drawDashedRect(
selectionColor = dropAvailableBorderColor,
strokeWidth = 12.dp.toPx(),
intervals = floatArrayOf(
24.dp.toPx(),
24.dp.toPx(),
),
phase = 12.dp.toPx(),
)
}
is CellStateDropState.DropPreview -> Unit
CellStateDropState.None -> Unit
}
}
.graphicsLayer {
this.translationX = -pixelOffsetFromCenter.x
this.translationY = -pixelOffsetFromCenter.y
}
.requiredSize(
scaledCellDpSize * cellWindow.width,
scaledCellDpSize * cellWindow.height,
)
.cellStateDragAndDropTarget(
mutableCellStateDropStateHolder = cellStateDropStateHolder,
) { dropOffset, cellState ->
selectionSessionStateValueHolder.setValue(
SelectionState.Selection(
cellState = cellState,
offset = (cellWindow.topLeft.toOffset() + (dropOffset / scaledCellPixelSize) -
cellState.boundingBox.size.toSize().center).round(),
),
setSelectionSessionState(
SessionValue(
sessionId = Uuid.random(),
valueId = Uuid.random(),
value = SelectionState.Selection(
cellState = cellState,
offset = (cellWindow.topLeft.toOffset() + (dropOffset / scaledCellPixelSize) -
cellState.boundingBox.size.toSize().center).round(),
),
)
)
}
.drawWithContent {
Expand All @@ -188,14 +199,13 @@ fun SelectionOverlay(
when (val cellStateDropState = cellStateDropStateHolder.cellStateDropState) {
CellStateDropState.ApplicableDropAvailable -> Unit
is CellStateDropState.DropPreview -> {
drawRect(Color.Green.copy(alpha = 0.3f))
drawDropPreview(
dropPreview = cellStateDropState,
scaledCellDpSize = scaledCellDpSize,
borderColor = dropPreviewBorderColor,
cellStateOutlineColor = dropPreviewCellStateBorderColor,
)
}

CellStateDropState.None -> Unit
}
},
Expand All @@ -204,6 +214,7 @@ fun SelectionOverlay(
SelectionState.NoSelection -> {
Spacer(Modifier.fillMaxSize())
}

is SelectionState.SelectingBox.FixedSelectingBox -> {
@Suppress("UNCHECKED_CAST")
(
Expand All @@ -220,6 +231,7 @@ fun SelectionOverlay(
)
)
}

is SelectionState.SelectingBox.TransientSelectingBox -> {
TransientSelectingBoxOverlay(
selectionState = targetSelectionState,
Expand All @@ -228,6 +240,7 @@ fun SelectionOverlay(
modifier = Modifier.fillMaxSize(),
)
}

is SelectionState.Selection -> {
@Suppress("UNCHECKED_CAST")
(
Expand All @@ -247,44 +260,20 @@ fun SelectionOverlay(
}
}

private fun ContentDrawScope.drawApplicableDropAvailable(
color: Color,
) {
drawSelectionRect(
selectionColor = color,
strokeWidth = 2.dp.toPx(),
intervals = floatArrayOf(
24.dp.toPx(),
24.dp.toPx(),
),
phase = 12.dp.toPx(),
)
}

private fun ContentDrawScope.drawDropPreview(
dropPreview: CellStateDropState.DropPreview,
scaledCellDpSize: Dp,
borderColor: Color,
cellStateOutlineColor: Color,
) {
drawSelectionRect(
selectionColor = borderColor,
strokeWidth = 2.dp.toPx(),
intervals = floatArrayOf(
24.dp.toPx(),
24.dp.toPx(),
),
phase = 12.dp.toPx(),
)
drawSelectionRect(
drawDashedRect(
selectionColor = cellStateOutlineColor,
strokeWidth = 2.dp.toPx(),
intervals = floatArrayOf(
24.dp.toPx(),
12.dp.toPx(),
),
phase = 12.dp.toPx(),
rect = ((dropPreview.cellState.boundingBox.intRect.toRect()
rect = ((dropPreview.cellState.boundingBox.size.toIntRect().toRect()
.translate(-dropPreview.cellState.boundingBox.size.toSize().center)
) * scaledCellDpSize.toPx()).translate(dropPreview.offset)
)
Expand Down Expand Up @@ -754,7 +743,7 @@ fun SelectingBox(
color = selectionColor,
alpha = 0.2f,
)
drawSelectionRect(
drawDashedRect(
selectionColor = selectionColor,
strokeWidth = 2.dp.toPx(),
intervals = floatArrayOf(
Expand All @@ -767,12 +756,12 @@ fun SelectingBox(
}

/**
* Draws a selection rectangle, with the given [PathEffect].
* Draws a dashed line rectangle, with intervals and phase applied symmetrically.
*
* This results in the [PathEffect] being applied symmetrically via 8 line segments: from the middle of each side,
* The dashed line effect is drawn in 8 line segments: from the middle of each side,
* to each corner.
*/
private fun DrawScope.drawSelectionRect(
private fun DrawScope.drawDashedRect(
selectionColor: Color,
strokeWidth: Float,
intervals: FloatArray,
Expand Down

0 comments on commit 2c30606

Please sign in to comment.