Skip to content

Commit

Permalink
Merge pull request #6 from MohamedRejeb/feature/toggleable
Browse files Browse the repository at this point in the history
Add Enable/Disable drag and drop
  • Loading branch information
MohamedRejeb committed Mar 23, 2024
2 parents da40cb2 + 34c855c commit e09c57e
Show file tree
Hide file tree
Showing 9 changed files with 116 additions and 16 deletions.
54 changes: 53 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
Compose DND is a library that allows you to easily add drag and drop functionality to your Jetpack Compose or Compose Multiplatform projects.


[![Kotlin](https://img.shields.io/badge/kotlin-1.9.21-blue.svg?logo=kotlin)](http://kotlinlang.org)
[![Kotlin](https://img.shields.io/badge/kotlin-1.9.22-blue.svg?logo=kotlin)](http://kotlinlang.org)
[![MohamedRejeb](https://raw.githubusercontent.com/MohamedRejeb/MohamedRejeb/main/badges/mohamedrejeb.svg)](https://github.com/MohamedRejeb)
[![Apache-2.0](https://img.shields.io/badge/License-Apache%202.0-green.svg)](https://opensource.org/licenses/Apache-2.0)
[![BuildPassing](https://shields.io/badge/build-passing-brightgreen)](https://github.com/MohamedRejeb/compose-dnd/actions)
Expand Down Expand Up @@ -112,6 +112,58 @@ The `ReorderableItem` composable is at the same time a `DraggableItem` and a `dr

> For more details, check out the [sample](https://github.com/MohamedRejeb/compose-dnd/tree/main/sample/common/src/commonMain/kotlin)

### Enable/Disable Drag and Drop

If you want to enable/disable drag and drop functionality, you can use the `enabled` parameter in the `DragAndDropContainer` and `ReorderContainer` composable.

```kotlin
DragAndDropContainer(
state = dragAndDropState,
enabled = false
) {

}
```

```kotlin
ReorderContainer(
state = reorderState,
enabled = false
) {

}
```

> This will disable the drag and drop functionality for all the draggable items.
If you want to disable drag and drop for a specific item, you can use the `enabled` parameter in the `DraggableItem` and `ReorderableItem` composable.

```kotlin
DraggableItem(
state = dragAndDropState,
key = task.id,
data = task,
enabled = false
) {

}
```

```kotlin
ReorderableItem(
state = reorderState,
key = task.id,
data = task,
onDrop = { state ->
// Handle drop
},
enabled = false
) {

}
```

## Contribution
If you've found an error in this sample, please file an issue. <br>
Feel free to help out by sending a pull request :heart:.
Expand Down
15 changes: 15 additions & 0 deletions compose-dnd/build.gradle.kts
Original file line number Diff line number Diff line change
@@ -1,3 +1,18 @@
/*
* 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.
*/
import org.jetbrains.kotlin.gradle.targets.js.dsl.ExperimentalWasmDsl

/*
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ import androidx.compose.foundation.gestures.awaitEachGesture
import androidx.compose.foundation.gestures.drag
import androidx.compose.foundation.layout.Box
import androidx.compose.runtime.Composable
import androidx.compose.runtime.LaunchedEffect
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
import androidx.compose.runtime.rememberCoroutineScope
Expand All @@ -38,12 +39,14 @@ import kotlinx.coroutines.launch
*
* @param state The state of the drag and drop
* @param modifier [Modifier]
* @param enabled whether the drag and drop is enabled
* @param content content of the container
*/
@Composable
fun <T> DragAndDropContainer(
state: DragAndDropState<T>,
modifier: Modifier = Modifier,
enabled: Boolean = true,
content: @Composable () -> Unit,
) {
val scope = rememberCoroutineScope()
Expand All @@ -52,12 +55,18 @@ fun <T> DragAndDropContainer(
mutableStateOf(Offset.Zero)
}

LaunchedEffect(enabled) {
state.enabled = enabled
}

Box(
modifier = modifier
.onGloballyPositioned {
positionInRoot.value = it.positionInRoot()
}
.pointerInput(state, state.pointerId) {
.pointerInput(enabled, state, state.pointerId) {
if (!enabled) return@pointerInput

awaitEachGesture {
if (state.pointerId == null) {
awaitPointerEvent()
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -64,6 +64,11 @@ fun <T> rememberDragAndDropState(
class DragAndDropState<T>(
internal val dragAfterLongPress: Boolean = false,
) {
/**
* If true, drag and drop is enabled
*/
internal var enabled by mutableStateOf(true)

// Drop Target

/**
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,6 @@ import androidx.compose.runtime.Composable
import androidx.compose.runtime.DisposableEffect
import androidx.compose.runtime.LaunchedEffect
import androidx.compose.runtime.remember
import androidx.compose.runtime.rememberCoroutineScope
import androidx.compose.ui.Modifier
import androidx.compose.ui.geometry.Offset
import androidx.compose.ui.input.pointer.pointerInput
Expand All @@ -39,6 +38,7 @@ import com.mohamedrejeb.compose.dnd.gesture.detectDragStartGesture
* @param key - unique key for this item
* @param data - data that will be passed to drop target on drop
* @param state - state of the drag and drop
* @param enabled - whether the drag and drop is enabled
* @param dragAfterLongPress if true, drag will start after long press, otherwise drag will start after simple press
* @param dropTargets - list of drop targets ids to which this item can be dropped, if empty, item can be dropped to any drop target
* @param dropAnimationSpec - animation spec for the drop animation
Expand All @@ -51,14 +51,13 @@ fun <T> DraggableItem(
key: Any,
data: T,
state: DragAndDropState<T>,
enabled: Boolean = true,
dragAfterLongPress: Boolean = state.dragAfterLongPress,
dropTargets: List<Any> = emptyList(),
dropAnimationSpec: AnimationSpec<Offset> = SpringSpec(),
draggableContent: (@Composable () -> Unit)? = null,
content: @Composable DraggableItemScope.() -> Unit,
) {
val scope = rememberCoroutineScope()

LaunchedEffect(key, state, data) {
state.draggableItemMap[key]?.data = data
}
Expand Down Expand Up @@ -112,12 +111,12 @@ fun <T> DraggableItem(
state = draggableItemState,
)
}
.pointerInput(key, state) {
.pointerInput(enabled, key, state, state.enabled) {
detectDragStartGesture(
key = key,
state = state,
enabled = enabled && state.enabled,
dragAfterLongPress = dragAfterLongPress,
scope = scope,
)
},
) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -23,15 +23,17 @@ 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.utils.awaitPointerSlopOrCancellation
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.coroutineScope
import kotlinx.coroutines.launch

internal suspend fun <T> PointerInputScope.detectDragStartGesture(
key: Any,
state: DragAndDropState<T>,
enabled: Boolean,
dragAfterLongPress: Boolean,
scope: CoroutineScope,
) {
) = coroutineScope {
if (!enabled) return@coroutineScope

awaitEachGesture {
val down = awaitFirstDown(requireUnconsumed = false, pass = PointerEventPass.Main)
var drag: PointerInputChange?
Expand All @@ -52,7 +54,7 @@ internal suspend fun <T> PointerInputScope.detectDragStartGesture(
if (drag != null) {
val draggableItemState = state.draggableItemMap[key] ?: return@awaitEachGesture

scope.launch {
launch {
state.handleDragStart(drag.position + draggableItemState.positionInRoot)
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -26,18 +26,21 @@ import com.mohamedrejeb.compose.dnd.annotation.ExperimentalDndApi
*
* @param state The state of the reorder
* @param modifier [Modifier]
* @param enabled whether the reorder is enabled
* @param content content of the container
*/
@OptIn(ExperimentalDndApi::class)
@Composable
fun <T> ReorderContainer(
state: ReorderState<T>,
modifier: Modifier = Modifier,
enabled: Boolean = true,
content: @Composable () -> Unit
) {
DragAndDropContainer(
state = state.dndState,
modifier = modifier,
enabled = enabled,
content = content,
)
}
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,6 @@ import androidx.compose.runtime.Composable
import androidx.compose.runtime.DisposableEffect
import androidx.compose.runtime.LaunchedEffect
import androidx.compose.runtime.remember
import androidx.compose.runtime.rememberCoroutineScope
import androidx.compose.ui.Modifier
import androidx.compose.ui.geometry.Offset
import androidx.compose.ui.input.pointer.pointerInput
Expand All @@ -42,6 +41,8 @@ import com.mohamedrejeb.compose.dnd.gesture.detectDragStartGesture
* @param state The reorder state.
* @param key The key used to identify the item.
* @param data The data associated with the item.
* @param zIndex The z-index of the item.
* @param enabled Whether the reorder is enabled.
* @param dragAfterLongPress if true, drag will start after long press, otherwise drag will start after simple press
* @param dropTargets - list of drop targets ids to which this item can be dropped, if empty, item can be dropped to any drop target
* @param onDrop The action to perform when an item is dropped onto the target.
Expand All @@ -62,6 +63,7 @@ fun <T> ReorderableItem(
key: Any,
data: T,
zIndex: Float = 0f,
enabled: Boolean = true,
dragAfterLongPress: Boolean = state.dndState.dragAfterLongPress,
dropTargets: List<Any> = emptyList(),
onDrop: (state: DraggedItemState<T>) -> Unit = {},
Expand All @@ -71,8 +73,6 @@ fun <T> ReorderableItem(
draggableContent: (@Composable () -> Unit)? = null,
content: @Composable ReorderableItemScope.() -> Unit,
) {
val scope = rememberCoroutineScope()

LaunchedEffect(key, state, data) {
state.dndState.draggableItemMap[key]?.data = data
}
Expand Down Expand Up @@ -125,12 +125,12 @@ fun <T> ReorderableItem(
state = draggableItemState,
)
}
.pointerInput(key, state) {
.pointerInput(enabled, key, state, state.dndState.enabled) {
detectDragStartGesture(
key = key,
state = state.dndState,
enabled = enabled && state.dndState.enabled,
dragAfterLongPress = dragAfterLongPress,
scope = scope,
)
}
.dropTarget(
Expand Down
15 changes: 15 additions & 0 deletions sample/common/build.gradle.kts
Original file line number Diff line number Diff line change
@@ -1,3 +1,18 @@
/*
* 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.
*/
import org.jetbrains.kotlin.gradle.targets.js.dsl.ExperimentalWasmDsl

/*
Expand Down

0 comments on commit e09c57e

Please sign in to comment.