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 }