Skip to content

Commit

Permalink
Merge pull request #35 from MohamedRejeb/0.2.x
Browse files Browse the repository at this point in the history
Support nested DraggableItem
  • Loading branch information
MohamedRejeb authored Oct 13, 2024
2 parents bc7e587 + fe19853 commit 6d2e040
Show file tree
Hide file tree
Showing 6 changed files with 105 additions and 22 deletions.
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
/*
* Copyright 2023, Mohamed Ben Rejeb and the Compose Dnd project contributors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.mohamedrejeb.compose.dnd

import androidx.compose.runtime.staticCompositionLocalOf

internal interface DragAndDropInfo {
/**
* Whether the current composition is a shadow composition or not.
*/
val isShadow: Boolean
}

internal class DragAndDropInfoImpl(
override val isShadow: Boolean,
) : DragAndDropInfo

internal val LocalDragAndDropInfo = staticCompositionLocalOf<DragAndDropInfo> {
DragAndDropInfoImpl(isShadow = false)
}
Original file line number Diff line number Diff line change
Expand Up @@ -152,15 +152,10 @@ class DragAndDropState<T>(
* @param offset - offset of the drag start position
*/
internal suspend fun handleDragStart(
key: Any,
offset: Offset,
) = coroutineScope {
val draggableItemState = draggableItemMap.values.find {
MathUtils.isPointInRectangle(
point = offset,
topLeft = it.positionInRoot,
size = it.size,
)
} ?: return@coroutineScope
val draggableItemState = draggableItemMap[key] ?: return@coroutineScope

launch {
dragPositionAnimatable.snapTo(Offset.Zero)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@ import androidx.compose.ui.layout.onSizeChanged
import androidx.compose.ui.layout.positionInRoot
import androidx.compose.ui.unit.toSize
import com.mohamedrejeb.compose.dnd.DragAndDropState
import com.mohamedrejeb.compose.dnd.LocalDragAndDropInfo
import com.mohamedrejeb.compose.dnd.gesture.detectDragStartGesture

/**
Expand Down Expand Up @@ -64,6 +65,18 @@ internal fun <T> CoreDraggableItem(
draggableContent: @Composable () -> Unit,
content: @Composable () -> Unit,
) {
val dndInfo = LocalDragAndDropInfo.current

if (dndInfo.isShadow) {
Box(
modifier = modifier
) {
content()
}

return
}

val draggableItemState = remember(key) {
DraggableItemState(
key = key,
Expand Down Expand Up @@ -123,12 +136,14 @@ internal fun <T> CoreDraggableItem(
enabled,
state,
state.enabled,
draggableItemState,
dragAfterLongPress,
requireFirstDownUnconsumed,
) {
detectDragStartGesture(
key = key,
state = state,
draggableItemState = draggableItemState,
enabled = enabled && state.enabled,
dragAfterLongPress = dragAfterLongPress,
requireFirstDownUnconsumed = requireFirstDownUnconsumed,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ package com.mohamedrejeb.compose.dnd.drag
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.size
import androidx.compose.runtime.Composable
import androidx.compose.runtime.CompositionLocalProvider
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
import androidx.compose.ui.Modifier
Expand All @@ -26,7 +27,9 @@ import androidx.compose.ui.graphics.graphicsLayer
import androidx.compose.ui.layout.onPlaced
import androidx.compose.ui.layout.positionInRoot
import androidx.compose.ui.platform.LocalDensity
import com.mohamedrejeb.compose.dnd.DragAndDropInfoImpl
import com.mohamedrejeb.compose.dnd.DragAndDropState
import com.mohamedrejeb.compose.dnd.LocalDragAndDropInfo

@Composable
internal fun <T> DraggedItemShadow(
Expand Down Expand Up @@ -54,6 +57,10 @@ internal fun <T> DraggedItemShadow(
translationY = dragPositionY - draggedItemPositionInRoot.value.y
},
) {
state.currentDraggableItem?.content?.invoke()
CompositionLocalProvider(
LocalDragAndDropInfo provides DragAndDropInfoImpl(isShadow = true)
) {
state.currentDraggableItem?.content?.invoke()
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -21,12 +21,15 @@ import androidx.compose.ui.geometry.Offset
import androidx.compose.ui.geometry.Size
import androidx.compose.ui.layout.LayoutCoordinates
import androidx.compose.ui.layout.positionInRoot
import androidx.compose.ui.node.CompositionLocalConsumerModifierNode
import androidx.compose.ui.node.LayoutAwareModifierNode
import androidx.compose.ui.node.ModifierNodeElement
import androidx.compose.ui.node.currentValueOf
import androidx.compose.ui.platform.InspectorInfo
import androidx.compose.ui.unit.IntSize
import androidx.compose.ui.unit.toSize
import com.mohamedrejeb.compose.dnd.DragAndDropState
import com.mohamedrejeb.compose.dnd.LocalDragAndDropInfo
import com.mohamedrejeb.compose.dnd.drag.DraggedItemState

/**
Expand Down Expand Up @@ -54,17 +57,18 @@ fun <T> Modifier.dropTarget(
onDrop: (state: DraggedItemState<T>) -> Unit = {},
onDragEnter: (state: DraggedItemState<T>) -> Unit = {},
onDragExit: (state: DraggedItemState<T>) -> Unit = {},
): Modifier = this then DropTargetNodeElement(
key = key,
state = state,
zIndex = zIndex,
dropAlignment = dropAlignment,
dropOffset = dropOffset,
dropAnimationEnabled = dropAnimationEnabled,
onDrop = onDrop,
onDragEnter = onDragEnter,
onDragExit = onDragExit,
)
): Modifier =
this then DropTargetNodeElement(
key = key,
state = state,
zIndex = zIndex,
dropAlignment = dropAlignment,
dropOffset = dropOffset,
dropAnimationEnabled = dropAnimationEnabled,
onDrop = onDrop,
onDragEnter = onDragEnter,
onDragExit = onDragExit,
)

private data class DropTargetNodeElement<T>(
val key: Any,
Expand Down Expand Up @@ -133,15 +137,27 @@ private data class DropTargetNodeElement<T>(
private data class DropTargetNode<T>(
val dropTargetState: DropTargetState<T>,
var state: DragAndDropState<T>,
) : Modifier.Node(), LayoutAwareModifierNode {
) : Modifier.Node(), LayoutAwareModifierNode, CompositionLocalConsumerModifierNode {

private val key get() = dropTargetState.key

private var isShadow = false

override fun onAttach() {
isShadow = currentValueOf(LocalDragAndDropInfo).isShadow

if (isShadow) {
return
}

state.addDropTarget(dropTargetState)
}

override fun onPlaced(coordinates: LayoutCoordinates) {
if (isShadow) {
return
}

state.addDropTarget(dropTargetState)

val size = coordinates.size.toSize()
Expand All @@ -152,14 +168,26 @@ private data class DropTargetNode<T>(
}

override fun onRemeasured(size: IntSize) {
if (isShadow) {
return
}

dropTargetState.size = size.toSize()
}

override fun onReset() {
if (isShadow) {
return
}

state.removeDropTarget(key)
}

override fun onDetach() {
if (isShadow) {
return
}

state.removeDropTarget(key)
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -22,13 +22,15 @@ import androidx.compose.ui.input.pointer.PointerEventPass
import androidx.compose.ui.input.pointer.PointerInputChange
import androidx.compose.ui.input.pointer.PointerInputScope
import com.mohamedrejeb.compose.dnd.DragAndDropState
import com.mohamedrejeb.compose.dnd.drag.DraggableItemState
import com.mohamedrejeb.compose.dnd.utils.awaitPointerSlopOrCancellation
import kotlinx.coroutines.coroutineScope
import kotlinx.coroutines.launch

internal suspend fun <T> PointerInputScope.detectDragStartGesture(
key: Any,
state: DragAndDropState<T>,
draggableItemState: DraggableItemState<T>,
enabled: Boolean,
dragAfterLongPress: Boolean,
requireFirstDownUnconsumed: Boolean,
Expand All @@ -53,10 +55,13 @@ internal suspend fun <T> PointerInputScope.detectDragStartGesture(
}

if (drag != null) {
val draggableItemState = state.draggableItemMap[key] ?: return@awaitEachGesture
val draggableItemState = state.draggableItemMap.getOrPut(key) { draggableItemState }

launch {
state.handleDragStart(drag.position + draggableItemState.positionInRoot)
state.handleDragStart(
key = key,
offset = drag.position + draggableItemState.positionInRoot
)
}

state.pointerId = drag.id
Expand Down

0 comments on commit 6d2e040

Please sign in to comment.