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 }