Skip to content

Commit

Permalink
Fixed drag-and-drop in the case when there are multiple compositions …
Browse files Browse the repository at this point in the history
…attached to the same root (AWT) container. (#1493)

There is currently a problem with the DnD implementation because on the
one hand the API implies a `DragAndDropManager` instance for each
composition (`RootNodeOwner`) and on other hand, `AwtDragAndDropManager`
needs to install its own AWT `TransferHandler` and `DropTarget` into the
root container.

In this PR the first attached `AwtDragAndDropManager` installs a
`TransferHandler` and a `DropTarget` into the root container, and the
following `AwtDragAndDropManager`s reuse them. The `DropTarget` forwards
its events to the `AwtDragAndDropManager` of the top-most "focusable"
`RootNodeOwner`.

Fixes
https://youtrack.jetbrains.com/issue/CMP-5855/Popup-breaks-drag-and-drop-functionality-on-Desktop

## Testing
Tested manually with 
```
@OptIn(ExperimentalFoundationApi::class, ExperimentalComposeUiApi::class)
fun main() = singleWindowApplication {
    val exportedText = "Hello, DnD!"
    Row(
        horizontalArrangement = Arrangement.SpaceEvenly,
        verticalAlignment = Alignment.CenterVertically,
        modifier = Modifier.fillMaxSize()
    ) {
        val textMeasurer = rememberTextMeasurer()
        Box(Modifier
            .size(200.dp)
            .background(Color.LightGray)
            .dragAndDropSource(
                drawDragDecoration = {
                    drawRect(
                        color = Color.White,
                        topLeft = Offset(x = 0f, y = size.height/4),
                        size = Size(size.width, size.height/2)
                    )
                    val textLayoutResult = textMeasurer.measure(
                        text = AnnotatedString(exportedText),
                        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(
                                    StringSelection(exportedText)
                                ),
                                supportedActions = listOf(
                                    DragAndDropTransferAction.Copy,
                                    DragAndDropTransferAction.Move,
                                    DragAndDropTransferAction.Link,
                                ),
                                dragDecorationOffset = offset,
                                onTransferCompleted = { action ->
                                    println("Action at source: $action")
                                }
                            )
                        )
                    },
                    onDrag = { _, _ -> },
                )
            }
        ) {
            Text("Drag Me", Modifier.align(Alignment.Center))
        }

        var showTargetBorder by remember { mutableStateOf(false)}
        var targetText by remember { mutableStateOf("Drop Here") }
        val coroutineScope = rememberCoroutineScope()
        val dragAndDropTarget = remember {
            object: DragAndDropTarget {

                override fun onStarted(event: DragAndDropEvent) {
                    showTargetBorder = true
                }

                override fun onEnded(event: DragAndDropEvent) {
                    showTargetBorder = false
                }

                override fun onDrop(event: DragAndDropEvent): Boolean {
                    println("Action at target: ${event.action}")
                    val result = (targetText == "Drop Here")// && (event.action == DragAndDropTransferAction.Link)
                    targetText = event.awtTransferable.let {
                        if (it.isDataFlavorSupported(DataFlavor.stringFlavor))
                            it.getTransferData(DataFlavor.stringFlavor) as String
                        else
                            it.transferDataFlavors.first().humanPresentableName
                    }
                    coroutineScope.launch {
                        delay(2000)
                        targetText = "Drop Here"
                    }
                    return result
                }
            }
        }

        Box(Modifier
            .size(200.dp)
            .background(Color.LightGray)
            .then(
                if (showTargetBorder)
                    Modifier.border(BorderStroke(3.dp, Color.Black))
                else
                    Modifier
            )
           .dragAndDropTarget(
                shouldStartDragAndDrop = { true },
                target = dragAndDropTarget
            )
        ) {
            Text(targetText, Modifier.align(Alignment.Center))
        }
    }

    var showPopup by remember { mutableStateOf(false) }
    if (showPopup) {
        Popup {
            Text("Popup is shown")
        }
    }
   LaunchedEffect(Unit) {
        delay(5000)
        showPopup = true
        delay(5000)
        showPopup = false
    }
}
```

This should be tested by QA

## Release Notes
### Fixes - Desktop
- _(prerelease fix)_ Fixed drag-and-drop not working after a popup is
displayed in the window.
  • Loading branch information
m-sasha authored Aug 27, 2024
1 parent 7dbac19 commit 70d6caa
Show file tree
Hide file tree
Showing 13 changed files with 529 additions and 319 deletions.
19 changes: 11 additions & 8 deletions compose/ui/ui/api/desktop/ui.api
Original file line number Diff line number Diff line change
Expand Up @@ -3286,7 +3286,6 @@ public abstract interface class androidx/compose/ui/platform/PlatformContext {
public fun convertLocalToWindowPosition-MK-Hz9U (J)J
public fun convertScreenToLocalPosition-MK-Hz9U (J)J
public fun convertWindowToLocalPosition-MK-Hz9U (J)J
public fun createDragAndDropManager ()Landroidx/compose/ui/platform/PlatformDragAndDropManager;
public abstract fun getInputModeManager ()Landroidx/compose/ui/input/InputModeManager;
public fun getMeasureDrawLayerBounds ()Z
public fun getParentFocusManager ()Landroidx/compose/ui/focus/FocusManager;
Expand All @@ -3299,6 +3298,7 @@ public abstract interface class androidx/compose/ui/platform/PlatformContext {
public fun isWindowTransparent ()Z
public fun requestFocus ()Z
public fun setPointerIcon (Landroidx/compose/ui/input/pointer/PointerIcon;)V
public fun startDrag-12SF9DM (Landroidx/compose/ui/draganddrop/DragAndDropTransferData;JLkotlin/jvm/functions/Function1;)Z
}

public final class androidx/compose/ui/platform/PlatformContext$Companion {
Expand All @@ -3317,13 +3317,6 @@ public abstract interface class androidx/compose/ui/platform/PlatformContext$Sem
public abstract fun onSemanticsOwnerRemoved (Landroidx/compose/ui/semantics/SemanticsOwner;)V
}

public abstract interface class androidx/compose/ui/platform/PlatformDragAndDropManager {
public abstract fun drag-12SF9DM (Landroidx/compose/ui/draganddrop/DragAndDropTransferData;JLkotlin/jvm/functions/Function1;)Z
public abstract fun getModifier ()Landroidx/compose/ui/Modifier;
public abstract fun isInterestedNode (Landroidx/compose/ui/draganddrop/DragAndDropModifierNode;)Z
public abstract fun registerNodeInterest (Landroidx/compose/ui/draganddrop/DragAndDropModifierNode;)V
}

public final class androidx/compose/ui/platform/PlatformInsets {
public static final field $stable I
public static final field Companion Landroidx/compose/ui/platform/PlatformInsets$Companion;
Expand Down Expand Up @@ -3522,6 +3515,7 @@ public abstract interface class androidx/compose/ui/scene/ComposeScene {
public abstract fun createLayer (Landroidx/compose/ui/unit/Density;Landroidx/compose/ui/unit/LayoutDirection;ZLandroidx/compose/runtime/CompositionContext;)Landroidx/compose/ui/scene/ComposeSceneLayer;
public abstract fun getCompositionLocalContext ()Landroidx/compose/runtime/CompositionLocalContext;
public abstract fun getDensity ()Landroidx/compose/ui/unit/Density;
public abstract fun getDropTarget ()Landroidx/compose/ui/scene/ComposeSceneDropTarget;
public abstract fun getFocusManager ()Landroidx/compose/ui/scene/ComposeSceneFocusManager;
public abstract fun getLayoutDirection ()Landroidx/compose/ui/unit/LayoutDirection;
public abstract fun getSize-bOM6tXw ()Landroidx/compose/ui/unit/IntSize;
Expand Down Expand Up @@ -3551,6 +3545,15 @@ public final class androidx/compose/ui/scene/ComposeSceneContext$Companion {
public final fun getEmpty ()Landroidx/compose/ui/scene/ComposeSceneContext;
}

public final class androidx/compose/ui/scene/ComposeSceneDropTarget {
public static final field $stable I
public final fun onChanged (Landroidx/compose/ui/draganddrop/DragAndDropEvent;)V
public final fun onDrop (Landroidx/compose/ui/draganddrop/DragAndDropEvent;)Z
public final fun onEntered (Landroidx/compose/ui/draganddrop/DragAndDropEvent;)Z
public final fun onExited (Landroidx/compose/ui/draganddrop/DragAndDropEvent;)V
public final fun onMoved (Landroidx/compose/ui/draganddrop/DragAndDropEvent;)V
}

public final class androidx/compose/ui/scene/ComposeSceneFocusManager {
public static final field $stable I
public final fun getFocusRect ()Landroidx/compose/ui/geometry/Rect;
Expand Down
Loading

0 comments on commit 70d6caa

Please sign in to comment.