Drag a proxy (drag.target) out of a clipped sub view
-
I must say I am enjoying QtQuick a fair bit and I am making good progress. However, I have stumbled accross another puzzler. I hope the example is not getting too complicated, but I want to provide some context as to what I am trying to do and have self-contained code.
Essentially, I have my StackView working perfectly now, allowing me to browse through different ListViews. Now, my problem is that I would like each item in those ListViews to have a handle, which can be used to drag ListItems via proxy from the ListView into another View (e.g. let's say a shopping basket). However, since the proxy is defined as a child, it is getting clipped by the ancestor Rectangle. Now, please note that the appearance of the proxy depends on the Item from which the drag was initiated, so I cannot simply avoid the problem by making one "global" proxy. Conceptually, each ListItem should have its own proxy.
import QtQuick 2.5 import QtQuick.Controls 1.4 import QtQuick.Window 2.2 Window { id: root width: 800; height: 600 visible: true ListModel { id: fruitModel ListElement { title: "Apple" elementColor: "green" } ListElement { title: "Orange" elementColor: "orange" } ListElement { title: "Banana" elementColor: "yellow" } } ListView { id: fruitList model: fruitModel delegate: Item { width: parent.width; height: 80 Rectangle { anchors.fill: parent color: "lightblue" Text { anchors.fill: parent color: "black" text: title } Rectangle { id: dragHandle anchors.right: parent.right anchors.rightMargin: 10 width: 60; height: 60 color: elementColor MouseArea { id: dragArea anchors.fill: parent drag.target: dragProxy Rectangle { id: dragProxy width: 60; height: 60 color: elementColor //Drag.active: parent.drag.active Drag.dragType: Drag.Automatic property bool dragActive: dragArea.drag.active onDragActiveChanged: { if (dragActive) { console.log("started") Drag.start(); } else { console.log("finished") // Reset position for next drag dragProxy.x = dragArea.x; dragProxy.y = dragArea.y; Drag.drop(); } } visible: parent.drag.active } } } } } visible: false } Item { anchors.top: aTitleBar.bottom; anchors.left: aTitleBar.left width: 300; height: 480 clip: true StackView { id: aStackView anchors.fill: parent initialItem: Rectangle { color: "lightblue" Text { anchors.fill: parent color: "black" text: "Fruit" } MouseArea { id: mouse anchors.fill: parent onClicked: aStackView.push({ item: fruitList, properties: { text: title, color: color }}) } } } } Rectangle { id: aTitleBar x: 50; y: 20 width: 300; height: 80 color: "darkblue" Text { anchors.fill: parent color: "white" text: "Store" } MouseArea { anchors.fill: parent onClicked: aStackView.pop() } } Rectangle { id: aBasket x: 450; y: 20 width: 300; height: 560 color: "lightblue" Text { anchors.fill: parent color: "black" text: "Basket" } DropArea { anchors.fill: parent onEntered: console.log("entered") onExited: console.log("exited") onDropped: console.log("dropped") } } }Any suggestions on how to proceed?
-
Ok, maybe the example was a bit too complicated. Here is a simplified version:
import QtQuick 2.5 import QtQuick.Controls 1.4 import QtQuick.Window 2.2 Window { id: root width: 800; height: 600 visible: true Rectangle { id: aDragSource x: 50; y: 20 width: 300; height: 560 color: "lightblue" clip: true Text { anchors.fill: parent color: "black" text: "Source" } Rectangle { id: aDragHandle anchors.right: parent.right anchors.rightMargin: 10 width: 60; height: 60 color: "red" MouseArea { id: aDragArea anchors.fill: parent drag.target: aDragProxy Rectangle { id: aDragProxy width: 60; height: 60 color: "red" //Drag.active: parent.drag.active Drag.dragType: Drag.Automatic property bool dragActive: aDragArea.drag.active onDragActiveChanged: { if (dragActive) { console.log("started") Drag.start(); } else { console.log("finished") // Reset position for next drag aDragProxy.x = aDragArea.x; aDragProxy.y = aDragArea.y; Drag.drop(); } } visible: parent.drag.active } } } } Rectangle { id: aDropTraget x: 450; y: 20 width: 300; height: 560 color: "lightblue" Text { anchors.fill: parent color: "black" text: "Target" } DropArea { anchors.fill: parent onEntered: console.log("entered") onExited: console.log("exited") onDropped: console.log("dropped") } } }This illustrates the basic problem. Ideally, I would like to define the proxy locally as is, but have it rendered on top of the scene and not be clipped by any of its ancestors. For more on the motivation behind this, have a look at the first example. Any feedback/help would be very much appreciated!
-
Well, this is proving much more difficult than I had expected. Since all my attempts to solve the problem in a declarative way have failed, I turned to programmatically creating the proxy element:
import QtQuick 2.5 import QtQuick.Controls 1.4 import QtQuick.Window 2.2 Window { id: window width: 800; height: 600 visible: true Item { id: root anchors.fill: parent Rectangle { id: aDropTraget x: 450; y: 20 width: 300; height: 560 color: "lightblue" Text { anchors.fill: parent color: "black" text: "Target" } DropArea { anchors.fill: parent onEntered: console.log("entered") onExited: console.log("exited") onDropped: { console.log(drop.hasText, drop.text) console.log("dropped") } } } Rectangle { id: aDragHandle x: 10; y: 80 width: 60; height: 60 color: "red" MouseArea { id: aDragArea anchors.fill: parent property var dragComponent: null property var dragProxy: null function moveProxy(mouse) { var pos = root.mapFromItem(aDragHandle, mouse.x, mouse.y); dragProxy.x = pos.x - dragProxy.height/2; dragProxy.y = pos.y - dragProxy.width/2; } onPressed: { if (!dragProxy) { dragProxy = dragComponent.createObject(root, { "width": aDragHandle.width, "height": aDragHandle.height, "color": "orange" }); dragProxy.Drag.mimeData = { "text/plain": "Hello Drag!" }; } dragProxy.Drag.start(); moveProxy(mouse); } onPositionChanged: { moveProxy(mouse); } onReleased: { moveProxy(mouse); dragProxy.Drag.drop(); if (dragProxy) { dragProxy.destroy(); dragProxy = null; } } Component.onCompleted: { dragComponent = Qt.createComponent("controls/DragProxy.qml"); if (dragComponent.status === Component.Error) { console.log("Error creating drag component."); console.log(dragComponent.errorString()); } } } } } }At first, this almost looks like it is working. However, the mime data is always empty, which means there is no way to send any information to the DropArea, rendering the whole thing useless. I am honestly at the end of my wits here...