kotlindrag-and-dropcompose-multiplatformcompose-desktopjetbrains-compose

How do I implement dragging from a Jetpack Compose application to somewhere outside?


I can write something like this in my Compose application:

var isDroppable by remember { mutableStateOf(false) }
val (textFieldValue, setTextFieldValue) = remember { mutableStateOf(TextFieldValue()) }

TextField(
    value = textFieldValue,
    onValueChange = setTextFieldValue,
    modifier = Modifier
        .offset(500.dp, 100.dp)
        .requiredSize(200.dp, 200.dp)
        .border(4.dp, if (isDroppable) Color.Green else Color.Red)
        .onExternalDrag(
            onDragStart = { externalDragValue ->
                isDroppable = externalDragValue.dragData is androidx.compose.ui.DragData.Text
            },
            onDragExit = {
                isDroppable = false
            },
            onDrop = { externalDragValue ->
                isDroppable = false
                val dragData = externalDragValue.dragData
                if (dragData is androidx.compose.ui.DragData.Text) {
                    setTextFieldValue(textFieldValue.copy(
                        text = textFieldValue.text.substring(0, textFieldValue.selection.start) +
                            dragData.readText() +
                            textFieldValue.text.substring(textFieldValue.selection.end),
                        selection = TextRange(textFieldValue.selection.end)
                    ))
                }
            },
        )
)

I can then open Notepad, type some text, and drag that text into the Compose app.

My question is: How to get the opposite?

If I select some text in the Compose application, I should be able to drag it back to Notepad, either moving or copying it depending on whether Ctrl is held down, same as text components in native applications.


Solution

  • This is for Compose Multiplatform 1.7.0-beta02 and tested on Windows.

    Modifier.dragAndDropSource() has been commonized for Desktop as well.
    Use it to drag from your app to other components of your app or other applications:

    val textToExport = "Hello from Compose Multiplatform app!"
    val textMeasurer = rememberTextMeasurer()
    Box(modifier = Modifier
        .size(200.dp)
        .background(Color.LightGray)
        .dragAndDropSource(
            drawDragDecoration = {
                // These are drawn as a visual representation of the component being dragged
                drawRect(
                    color = Color.Red,
                    topLeft = Offset(x = 0f, y = size.height / 4),
                    size = Size(size.width, size.height / 2)
                )
                val textLayoutResult = textMeasurer.measure(
                    text = AnnotatedString("I'm being dragged"),
                    layoutDirection = layoutDirection,
                    density = this
                )
                drawText(
                    textLayoutResult = textLayoutResult,
                    topLeft = Offset(
                        x = (size.width - textLayoutResult.size.width) / 2,
                        y = (size.height - textLayoutResult.size.height) / 2,
                    )
                )
            }
        ) {
            detectDragGestures(
                onDragStart = { offset ->
                    startTransfer(
                        DragAndDropTransferData(
        
                            transferable = DragAndDropTransferable(
                                // This is what you want to export/transfer/move/drop
                                StringSelection(textToExport)
                            ),
        
                            supportedActions = listOf(
                                DragAndDropTransferAction.Copy,
                                DragAndDropTransferAction.Move,
                                DragAndDropTransferAction.Link,
                            ),
                            dragDecorationOffset = offset,
                            onTransferCompleted = { action ->
                                when (action) {
                                    DragAndDropTransferAction.Copy -> println("Copied")
                                    DragAndDropTransferAction.Move -> println("Moved")
                                    DragAndDropTransferAction.Link -> println("Linked")
                                    null -> println("Transfer aborted")
                                }
                            }
                        )
                    )
                },
                onDrag = { _, _ -> },
            )
        }
    ) {
        Text("Drag Me", Modifier.align(Alignment.Center))
    }
    

    The code was adopted from the example in this pull request.

    See this related Compose Multiplatform issue.