Warning, /plasma/kwin/src/plugins/private/qml/WindowHeap.qml is written in an unsupported language. File is not indexed.

0001 /*
0002     SPDX-FileCopyrightText: 2021 Vlad Zahorodnii <vlad.zahorodnii@kde.org>
0003     SPDX-FileCopyrightText: 2022 ivan tkachenko <me@ratijas.tk>
0004 
0005     SPDX-License-Identifier: GPL-2.0-or-later
0006 */
0007 
0008 import QtQuick
0009 import QtQuick.Window
0010 import org.kde.kirigami as Kirigami
0011 import org.kde.kwin as KWinComponents
0012 import org.kde.kwin.private.effects
0013 
0014 FocusScope {
0015     id: heap
0016 
0017     enum Direction {
0018         Left,
0019         Right,
0020         Up,
0021         Down
0022     }
0023 
0024     property alias model: windowsInstantiator.model
0025     property alias delegate: windowsInstantiator.delegate
0026     readonly property alias count: windowsInstantiator.count
0027     readonly property bool activeEmpty: {
0028         var children = expoLayout.visibleChildren;
0029         for (var i = 0; i < children.length; i++) {
0030             var child = children[i];
0031             if (child instanceof WindowHeapDelegate && !child.activeHidden) {
0032                 return false;
0033             }
0034         }
0035         return true;
0036     }
0037 
0038     property alias layout: expoLayout
0039     property int selectedIndex: -1
0040     property int animationDuration: Kirigami.Units.longDuration
0041     property bool animationEnabled: false
0042     property bool absolutePositioning: true
0043     property real padding: 0
0044     // Either a string "activeClass" or a list internalIds of windows
0045     property var showOnly: []
0046 
0047     required property bool organized
0048     readonly property bool effectiveOrganized: expoLayout.ready && organized
0049     property bool dragActive: false
0050 
0051     signal activated()
0052 
0053     function activateIndex(index) {
0054         KWinComponents.Workspace.activeWindow = windowsInstantiator.objectAt(index).window;
0055         activated();
0056     }
0057 
0058     property var dndManagerStore: ({})
0059 
0060     function saveDND(key: int, rect: rect) {
0061         dndManagerStore[key] = rect;
0062     }
0063     function containsDND(key: int): bool {
0064         return key in dndManagerStore;
0065     }
0066     function restoreDND(key: int): rect {
0067         return dndManagerStore[key];
0068     }
0069     function deleteDND(key: int) {
0070         delete dndManagerStore[key];
0071     }
0072 
0073     KWinComponents.WindowThumbnail {
0074         id: otherScreenThumbnail
0075         z: 2
0076         property KWinComponents.WindowThumbnail cloneOf
0077         visible: false
0078         client: cloneOf ? cloneOf.client : null
0079         width: cloneOf ? cloneOf.width : 0
0080         height: cloneOf ? cloneOf.height : 0
0081         onCloneOfChanged: {
0082             if (!cloneOf) {
0083                 visible = false;
0084             }
0085         }
0086     }
0087 
0088     Connections {
0089         target: effect
0090         function onItemDraggedOutOfScreen(item, screens) {
0091             let found = false;
0092 
0093             // don't put a proxy for item's own screen
0094             if (screens.length === 0 || item.screen === targetScreen) {
0095                 otherScreenThumbnail.visible = false;
0096                 return;
0097             }
0098 
0099             for (let i in screens) {
0100                 if (targetScreen === screens[i]) {
0101                     found = true;
0102                     const heapRelativePos = heap.mapFromGlobal(item.mapToGlobal(0, 0));
0103                     otherScreenThumbnail.cloneOf = item
0104                     otherScreenThumbnail.x = heapRelativePos.x;
0105                     otherScreenThumbnail.y = heapRelativePos.y;
0106                     otherScreenThumbnail.visible = true;
0107                 }
0108             }
0109 
0110             if (!found) {
0111                 otherScreenThumbnail.visible = false;
0112             }
0113         }
0114         function onItemDroppedOutOfScreen(pos, item, screen) {
0115             if (screen === targetScreen) {
0116                 // To actually move we neeed a screen number rather than an EffectScreen
0117                 KWinComponents.Workspace.sendClientToScreen(item.client, KWinComponents.Workspace.screenAt(pos));
0118             }
0119         }
0120     }
0121 
0122     ExpoLayout {
0123         id: expoLayout
0124 
0125         anchors.fill: parent
0126         anchors.margins: heap.padding
0127         fillGaps: true
0128         spacing: Kirigami.Units.smallSpacing * 5
0129 
0130         Instantiator {
0131             id: windowsInstantiator
0132 
0133             asynchronous: true
0134 
0135             delegate: WindowHeapDelegate {
0136                 windowHeap: heap
0137             }
0138 
0139             onObjectAdded: (index, object) => {
0140                 object.parent = expoLayout
0141                 var key = object.window.internalId;
0142                 if (heap.containsDND(key)) {
0143                     expoLayout.forceLayout();
0144                     var oldGlobalRect = heap.restoreDND(key);
0145                     object.restoreDND(oldGlobalRect);
0146                     heap.deleteDND(key);
0147                 } else if (heap.effectiveOrganized) {
0148                     // New window has opened in the middle of a running effect.
0149                     // Make sure it is positioned before enabling its animations.
0150                     expoLayout.forceLayout();
0151                 }
0152                 object.animationEnabled = true;
0153             }
0154         }
0155     }
0156 
0157     function findFirstItem() {
0158         for (let candidateIndex = 0; candidateIndex < windowsInstantiator.count; ++candidateIndex) {
0159             const candidateItem = windowsInstantiator.objectAt(candidateIndex);
0160             if (!candidateItem.activeHidden) {
0161                 return candidateIndex;
0162             }
0163         }
0164         return -1;
0165     }
0166 
0167     function findNextItem(selectedIndex, direction) {
0168         if (selectedIndex === -1) {
0169             return findFirstItem();
0170         }
0171 
0172         const selectedItem = windowsInstantiator.objectAt(selectedIndex);
0173         let nextIndex = -1;
0174 
0175         switch (direction) {
0176         case WindowHeap.Direction.Left:
0177             for (let candidateIndex = 0; candidateIndex < windowsInstantiator.count; ++candidateIndex) {
0178                 const candidateItem = windowsInstantiator.objectAt(candidateIndex);
0179                 if (candidateItem.activeHidden) {
0180                     continue;
0181                 }
0182 
0183                 if (candidateItem.y + candidateItem.height <= selectedItem.y) {
0184                     continue;
0185                 } else if (selectedItem.y + selectedItem.height <= candidateItem.y) {
0186                     continue;
0187                 }
0188 
0189                 if (candidateItem.x + candidateItem.width < selectedItem.x + selectedItem.width) {
0190                     if (nextIndex === -1) {
0191                         nextIndex = candidateIndex;
0192                     } else {
0193                         const nextItem = windowsInstantiator.objectAt(nextIndex);
0194                         if (candidateItem.x + candidateItem.width > nextItem.x + nextItem.width) {
0195                             nextIndex = candidateIndex;
0196                         }
0197                     }
0198                 }
0199             }
0200             break;
0201         case WindowHeap.Direction.Right:
0202             for (let candidateIndex = 0; candidateIndex < windowsInstantiator.count; ++candidateIndex) {
0203                 const candidateItem = windowsInstantiator.objectAt(candidateIndex);
0204                 if (candidateItem.activeHidden) {
0205                     continue;
0206                 }
0207 
0208                 if (candidateItem.y + candidateItem.height <= selectedItem.y) {
0209                     continue;
0210                 } else if (selectedItem.y + selectedItem.height <= candidateItem.y) {
0211                     continue;
0212                 }
0213 
0214                 if (selectedItem.x < candidateItem.x) {
0215                     if (nextIndex === -1) {
0216                         nextIndex = candidateIndex;
0217                     } else {
0218                         const nextItem = windowsInstantiator.objectAt(nextIndex);
0219                         if (nextIndex === -1 || candidateItem.x < nextItem.x) {
0220                             nextIndex = candidateIndex;
0221                         }
0222                     }
0223                 }
0224             }
0225             break;
0226         case WindowHeap.Direction.Up:
0227             for (let candidateIndex = 0; candidateIndex < windowsInstantiator.count; ++candidateIndex) {
0228                 const candidateItem = windowsInstantiator.objectAt(candidateIndex);
0229                 if (candidateItem.activeHidden) {
0230                     continue;
0231                 }
0232 
0233                 if (candidateItem.x + candidateItem.width <= selectedItem.x) {
0234                     continue;
0235                 } else if (selectedItem.x + selectedItem.width <= candidateItem.x) {
0236                     continue;
0237                 }
0238 
0239                 if (candidateItem.y + candidateItem.height < selectedItem.y + selectedItem.height) {
0240                     if (nextIndex === -1) {
0241                         nextIndex = candidateIndex;
0242                     } else {
0243                         const nextItem = windowsInstantiator.objectAt(nextIndex);
0244                         if (nextItem.y + nextItem.height < candidateItem.y + candidateItem.height) {
0245                             nextIndex = candidateIndex;
0246                         }
0247                     }
0248                 }
0249             }
0250             break;
0251         case WindowHeap.Direction.Down:
0252             for (let candidateIndex = 0; candidateIndex < windowsInstantiator.count; ++candidateIndex) {
0253                 const candidateItem = windowsInstantiator.objectAt(candidateIndex);
0254                 if (candidateItem.activeHidden) {
0255                     continue;
0256                 }
0257 
0258                 if (candidateItem.x + candidateItem.width <= selectedItem.x) {
0259                     continue;
0260                 } else if (selectedItem.x + selectedItem.width <= candidateItem.x) {
0261                     continue;
0262                 }
0263 
0264                 if (selectedItem.y < candidateItem.y) {
0265                     if (nextIndex === -1) {
0266                         nextIndex = candidateIndex;
0267                     } else {
0268                         const nextItem = windowsInstantiator.objectAt(nextIndex);
0269                         if (candidateItem.y < nextItem.y) {
0270                             nextIndex = candidateIndex;
0271                         }
0272                     }
0273                 }
0274             }
0275             break;
0276         }
0277 
0278         return nextIndex;
0279     }
0280 
0281     function resetSelected() {
0282         selectedIndex = -1;
0283     }
0284 
0285     function selectNextItem(direction) {
0286         const nextIndex = findNextItem(selectedIndex, direction);
0287         if (nextIndex !== -1) {
0288             selectedIndex = nextIndex;
0289             return true;
0290         }
0291         return false;
0292     }
0293 
0294     function selectLastItem(direction) {
0295         let last = selectedIndex;
0296         while (true) {
0297             const next = findNextItem(last, direction);
0298             if (next === -1) {
0299                 break;
0300             } else {
0301                 last = next;
0302             }
0303         }
0304         if (last !== -1) {
0305             selectedIndex = last;
0306             return true;
0307         }
0308         return false;
0309     }
0310 
0311 
0312     Keys.onPressed: event => {
0313         let handled = false;
0314         switch (event.key) {
0315         case Qt.Key_Up:
0316             handled = selectNextItem(WindowHeap.Direction.Up);
0317             heap.focus = true;
0318             break;
0319         case Qt.Key_Down:
0320             handled = selectNextItem(WindowHeap.Direction.Down);
0321             heap.focus = true;
0322             break;
0323         case Qt.Key_Left:
0324             handled = selectNextItem(WindowHeap.Direction.Left);
0325             heap.focus = true;
0326             break;
0327         case Qt.Key_Right:
0328             handled = selectNextItem(WindowHeap.Direction.Right);
0329             heap.focus = true;
0330             break;
0331         case Qt.Key_Home:
0332             handled = selectLastItem(WindowHeap.Direction.Left);
0333             heap.focus = true;
0334             break;
0335         case Qt.Key_End:
0336             handled = selectLastItem(WindowHeap.Direction.Right);
0337             heap.focus = true;
0338             break;
0339         case Qt.Key_PageUp:
0340             handled = selectLastItem(WindowHeap.Direction.Up);
0341             heap.focus = true;
0342             break;
0343         case Qt.Key_PageDown:
0344             handled = selectLastItem(WindowHeap.Direction.Down);
0345             heap.focus = true;
0346             break;
0347         case Qt.Key_Space:
0348             if (!heap.focus) {
0349                 break;
0350             }
0351         case Qt.Key_Return:
0352             handled = false;
0353             let selectedItem = null;
0354             if (selectedIndex !== -1) {
0355                 selectedItem = windowsInstantiator.objectAt(selectedIndex);
0356             } else {
0357                 // If the window heap has only one visible window, activate it.
0358                 for (let i = 0; i < windowsInstantiator.count; ++i) {
0359                     const candidateItem = windowsInstantiator.objectAt(i);
0360                     if (candidateItem.activeHidden) {
0361                         continue;
0362                     } else if (selectedItem) {
0363                         selectedItem = null;
0364                         break;
0365                     }
0366                     selectedItem = candidateItem;
0367                 }
0368             }
0369             if (selectedItem) {
0370                 handled = true;
0371                 KWinComponents.Workspace.activeWindow = selectedItem.window;
0372                 activated();
0373             }
0374             break;
0375         default:
0376             return;
0377         }
0378         event.accepted = handled;
0379     }
0380 }