From b01ea99c0170d34cb0965b05db9b27c070ff1eea Mon Sep 17 00:00:00 2001 From: ivan tkachenko Date: Tue, 13 Sep 2022 01:32:35 +0300 Subject: [PATCH] effects/desktopgrid: Restore position correctly when dropping a window This required a bit of a magic on the WindowHeap side to store and restore global position of a WindowHeapDelegates' window thumbnails. An additional property bool animationEnabled on a delegate level enables the heap to restore position without playing unneeded initial animation, just like the heap itself. Windows that are being dragged or already returning form a drop are positioned higher than others on a z-stack. BUG: 453995 --- src/effects/desktopgrid/qml/DesktopView.qml | 4 + src/effects/desktopgrid/qml/main.qml | 7 ++ src/effects/private/qml/WindowHeap.qml | 24 ++++++ .../private/qml/WindowHeapDelegate.qml | 82 +++++++++++++++++-- 4 files changed, 109 insertions(+), 8 deletions(-) diff --git a/src/effects/desktopgrid/qml/DesktopView.qml b/src/effects/desktopgrid/qml/DesktopView.qml index 3aa8dc4966..876d23758c 100644 --- a/src/effects/desktopgrid/qml/DesktopView.qml +++ b/src/effects/desktopgrid/qml/DesktopView.qml @@ -1,6 +1,7 @@ /* SPDX-FileCopyrightText: 2021 Vlad Zahorodnii SPDX-FileCopyrightText: 2022 Marco Martin + SPDX-FileCopyrightText: 2022 ivan tkachenko SPDX-License-Identifier: GPL-2.0-or-later */ @@ -17,6 +18,8 @@ FocusScope { required property QtObject clientModel required property QtObject desktop + /** @type {{int: rect}} */ + required property var dndManagerStore readonly property bool dragActive: heap.dragActive || dragHandler.active || xAnim.running || yAnim.running property real panelOpacity: 1 focus: true @@ -115,6 +118,7 @@ FocusScope { animationEnabled: container.animationEnabled organized: container.organized layout.mode: effect.layout + dndManagerStore: desktopView.dndManagerStore model: KWinComponents.ClientFilterModel { activity: KWinComponents.Workspace.currentActivity desktop: desktopView.desktop diff --git a/src/effects/desktopgrid/qml/main.qml b/src/effects/desktopgrid/qml/main.qml index 330177a769..a8ebd1945c 100644 --- a/src/effects/desktopgrid/qml/main.qml +++ b/src/effects/desktopgrid/qml/main.qml @@ -1,6 +1,7 @@ /* SPDX-FileCopyrightText: 2021 Vlad Zahorodnii SPDX-FileCopyrightText: 2022 Marco Martin + SPDX-FileCopyrightText: 2022 ivan tkachenko SPDX-License-Identifier: GPL-2.0-or-later */ @@ -23,6 +24,11 @@ Rectangle { property bool animationEnabled: false property bool organized: false + /** Shared Drag&Drop store to keep track of DND state across desktops. + * @type {{client.internalId: rect}} + */ + property var dndManagerStore: ({}) + color: "black" function start() { @@ -261,6 +267,7 @@ Rectangle { height: container.height clientModel: stackModel + dndManagerStore: container.dndManagerStore Rectangle { anchors.fill: parent color: "transparent" diff --git a/src/effects/private/qml/WindowHeap.qml b/src/effects/private/qml/WindowHeap.qml index f9a8fc4222..07ccf5411c 100644 --- a/src/effects/private/qml/WindowHeap.qml +++ b/src/effects/private/qml/WindowHeap.qml @@ -1,5 +1,6 @@ /* SPDX-FileCopyrightText: 2021 Vlad Zahorodnii + SPDX-FileCopyrightText: 2022 ivan tkachenko SPDX-License-Identifier: GPL-2.0-or-later */ @@ -49,6 +50,13 @@ FocusScope { activated(); } + /** @type {{client.internalId: rect}} */ + property var dndManagerStore: ({}) + + function saveDND(key: int, rect: rect) { + dndManagerStore[key] = rect; + } + KWinComponents.WindowThumbnailItem { id: otherScreenThumbnail z: 2 @@ -109,6 +117,22 @@ FocusScope { Repeater { id: windowsRepeater + + onItemAdded: (index, item) => { + // restore/reparent from drop + var key = item.client.internalId; + if (key in heap.dndManagerStore) { + expoLayout.forceLayout(); + var oldGlobalRect = heap.dndManagerStore[key]; + item.restoreDND(oldGlobalRect); + delete heap.dndManagerStore[key]; + } else if (heap.effectiveOrganized) { + // New window has opened in the middle of a running effect. + // Make sure it is positioned before enabling its animations. + expoLayout.forceLayout(); + } + item.animationEnabled = true; + } delegate: WindowHeapDelegate { windowHeap: heap } diff --git a/src/effects/private/qml/WindowHeapDelegate.qml b/src/effects/private/qml/WindowHeapDelegate.qml index 07459d86c1..fa440e9c3d 100644 --- a/src/effects/private/qml/WindowHeapDelegate.qml +++ b/src/effects/private/qml/WindowHeapDelegate.qml @@ -1,5 +1,6 @@ /* SPDX-FileCopyrightText: 2021 Vlad Zahorodnii + SPDX-FileCopyrightText: 2022 ivan tkachenko SPDX-License-Identifier: GPL-2.0-or-later */ @@ -39,6 +40,9 @@ Item { // Show a text label under this thumbnail property bool windowTitleVisible: true + // Same as for window heap + property bool animationEnabled: false + //scale up and down the whole thumbnail without affecting layouting property real targetScale: 1.0 @@ -73,11 +77,15 @@ Item { } visible: opacity > 0 - z: activeDragHandler.active ? 1000 + z: (activeDragHandler.active || returning.running) ? 1000 : client.stackingOrder * (presentOnCurrentDesktop ? 1 : 0.001) + function restoreDND(oldGlobalRect: rect) { + thumbSource.restoreDND(oldGlobalRect); + } + component TweenBehavior : Behavior { - enabled: thumb.state !== "partial" && thumb.windowHeap.animationEnabled && !thumb.activeDragHandler.active + enabled: thumb.state !== "partial" && thumb.windowHeap.animationEnabled && thumb.animationEnabled && !thumb.activeDragHandler.active NumberAnimation { duration: thumb.windowHeap.animationDuration easing.type: Easing.OutCubic @@ -92,9 +100,7 @@ Item { KWinComponents.WindowThumbnailItem { id: thumbSource wId: thumb.client.internalId - state: thumb.activeDragHandler.active ? "drag" : "normal" - Drag.active: thumb.activeDragHandler.active Drag.proposedAction: Qt.MoveAction Drag.supportedActions: Qt.MoveAction Drag.source: thumb.client @@ -105,6 +111,23 @@ Item { onXChanged: effect.checkItemDraggedOutOfScreen(thumbSource) onYChanged: effect.checkItemDraggedOutOfScreen(thumbSource) + state: "normal" + function saveDND() { + const oldGlobalRect = mapToItem(null, 0, 0, width, height); + thumb.windowHeap.saveDND(thumb.client.internalId, oldGlobalRect); + } + function restoreDND(oldGlobalRect: rect) { + state = "reparenting"; + + const newGlobalRect = mapFromItem(null, oldGlobalRect); + + x = newGlobalRect.x; + y = newGlobalRect.y; + width = newGlobalRect.width; + height = newGlobalRect.height; + + state = "normal"; + } states: [ State { name: "normal" @@ -116,6 +139,14 @@ Item { height: thumb.height } }, + State { + name: "pressed" + PropertyChanges { + target: thumbSource + width: thumb.width + height: thumb.height + } + }, State { name: "drag" PropertyChanges { @@ -124,17 +155,25 @@ Item { thumb.activeDragHandler.centroid.position.x y: -thumb.activeDragHandler.centroid.pressPosition.y * thumb.targetScale + thumb.activeDragHandler.centroid.position.y - width: cell.width * thumb.targetScale - height: cell.height * thumb.targetScale + width: thumb.width * thumb.targetScale + height: thumb.height * thumb.targetScale + } + }, + State { + name: "reparenting" + PropertyChanges { + target: thumbSource } } ] transitions: Transition { + id: returning + from: "drag,reparenting" to: "normal" enabled: thumb.windowHeap.animationEnabled NumberAnimation { duration: thumb.windowHeap.animationDuration - properties: "x, y, width, height, opacity" + properties: "x, y, width, height" easing.type: Easing.OutCubic } } @@ -316,25 +355,52 @@ Item { onTapped: { thumb.windowHeap.windowClicked(thumb.client, eventPoint) } + onPressedChanged: { + if (pressed) { + var saved = Qt.point(thumbSource.x, thumbSource.y); + thumbSource.Drag.active = true; + thumbSource.state = "pressed"; + thumbSource.x = saved.x; + thumbSource.y = saved.y; + } else if (!thumb.activeDragHandler.active) { + thumbSource.Drag.active = false; + thumbSource.state = "normal"; + } + } } component DragManager : DragHandler { target: null + dragThreshold: 0 grabPermissions: PointerHandler.CanTakeOverFromAnything + // This does not work when moving pointer fast and pressing along the way + // See also QTBUG-105903, QTBUG-105904 + // enabled: thumbSource.state !== "normal" onActiveChanged: { thumb.windowHeap.dragActive = active; if (active) { thumb.activeDragHandler = this; + thumbSource.state = "drag"; } else { + thumbSource.saveDND(); + var action = thumbSource.Drag.drop(); if (action === Qt.MoveAction) { - // this whole component is in the process of being destroyed due to drop onto + // This whole component is in the process of being destroyed due to drop onto // another virtual desktop (not another screen). + if (typeof thumbSource !== "undefined") { + // Except the case when it was dropped on the same desktop which it's already on, so let's return to normal state anyway. + thumbSource.state = "normal"; + } return; } + var globalPos = targetScreen.mapToGlobal(centroid.scenePosition); effect.checkItemDroppedOutOfScreen(globalPos, thumbSource); + + // else, return to normal without reparenting + thumbSource.state = "normal"; } } }